├── .gitignore ├── .vscode └── settings.json ├── README.md ├── abstraction └── abstract.ipynb ├── build_graph.py ├── convex ├── .gitignore ├── convex │ ├── README.md │ ├── _generated │ │ ├── api.d.ts │ │ ├── api.js │ │ ├── dataModel.d.ts │ │ ├── server.d.ts │ │ └── server.js │ └── tsconfig.json ├── package-lock.json └── package.json ├── delete.py ├── embedding.py ├── extension-old ├── .github │ └── workflows │ │ └── submit.yml ├── .gitignore ├── .prettierrc.mjs ├── README.md ├── assets │ └── icon.png ├── bun.lockb ├── package.json ├── pnpm-lock.yaml ├── popup.tsx ├── postcss.config.js ├── style.css ├── tailwind.config.js └── tsconfig.json ├── extract.py ├── forms ├── .gitignore ├── assets │ └── favicon.ico ├── forms │ ├── __init__.py │ ├── forms.py │ └── submissions.json ├── requirements.txt └── rxconfig.py ├── graph ├── .gitignore ├── bun.lockb ├── data.js ├── favicon.ico ├── fonts │ ├── Geist.Mono │ │ ├── GeistMono-Black.otf │ │ ├── GeistMono-Black.woff2 │ │ ├── GeistMono-Bold.otf │ │ ├── GeistMono-Bold.woff2 │ │ ├── GeistMono-Light.otf │ │ ├── GeistMono-Light.woff2 │ │ ├── GeistMono-Medium.otf │ │ ├── GeistMono-Medium.woff2 │ │ ├── GeistMono-Regular.otf │ │ ├── GeistMono-Regular.woff2 │ │ ├── GeistMono-SemiBold.otf │ │ ├── GeistMono-SemiBold.woff2 │ │ ├── GeistMono-Thin.otf │ │ ├── GeistMono-Thin.woff2 │ │ ├── GeistMono-UltraBlack.otf │ │ ├── GeistMono-UltraBlack.woff2 │ │ ├── GeistMono-UltraLight.otf │ │ ├── GeistMono-UltraLight.woff2 │ │ ├── GeistMonoVariableVF.ttf │ │ ├── GeistMonoVariableVF.woff2 │ │ └── LICENSE.TXT │ └── Geist │ │ ├── Geist-Black.otf │ │ ├── Geist-Black.woff2 │ │ ├── Geist-Bold.otf │ │ ├── Geist-Bold.woff2 │ │ ├── Geist-Light.otf │ │ ├── Geist-Light.woff2 │ │ ├── Geist-Medium.otf │ │ ├── Geist-Medium.woff2 │ │ ├── Geist-Regular.otf │ │ ├── Geist-Regular.woff2 │ │ ├── Geist-SemiBold.otf │ │ ├── Geist-SemiBold.woff2 │ │ ├── Geist-Thin.otf │ │ ├── Geist-Thin.woff2 │ │ ├── Geist-UltraBlack.otf │ │ ├── Geist-UltraBlack.woff2 │ │ ├── Geist-UltraLight.otf │ │ ├── Geist-UltraLight.woff2 │ │ ├── GeistVariableVF.ttf │ │ ├── GeistVariableVF.woff2 │ │ └── LICENSE.TXT ├── index.html ├── index.js ├── nodeSettings.js ├── package.json ├── search │ └── search.js └── style.css ├── main.py ├── main.ts └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python,node,macos,visualstudiocode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,node,macos,visualstudiocode 3 | 4 | ### macOS ### 5 | # General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### macOS Patch ### 34 | # iCloud generated files 35 | *.icloud 36 | 37 | ### Node ### 38 | # Logs 39 | logs 40 | *.log 41 | npm-debug.log* 42 | yarn-debug.log* 43 | yarn-error.log* 44 | lerna-debug.log* 45 | .pnpm-debug.log* 46 | 47 | # Diagnostic reports (https://nodejs.org/api/report.html) 48 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 49 | 50 | # Runtime data 51 | pids 52 | *.pid 53 | *.seed 54 | *.pid.lock 55 | 56 | # Directory for instrumented libs generated by jscoverage/JSCover 57 | lib-cov 58 | 59 | # Coverage directory used by tools like istanbul 60 | coverage 61 | *.lcov 62 | 63 | # nyc test coverage 64 | .nyc_output 65 | 66 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 67 | .grunt 68 | 69 | # Bower dependency directory (https://bower.io/) 70 | bower_components 71 | 72 | # node-waf configuration 73 | .lock-wscript 74 | 75 | # Compiled binary addons (https://nodejs.org/api/addons.html) 76 | build/Release 77 | 78 | # Dependency directories 79 | node_modules/ 80 | jspm_packages/ 81 | 82 | # Snowpack dependency directory (https://snowpack.dev/) 83 | web_modules/ 84 | 85 | # TypeScript cache 86 | *.tsbuildinfo 87 | 88 | # Optional npm cache directory 89 | .npm 90 | 91 | # Optional eslint cache 92 | .eslintcache 93 | 94 | # Optional stylelint cache 95 | .stylelintcache 96 | 97 | # Microbundle cache 98 | .rpt2_cache/ 99 | .rts2_cache_cjs/ 100 | .rts2_cache_es/ 101 | .rts2_cache_umd/ 102 | 103 | # Optional REPL history 104 | .node_repl_history 105 | 106 | # Output of 'npm pack' 107 | *.tgz 108 | 109 | # Yarn Integrity file 110 | .yarn-integrity 111 | 112 | # dotenv environment variable files 113 | .env 114 | .env.development.local 115 | .env.test.local 116 | .env.production.local 117 | .env.local 118 | 119 | # parcel-bundler cache (https://parceljs.org/) 120 | .cache 121 | .parcel-cache 122 | 123 | # Next.js build output 124 | .next 125 | out 126 | 127 | # Nuxt.js build / generate output 128 | .nuxt 129 | dist 130 | 131 | # Gatsby files 132 | .cache/ 133 | # Comment in the public line in if your project uses Gatsby and not Next.js 134 | # https://nextjs.org/blog/next-9-1#public-directory-support 135 | # public 136 | 137 | # vuepress build output 138 | .vuepress/dist 139 | 140 | # vuepress v2.x temp and cache directory 141 | .temp 142 | 143 | # Docusaurus cache and generated files 144 | .docusaurus 145 | 146 | # Serverless directories 147 | .serverless/ 148 | 149 | # FuseBox cache 150 | .fusebox/ 151 | 152 | # DynamoDB Local files 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | .tern-port 157 | 158 | # Stores VSCode versions used for testing VSCode extensions 159 | .vscode-test 160 | 161 | # yarn v2 162 | .yarn/cache 163 | .yarn/unplugged 164 | .yarn/build-state.yml 165 | .yarn/install-state.gz 166 | .pnp.* 167 | 168 | ### Node Patch ### 169 | # Serverless Webpack directories 170 | .webpack/ 171 | 172 | # Optional stylelint cache 173 | 174 | # SvelteKit build / generate output 175 | .svelte-kit 176 | 177 | ### Python ### 178 | # Byte-compiled / optimized / DLL files 179 | __pycache__/ 180 | *.py[cod] 181 | *$py.class 182 | 183 | # C extensions 184 | *.so 185 | 186 | # Distribution / packaging 187 | .Python 188 | build/ 189 | develop-eggs/ 190 | dist/ 191 | downloads/ 192 | eggs/ 193 | .eggs/ 194 | lib/ 195 | lib64/ 196 | parts/ 197 | sdist/ 198 | var/ 199 | wheels/ 200 | share/python-wheels/ 201 | *.egg-info/ 202 | .installed.cfg 203 | *.egg 204 | MANIFEST 205 | 206 | # PyInstaller 207 | # Usually these files are written by a python script from a template 208 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 209 | *.manifest 210 | *.spec 211 | 212 | # Installer logs 213 | pip-log.txt 214 | pip-delete-this-directory.txt 215 | 216 | # Unit test / coverage reports 217 | htmlcov/ 218 | .tox/ 219 | .nox/ 220 | .coverage 221 | .coverage.* 222 | nosetests.xml 223 | coverage.xml 224 | *.cover 225 | *.py,cover 226 | .hypothesis/ 227 | .pytest_cache/ 228 | cover/ 229 | 230 | # Translations 231 | *.mo 232 | *.pot 233 | 234 | # Django stuff: 235 | local_settings.py 236 | db.sqlite3 237 | db.sqlite3-journal 238 | 239 | # Flask stuff: 240 | instance/ 241 | .webassets-cache 242 | 243 | # Scrapy stuff: 244 | .scrapy 245 | 246 | # Sphinx documentation 247 | docs/_build/ 248 | 249 | # PyBuilder 250 | .pybuilder/ 251 | target/ 252 | 253 | # Jupyter Notebook 254 | .ipynb_checkpoints 255 | 256 | # IPython 257 | profile_default/ 258 | ipython_config.py 259 | 260 | # pyenv 261 | # For a library or package, you might want to ignore these files since the code is 262 | # intended to run in multiple environments; otherwise, check them in: 263 | # .python-version 264 | 265 | # pipenv 266 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 267 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 268 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 269 | # install all needed dependencies. 270 | #Pipfile.lock 271 | 272 | # poetry 273 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 274 | # This is especially recommended for binary packages to ensure reproducibility, and is more 275 | # commonly ignored for libraries. 276 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 277 | #poetry.lock 278 | 279 | # pdm 280 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 281 | #pdm.lock 282 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 283 | # in version control. 284 | # https://pdm.fming.dev/#use-with-ide 285 | .pdm.toml 286 | 287 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 288 | __pypackages__/ 289 | 290 | # Celery stuff 291 | celerybeat-schedule 292 | celerybeat.pid 293 | 294 | # SageMath parsed files 295 | *.sage.py 296 | 297 | # Environments 298 | .venv 299 | env/ 300 | venv/ 301 | ENV/ 302 | env.bak/ 303 | venv.bak/ 304 | 305 | # Spyder project settings 306 | .spyderproject 307 | .spyproject 308 | 309 | # Rope project settings 310 | .ropeproject 311 | 312 | # mkdocs documentation 313 | /site 314 | 315 | # mypy 316 | .mypy_cache/ 317 | .dmypy.json 318 | dmypy.json 319 | 320 | # Pyre type checker 321 | .pyre/ 322 | 323 | # pytype static type analyzer 324 | .pytype/ 325 | 326 | # Cython debug symbols 327 | cython_debug/ 328 | 329 | # PyCharm 330 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 331 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 332 | # and can be added to the global gitignore or merged into this file. For a more nuclear 333 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 334 | #.idea/ 335 | 336 | ### Python Patch ### 337 | # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration 338 | poetry.toml 339 | 340 | # ruff 341 | .ruff_cache/ 342 | 343 | # LSP config files 344 | pyrightconfig.json 345 | 346 | ### VisualStudioCode ### 347 | .vscode/* 348 | !.vscode/settings.json 349 | !.vscode/tasks.json 350 | !.vscode/launch.json 351 | !.vscode/extensions.json 352 | !.vscode/*.code-snippets 353 | 354 | # Local History for Visual Studio Code 355 | .history/ 356 | 357 | # Built Visual Studio Code Extensions 358 | *.vsix 359 | 360 | ### VisualStudioCode Patch ### 361 | # Ignore all local history of files 362 | .history 363 | .ionide 364 | 365 | # End of https://www.toptal.com/developers/gitignore/api/python,node,macos,visualstudiocode 366 | 367 | tree-messages.* 368 | chromadb 369 | graphData.json 370 | people.json 371 | *messages*.json 372 | *treeHacks*.json -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "yaml.schemas": { 3 | "https://www.artillery.io/schema.json": [] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | image 2 | 3 | # Nexus - 3D semantic graph of hacker interests 4 | 5 | [Nexus](https://nexusgraph.vercel.app/) is a data visualization of hacker interests at TreeHacks, Hack the North, and Calhacks. 6 | 7 | ### Quickstart 8 | ```sh 9 | python3 -m venv .venv 10 | source .venv/bin/activate 11 | pip install -r requirements.txt 12 | ``` 13 | 14 | We use [Together AI](https://www.together.ai/) for inferencing and embeddings. 15 | Create a **.env** with 16 | 17 | ```sh 18 | # https://api.together.xyz/settings/api-keys 19 | TOGETHER_API_KEY="" 20 | ``` 21 | 22 | Create a file called **messages-htn-calhacks.json** containing the scraped data. 23 | ```json 24 | [ 25 | { 26 | "Time": "1:47 PM", 27 | "Name": "John Doe", 28 | "String": "Hello everyone! My name is John and I'm a X year student from Y university. My background is in Z and I'm looking for ..." 29 | }, 30 | ... 31 | ] 32 | ``` 33 | 34 | Run **main.py** then **build_graph.py**. Your graph will be in **graphData.json** 35 | 36 | -------------------------------------------------------------------------------- /abstraction/abstract.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "data = \"\"\"insert here [not displaying for privacy purposes]\"\"\"" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 3, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "[]\n" 22 | ] 23 | } 24 | ], 25 | "source": [ 26 | "import re\n", 27 | "import json\n", 28 | "import numpy as np\n", 29 | "\n", 30 | "messages = []\n", 31 | "current_message = {}\n", 32 | "lines = data.split('\\n')\n", 33 | "\n", 34 | "for line in lines:\n", 35 | " if re.match(r'\\s*\\d{1,2}:\\d{2} (AM|PM)', line):\n", 36 | " if current_message:\n", 37 | " messages.append(current_message)\n", 38 | " current_message = {}\n", 39 | " current_message[\"Time\"] = line.strip()\n", 40 | " elif line.strip():\n", 41 | " if \"Time\" not in current_message:\n", 42 | " current_message[\"Name\"] = line.strip()\n", 43 | " else:\n", 44 | " if \"Message\" not in current_message:\n", 45 | " current_message[\"Message\"] = []\n", 46 | " current_message[\"Message\"].append(line.strip())\n", 47 | "\n", 48 | "if current_message:\n", 49 | " messages.append(current_message)\n", 50 | "\n", 51 | "for i, message in enumerate(messages):\n", 52 | " if i > 0:\n", 53 | " if \"Message\" in messages[i-1]:\n", 54 | " message[\"Name\"] = messages[i-1][\"Message\"][-1]\n", 55 | " temp = ' '.join(messages[i-1][\"Message\"])\n", 56 | " temp = temp[0:(len(temp) - len(message[\"Name\"]))]\n", 57 | " messages[i-1][\"String\"] = temp\n", 58 | " message[\"String\"] = ' '.join(message[\"Message\"])\n", 59 | " else:\n", 60 | " message[\"Name\"] = \"\"\n", 61 | " else:\n", 62 | " message[\"Name\"] = \"\"\n", 63 | "\n", 64 | "for message in messages:\n", 65 | " if \"Message\" in message:\n", 66 | " del message[\"Message\"]\n", 67 | "\n", 68 | "messages = messages[2:]\n", 69 | "\n", 70 | "print(json.dumps(messages, indent=2))" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": null, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [] 79 | } 80 | ], 81 | "metadata": { 82 | "kernelspec": { 83 | "display_name": "venv", 84 | "language": "python", 85 | "name": "python3" 86 | }, 87 | "language_info": { 88 | "codemirror_mode": { 89 | "name": "ipython", 90 | "version": 3 91 | }, 92 | "file_extension": ".py", 93 | "mimetype": "text/x-python", 94 | "name": "python", 95 | "nbconvert_exporter": "python", 96 | "pygments_lexer": "ipython3", 97 | "version": "3.10.13" 98 | } 99 | }, 100 | "nbformat": 4, 101 | "nbformat_minor": 2 102 | } 103 | -------------------------------------------------------------------------------- /build_graph.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from pydantic import BaseModel, RootModel 4 | 5 | import chromadb 6 | 7 | chroma_client = chromadb.PersistentClient(path="chromadb") 8 | 9 | collection = chroma_client.get_collection(name="background_embeddings") 10 | 11 | 12 | print(collection) 13 | 14 | # Define the model for the nested "data" part 15 | 16 | N_RESULTS = 50 17 | DISTANCE_THRESHOLD = 8 18 | 19 | 20 | class NodeData(BaseModel): 21 | name: str 22 | interests: str 23 | background: str 24 | school: str 25 | major: str 26 | 27 | 28 | class Node(BaseModel): 29 | id: str 30 | data: NodeData 31 | 32 | 33 | class Link(BaseModel): 34 | source: str 35 | target: str 36 | 37 | 38 | # Get all embeddings 39 | results = collection.get(ids=None, include=["metadatas", "embeddings"]) 40 | if results["embeddings"] is None or results["metadatas"] is None: 41 | raise ValueError("No embeddings found in the collection") 42 | 43 | nodes: list[Node] = [] 44 | links: list[Link] = [] 45 | 46 | # For each embedding, find the 5 nearest neighbors 47 | 48 | for i, embedding in enumerate(results["embeddings"]): 49 | 50 | # Get the 5 nearest neighbors to the embedding 51 | self_id = results["ids"][i] 52 | self_name = results["metadatas"][i]["name"] 53 | self_interests = results["metadatas"][i]["interests"] if "interests" in results["metadatas"][i] else "" 54 | self_school = results["metadatas"][i]["school"] if "school" in results["metadatas"][i] else "" 55 | self_background = results["metadatas"][i]["background"] if "background" in results["metadatas"][i] else "" 56 | self_major = results["metadatas"][i]["major"] if "major" in results["metadatas"][i] else "" 57 | 58 | query = collection.query( 59 | n_results=N_RESULTS, 60 | query_embeddings=[embedding], 61 | include=["metadatas", "distances"], 62 | where={ 63 | "name": { 64 | "$ne": self_name, 65 | } 66 | } 67 | ) 68 | 69 | nearest_ids = query["ids"][0] 70 | 71 | if not query["distances"]: 72 | raise ValueError("No distances found in the query") 73 | distances = query["distances"][0] 74 | 75 | name = str(results["metadatas"][i]["name"]) 76 | source_id = results["ids"][i] 77 | # Add the node to the list of nodes 78 | new_node = Node( 79 | id=source_id, 80 | data=NodeData( 81 | name=name, 82 | interests=self_interests, 83 | school=self_school, 84 | background=self_background, 85 | major=self_major 86 | ) 87 | ) 88 | # Add the node to the list of nodes 89 | nodes.append(new_node) 90 | 91 | for i, distance in enumerate(distances): 92 | if distance < DISTANCE_THRESHOLD: 93 | links.append( 94 | Link( 95 | source=source_id, 96 | target=nearest_ids[i] 97 | ) 98 | ) 99 | 100 | 101 | print(nodes) 102 | print(links) 103 | 104 | # Dump to graphData.json 105 | with open("graphData.json", "w") as f: 106 | json.dump({"nodes": [n.model_dump() for n in nodes], 107 | "links": [l.model_dump() for l in links]}, f) 108 | -------------------------------------------------------------------------------- /convex/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .env.local 3 | 4 | **/tree-messages.json 5 | **/tree-messages.jsonl -------------------------------------------------------------------------------- /convex/convex/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your Convex functions directory! 2 | 3 | Write your Convex functions here. See 4 | https://docs.convex.dev/using/writing-convex-functions for more. 5 | 6 | A query function that takes two arguments looks like: 7 | 8 | ```ts 9 | // functions.js 10 | import { query } from "./_generated/server"; 11 | import { v } from "convex/values"; 12 | 13 | export const myQueryFunction = query({ 14 | // Validators for arguments. 15 | args: { 16 | first: v.number(), 17 | second: v.string(), 18 | }, 19 | 20 | // Function implementation. 21 | handler: async (ctx, args) => { 22 | // Read the database as many times as you need here. 23 | // See https://docs.convex.dev/database/reading-data. 24 | const documents = await ctx.db.query("tablename").collect(); 25 | 26 | // Arguments passed from the client are properties of the args object. 27 | console.log(args.first, args.second); 28 | 29 | // Write arbitrary JavaScript here: filter, aggregate, build derived data, 30 | // remove non-public properties, or create new objects. 31 | return documents; 32 | }, 33 | }); 34 | ``` 35 | 36 | Using this query function in a React component looks like: 37 | 38 | ```ts 39 | const data = useQuery(api.functions.myQueryFunction, { 40 | first: 10, 41 | second: "hello", 42 | }); 43 | ``` 44 | 45 | A mutation function looks like: 46 | 47 | ```ts 48 | // functions.js 49 | import { mutation } from "./_generated/server"; 50 | import { v } from "convex/values"; 51 | 52 | export const myMutationFunction = mutation({ 53 | // Validators for arguments. 54 | args: { 55 | first: v.string(), 56 | second: v.string(), 57 | }, 58 | 59 | // Function implementation. 60 | handler: async (ctx, args) => { 61 | // Insert or modify documents in the database here. 62 | // Mutations can also read from the database like queries. 63 | // See https://docs.convex.dev/database/writing-data. 64 | const message = { body: args.first, author: args.second }; 65 | const id = await ctx.db.insert("messages", message); 66 | 67 | // Optionally, return a value from your mutation. 68 | return await ctx.db.get(id); 69 | }, 70 | }); 71 | ``` 72 | 73 | Using this mutation function in a React component looks like: 74 | 75 | ```ts 76 | const mutation = useMutation(api.functions.myMutationFunction); 77 | function handleButtonPress() { 78 | // fire and forget, the most common way to use mutations 79 | mutation({ first: "Hello!", second: "me" }); 80 | // OR 81 | // use the result once the mutation has completed 82 | mutation({ first: "Hello!", second: "me" }).then((result) => 83 | console.log(result) 84 | ); 85 | } 86 | ``` 87 | 88 | Use the Convex CLI to push your functions to a deployment. See everything 89 | the Convex CLI can do by running `npx convex -h` in your project root 90 | directory. To learn more, launch the docs with `npx convex docs`. 91 | -------------------------------------------------------------------------------- /convex/convex/_generated/api.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated `api` utility. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.9.0. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import type { 13 | ApiFromModules, 14 | FilterApi, 15 | FunctionReference, 16 | } from "convex/server"; 17 | 18 | /** 19 | * A utility for referencing Convex functions in your app's API. 20 | * 21 | * Usage: 22 | * ```js 23 | * const myFunctionReference = api.myModule.myFunction; 24 | * ``` 25 | */ 26 | declare const fullApi: ApiFromModules<{}>; 27 | export declare const api: FilterApi< 28 | typeof fullApi, 29 | FunctionReference 30 | >; 31 | export declare const internal: FilterApi< 32 | typeof fullApi, 33 | FunctionReference 34 | >; 35 | -------------------------------------------------------------------------------- /convex/convex/_generated/api.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated `api` utility. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.9.0. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { anyApi } from "convex/server"; 13 | 14 | /** 15 | * A utility for referencing Convex functions in your app's API. 16 | * 17 | * Usage: 18 | * ```js 19 | * const myFunctionReference = api.myModule.myFunction; 20 | * ``` 21 | */ 22 | export const api = anyApi; 23 | export const internal = anyApi; 24 | -------------------------------------------------------------------------------- /convex/convex/_generated/dataModel.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated data model types. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.9.0. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { AnyDataModel } from "convex/server"; 13 | import type { GenericId } from "convex/values"; 14 | 15 | /** 16 | * No `schema.ts` file found! 17 | * 18 | * This generated code has permissive types like `Doc = any` because 19 | * Convex doesn't know your schema. If you'd like more type safety, see 20 | * https://docs.convex.dev/using/schemas for instructions on how to add a 21 | * schema file. 22 | * 23 | * After you change a schema, rerun codegen with `npx convex dev`. 24 | */ 25 | 26 | /** 27 | * The names of all of your Convex tables. 28 | */ 29 | export type TableNames = string; 30 | 31 | /** 32 | * The type of a document stored in Convex. 33 | */ 34 | export type Doc = any; 35 | 36 | /** 37 | * An identifier for a document in Convex. 38 | * 39 | * Convex documents are uniquely identified by their `Id`, which is accessible 40 | * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids). 41 | * 42 | * Documents can be loaded using `db.get(id)` in query and mutation functions. 43 | * 44 | * IDs are just strings at runtime, but this type can be used to distinguish them from other 45 | * strings when type checking. 46 | */ 47 | export type Id = 48 | GenericId; 49 | 50 | /** 51 | * A type describing your Convex data model. 52 | * 53 | * This type includes information about what tables you have, the type of 54 | * documents stored in those tables, and the indexes defined on them. 55 | * 56 | * This type is used to parameterize methods like `queryGeneric` and 57 | * `mutationGeneric` to make them type-safe. 58 | */ 59 | export type DataModel = AnyDataModel; 60 | -------------------------------------------------------------------------------- /convex/convex/_generated/server.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated utilities for implementing server-side Convex query and mutation functions. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.9.0. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { 13 | ActionBuilder, 14 | HttpActionBuilder, 15 | MutationBuilder, 16 | QueryBuilder, 17 | GenericActionCtx, 18 | GenericMutationCtx, 19 | GenericQueryCtx, 20 | GenericDatabaseReader, 21 | GenericDatabaseWriter, 22 | } from "convex/server"; 23 | import type { DataModel } from "./dataModel.js"; 24 | 25 | /** 26 | * Define a query in this Convex app's public API. 27 | * 28 | * This function will be allowed to read your Convex database and will be accessible from the client. 29 | * 30 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 31 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 32 | */ 33 | export declare const query: QueryBuilder; 34 | 35 | /** 36 | * Define a query that is only accessible from other Convex functions (but not from the client). 37 | * 38 | * This function will be allowed to read from your Convex database. It will not be accessible from the client. 39 | * 40 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 41 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 42 | */ 43 | export declare const internalQuery: QueryBuilder; 44 | 45 | /** 46 | * Define a mutation in this Convex app's public API. 47 | * 48 | * This function will be allowed to modify your Convex database and will be accessible from the client. 49 | * 50 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 51 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 52 | */ 53 | export declare const mutation: MutationBuilder; 54 | 55 | /** 56 | * Define a mutation that is only accessible from other Convex functions (but not from the client). 57 | * 58 | * This function will be allowed to modify your Convex database. It will not be accessible from the client. 59 | * 60 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 61 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 62 | */ 63 | export declare const internalMutation: MutationBuilder; 64 | 65 | /** 66 | * Define an action in this Convex app's public API. 67 | * 68 | * An action is a function which can execute any JavaScript code, including non-deterministic 69 | * code and code with side-effects, like calling third-party services. 70 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive. 71 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}. 72 | * 73 | * @param func - The action. It receives an {@link ActionCtx} as its first argument. 74 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible. 75 | */ 76 | export declare const action: ActionBuilder; 77 | 78 | /** 79 | * Define an action that is only accessible from other Convex functions (but not from the client). 80 | * 81 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 82 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible. 83 | */ 84 | export declare const internalAction: ActionBuilder; 85 | 86 | /** 87 | * Define an HTTP action. 88 | * 89 | * This function will be used to respond to HTTP requests received by a Convex 90 | * deployment if the requests matches the path and method where this action 91 | * is routed. Be sure to route your action in `convex/http.js`. 92 | * 93 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 94 | * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up. 95 | */ 96 | export declare const httpAction: HttpActionBuilder; 97 | 98 | /** 99 | * A set of services for use within Convex query functions. 100 | * 101 | * The query context is passed as the first argument to any Convex query 102 | * function run on the server. 103 | * 104 | * This differs from the {@link MutationCtx} because all of the services are 105 | * read-only. 106 | */ 107 | export type QueryCtx = GenericQueryCtx; 108 | 109 | /** 110 | * A set of services for use within Convex mutation functions. 111 | * 112 | * The mutation context is passed as the first argument to any Convex mutation 113 | * function run on the server. 114 | */ 115 | export type MutationCtx = GenericMutationCtx; 116 | 117 | /** 118 | * A set of services for use within Convex action functions. 119 | * 120 | * The action context is passed as the first argument to any Convex action 121 | * function run on the server. 122 | */ 123 | export type ActionCtx = GenericActionCtx; 124 | 125 | /** 126 | * An interface to read from the database within Convex query functions. 127 | * 128 | * The two entry points are {@link DatabaseReader.get}, which fetches a single 129 | * document by its {@link Id}, or {@link DatabaseReader.query}, which starts 130 | * building a query. 131 | */ 132 | export type DatabaseReader = GenericDatabaseReader; 133 | 134 | /** 135 | * An interface to read from and write to the database within Convex mutation 136 | * functions. 137 | * 138 | * Convex guarantees that all writes within a single mutation are 139 | * executed atomically, so you never have to worry about partial writes leaving 140 | * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control) 141 | * for the guarantees Convex provides your functions. 142 | */ 143 | export type DatabaseWriter = GenericDatabaseWriter; 144 | -------------------------------------------------------------------------------- /convex/convex/_generated/server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * Generated utilities for implementing server-side Convex query and mutation functions. 4 | * 5 | * THIS CODE IS AUTOMATICALLY GENERATED. 6 | * 7 | * Generated by convex@1.9.0. 8 | * To regenerate, run `npx convex dev`. 9 | * @module 10 | */ 11 | 12 | import { 13 | actionGeneric, 14 | httpActionGeneric, 15 | queryGeneric, 16 | mutationGeneric, 17 | internalActionGeneric, 18 | internalMutationGeneric, 19 | internalQueryGeneric, 20 | } from "convex/server"; 21 | 22 | /** 23 | * Define a query in this Convex app's public API. 24 | * 25 | * This function will be allowed to read your Convex database and will be accessible from the client. 26 | * 27 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 28 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 29 | */ 30 | export const query = queryGeneric; 31 | 32 | /** 33 | * Define a query that is only accessible from other Convex functions (but not from the client). 34 | * 35 | * This function will be allowed to read from your Convex database. It will not be accessible from the client. 36 | * 37 | * @param func - The query function. It receives a {@link QueryCtx} as its first argument. 38 | * @returns The wrapped query. Include this as an `export` to name it and make it accessible. 39 | */ 40 | export const internalQuery = internalQueryGeneric; 41 | 42 | /** 43 | * Define a mutation in this Convex app's public API. 44 | * 45 | * This function will be allowed to modify your Convex database and will be accessible from the client. 46 | * 47 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 48 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 49 | */ 50 | export const mutation = mutationGeneric; 51 | 52 | /** 53 | * Define a mutation that is only accessible from other Convex functions (but not from the client). 54 | * 55 | * This function will be allowed to modify your Convex database. It will not be accessible from the client. 56 | * 57 | * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument. 58 | * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible. 59 | */ 60 | export const internalMutation = internalMutationGeneric; 61 | 62 | /** 63 | * Define an action in this Convex app's public API. 64 | * 65 | * An action is a function which can execute any JavaScript code, including non-deterministic 66 | * code and code with side-effects, like calling third-party services. 67 | * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive. 68 | * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}. 69 | * 70 | * @param func - The action. It receives an {@link ActionCtx} as its first argument. 71 | * @returns The wrapped action. Include this as an `export` to name it and make it accessible. 72 | */ 73 | export const action = actionGeneric; 74 | 75 | /** 76 | * Define an action that is only accessible from other Convex functions (but not from the client). 77 | * 78 | * @param func - The function. It receives an {@link ActionCtx} as its first argument. 79 | * @returns The wrapped function. Include this as an `export` to name it and make it accessible. 80 | */ 81 | export const internalAction = internalActionGeneric; 82 | 83 | /** 84 | * Define a Convex HTTP action. 85 | * 86 | * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object 87 | * as its second. 88 | * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`. 89 | */ 90 | export const httpAction = httpActionGeneric; 91 | -------------------------------------------------------------------------------- /convex/convex/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | /* This TypeScript project config describes the environment that 3 | * Convex functions run in and is used to typecheck them. 4 | * You can modify it, but some settings required to use Convex. 5 | */ 6 | "compilerOptions": { 7 | /* These settings are not required by Convex and can be modified. */ 8 | "allowJs": true, 9 | "strict": true, 10 | 11 | /* These compiler options are required by Convex */ 12 | "target": "ESNext", 13 | "lib": ["ES2021", "dom"], 14 | "forceConsistentCasingInFileNames": true, 15 | "allowSyntheticDefaultImports": true, 16 | "module": "ESNext", 17 | "moduleResolution": "Node", 18 | "isolatedModules": true, 19 | "skipLibCheck": true, 20 | "noEmit": true 21 | }, 22 | "include": ["./**/*"], 23 | "exclude": ["./_generated"] 24 | } 25 | -------------------------------------------------------------------------------- /convex/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "my-app", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "convex": "^1.9.0" 13 | } 14 | }, 15 | "node_modules/@esbuild/android-arm": { 16 | "version": "0.17.19", 17 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", 18 | "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", 19 | "cpu": [ 20 | "arm" 21 | ], 22 | "optional": true, 23 | "os": [ 24 | "android" 25 | ], 26 | "engines": { 27 | "node": ">=12" 28 | } 29 | }, 30 | "node_modules/@esbuild/android-arm64": { 31 | "version": "0.17.19", 32 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", 33 | "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", 34 | "cpu": [ 35 | "arm64" 36 | ], 37 | "optional": true, 38 | "os": [ 39 | "android" 40 | ], 41 | "engines": { 42 | "node": ">=12" 43 | } 44 | }, 45 | "node_modules/@esbuild/android-x64": { 46 | "version": "0.17.19", 47 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", 48 | "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", 49 | "cpu": [ 50 | "x64" 51 | ], 52 | "optional": true, 53 | "os": [ 54 | "android" 55 | ], 56 | "engines": { 57 | "node": ">=12" 58 | } 59 | }, 60 | "node_modules/@esbuild/darwin-arm64": { 61 | "version": "0.17.19", 62 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", 63 | "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", 64 | "cpu": [ 65 | "arm64" 66 | ], 67 | "optional": true, 68 | "os": [ 69 | "darwin" 70 | ], 71 | "engines": { 72 | "node": ">=12" 73 | } 74 | }, 75 | "node_modules/@esbuild/darwin-x64": { 76 | "version": "0.17.19", 77 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", 78 | "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", 79 | "cpu": [ 80 | "x64" 81 | ], 82 | "optional": true, 83 | "os": [ 84 | "darwin" 85 | ], 86 | "engines": { 87 | "node": ">=12" 88 | } 89 | }, 90 | "node_modules/@esbuild/freebsd-arm64": { 91 | "version": "0.17.19", 92 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", 93 | "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", 94 | "cpu": [ 95 | "arm64" 96 | ], 97 | "optional": true, 98 | "os": [ 99 | "freebsd" 100 | ], 101 | "engines": { 102 | "node": ">=12" 103 | } 104 | }, 105 | "node_modules/@esbuild/freebsd-x64": { 106 | "version": "0.17.19", 107 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", 108 | "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", 109 | "cpu": [ 110 | "x64" 111 | ], 112 | "optional": true, 113 | "os": [ 114 | "freebsd" 115 | ], 116 | "engines": { 117 | "node": ">=12" 118 | } 119 | }, 120 | "node_modules/@esbuild/linux-arm": { 121 | "version": "0.17.19", 122 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", 123 | "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", 124 | "cpu": [ 125 | "arm" 126 | ], 127 | "optional": true, 128 | "os": [ 129 | "linux" 130 | ], 131 | "engines": { 132 | "node": ">=12" 133 | } 134 | }, 135 | "node_modules/@esbuild/linux-arm64": { 136 | "version": "0.17.19", 137 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", 138 | "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", 139 | "cpu": [ 140 | "arm64" 141 | ], 142 | "optional": true, 143 | "os": [ 144 | "linux" 145 | ], 146 | "engines": { 147 | "node": ">=12" 148 | } 149 | }, 150 | "node_modules/@esbuild/linux-ia32": { 151 | "version": "0.17.19", 152 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", 153 | "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", 154 | "cpu": [ 155 | "ia32" 156 | ], 157 | "optional": true, 158 | "os": [ 159 | "linux" 160 | ], 161 | "engines": { 162 | "node": ">=12" 163 | } 164 | }, 165 | "node_modules/@esbuild/linux-loong64": { 166 | "version": "0.17.19", 167 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", 168 | "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", 169 | "cpu": [ 170 | "loong64" 171 | ], 172 | "optional": true, 173 | "os": [ 174 | "linux" 175 | ], 176 | "engines": { 177 | "node": ">=12" 178 | } 179 | }, 180 | "node_modules/@esbuild/linux-mips64el": { 181 | "version": "0.17.19", 182 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", 183 | "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", 184 | "cpu": [ 185 | "mips64el" 186 | ], 187 | "optional": true, 188 | "os": [ 189 | "linux" 190 | ], 191 | "engines": { 192 | "node": ">=12" 193 | } 194 | }, 195 | "node_modules/@esbuild/linux-ppc64": { 196 | "version": "0.17.19", 197 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", 198 | "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", 199 | "cpu": [ 200 | "ppc64" 201 | ], 202 | "optional": true, 203 | "os": [ 204 | "linux" 205 | ], 206 | "engines": { 207 | "node": ">=12" 208 | } 209 | }, 210 | "node_modules/@esbuild/linux-riscv64": { 211 | "version": "0.17.19", 212 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", 213 | "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", 214 | "cpu": [ 215 | "riscv64" 216 | ], 217 | "optional": true, 218 | "os": [ 219 | "linux" 220 | ], 221 | "engines": { 222 | "node": ">=12" 223 | } 224 | }, 225 | "node_modules/@esbuild/linux-s390x": { 226 | "version": "0.17.19", 227 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", 228 | "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", 229 | "cpu": [ 230 | "s390x" 231 | ], 232 | "optional": true, 233 | "os": [ 234 | "linux" 235 | ], 236 | "engines": { 237 | "node": ">=12" 238 | } 239 | }, 240 | "node_modules/@esbuild/linux-x64": { 241 | "version": "0.17.19", 242 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", 243 | "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", 244 | "cpu": [ 245 | "x64" 246 | ], 247 | "optional": true, 248 | "os": [ 249 | "linux" 250 | ], 251 | "engines": { 252 | "node": ">=12" 253 | } 254 | }, 255 | "node_modules/@esbuild/netbsd-x64": { 256 | "version": "0.17.19", 257 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", 258 | "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", 259 | "cpu": [ 260 | "x64" 261 | ], 262 | "optional": true, 263 | "os": [ 264 | "netbsd" 265 | ], 266 | "engines": { 267 | "node": ">=12" 268 | } 269 | }, 270 | "node_modules/@esbuild/openbsd-x64": { 271 | "version": "0.17.19", 272 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", 273 | "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", 274 | "cpu": [ 275 | "x64" 276 | ], 277 | "optional": true, 278 | "os": [ 279 | "openbsd" 280 | ], 281 | "engines": { 282 | "node": ">=12" 283 | } 284 | }, 285 | "node_modules/@esbuild/sunos-x64": { 286 | "version": "0.17.19", 287 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", 288 | "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", 289 | "cpu": [ 290 | "x64" 291 | ], 292 | "optional": true, 293 | "os": [ 294 | "sunos" 295 | ], 296 | "engines": { 297 | "node": ">=12" 298 | } 299 | }, 300 | "node_modules/@esbuild/win32-arm64": { 301 | "version": "0.17.19", 302 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", 303 | "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", 304 | "cpu": [ 305 | "arm64" 306 | ], 307 | "optional": true, 308 | "os": [ 309 | "win32" 310 | ], 311 | "engines": { 312 | "node": ">=12" 313 | } 314 | }, 315 | "node_modules/@esbuild/win32-ia32": { 316 | "version": "0.17.19", 317 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", 318 | "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", 319 | "cpu": [ 320 | "ia32" 321 | ], 322 | "optional": true, 323 | "os": [ 324 | "win32" 325 | ], 326 | "engines": { 327 | "node": ">=12" 328 | } 329 | }, 330 | "node_modules/@esbuild/win32-x64": { 331 | "version": "0.17.19", 332 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", 333 | "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", 334 | "cpu": [ 335 | "x64" 336 | ], 337 | "optional": true, 338 | "os": [ 339 | "win32" 340 | ], 341 | "engines": { 342 | "node": ">=12" 343 | } 344 | }, 345 | "node_modules/convex": { 346 | "version": "1.9.0", 347 | "resolved": "https://registry.npmjs.org/convex/-/convex-1.9.0.tgz", 348 | "integrity": "sha512-ORaiHNmzofLUui6hVnFRVwjFh4fnWU3hpneyru0C49bLYc4s1UVC/+s3fIaXYnpKoWj+snspaHOteRuV6eF5uA==", 349 | "dependencies": { 350 | "esbuild": "^0.17.5", 351 | "jwt-decode": "^3.1.2", 352 | "node-fetch": "^2.6.1" 353 | }, 354 | "bin": { 355 | "convex": "bin/main.js" 356 | }, 357 | "engines": { 358 | "node": ">=16.15.1", 359 | "npm": ">=7.0.0" 360 | }, 361 | "peerDependencies": { 362 | "@auth0/auth0-react": "^2.0.1", 363 | "@clerk/clerk-react": "^4.12.8", 364 | "react": "^17.0.2 || ^18.0.0", 365 | "react-dom": "^17.0.2 || ^18.0.0" 366 | }, 367 | "peerDependenciesMeta": { 368 | "@auth0/auth0-react": { 369 | "optional": true 370 | }, 371 | "@clerk/clerk-react": { 372 | "optional": true 373 | }, 374 | "react": { 375 | "optional": true 376 | }, 377 | "react-dom": { 378 | "optional": true 379 | } 380 | } 381 | }, 382 | "node_modules/esbuild": { 383 | "version": "0.17.19", 384 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", 385 | "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", 386 | "hasInstallScript": true, 387 | "bin": { 388 | "esbuild": "bin/esbuild" 389 | }, 390 | "engines": { 391 | "node": ">=12" 392 | }, 393 | "optionalDependencies": { 394 | "@esbuild/android-arm": "0.17.19", 395 | "@esbuild/android-arm64": "0.17.19", 396 | "@esbuild/android-x64": "0.17.19", 397 | "@esbuild/darwin-arm64": "0.17.19", 398 | "@esbuild/darwin-x64": "0.17.19", 399 | "@esbuild/freebsd-arm64": "0.17.19", 400 | "@esbuild/freebsd-x64": "0.17.19", 401 | "@esbuild/linux-arm": "0.17.19", 402 | "@esbuild/linux-arm64": "0.17.19", 403 | "@esbuild/linux-ia32": "0.17.19", 404 | "@esbuild/linux-loong64": "0.17.19", 405 | "@esbuild/linux-mips64el": "0.17.19", 406 | "@esbuild/linux-ppc64": "0.17.19", 407 | "@esbuild/linux-riscv64": "0.17.19", 408 | "@esbuild/linux-s390x": "0.17.19", 409 | "@esbuild/linux-x64": "0.17.19", 410 | "@esbuild/netbsd-x64": "0.17.19", 411 | "@esbuild/openbsd-x64": "0.17.19", 412 | "@esbuild/sunos-x64": "0.17.19", 413 | "@esbuild/win32-arm64": "0.17.19", 414 | "@esbuild/win32-ia32": "0.17.19", 415 | "@esbuild/win32-x64": "0.17.19" 416 | } 417 | }, 418 | "node_modules/jwt-decode": { 419 | "version": "3.1.2", 420 | "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", 421 | "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" 422 | }, 423 | "node_modules/node-fetch": { 424 | "version": "2.7.0", 425 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 426 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 427 | "dependencies": { 428 | "whatwg-url": "^5.0.0" 429 | }, 430 | "engines": { 431 | "node": "4.x || >=6.0.0" 432 | }, 433 | "peerDependencies": { 434 | "encoding": "^0.1.0" 435 | }, 436 | "peerDependenciesMeta": { 437 | "encoding": { 438 | "optional": true 439 | } 440 | } 441 | }, 442 | "node_modules/tr46": { 443 | "version": "0.0.3", 444 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 445 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 446 | }, 447 | "node_modules/webidl-conversions": { 448 | "version": "3.0.1", 449 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 450 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 451 | }, 452 | "node_modules/whatwg-url": { 453 | "version": "5.0.0", 454 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 455 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 456 | "dependencies": { 457 | "tr46": "~0.0.3", 458 | "webidl-conversions": "^3.0.0" 459 | } 460 | } 461 | }, 462 | "dependencies": { 463 | "@esbuild/android-arm": { 464 | "version": "0.17.19", 465 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", 466 | "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", 467 | "optional": true 468 | }, 469 | "@esbuild/android-arm64": { 470 | "version": "0.17.19", 471 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", 472 | "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", 473 | "optional": true 474 | }, 475 | "@esbuild/android-x64": { 476 | "version": "0.17.19", 477 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", 478 | "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", 479 | "optional": true 480 | }, 481 | "@esbuild/darwin-arm64": { 482 | "version": "0.17.19", 483 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", 484 | "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", 485 | "optional": true 486 | }, 487 | "@esbuild/darwin-x64": { 488 | "version": "0.17.19", 489 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", 490 | "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", 491 | "optional": true 492 | }, 493 | "@esbuild/freebsd-arm64": { 494 | "version": "0.17.19", 495 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", 496 | "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", 497 | "optional": true 498 | }, 499 | "@esbuild/freebsd-x64": { 500 | "version": "0.17.19", 501 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", 502 | "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", 503 | "optional": true 504 | }, 505 | "@esbuild/linux-arm": { 506 | "version": "0.17.19", 507 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", 508 | "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", 509 | "optional": true 510 | }, 511 | "@esbuild/linux-arm64": { 512 | "version": "0.17.19", 513 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", 514 | "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", 515 | "optional": true 516 | }, 517 | "@esbuild/linux-ia32": { 518 | "version": "0.17.19", 519 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", 520 | "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", 521 | "optional": true 522 | }, 523 | "@esbuild/linux-loong64": { 524 | "version": "0.17.19", 525 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", 526 | "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", 527 | "optional": true 528 | }, 529 | "@esbuild/linux-mips64el": { 530 | "version": "0.17.19", 531 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", 532 | "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", 533 | "optional": true 534 | }, 535 | "@esbuild/linux-ppc64": { 536 | "version": "0.17.19", 537 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", 538 | "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", 539 | "optional": true 540 | }, 541 | "@esbuild/linux-riscv64": { 542 | "version": "0.17.19", 543 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", 544 | "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", 545 | "optional": true 546 | }, 547 | "@esbuild/linux-s390x": { 548 | "version": "0.17.19", 549 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", 550 | "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", 551 | "optional": true 552 | }, 553 | "@esbuild/linux-x64": { 554 | "version": "0.17.19", 555 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", 556 | "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", 557 | "optional": true 558 | }, 559 | "@esbuild/netbsd-x64": { 560 | "version": "0.17.19", 561 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", 562 | "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", 563 | "optional": true 564 | }, 565 | "@esbuild/openbsd-x64": { 566 | "version": "0.17.19", 567 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", 568 | "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", 569 | "optional": true 570 | }, 571 | "@esbuild/sunos-x64": { 572 | "version": "0.17.19", 573 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", 574 | "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", 575 | "optional": true 576 | }, 577 | "@esbuild/win32-arm64": { 578 | "version": "0.17.19", 579 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", 580 | "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", 581 | "optional": true 582 | }, 583 | "@esbuild/win32-ia32": { 584 | "version": "0.17.19", 585 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", 586 | "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", 587 | "optional": true 588 | }, 589 | "@esbuild/win32-x64": { 590 | "version": "0.17.19", 591 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", 592 | "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", 593 | "optional": true 594 | }, 595 | "convex": { 596 | "version": "1.9.0", 597 | "resolved": "https://registry.npmjs.org/convex/-/convex-1.9.0.tgz", 598 | "integrity": "sha512-ORaiHNmzofLUui6hVnFRVwjFh4fnWU3hpneyru0C49bLYc4s1UVC/+s3fIaXYnpKoWj+snspaHOteRuV6eF5uA==", 599 | "requires": { 600 | "esbuild": "^0.17.5", 601 | "jwt-decode": "^3.1.2", 602 | "node-fetch": "^2.6.1" 603 | } 604 | }, 605 | "esbuild": { 606 | "version": "0.17.19", 607 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", 608 | "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", 609 | "requires": { 610 | "@esbuild/android-arm": "0.17.19", 611 | "@esbuild/android-arm64": "0.17.19", 612 | "@esbuild/android-x64": "0.17.19", 613 | "@esbuild/darwin-arm64": "0.17.19", 614 | "@esbuild/darwin-x64": "0.17.19", 615 | "@esbuild/freebsd-arm64": "0.17.19", 616 | "@esbuild/freebsd-x64": "0.17.19", 617 | "@esbuild/linux-arm": "0.17.19", 618 | "@esbuild/linux-arm64": "0.17.19", 619 | "@esbuild/linux-ia32": "0.17.19", 620 | "@esbuild/linux-loong64": "0.17.19", 621 | "@esbuild/linux-mips64el": "0.17.19", 622 | "@esbuild/linux-ppc64": "0.17.19", 623 | "@esbuild/linux-riscv64": "0.17.19", 624 | "@esbuild/linux-s390x": "0.17.19", 625 | "@esbuild/linux-x64": "0.17.19", 626 | "@esbuild/netbsd-x64": "0.17.19", 627 | "@esbuild/openbsd-x64": "0.17.19", 628 | "@esbuild/sunos-x64": "0.17.19", 629 | "@esbuild/win32-arm64": "0.17.19", 630 | "@esbuild/win32-ia32": "0.17.19", 631 | "@esbuild/win32-x64": "0.17.19" 632 | } 633 | }, 634 | "jwt-decode": { 635 | "version": "3.1.2", 636 | "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", 637 | "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" 638 | }, 639 | "node-fetch": { 640 | "version": "2.7.0", 641 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 642 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 643 | "requires": { 644 | "whatwg-url": "^5.0.0" 645 | } 646 | }, 647 | "tr46": { 648 | "version": "0.0.3", 649 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 650 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 651 | }, 652 | "webidl-conversions": { 653 | "version": "3.0.1", 654 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 655 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 656 | }, 657 | "whatwg-url": { 658 | "version": "5.0.0", 659 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 660 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 661 | "requires": { 662 | "tr46": "~0.0.3", 663 | "webidl-conversions": "^3.0.0" 664 | } 665 | } 666 | } 667 | } 668 | -------------------------------------------------------------------------------- /convex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "convex": "^1.9.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /delete.py: -------------------------------------------------------------------------------- 1 | # import chromadb 2 | 3 | # chroma_client = chromadb.PersistentClient(path="chromadb") 4 | 5 | # chroma_client.delete_collection(name="background_embeddings") 6 | -------------------------------------------------------------------------------- /embedding.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/embedding.py -------------------------------------------------------------------------------- /extension-old/.github/workflows/submit.yml: -------------------------------------------------------------------------------- 1 | name: "Submit to Web Store" 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - name: Cache pnpm modules 11 | uses: actions/cache@v3 12 | with: 13 | path: ~/.pnpm-store 14 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 15 | restore-keys: | 16 | ${{ runner.os }}- 17 | - uses: pnpm/action-setup@v2.2.4 18 | with: 19 | version: latest 20 | run_install: true 21 | - name: Use Node.js 16.x 22 | uses: actions/setup-node@v3.4.1 23 | with: 24 | node-version: 16.x 25 | cache: "pnpm" 26 | - name: Build the extension 27 | run: pnpm build 28 | - name: Package the extension into a zip artifact 29 | run: pnpm package 30 | - name: Browser Platform Publish 31 | uses: PlasmoHQ/bpp@v3 32 | with: 33 | keys: ${{ secrets.SUBMIT_KEYS }} 34 | artifact: build/chrome-mv3-prod.zip 35 | -------------------------------------------------------------------------------- /extension-old/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # misc 13 | .DS_Store 14 | *.pem 15 | 16 | # debug 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | .pnpm-debug.log* 21 | 22 | # local env files 23 | .env*.local 24 | 25 | out/ 26 | build/ 27 | dist/ 28 | 29 | # plasmo 30 | .plasmo 31 | 32 | # typescript 33 | .tsbuildinfo 34 | -------------------------------------------------------------------------------- /extension-old/.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('prettier').Options} 3 | */ 4 | export default { 5 | printWidth: 80, 6 | tabWidth: 2, 7 | useTabs: false, 8 | semi: false, 9 | singleQuote: false, 10 | trailingComma: "none", 11 | bracketSpacing: true, 12 | bracketSameLine: true, 13 | plugins: ["@ianvs/prettier-plugin-sort-imports"], 14 | importOrder: [ 15 | "", // Node.js built-in modules 16 | "", // Imports not matched by other special words or groups. 17 | "", // Empty line 18 | "^@plasmo/(.*)$", 19 | "", 20 | "^@plasmohq/(.*)$", 21 | "", 22 | "^~(.*)$", 23 | "", 24 | "^[./]" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /extension-old/README.md: -------------------------------------------------------------------------------- 1 | This is a [Plasmo extension](https://docs.plasmo.com/) project bootstrapped with [`plasmo init`](https://www.npmjs.com/package/plasmo). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | pnpm dev 9 | # or 10 | npm run dev 11 | ``` 12 | 13 | Open your browser and load the appropriate development build. For example, if you are developing for the chrome browser, using manifest v3, use: `build/chrome-mv3-dev`. 14 | 15 | You can start editing the popup by modifying `popup.tsx`. It should auto-update as you make changes. To add an options page, simply add a `options.tsx` file to the root of the project, with a react component default exported. Likewise to add a content page, add a `content.ts` file to the root of the project, importing some module and do some logic, then reload the extension on your browser. 16 | 17 | For further guidance, [visit our Documentation](https://docs.plasmo.com/) 18 | 19 | ## Making production build 20 | 21 | Run the following: 22 | 23 | ```bash 24 | pnpm build 25 | # or 26 | npm run build 27 | ``` 28 | 29 | This should create a production bundle for your extension, ready to be zipped and published to the stores. 30 | 31 | ## Submit to the webstores 32 | 33 | The easiest way to deploy your Plasmo extension is to use the built-in [bpp](https://bpp.browser.market) GitHub action. Prior to using this action however, make sure to build your extension and upload the first version to the store to establish the basic credentials. Then, simply follow [this setup instruction](https://docs.plasmo.com/framework/workflows/submit) and you should be on your way for automated submission! 34 | -------------------------------------------------------------------------------- /extension-old/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/extension-old/assets/icon.png -------------------------------------------------------------------------------- /extension-old/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/extension-old/bun.lockb -------------------------------------------------------------------------------- /extension-old/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "treehacks", 3 | "displayName": "Treehacks", 4 | "version": "0.0.1", 5 | "description": "A basic Plasmo extension.", 6 | "author": "Plasmo Corp. ", 7 | "scripts": { 8 | "dev": "plasmo dev", 9 | "build": "plasmo build", 10 | "package": "plasmo package" 11 | }, 12 | "dependencies": { 13 | "plasmo": "0.84.2", 14 | "react": "18.2.0", 15 | "react-dom": "18.2.0" 16 | }, 17 | "devDependencies": { 18 | "@ianvs/prettier-plugin-sort-imports": "4.1.1", 19 | "@types/chrome": "0.0.258", 20 | "@types/node": "20.11.5", 21 | "@types/react": "18.2.48", 22 | "@types/react-dom": "18.2.18", 23 | "autoprefixer": "^10.4.17", 24 | "postcss": "^8.4.35", 25 | "prettier": "3.2.4", 26 | "tailwindcss": "^3.4.1", 27 | "typescript": "5.3.3" 28 | }, 29 | "manifest": { 30 | "host_permissions": [ 31 | "https://*/*" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /extension-old/popup.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | 3 | import "./style.css" 4 | 5 | function IndexPopup() { 6 | const [data, setData] = useState("") 7 | 8 | return ( 9 |
10 |

11 | Welcome to your{" "} 12 | 13 | Plasmo 14 | {" "} 15 | Extension! 16 |

17 | setData(e.target.value)} value={data} /> 18 | 19 | View Docs 20 | 21 |
22 | ) 23 | } 24 | 25 | export default IndexPopup 26 | -------------------------------------------------------------------------------- /extension-old/postcss.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('postcss').ProcessOptions} 3 | */ 4 | module.exports = { 5 | plugins: { 6 | tailwindcss: {}, 7 | autoprefixer: {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /extension-old/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /extension-old/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | mode: "jit", 4 | darkMode: "class", 5 | content: ["./**/*.tsx"], 6 | plugins: [] 7 | } 8 | -------------------------------------------------------------------------------- /extension-old/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "plasmo/templates/tsconfig.base", 3 | "exclude": [ 4 | "node_modules" 5 | ], 6 | "include": [ 7 | ".plasmo/index.d.ts", 8 | "./**/*.ts", 9 | "./**/*.tsx" 10 | ], 11 | "compilerOptions": { 12 | "paths": { 13 | "~*": [ 14 | "./*" 15 | ] 16 | }, 17 | "baseUrl": "." 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /extract.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Optional 3 | 4 | import dotenv 5 | import together 6 | from pydantic import BaseModel, ValidationError 7 | from tenacity import (retry, stop_after_attempt, wait_random, 8 | wait_random_exponential) 9 | 10 | model = "mistralai/Mixtral-8x7B-Instruct-v0.1" 11 | 12 | 13 | class Person(BaseModel): 14 | school: str 15 | major: str 16 | background: str 17 | name: str 18 | interests: str 19 | 20 | 21 | @retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(5)) 22 | def extract_person(name: str, msg: str) -> Person: 23 | print(f"Extracting person: {name}") 24 | prompt = f"""Given the following intro message: 25 | 26 | Name: "{name}" 27 | Message: "{msg}" 28 | Please extract the following properties for this person. 29 | interface Response {{ 30 | // If a property is not known, put empty string 31 | school: string; // eg. University of Michigan, University of Waterloo 32 | name: string; // eg. John Doe, Jane Smith 33 | major: string; // eg. Computer Science 34 | background: string; // optimize for embedding search, remove punctuation, keep keywords, remove people's names, only keep relevant information. remove emojis. 35 | interests: string; // optimize for embedding search: remove punctuation, keep keywords, remove other people's names, only keep relevant information 36 | }} 37 | 38 | Please return your answer in the form of a JSON object conforming to the Typescript interface definition ONLY. DO NOT change the format of the JSON object. Make sure to include school, name, major, background, interests. DO NOT include any other information in your response. DO NOT include links. Your response: 39 | """ 40 | 41 | for _ in range(5): # Retry up to 5 times 42 | try: 43 | generation = together.Complete.create( 44 | max_tokens=256, 45 | stop=["\n\n"], 46 | temperature=0.5, 47 | top_k=10, 48 | prompt=prompt, 49 | model=model 50 | ) 51 | 52 | raw_json = generation['output']['choices'][0]['text'] 53 | # Extract the {} from the string 54 | raw_json = raw_json[raw_json.find("{"): raw_json.rfind("}") + 1] 55 | 56 | person = Person.model_validate_json(raw_json) 57 | person.name = name 58 | return person 59 | except ValidationError: 60 | print(f"Validation error, retrying generation: {raw_json}") 61 | continue # If validation error occurs, retry the generation 62 | 63 | raise Exception("Failed to generate valid person after 3 attempts") 64 | -------------------------------------------------------------------------------- /forms/.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | *.py[cod] 3 | .web 4 | __pycache__/ -------------------------------------------------------------------------------- /forms/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/forms/assets/favicon.ico -------------------------------------------------------------------------------- /forms/forms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/forms/forms/__init__.py -------------------------------------------------------------------------------- /forms/forms/forms.py: -------------------------------------------------------------------------------- 1 | from rxconfig import config 2 | import reflex as rx 3 | import json 4 | import os 5 | 6 | class FormState(rx.State): 7 | form_data: dict = {} 8 | 9 | @staticmethod 10 | def handle_submit(form_data: dict): 11 | """Handle the form submit.""" 12 | # Save form_data to the class attribute 13 | FormState.form_data = form_data 14 | # Call the function to save data to a JSON file 15 | FormState.save_to_json(form_data) 16 | 17 | @staticmethod 18 | def save_to_json(data: dict): 19 | """Save submitted data to a JSON file.""" 20 | json_filename = f"{config.app_name}/submissions.json" 21 | if not os.path.isfile(json_filename): 22 | with open(json_filename, "w") as file: 23 | json.dump([], file) # Create the file with an empty list 24 | 25 | with open(json_filename, "r+") as file: 26 | submissions = json.load(file) 27 | submissions.append(data) 28 | file.seek(0) 29 | file.truncate() # Clear the file before writing the updated submissions list 30 | json.dump(submissions, file, indent=4) 31 | 32 | def form_example(): 33 | state = FormState() 34 | 35 | return rx.vstack( 36 | rx.form( 37 | rx.vstack( 38 | rx.input( 39 | placeholder="First Name", 40 | name="first_name", 41 | ), 42 | rx.input( 43 | placeholder="Last Name", 44 | name="last_name", 45 | ), 46 | rx.hstack( 47 | rx.checkbox("Checked", name="check"), 48 | rx.switch("Switched", name="switch"), 49 | ), 50 | rx.button("Submit", type="submit"), 51 | ), 52 | on_submit=lambda form_data: state.handle_submit(form_data), 53 | reset_on_submit=True, 54 | ), 55 | rx.divider(), 56 | rx.heading("Results"), 57 | # Since form_data is a dictionary, we need to properly format it for display 58 | rx.text(json.dumps(FormState.form_data, indent=2)), 59 | ) 60 | 61 | app = rx.App() 62 | app.add_page(form_example) -------------------------------------------------------------------------------- /forms/forms/submissions.json: -------------------------------------------------------------------------------- 1 | [ 2 | -------------------------------------------------------------------------------- /forms/requirements.txt: -------------------------------------------------------------------------------- 1 | reflex==0.4.0 2 | -------------------------------------------------------------------------------- /forms/rxconfig.py: -------------------------------------------------------------------------------- 1 | import reflex as rx 2 | 3 | config = rx.Config( 4 | app_name="forms", 5 | ) -------------------------------------------------------------------------------- /graph/.gitignore: -------------------------------------------------------------------------------- 1 | bundle.js 2 | .vercel 3 | -------------------------------------------------------------------------------- /graph/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/bun.lockb -------------------------------------------------------------------------------- /graph/data.js: -------------------------------------------------------------------------------- 1 | const nodes = [ 2 | { 3 | id: "1", 4 | data: { 5 | name: "John Doe", 6 | interests: ["AI", "Blockchain"], 7 | school: "University of Waterloo", 8 | }, 9 | }, 10 | { 11 | id: "2", 12 | data: { 13 | name: "Jane Doe", 14 | interests: ["Backend"], 15 | school: "Stanford University", 16 | }, 17 | }, 18 | { 19 | id: "3", 20 | data: { 21 | name: "James Doe", 22 | interests: ["iOS", "Frontend"], 23 | school: "MIT", 24 | }, 25 | }, 26 | { 27 | id: "4", 28 | data: { 29 | name: "Jill Doe", 30 | interests: ["Design", "Entrepreneurship"], 31 | school: "UC Berkeley", 32 | }, 33 | }, 34 | { 35 | id: "5", 36 | data: { 37 | name: "Jack Doe", 38 | interests: ["Data Science"], 39 | school: "Stanford University", 40 | }, 41 | }, 42 | { 43 | id: "6", 44 | data: { 45 | name: "Jenny Doe", 46 | interests: ["Design", "Frontend"], 47 | school: "University of Waterloo", 48 | }, 49 | }, 50 | { 51 | id: "7", 52 | data: { 53 | name: "Jared Doe", 54 | interests: ["Backend", "Blockchain"], 55 | school: "UC Berkeley", 56 | }, 57 | }, 58 | { 59 | id: "8", 60 | data: { 61 | name: "Jasmine Doe", 62 | interests: ["iOS", "Data Science"], 63 | school: "MIT", 64 | }, 65 | }, 66 | { 67 | id: "9", 68 | data: { 69 | name: "Jasper Doe", 70 | interests: ["AI", "Entrepreneurship"], 71 | school: "University of Waterloo", 72 | }, 73 | }, 74 | ]; 75 | 76 | const links = [ 77 | { source: "1", target: "2" }, 78 | { source: "1", target: "3" }, 79 | { source: "1", target: "4" }, 80 | { source: "1", target: "5" }, 81 | { source: "1", target: "6" }, 82 | { source: "1", target: "7" }, 83 | { source: "1", target: "8" }, 84 | { source: "1", target: "9" }, 85 | ]; 86 | 87 | module.exports = { 88 | nodes, 89 | links, 90 | }; 91 | -------------------------------------------------------------------------------- /graph/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/favicon.ico -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-Black.otf -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-Black.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-Bold.otf -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-Bold.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-Light.otf -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-Light.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-Medium.otf -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-Medium.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-Regular.otf -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-Regular.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-SemiBold.otf -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-SemiBold.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-Thin.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-Thin.otf -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-Thin.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-UltraBlack.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-UltraBlack.otf -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-UltraBlack.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-UltraBlack.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-UltraLight.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-UltraLight.otf -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMono-UltraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMono-UltraLight.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMonoVariableVF.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMonoVariableVF.ttf -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/GeistMonoVariableVF.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist.Mono/GeistMonoVariableVF.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist.Mono/LICENSE.TXT: -------------------------------------------------------------------------------- 1 | Geist Sans and Geist Mono Font 2 | (C) 2023 Vercel, made in collaboration with basement.studio 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is available with a FAQ at: http://scripts.sil.org/OFL and copied below 6 | 7 | ----------------------------------------------------------- 8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 9 | ----------------------------------------------------------- 10 | 11 | PREAMBLE 12 | The goals of the Open Font License (OFL) are to stimulate worldwide 13 | development of collaborative font projects, to support the font creation 14 | efforts of academic and linguistic communities, and to provide a free and 15 | open framework in which fonts may be shared and improved in partnership 16 | with others. 17 | 18 | The OFL allows the licensed fonts to be used, studied, modified and 19 | redistributed freely as long as they are not sold by themselves. The 20 | fonts, including any derivative works, can be bundled, embedded, 21 | redistributed and/or sold with any software provided that any reserved 22 | names are not used by derivative works. The fonts and derivatives, 23 | however, cannot be released under any other type of license. The 24 | requirement for fonts to remain under this license does not apply 25 | to any document created using the fonts or their derivatives. 26 | 27 | DEFINITIONS 28 | "Font Software" refers to the set of files released by the Copyright 29 | Holder(s) under this license and clearly marked as such. This may 30 | include source files, build scripts and documentation. 31 | 32 | "Reserved Font Name" refers to any names specified as such after the 33 | copyright statement(s). 34 | 35 | "Original Version" refers to the collection of Font Software components as 36 | distributed by the Copyright Holder(s). 37 | 38 | "Modified Version" refers to any derivative made by adding to, deleting, 39 | or substituting -- in part or in whole -- any of the components of the 40 | Original Version, by changing formats or by porting the Font Software to a 41 | new environment. 42 | 43 | "Author" refers to any designer, engineer, programmer, technical 44 | writer or other person who contributed to the Font Software. 45 | 46 | PERMISSION AND CONDITIONS 47 | Permission is hereby granted, free of charge, to any person obtaining 48 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 49 | redistribute, and sell modified and unmodified copies of the Font 50 | Software, subject to the following conditions: 51 | 52 | 1) Neither the Font Software nor any of its individual components, 53 | in Original or Modified Versions, may be sold by itself. 54 | 55 | 2) Original or Modified Versions of the Font Software may be bundled, 56 | redistributed and/or sold with any software, provided that each copy 57 | contains the above copyright notice and this license. These can be 58 | included either as stand-alone text files, human-readable headers or 59 | in the appropriate machine-readable metadata fields within text or 60 | binary files as long as those fields can be easily viewed by the user. 61 | 62 | 3) No Modified Version of the Font Software may use the Reserved Font 63 | Name(s) unless explicit written permission is granted by the corresponding 64 | Copyright Holder. This restriction only applies to the primary font name as 65 | presented to the users. 66 | 67 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 68 | Software shall not be used to promote, endorse or advertise any 69 | Modified Version, except to acknowledge the contribution(s) of the 70 | Copyright Holder(s) and the Author(s) or with their explicit written 71 | permission. 72 | 73 | 5) The Font Software, modified or unmodified, in part or in whole, 74 | must be distributed entirely under this license, and must not be 75 | distributed under any other license. The requirement for fonts to 76 | remain under this license does not apply to any document created 77 | using the Font Software. 78 | 79 | TERMINATION 80 | This license becomes null and void if any of the above conditions are 81 | not met. 82 | 83 | DISCLAIMER 84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 88 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 92 | OTHER DEALINGS IN THE FONT SOFTWARE. 93 | -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-Black.otf -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-Black.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-Bold.otf -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-Bold.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-Light.otf -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-Light.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-Medium.otf -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-Medium.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-Regular.otf -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-Regular.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-SemiBold.otf -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-SemiBold.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-Thin.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-Thin.otf -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-Thin.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-UltraBlack.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-UltraBlack.otf -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-UltraBlack.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-UltraBlack.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-UltraLight.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-UltraLight.otf -------------------------------------------------------------------------------- /graph/fonts/Geist/Geist-UltraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/Geist-UltraLight.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist/GeistVariableVF.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/GeistVariableVF.ttf -------------------------------------------------------------------------------- /graph/fonts/Geist/GeistVariableVF.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freeman-jiang/nexus/f00062a018d0e7d3eea219823c738bf692137747/graph/fonts/Geist/GeistVariableVF.woff2 -------------------------------------------------------------------------------- /graph/fonts/Geist/LICENSE.TXT: -------------------------------------------------------------------------------- 1 | Geist Sans and Geist Mono Font 2 | (C) 2023 Vercel, made in collaboration with basement.studio 3 | 4 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 5 | This license is available with a FAQ at: http://scripts.sil.org/OFL and copied below 6 | 7 | ----------------------------------------------------------- 8 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 9 | ----------------------------------------------------------- 10 | 11 | PREAMBLE 12 | The goals of the Open Font License (OFL) are to stimulate worldwide 13 | development of collaborative font projects, to support the font creation 14 | efforts of academic and linguistic communities, and to provide a free and 15 | open framework in which fonts may be shared and improved in partnership 16 | with others. 17 | 18 | The OFL allows the licensed fonts to be used, studied, modified and 19 | redistributed freely as long as they are not sold by themselves. The 20 | fonts, including any derivative works, can be bundled, embedded, 21 | redistributed and/or sold with any software provided that any reserved 22 | names are not used by derivative works. The fonts and derivatives, 23 | however, cannot be released under any other type of license. The 24 | requirement for fonts to remain under this license does not apply 25 | to any document created using the fonts or their derivatives. 26 | 27 | DEFINITIONS 28 | "Font Software" refers to the set of files released by the Copyright 29 | Holder(s) under this license and clearly marked as such. This may 30 | include source files, build scripts and documentation. 31 | 32 | "Reserved Font Name" refers to any names specified as such after the 33 | copyright statement(s). 34 | 35 | "Original Version" refers to the collection of Font Software components as 36 | distributed by the Copyright Holder(s). 37 | 38 | "Modified Version" refers to any derivative made by adding to, deleting, 39 | or substituting -- in part or in whole -- any of the components of the 40 | Original Version, by changing formats or by porting the Font Software to a 41 | new environment. 42 | 43 | "Author" refers to any designer, engineer, programmer, technical 44 | writer or other person who contributed to the Font Software. 45 | 46 | PERMISSION AND CONDITIONS 47 | Permission is hereby granted, free of charge, to any person obtaining 48 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 49 | redistribute, and sell modified and unmodified copies of the Font 50 | Software, subject to the following conditions: 51 | 52 | 1) Neither the Font Software nor any of its individual components, 53 | in Original or Modified Versions, may be sold by itself. 54 | 55 | 2) Original or Modified Versions of the Font Software may be bundled, 56 | redistributed and/or sold with any software, provided that each copy 57 | contains the above copyright notice and this license. These can be 58 | included either as stand-alone text files, human-readable headers or 59 | in the appropriate machine-readable metadata fields within text or 60 | binary files as long as those fields can be easily viewed by the user. 61 | 62 | 3) No Modified Version of the Font Software may use the Reserved Font 63 | Name(s) unless explicit written permission is granted by the corresponding 64 | Copyright Holder. This restriction only applies to the primary font name as 65 | presented to the users. 66 | 67 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 68 | Software shall not be used to promote, endorse or advertise any 69 | Modified Version, except to acknowledge the contribution(s) of the 70 | Copyright Holder(s) and the Author(s) or with their explicit written 71 | permission. 72 | 73 | 5) The Font Software, modified or unmodified, in part or in whole, 74 | must be distributed entirely under this license, and must not be 75 | distributed under any other license. The requirement for fonts to 76 | remain under this license does not apply to any document created 77 | using the Font Software. 78 | 79 | TERMINATION 80 | This license becomes null and void if any of the above conditions are 81 | not met. 82 | 83 | DISCLAIMER 84 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 86 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 87 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 88 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 89 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 90 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 91 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 92 | OTHER DEALINGS IN THE FONT SOFTWARE. 93 | -------------------------------------------------------------------------------- /graph/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Nexus 5 | 6 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /graph/index.js: -------------------------------------------------------------------------------- 1 | const NODE_COLOR = 0x059669; 2 | const NODE_SIZE = 15; 3 | const NODE_HOVER_COLOR = 0xffe213; 4 | const NODE_CONNECTION_COLOR = 0xe37622; 5 | const LINK_FROM_COLOR = 0x732196; 6 | const LINK_TO_COLOR = 0x82a8f5; 7 | const LINK_CONNECTION_FROM_COLOR = 0xffffff; 8 | const LINK_CONNECTION_TO_COLOR = 0xffe213; 9 | const SPRING_LENGTH = 110; 10 | const SPRING_COEFF = 0.00111; 11 | const GRAVITY = -42; 12 | const THETA = 0.8; 13 | const DRAG_COEFF = 0.154; 14 | const TIME_STEP = 2; 15 | 16 | var createSettingsView = require("config.pixel"); 17 | var query = require("query-string").parse(window.location.search.substring(1)); 18 | const json = query.treehacks 19 | ? require("./treehacksData.json") 20 | : require("./graphData.json"); 21 | var graph = getGraphFromQueryString(query); 22 | var renderGraph = require("ngraph.pixel"); 23 | // var addCurrentNodeSettings = require("./nodeSettings.js"); 24 | var THREE = require("three"); 25 | var createLayout = require("pixel.layout"); 26 | 27 | const layout = createLayout(graph); 28 | 29 | var renderer = renderGraph(graph, { 30 | node: () => { 31 | return { 32 | color: NODE_COLOR, 33 | size: NODE_SIZE, 34 | }; 35 | }, 36 | link: () => { 37 | return { 38 | fromColor: LINK_FROM_COLOR, 39 | toColor: LINK_TO_COLOR, 40 | }; 41 | }, 42 | }); 43 | 44 | var simulator = renderer.layout().simulator; 45 | simulator.springLength(SPRING_LENGTH); 46 | simulator.springCoeff(SPRING_COEFF); 47 | simulator.gravity(GRAVITY); 48 | simulator.theta(THETA); 49 | simulator.dragCoeff(DRAG_COEFF); 50 | simulator.timeStep(TIME_STEP); 51 | renderer.focus(); 52 | 53 | // var settingsView = createSettingsView(renderer); 54 | // var gui = settingsView.gui(); 55 | 56 | // var nodeSettings = addCurrentNodeSettings(gui, renderer); 57 | 58 | renderer.on("nodehover", showNodeDetails); 59 | renderer.on("nodeclick", resetNodeDetails); 60 | 61 | function showNodeDetails(node) { 62 | if (!node) return; 63 | 64 | // nodeSettings.setUI(node); 65 | resetNodeDetails(); 66 | 67 | var nodeUI = renderer.getNode(node.id); 68 | nodeUI.color = NODE_HOVER_COLOR; 69 | 70 | if (graph.getLinks(node.id)) { 71 | graph.getLinks(node.id).forEach(function (link) { 72 | var toNode = link.toId === node.id ? link.fromId : link.toId; 73 | var toNodeUI = renderer.getNode(toNode); 74 | toNodeUI.color = NODE_CONNECTION_COLOR; 75 | 76 | var linkUI = renderer.getLink(link.id); 77 | linkUI.fromColor = LINK_CONNECTION_FROM_COLOR; 78 | linkUI.toColor = LINK_CONNECTION_TO_COLOR; 79 | }); 80 | } 81 | showNodePanel(node); 82 | } 83 | 84 | function resetNodeDetails() { 85 | graph.forEachNode(function (node) { 86 | var nodeUI = renderer.getNode(node.id); 87 | nodeUI.color = NODE_COLOR; 88 | }); 89 | graph.forEachLink(function (link) { 90 | var linkUI = renderer.getLink(link.id); 91 | linkUI.fromColor = LINK_FROM_COLOR; 92 | linkUI.toColor = LINK_TO_COLOR; 93 | }); 94 | 95 | if (document.getElementById("nodePanel")) { 96 | document.getElementById("nodePanel").remove(); 97 | } 98 | 99 | showInitialNodePanel(); 100 | } 101 | 102 | function getGraphFromQueryString(query) { 103 | var graphGenerators = require("ngraph.generators"); 104 | var createGraph = graphGenerators[query.graph] || graphGenerators.grid; 105 | return query.graph 106 | ? createGraph(getNumber(query.n), getNumber(query.m), getNumber(query.k)) 107 | : populateGraph(); 108 | } 109 | 110 | function getNumber(string, defaultValue) { 111 | var number = parseFloat(string); 112 | return typeof number === "number" && !isNaN(number) 113 | ? number 114 | : defaultValue || 10; 115 | } 116 | 117 | function populateGraph() { 118 | var createGraph = require("ngraph.graph"); 119 | var g = createGraph(); 120 | 121 | // Extract the "nodes" and "links" from the JSON file 122 | var nodes = json.nodes; 123 | var links = json.links; 124 | 125 | nodes.forEach(function (node) { 126 | g.addNode(node.id, node.data); 127 | }); 128 | links.forEach(function (link) { 129 | g.addLink(link.source, link.target); 130 | }); 131 | 132 | return g; 133 | } 134 | 135 | function showNodePanel(node) { 136 | if (document.getElementById("nodePanel")) { 137 | document.getElementById("nodePanel").remove(); 138 | } 139 | var panel = document.createElement("div"); 140 | panel.style.position = "absolute"; 141 | panel.style.top = "0"; 142 | panel.style.left = "0"; 143 | panel.style.color = "white"; 144 | panel.style.padding = "10px"; 145 | panel.style.marginLeft = "20px"; 146 | panel.style.width = "300px"; 147 | panel.style.fontFamily = "Geist, sans-serif"; 148 | panel.style.maxHeight = "65%"; 149 | // panel.style.overflowY = "auto"; 150 | panel.id = "nodePanel"; 151 | panel.innerHTML = "

" + node.data.name + "

"; 152 | panel.innerHTML += "

" + node.data.school + "

"; 153 | if (node.data.major) { 154 | panel.innerHTML += "

Major: " + node.data.major + "

"; 155 | } 156 | 157 | if (node.data.interests) { 158 | panel.innerHTML += `

Interests: ${node.data.interests}

`; 159 | } 160 | 161 | if (node.data.background) { 162 | panel.innerHTML += `

Background: ${node.data.background}

`; 163 | } 164 | if (graph.getLinks(node.id)) { 165 | panel.innerHTML += `

Potential connections: ${ 166 | graph.getLinks(node.id).length 167 | }

`; 168 | panel.innerHTML += `

Top match:

`; 169 | var topMatch = document.createElement("div"); 170 | topMatch.style.display = "flex"; 171 | topMatch.style.flexDirection = "column"; 172 | topMatch.style.gap = "5px"; 173 | topMatch.style.marginBottom = "10px"; 174 | var link = graph.getLinks(node.id)[0]; 175 | var toNode = link.toId === node.id ? link.fromId : link.toId; 176 | var toNodeData = graph.getNode(toNode).data; 177 | topMatch.innerHTML = `${toNodeData.name}`; 178 | topMatch.addEventListener("click", function () { 179 | showNodeDetails(graph.getNode(toNode)); 180 | }); 181 | panel.appendChild(topMatch); 182 | } 183 | 184 | document.body.appendChild(panel); 185 | } 186 | 187 | function showInitialNodePanel() { 188 | var panel = document.createElement("div"); 189 | panel.style.position = "absolute"; 190 | panel.style.top = "0"; 191 | panel.style.left = "0"; 192 | panel.style.color = "white"; 193 | panel.style.padding = "10px"; 194 | panel.style.marginLeft = "20px"; 195 | panel.style.width = "300px"; 196 | panel.style.fontFamily = "Geist, sans-serif"; 197 | panel.id = "nodePanel"; 198 | panel.innerHTML = "

Hover over a node to see more details

"; 199 | document.body.appendChild(panel); 200 | } 201 | 202 | function leftInstructions() { 203 | var footer = document.createElement("div"); 204 | footer.style.position = "absolute"; 205 | footer.style.bottom = "0"; 206 | footer.style.left = "0"; 207 | footer.style.color = "grey"; 208 | footer.style.padding = "10px"; 209 | footer.style.marginLeft = "20px"; 210 | footer.style.fontFamily = "Geist, sans-serif"; 211 | footer.style.fontSize = "11px"; 212 | footer.innerHTML = 213 | "

W: Move forward

" + 214 | "

S: Move backward

" + 215 | "

A: Move left

" + 216 | "

D: Move right

" + 217 | "

R: Move up

" + 218 | "

F: Move down

"; 219 | document.body.appendChild(footer); 220 | } 221 | 222 | function rightInstructions() { 223 | var footer = document.createElement("div"); 224 | footer.style.position = "absolute"; 225 | footer.style.bottom = "0"; 226 | footer.style.left = "120px"; 227 | footer.style.color = "grey"; 228 | footer.style.padding = "10px"; 229 | footer.style.marginLeft = "20px"; 230 | footer.style.fontFamily = "Geist, sans-serif"; 231 | footer.style.fontSize = "11px"; 232 | footer.innerHTML = 233 | "

Q: Turn clockwise

" + 234 | "

E: Turn counter-clockwise

" + 235 | "

↑: Rotate up

" + 236 | "

↓: Rotate down

" + 237 | "

←: Rotate left

" + 238 | "

→: Rotate right

"; 239 | document.body.appendChild(footer); 240 | } 241 | 242 | function leftFooter() { 243 | leftInstructions(); 244 | rightInstructions(); 245 | } 246 | 247 | function rightFooter() { 248 | var footer = document.createElement("div"); 249 | footer.style.position = "absolute"; 250 | footer.style.bottom = "0"; 251 | footer.style.right = "0"; 252 | footer.style.color = "grey"; 253 | footer.style.padding = "10px"; 254 | footer.style.marginRight = "20px"; 255 | footer.style.fontFamily = "Geist, sans-serif"; 256 | footer.style.fontSize = "11px"; 257 | footer.innerHTML = 258 | "

Made with love at TreeHacks 

"; 259 | 260 | const location = query.treehacks ? "index.html" : "index.html?treehacks=true"; 261 | 262 | footer.innerHTML += ``; 265 | document.body.appendChild(footer); 266 | } 267 | 268 | function searchByNameOrSchool(nodes, query) { 269 | const resultIds = nodes 270 | .filter((node) => { 271 | const nameMatch = node.data.name 272 | .toLowerCase() 273 | .includes(query.toLowerCase()); 274 | const schoolMatch = node.data.school 275 | .toLowerCase() 276 | .includes(query.toLowerCase()); 277 | const interestMatch = node.data.interests 278 | .toLowerCase() 279 | .includes(query.toLowerCase()); 280 | return nameMatch || schoolMatch || interestMatch; 281 | }) 282 | .map((node) => node.id); 283 | 284 | return resultIds; 285 | } 286 | 287 | function updateNodePosition(nodeId) { 288 | // Example function to get the most up-to-date position of a node 289 | var nodeUI = renderer.getNode(nodeId); 290 | if (!nodeUI) return null; // Node not found 291 | 292 | // Assuming 'layout' is your layout engine that has the latest node positions 293 | var updatedPosition = layout.getNodePosition(nodeId); 294 | 295 | // Update the nodeUI's position if necessary 296 | // This step depends on whether your rendering library allows direct position updates 297 | nodeUI.position.x = updatedPosition.x; 298 | nodeUI.position.y = updatedPosition.y; 299 | nodeUI.position.z = updatedPosition.z; // If your layout is 3D 300 | 301 | return updatedPosition; 302 | } 303 | 304 | function focusOnNode(nodeId) { 305 | var position = updateNodePosition(nodeId); 306 | } 307 | 308 | function showSearchBar() { 309 | if (document.getElementById("searchBarContainer")) { 310 | document.getElementById("searchBarContainer").remove(); 311 | } 312 | 313 | var nodes = json.nodes; 314 | 315 | var searchBarContainer = document.createElement("div"); 316 | searchBarContainer.id = "searchBarContainer"; 317 | searchBarContainer.style.position = "absolute"; 318 | searchBarContainer.style.top = "20px"; 319 | searchBarContainer.style.right = "20px"; 320 | searchBarContainer.style.background = "rgba(255, 255, 255, 0.2)"; 321 | searchBarContainer.style.borderRadius = "12px"; 322 | searchBarContainer.style.border = "1px solid rgba(255, 255, 255, 0.18)"; 323 | searchBarContainer.style.backdropFilter = "blur(5px)"; 324 | searchBarContainer.style.padding = "20px"; 325 | searchBarContainer.style.width = "300px"; 326 | searchBarContainer.style.boxSizing = "border-box"; 327 | searchBarContainer.style.fontFamily = "'Geist', sans-serif"; 328 | searchBarContainer.style.display = "flex"; 329 | searchBarContainer.style.flexDirection = "column"; 330 | searchBarContainer.style.gap = "10px"; 331 | 332 | var input = document.createElement("input"); 333 | input.style.padding = "10px"; 334 | input.style.borderRadius = "8px"; 335 | input.style.border = "none"; 336 | input.style.background = "rgba(0, 0, 0, 0.4)"; 337 | input.style.borderRadius = "12px"; 338 | input.style.border = "1px solid rgba(255, 255, 255, 0.18)"; 339 | input.style.backdropFilter = "blur(5px)"; 340 | input.style.fontFamily = "'Geist', sans-serif"; 341 | input.style.outlineColor = "rgba(255, 255, 255, 0.1)"; 342 | input.style.color = "white"; 343 | 344 | var button = document.createElement("button"); 345 | button.style.fontFamily = "'Geist', sans-serif"; 346 | button.textContent = "Search"; 347 | button.style.color = "white"; 348 | button.style.padding = "10px"; 349 | button.style.borderRadius = "8px"; 350 | button.style.border = "none"; 351 | button.style.cursor = "pointer"; 352 | button.style.background = "rgba(0, 0, 0, 0.4)"; 353 | 354 | // Modify the input element to add an 'keyup' event listener 355 | input.addEventListener("keyup", function (event) { 356 | // Check if the pressed key is 'Enter' 357 | if (event.key === "Enter") { 358 | // Prevent the default action to avoid submitting a form if there is one 359 | event.preventDefault(); 360 | // Trigger the click event on the search button 361 | button.click(); 362 | } 363 | }); 364 | 365 | var resultsContainer = document.createElement("div"); 366 | resultsContainer.id = "resultsContainer"; 367 | resultsContainer.style.maxHeight = "150px"; 368 | resultsContainer.style.overflowY = "auto"; 369 | resultsContainer.style.marginTop = "10px"; 370 | resultsContainer.style.display = "flex"; 371 | resultsContainer.style.flexDirection = "column"; 372 | resultsContainer.style.gap = "5px"; 373 | resultsContainer.style.color = "white"; 374 | 375 | searchBarContainer.appendChild(input); 376 | searchBarContainer.appendChild(button); 377 | searchBarContainer.appendChild(resultsContainer); 378 | 379 | document.body.appendChild(searchBarContainer); 380 | 381 | button.addEventListener("click", function () { 382 | resultsContainer.innerHTML = ""; 383 | 384 | var query = input.value ? input.value : "Matthew"; 385 | var matchingIndexes = searchByNameOrSchool(nodes, query); 386 | 387 | matchingIndexes.forEach((index) => { 388 | var node = nodes.find((node) => node.id === index); 389 | if (node) { 390 | var result = document.createElement("div"); 391 | result.innerHTML = `${node.data.name}
${node.data.school}
`; 392 | resultsContainer.appendChild(result); 393 | result.style.cursor = "pointer"; 394 | 395 | result.addEventListener("click", function () { 396 | var nodePosition = layout.getNodePosition 397 | ? layout.getNodePosition(node.id) 398 | : { x: 0, y: 0, z: 0 }; 399 | focusOnNode(node.id); 400 | showNodeDetails(node); 401 | console.log(renderer.camera()); 402 | console.log(nodePosition); 403 | }); 404 | } 405 | }); 406 | 407 | if (matchingIndexes.length === 0) { 408 | resultsContainer.innerHTML = "
No results found
"; 409 | } 410 | }); 411 | } 412 | 413 | function intersect(from, to, r) { 414 | var dx = from.x - to.x; 415 | var dy = from.y - to.y; 416 | var dz = from.z - to.z; 417 | var r1 = Math.sqrt(dx * dx + dy * dy + dz * dz); 418 | var teta = Math.acos(dz / r1); 419 | var phi = Math.atan2(dy, dx); 420 | 421 | return { 422 | x: r * Math.sin(teta) * Math.cos(phi) + to.x, 423 | y: r * Math.sin(teta) * Math.sin(phi) + to.y, 424 | z: r * Math.cos(teta) + to.z, 425 | }; 426 | } 427 | 428 | function flyTo(camera, to, radius) { 429 | // if (!to || to.x === undefined || to.y === undefined || to.z === undefined) { 430 | // console.error('Invalid target position:', to); 431 | // return; // Exit if 'to' is not a valid object 432 | // } 433 | 434 | // var from = { 435 | // x: camera.position.x, 436 | // y: camera.position.y, 437 | // z: camera.position.z, 438 | // }; 439 | 440 | // var cameraOffset = radius / Math.tan(Math.PI / 180.0 * camera.fov * 0.5); 441 | // var cameraEndPos = intersect(from, to, cameraOffset); 442 | 443 | // if (!cameraEndPos) { 444 | // console.error('Failed to calculate camera end position.'); 445 | // return; // Exit if 'cameraEndPos' is undefined 446 | // } 447 | 448 | camera.position.set(0, 0, 0); 449 | // camera.lookAt(new THREE.Vector3(to.x, to.y, to.z)); 450 | } 451 | 452 | showSearchBar(); 453 | 454 | showInitialNodePanel(); 455 | leftFooter(); 456 | rightFooter(); 457 | -------------------------------------------------------------------------------- /graph/nodeSettings.js: -------------------------------------------------------------------------------- 1 | module.exports = createNodeSettings; 2 | 3 | function createNodeSettings(gui, renderer) { 4 | var nodeSettings = gui.addFolder("Current Node"); 5 | var currentNode = { 6 | id: "", 7 | color: 0, 8 | size: 0, 9 | isPinned: false, 10 | }; 11 | 12 | nodeSettings.add(currentNode, "id"); 13 | nodeSettings.addColor(currentNode, "color").onChange(setColor); 14 | nodeSettings.add(currentNode, "size", 0, 200).onChange(setSize); 15 | nodeSettings.add(currentNode, "isPinned").onChange(setPinned); 16 | 17 | return { 18 | setUI: setUI, 19 | }; 20 | 21 | function setUI(nodeUI) { 22 | if (nodeUI) { 23 | currentNode.id = nodeUI.id; 24 | currentNode.color = nodeUI.color; 25 | currentNode.size = nodeUI.size; 26 | var layout = renderer.layout(); 27 | if (layout && layout.pinNode) { 28 | currentNode.isPinned = layout.pinNode(nodeUI.id); 29 | } 30 | } else { 31 | currentNode.id = ""; 32 | currentNode.color = 0; 33 | currentNode.size = 0; 34 | currentNode.isPinned = false; 35 | } 36 | gui.update(); 37 | } 38 | 39 | function setColor() { 40 | var node = renderer.getNode(currentNode.id); 41 | if (node) { 42 | node.color = currentNode.color; 43 | renderer.focus(); 44 | } 45 | } 46 | 47 | function setSize() { 48 | var node = renderer.getNode(currentNode.id); 49 | if (node) { 50 | node.size = currentNode.size; 51 | renderer.focus(); 52 | } 53 | } 54 | 55 | function setPinned() { 56 | if (!currentNode.id) return; 57 | 58 | var layout = renderer.layout(); 59 | if (layout.pinNode) { 60 | layout.pinNode(currentNode.id, currentNode.isPinned); 61 | } else { 62 | currentNode.isPinned = false; 63 | gui.update(); 64 | } 65 | renderer.focus(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /graph/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "config.pixel": "2.0.0", 4 | "ngraph.generators": "0.0.18", 5 | "query-string": "^1.0.0", 6 | "watchify": "^4.0.0" 7 | }, 8 | "scripts": { 9 | "start": "browserify index.js -o bundle.js", 10 | "dev": "watchify index.js -o bundle.js" 11 | }, 12 | "dependencies": { 13 | "browserify": "^17.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /graph/search/search.js: -------------------------------------------------------------------------------- 1 | var createGraph = require("ngraph.graph"); 2 | var g = createGraph(); 3 | 4 | var nodes = require("../data.js").nodes; 5 | var links = require("../data.js").links; 6 | 7 | function searchByNameOrSchool(nodes, query) { 8 | const resultIds = nodes 9 | .filter((node) => { 10 | const nameMatch = node.data.name 11 | .toLowerCase() 12 | .includes(query.toLowerCase()); 13 | const schoolMatch = node.data.school 14 | .toLowerCase() 15 | .includes(query.toLowerCase()); 16 | // const interestMatch = node.data.interests 17 | // .toLowerCase() 18 | // .includes(query.toLowerCase()); 19 | return nameMatch || schoolMatch; 20 | }) 21 | .map((node) => node.id); 22 | 23 | return resultIds; 24 | } 25 | 26 | var searchQuery = "Waterloo"; 27 | var matchingIds = searchByNameOrSchool(nodes, searchQuery); 28 | console.log(matchingIds); 29 | -------------------------------------------------------------------------------- /graph/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Geist"; 3 | src: url("fonts/Geist/Geist-Regular.woff2") format("woff2"); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: "Geist"; 10 | src: url("fonts/Geist/Geist-Bold.woff2") format("woff2"); 11 | font-weight: bold; 12 | font-style: normal; 13 | } 14 | 15 | // Semibold 16 | @font-face { 17 | font-family: "Geist"; 18 | src: url("fonts/Geist/Geist-SemiBold.woff2") format("woff2"); 19 | font-weight: 600; 20 | font-style: normal; 21 | } 22 | 23 | // Medium 24 | @font-face { 25 | font-family: "Geist"; 26 | src: url("fonts/Geist/Geist-Medium.woff2") format("woff2"); 27 | font-weight: 500; 28 | font-style: normal; 29 | } 30 | 31 | // Light 32 | @font-face { 33 | font-family: "Geist"; 34 | src: url("fonts/Geist/Geist-Light.woff2") format("woff2"); 35 | font-weight: 300; 36 | font-style: normal; 37 | } 38 | 39 | // Add mono 40 | @font-face { 41 | font-family: "Geist Mono"; 42 | src: url("fonts/Geist.Mono/GeistMono-Regular.woff2") format("woff2"); 43 | font-weight: normal; 44 | font-style: normal; 45 | } 46 | 47 | // Add bold mono 48 | @font-face { 49 | font-family: "Geist Mono"; 50 | src: url("fonts/Geist.Mono/GeistMono-Bold.woff2") format("woff2"); 51 | font-weight: bold; 52 | font-style: normal; 53 | } 54 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import time 4 | from importlib import metadata 5 | from typing import Sequence 6 | 7 | import dotenv 8 | import together 9 | 10 | import chromadb 11 | from chromadb import Documents, EmbeddingFunction, Embeddings 12 | from extract import Person, extract_person 13 | 14 | dotenv.load_dotenv() 15 | TOGETHER_API_KEY = os.getenv("TOGETHER_API_KEY") 16 | if not TOGETHER_API_KEY: 17 | raise ValueError("TOGETHER_API_KEY is not set") 18 | 19 | together.api_key = TOGETHER_API_KEY 20 | client = together.Together() 21 | 22 | 23 | class CustomTogetherEmbeddingFn(EmbeddingFunction): 24 | def __call__(self, input: Documents) -> Embeddings: 25 | return get_embeddings(input) 26 | 27 | 28 | def get_embeddings(texts: list[str]) -> list[Sequence[float]]: 29 | texts = [text.replace("\n", " ") for text in texts] 30 | outputs = client.embeddings.create( 31 | input=texts, model="togethercomputer/m2-bert-80M-2k-retrieval") 32 | return [outputs.data[i].embedding for i in range(len(texts))] 33 | 34 | 35 | chroma_client = chromadb.PersistentClient(path="chromadb") 36 | 37 | 38 | collection = chroma_client.get_or_create_collection( 39 | name="background_embeddings", embedding_function=CustomTogetherEmbeddingFn()) 40 | 41 | 42 | # Load a JSON array from tree-messages.json 43 | with open("messages-htn-calhacks.json", "r") as f: 44 | tree_messages = json.loads(f.read()) 45 | 46 | all_extracted: list[Person] = [] 47 | 48 | for i, message in enumerate(tree_messages): 49 | if len(message["String"]) < 150: 50 | print(f"Skipping message {i}, too short.") 51 | continue 52 | # Extract the name and message from the message 53 | name = message["Name"] 54 | msg = message["String"] 55 | 56 | # If person already exists in the database, skip 57 | ident = f"{name}_{i}" 58 | existing = collection.get(ids=[ident], include=["metadatas"]) 59 | if existing["ids"]: 60 | metadata = existing["metadatas"][0] 61 | person = Person( 62 | background=metadata['background'], 63 | interests=metadata['interests'], 64 | major=metadata['major'] if 'major' in metadata else "", 65 | name=metadata['name'] if 'name' in metadata else "", 66 | school=metadata['school'] if 'school' in metadata else "" 67 | ) 68 | all_extracted.append(person) 69 | print(f"Skipping {ident}, already exists.") 70 | continue 71 | 72 | # Extract the person 73 | person = extract_person(name, msg) 74 | all_extracted.append(person) 75 | 76 | # sleep every 5 iterations 77 | time.sleep(1) 78 | 79 | collection.upsert( 80 | ids=[ident], 81 | documents=[person.background + 82 | " . Interests: " + person.interests], 83 | metadatas=[{"name": name, "school": str(person.school), 84 | "interests": person.interests, "background": person.background, "major": person.major}] 85 | ) 86 | 87 | print( 88 | f"\nEmbedded person {i}: {person.name} ({person.school})",) 89 | print(person) 90 | 91 | time.sleep(1) 92 | 93 | # Write the extracted people to a file 94 | with open("people.json", "w") as f: 95 | f.write(json.dumps([person.model_dump() 96 | for person in all_extracted], indent=2)) 97 | 98 | # print(person) 99 | 100 | 101 | # collection = chroma_client.create_collection(name="my_collection") 102 | 103 | 104 | # collection.add( 105 | # documents=["This is a document", 106 | # "This is another document", 107 | # "I am currently a student (MBA) at Carnegie Mellon. My undergrad major is Computer Science. I worked for 4 years as a software engineer (full stack dev ). Participated in various hackathons such as RedHacks by Cornell, NASA Space App Challenge, and HackPrinceton. Very interested in AR/VR-related technology. I am very excited to create something great! Looking forward to creating something new in the healthcare or sustainability track.", # 3: VR healthcare 108 | # "I'm a current senior at Berkeley studying CS. My main technical passions are computer vision, ML, AR/VR, but I'm excited about solving all types of problems. My most fluent languages are Python, Java, and C++. I have a good amount of experience with full stack development across a couple different frameworks.", # 4: ML, AR/VR 109 | # "I have experience working in all sorts of technology stacks from frontend, backend, and also have experience working with LLM, and machine learning. I'm thinking about make a cool mobile app for the entertainment track.", # 5: LLM, ML 110 | # ], 111 | # ids=["1", "2", "3", "4", "5"] 112 | # ) 113 | 114 | 115 | # results = collection.query( 116 | # query_texts=[ 117 | # "LLM experience"], 118 | # n_results=2 119 | # ) 120 | 121 | # print(results) 122 | 123 | 124 | results = collection.query( 125 | query_texts=[ 126 | "I'm a current senior at Berkeley studying CS. My main technical passions are computer vision, ML, AR/VR, but I'm excited about solving all types of problems. My most fluent languages are Python, Java, and C++. I have a good amount of experience with full stack development across a couple different frameworks."], 127 | n_results=10 128 | ) 129 | 130 | print(results) 131 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | interface Response { 2 | // If a property is not known, it can be left out 3 | school?: string; // eg. University of Michigan, University of Waterloo 4 | name?: string; // eg. John Doe, Jane Smith 5 | year?: string; // eg. Sophomore, Senior, Graduate 6 | major?: string; // eg. Computer Science 7 | background: string; // optimize for embedding search: remove punctuation, keep keywords, remove other people's names, only keep relevant information 8 | interests: string; // optimize for embedding search: remove punctuation, keep keywords, remove other people's names, only keep relevant information 9 | } 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.9.3 2 | aiosignal==1.3.1 3 | annotated-types==0.6.0 4 | anyio==4.2.0 5 | asgiref==3.7.2 6 | attrs==23.2.0 7 | backoff==2.2.1 8 | bcrypt==4.1.2 9 | build==1.0.3 10 | cachetools==5.3.2 11 | certifi==2024.2.2 12 | charset-normalizer==3.3.2 13 | chroma-hnswlib==0.7.3 14 | chromadb==0.4.22 15 | click==8.1.7 16 | coloredlogs==15.0.1 17 | convex==0.5.0 18 | Deprecated==1.2.14 19 | fastapi==0.109.2 20 | filelock==3.13.1 21 | flatbuffers==23.5.26 22 | frozenlist==1.4.1 23 | fsspec==2024.2.0 24 | google-auth==2.28.0 25 | googleapis-common-protos==1.62.0 26 | grpcio==1.60.1 27 | h11==0.14.0 28 | httptools==0.6.1 29 | huggingface-hub==0.20.3 30 | humanfriendly==10.0 31 | idna==3.6 32 | importlib-metadata==6.11.0 33 | importlib-resources==6.1.1 34 | kubernetes==29.0.0 35 | mmh3==4.1.0 36 | monotonic==1.6 37 | mpmath==1.3.0 38 | multidict==6.0.5 39 | numpy==1.26.4 40 | oauthlib==3.2.2 41 | onnxruntime==1.17.0 42 | opentelemetry-api==1.22.0 43 | opentelemetry-exporter-otlp-proto-common==1.22.0 44 | opentelemetry-exporter-otlp-proto-grpc==1.22.0 45 | opentelemetry-instrumentation==0.43b0 46 | opentelemetry-instrumentation-asgi==0.43b0 47 | opentelemetry-instrumentation-fastapi==0.43b0 48 | opentelemetry-proto==1.22.0 49 | opentelemetry-sdk==1.22.0 50 | opentelemetry-semantic-conventions==0.43b0 51 | opentelemetry-util-http==0.43b0 52 | overrides==7.7.0 53 | packaging==23.2 54 | posthog==3.4.1 55 | protobuf==4.25.3 56 | pulsar-client==3.4.0 57 | pyasn1==0.5.1 58 | pyasn1-modules==0.3.0 59 | pydantic==2.6.1 60 | pydantic_core==2.16.2 61 | PyPika==0.48.9 62 | pyproject_hooks==1.0.0 63 | python-dateutil==2.8.2 64 | python-dotenv==1.0.1 65 | PyYAML==6.0.1 66 | requests==2.31.0 67 | requests-oauthlib==1.3.1 68 | rsa==4.9 69 | six==1.16.0 70 | sniffio==1.3.0 71 | sseclient-py==1.8.0 72 | starlette==0.36.3 73 | sympy==1.12 74 | tabulate==0.9.0 75 | tenacity==8.2.3 76 | together==0.2.11 77 | tokenizers==0.15.2 78 | tqdm==4.66.2 79 | typer==0.9.0 80 | typing_extensions==4.9.0 81 | urllib3==2.2.0 82 | uvicorn==0.27.1 83 | uvloop==0.19.0 84 | watchfiles==0.21.0 85 | websocket-client==1.7.0 86 | websockets==12.0 87 | wrapt==1.16.0 88 | yarl==1.9.4 89 | zipp==3.17.0 90 | --------------------------------------------------------------------------------