diff options
| author | polwex <polwex@sortug.com> | 2025-11-23 13:29:28 +0700 |
|---|---|---|
| committer | polwex <polwex@sortug.com> | 2025-11-23 13:29:28 +0700 |
| commit | ba2dbc660c229d3e86662d35513dfa7c904d9870 (patch) | |
| tree | afdc039ac31587be0a3d089d024222fb2023fbe9 | |
| parent | cb1b56f5a0eddbf77446f415f2beda57c8305f85 (diff) | |
74 files changed, 2379 insertions, 1664 deletions
diff --git a/packages/tweetdeck/bun.lock b/bun.lock index a51402d..2a8f986 100644 --- a/packages/tweetdeck/bun.lock +++ b/bun.lock @@ -2,16 +2,104 @@ "lockfileVersion": 1, "workspaces": { "": { + "name": "@sortug/sorlang", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + "packages/ai": { + "name": "@sortug/ai", + "version": "0.1.0", + "dependencies": { + "@anthropic-ai/sdk": "latest", + "@elevenlabs/elevenlabs-js": "^2.24.1", + "@google/genai": "latest", + "@sortug/langlib": "workspace:*", + "@sortug/lib": "workspace:*", + "groq-sdk": "latest", + "openai": "latest", + "playht": "latest", + "replicate": "latest", + }, + "devDependencies": { + "@types/bun": "latest", + "@types/mime-types": "^3.0.1", + }, + "peerDependencies": { + "typescript": "latest", + }, + }, + "packages/db": { + "name": "@sortug/sorlang-db", + "dependencies": { + "@sortug/langlib": "workspace:*", + "@sortug/lib": "workspace:*", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + "packages/langlib": { + "name": "@sortug/langlib", + "dependencies": { + "franc-all": "^7.2.0", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + "packages/prosody-ui": { + "name": "@sortug/prosody-ui", + "version": "0.1.0", + "dependencies": { + "@sortug/ai": "workspace:*", + "@sortug/langlib": "workspace:*", + "@sortug/lib": "workspace:*", + "@tabler/icons-react": "^3.35.0", + "franc-all": "^7.2.0", + "motion": "^12.11.3", + }, + "devDependencies": { + "@types/bun": "^1.3.2", + "@types/react": "^19.2.6", + }, + "peerDependencies": { + "react": ">=19.0.0", + "typescript": "^5.0.0", + }, + }, + "packages/sortug": { + "name": "@sortug/lib", + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + "packages/tweetdeck": { "name": "bun-react-tweetdeck", + "version": "0.1.0", "dependencies": { + "@sortug/ai": "workspace:*", + "@sortug/lib": "workspace:*", + "@sortug/prosody-ui": "workspace:*", + "@sortug/sorlang-db": "workspace:*", "bun_python": "^0.1.10", "lucide-react": "latest", "node-html-parser": "^7.0.1", - "prosody-ui": "file:../../libs/prosody-ui", "react": "^19", "react-dom": "^19", - "sortug": "file:../../libs/sortug", - "sortug-ai": "file:../../libs/models", + "react-hot-toast": "^2.6.0", }, "devDependencies": { "@types/bun": "latest", @@ -25,7 +113,7 @@ "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], - "@elevenlabs/elevenlabs-js": ["@elevenlabs/elevenlabs-js@2.24.1", "", { "dependencies": { "command-exists": "^1.2.9", "node-fetch": "^2.7.0", "ws": "^8.18.3" } }, "sha512-i6bDExgK9lYne1vLhy85JJ3O8bNi5vPTfcgq8kT3HG4+3rgkUJtg5UP29Mn1KONc4ZOeYUomzxJ820uLkT9z6g=="], + "@elevenlabs/elevenlabs-js": ["@elevenlabs/elevenlabs-js@2.25.0", "", { "dependencies": { "command-exists": "^1.2.9", "node-fetch": "^2.7.0", "ws": "^8.18.3" } }, "sha512-ySSGvjk1YwyywD7g6YuRY4L/KFLr2tYraaIw6Jlj1w+H4mqePo+Jzq2rsBcI5i71epzrw/51qXuAFp9qobZbuQ=="], "@google/genai": ["@google/genai@1.30.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.20.1" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-3MRcgczBFbUat1wIlZoLJ0vCCfXgm7Qxjh59cZi2X08RgWLtm9hKOspzp7TOg1TV2e26/MLxR2GR5yD5GmBV2w=="], @@ -59,9 +147,23 @@ "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], + "@sortug/ai": ["@sortug/ai@workspace:packages/ai"], + + "@sortug/langlib": ["@sortug/langlib@workspace:packages/langlib"], + + "@sortug/lib": ["@sortug/lib@workspace:packages/sortug"], + + "@sortug/prosody-ui": ["@sortug/prosody-ui@workspace:packages/prosody-ui"], + + "@sortug/sorlang-db": ["@sortug/sorlang-db@workspace:packages/db"], + + "@tabler/icons": ["@tabler/icons@3.35.0", "", {}, "sha512-yYXe+gJ56xlZFiXwV9zVoe3FWCGuZ/D7/G4ZIlDtGxSx5CGQK110wrnT29gUj52kEZoxqF7oURTk97GQxELOFQ=="], + + "@tabler/icons-react": ["@tabler/icons-react@3.35.0", "", { "dependencies": { "@tabler/icons": "3.35.0" }, "peerDependencies": { "react": ">= 16" } }, "sha512-XG7t2DYf3DyHT5jxFNp5xyLVbL4hMJYJhiSdHADzAjLRYfL7AnjlRfiHDHeXxkb2N103rEIvTsBRazxXtAUz2g=="], + "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - "@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="], + "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], "@types/mime-types": ["@types/mime-types@3.0.1", "", {}, "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ=="], @@ -91,8 +193,6 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="], - "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], @@ -103,7 +203,9 @@ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], - "bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="], + "bun-react-tweetdeck": ["bun-react-tweetdeck@workspace:packages/tweetdeck"], + + "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], "bun_python": ["bun_python@0.1.10", "", { "peerDependencies": { "typescript": "^5.7.3" } }, "sha512-6c5owYOI7lYI7lBbYX99L6SQ5dT4jsabsV8yKDX15zi3cRurl/nWO576L3KbSTpGUR8wqQ8TDGRS7Wqwg9gunQ=="], @@ -207,6 +309,8 @@ "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "goober": ["goober@2.1.18", "", { "peerDependencies": { "csstype": "^3.0.10" } }, "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw=="], + "google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="], "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], @@ -231,12 +335,6 @@ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], - - "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], - - "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], @@ -301,8 +399,6 @@ "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], - "prosody-ui": ["prosody-ui@file:../../libs/prosody-ui", { "dependencies": { "franc-all": "^7.2.0", "glotscript": "file:../glotscript", "motion": "^12.11.3", "sortug": "file:../sortug", "sortug-ai": "file:../models" }, "peerDependencies": { "react": ">=19.0.0", "react-dom": ">=19.0.0", "typescript": "^5.0.0" } }], - "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], @@ -311,6 +407,8 @@ "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], + "react-hot-toast": ["react-hot-toast@2.6.0", "", { "dependencies": { "csstype": "^3.1.3", "goober": "^2.1.16" }, "peerDependencies": { "react": ">=16", "react-dom": ">=16" } }, "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg=="], + "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="], @@ -331,10 +429,6 @@ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "sortug": ["@sortug/lib@file:../../libs/sortug", { "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }], - - "sortug-ai": ["@sortug/ai@file:../../libs/models", { "dependencies": { "@anthropic-ai/sdk": "latest", "@elevenlabs/elevenlabs-js": "^2.24.1", "@google/genai": "latest", "bcp-47": "^2.1.0", "franc-all": "^7.2.0", "groq-sdk": "latest", "iso-639-3": "file:../lang", "openai": "latest", "playht": "latest", "replicate": "latest", "sortug": "file:../:sortug" }, "devDependencies": { "@types/bun": "latest", "@types/mime-types": "^3.0.1" }, "peerDependencies": { "typescript": "latest" } }], - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -391,16 +485,6 @@ "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - "prosody-ui/glotscript": ["glotscript@file:../../libs/glotscript", {}], - - "prosody-ui/sortug": ["sortug@file:../../libs/sortug", {}], - - "prosody-ui/sortug-ai": ["sortug-ai@file:../../libs/models", {}], - - "sortug-ai/iso-639-3": ["iso-639-3@file:../../libs/lang", {}], - - "sortug-ai/sortug": ["sortug@file:../../libs/:sortug", {}], - "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], diff --git a/packages/ai/bun.lock b/packages/ai/bun.lock deleted file mode 100644 index 8cdbaaf..0000000 --- a/packages/ai/bun.lock +++ /dev/null @@ -1,361 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "@sortug/ai", - "dependencies": { - "@anthropic-ai/sdk": "latest", - "@elevenlabs/elevenlabs-js": "^2.24.1", - "@google/genai": "latest", - "bcp-47": "^2.1.0", - "franc-all": "^7.2.0", - "groq-sdk": "latest", - "iso-639-3": "file:../lang", - "openai": "latest", - "playht": "latest", - "replicate": "latest", - "sortug": "file:../sortug", - }, - "devDependencies": { - "@types/bun": "latest", - "@types/mime-types": "^3.0.1", - }, - "peerDependencies": { - "typescript": "latest", - }, - }, - }, - "packages": { - "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.70.1", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-AGEhifuvE22VxfQ5ROxViTgM8NuVQzEvqcN8bttR4AP24ythmNE/cL/SrOz79xiv7/osrsmCyErjsistJi7Z8A=="], - - "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], - - "@elevenlabs/elevenlabs-js": ["@elevenlabs/elevenlabs-js@2.24.1", "", { "dependencies": { "command-exists": "^1.2.9", "node-fetch": "^2.7.0", "ws": "^8.18.3" } }, "sha512-i6bDExgK9lYne1vLhy85JJ3O8bNi5vPTfcgq8kT3HG4+3rgkUJtg5UP29Mn1KONc4ZOeYUomzxJ820uLkT9z6g=="], - - "@google/genai": ["@google/genai@1.30.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.20.1" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-3MRcgczBFbUat1wIlZoLJ0vCCfXgm7Qxjh59cZi2X08RgWLtm9hKOspzp7TOg1TV2e26/MLxR2GR5yD5GmBV2w=="], - - "@grpc/grpc-js": ["@grpc/grpc-js@1.14.1", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-sPxgEWtPUR3EnRJCEtbGZG2iX8LQDUls2wUS3o27jg07KqJFMq6YDeWvMo1wfpmy3rqRdS0rivpLwhqQtEyCuQ=="], - - "@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], - - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], - - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - - "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], - - "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], - - "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], - - "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], - - "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], - - "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], - - "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], - - "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], - - "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], - - "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], - - "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - - "@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="], - - "@types/mime-types": ["@types/mime-types@3.0.1", "", {}, "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ=="], - - "@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], - - "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="], - - "@types/react": ["@types/react@19.2.6", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w=="], - - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], - - "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], - - "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], - - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - - "axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="], - - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - - "bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="], - - "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="], - - "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - - "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - - "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], - - "bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - - "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - - "command-exists": ["command-exists@1.2.9", "", {}, "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w=="], - - "cross-fetch": ["cross-fetch@4.1.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], - - "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - - "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], - - "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - - "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - - "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], - - "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], - - "file-type": ["file-type@18.7.0", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.2", "strtok3": "^7.0.0", "token-types": "^5.0.1" } }, "sha512-ihHtXRzXEziMrQ56VSgU7wkxh55iNchFkosu7Y9/S+tXHdKyrGjVK0ujbqNnsxzea+78MaLhN6PGmfYSAv1ACw=="], - - "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], - - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], - - "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="], - - "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="], - - "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], - - "franc-all": ["franc-all@7.2.0", "", { "dependencies": { "trigram-utils": "^2.0.0" } }, "sha512-ZR6ciLQTDBaOvBdkOd8+vqDzaLtmIXRa9GCzcAlaBpqNAKg9QrtClPmqiKac5/xZXfCZGMo1d8dIu1T0BLhHEg=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="], - - "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], - - "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="], - - "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "groq-sdk": ["groq-sdk@0.36.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-wvxl7i6QWxLcIfM00mQQybYk15OAXJG0NBBQuMDHrQ2vi68uz2RqFTBKUNfEOVz8Lwy4eAgQIPBEFW5P3cXybA=="], - - "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - - "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], - - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], - - "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], - - "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "iso-639-3": ["@sortug/lang@file:../lang", { "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }], - - "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], - - "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="], - - "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], - - "jws": ["jws@4.0.0", "", { "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg=="], - - "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], - - "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], - - "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - - "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "n-gram": ["n-gram@2.0.2", "", {}, "sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ=="], - - "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], - - "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - - "openai": ["openai@6.9.1", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-vQ5Rlt0ZgB3/BNmTa7bIijYFhz3YBceAA3Z4JuoMSBftBF9YqFHIEhZakSs+O/Ad7EaoEimZvHxD5ylRjN11Lg=="], - - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - - "peek-readable": ["peek-readable@5.4.2", "", {}, "sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg=="], - - "playht": ["playht@0.21.0", "", { "dependencies": { "@grpc/grpc-js": "^1.9.4", "axios": "^1.4.0", "cross-fetch": "^4.0.0", "deepmerge-ts": "^7.1.5", "file-type": "^18.5.0", "protobufjs": "^7.2.5", "tslib": "^2.1.0" } }, "sha512-63dWfsoGNOxfl91U3knrON4HcgtdPZ+e0Q3F8JX22T6dvX17i217lfw8cq1OzIBWVxpHms8ebhgUU/Gvs0/8Eg=="], - - "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], - - "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], - - "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - - "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], - - "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="], - - "replicate": ["replicate@1.4.0", "", { "optionalDependencies": { "readable-stream": ">=4.0.0" } }, "sha512-1ufKejfUVz/azy+5TnzQP7U1+MHVWZ6psnQ06az8byUUnRhT+DZ/MvewzB1NQYBVMgNKR7xPDtTwlcP5nv/5+w=="], - - "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], - - "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], - - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "sortug": ["@sortug/lib@file:../sortug", { "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }], - - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], - - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strtok3": ["strtok3@7.1.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^5.1.3" } }, "sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg=="], - - "token-types": ["token-types@5.0.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg=="], - - "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], - - "trigram-utils": ["trigram-utils@2.0.1", "", { "dependencies": { "collapse-white-space": "^2.0.0", "n-gram": "^2.0.0" } }, "sha512-nfWIXHEaB+HdyslAfMxSqWKDdmqY9I32jS7GnqpdWQnLH89r6A5sdk3fDVYqGAZ0CrT8ovAFSAo6HRiWcWNIGQ=="], - - "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], - - "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], - - "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - - "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], - - "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - - "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], - - "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - - "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], - - "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], - - "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - - "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - } -} diff --git a/packages/ai/src/nlp/index.ts b/packages/ai/src/nlp/index.ts index ebed586..0b71b69 100644 --- a/packages/ai/src/nlp/index.ts +++ b/packages/ai/src/nlp/index.ts @@ -1,6 +1,5 @@ import * as Spacy from "./spacy"; import * as Stanza from "./stanza"; -import * as ISO from "./iso"; import { ocr } from "./ocr"; import type * as Types from "./types"; export * from "./nlp"; diff --git a/packages/ai/src/nlp/spacy.ts b/packages/ai/src/nlp/spacy.ts index 829c77e..cdaff9e 100644 --- a/packages/ai/src/nlp/spacy.ts +++ b/packages/ai/src/nlp/spacy.ts @@ -1,5 +1,5 @@ import type { AsyncRes, Result } from "@sortug/lib"; -import { detectLang } from "./iso"; +import { detectLang } from "@sortug/langlib"; const ENDPOINT = "http://localhost:8102"; export async function run(text: string, langg?: string): AsyncRes<SpacyRes> { diff --git a/packages/ai/src/nlp/stanza.ts b/packages/ai/src/nlp/stanza.ts index 90fa1fc..a2892bf 100644 --- a/packages/ai/src/nlp/stanza.ts +++ b/packages/ai/src/nlp/stanza.ts @@ -1,5 +1,5 @@ import type { AsyncRes, Result } from "@sortug/lib"; -import { detectLang } from "./iso"; +import { detectLang } from "@sortug/langlib"; const ENDPOINT = "http://localhost:8102"; export async function segmenter( diff --git a/packages/db/bun.lock b/packages/db/bun.lock deleted file mode 100644 index cedc049..0000000 --- a/packages/db/bun.lock +++ /dev/null @@ -1,25 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "@sortug/sorlang-db", - "devDependencies": { - "@types/bun": "latest", - }, - "peerDependencies": { - "typescript": "^5", - }, - }, - }, - "packages": { - "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], - - "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], - - "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - } -} diff --git a/packages/db/index.ts b/packages/db/index.ts index ceecd52..c5660a0 100644 --- a/packages/db/index.ts +++ b/packages/db/index.ts @@ -1,3 +1,4 @@ import DB from "./src"; export default DB; +export { handler } from "./src/server"; diff --git a/packages/db/package.json b/packages/db/package.json index a121b42..fc2c534 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -7,5 +7,9 @@ }, "peerDependencies": { "typescript": "^5" + }, + "dependencies": { + "@sortug/lib": "workspace:*", + "@sortug/langlib": "workspace:*" } } diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index 6a89ee2..1425a43 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -1,39 +1,242 @@ -import { Database } from "bun:sqlite"; -import type { FullWordDataDB } from "./types"; +import { SQL } from "bun"; import { SRSQueries } from "./srs"; +// NEW API +// +// https://bun.com/docs/runtime/sql +// import { sql } from "bun"; + +// // Basic insert with direct values +// const [user] = await sql` +// INSERT INTO users (name, email) +// VALUES (${name}, ${email}) +// RETURNING * +// `; + +// // Using object helper for cleaner syntax +// const userData = { +// name: "Alice", +// email: "alice@example.com", +// }; + +// const [newUser] = await sql` +// INSERT INTO users ${sql(userData)} +// RETURNING * +// `; +// // Expands to: INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com') + +const DB_PATH = "/home/y/code/bun/sorlang/bulkdata/db.db"; +import type { FullWordDataDB } from "@sortug/langlib"; export class Queries { - db: Database; - srs: SRSQueries; + db: SQL; + ready = false; + // srs: SRSQueries; constructor() { - const db = new Database("/home/y/code/bun/sorlang/bulkdata/unified.db"); - db.exec("PRAGMA journal_mode = WAL"); // Enable Write-Ahead Logging for better performance - db.exec("PRAGMA foreign_keys = ON"); - db.exec("PRAGMA cache_size = -8000"); // Increase cache size to 8MB - db.exec("PRAGMA mmap_size = 30000000000"); - db.exec("PRAGMA temp_store = MEMORY"); // Store temp tables in memory - db.exec("PRAGMA synchronous = NORMAL"); // Slightly less safe but faster - + console.log("setting sorlang DB"); + const db = new SQL({ + adapter: "sqlite", + filename: DB_PATH, + create: false, + }); this.db = db; - this.srs = new SRSQueries(db); + // this.srs = new SRSQueries(db); + } + async init() { + try { + await this.db`PRAGMA journal_mode = WAL`; // Enable Write-Ahead Logging for better performance + await this.db`PRAGMA foreign_keys = ON`; + await this.db`PRAGMA cache_size = -8000`; // Increase cache size to 8MB + await this.db`PRAGMA mmap_size = 30000000000`; + await this.db`PRAGMA temp_store = MEMORY`; // Store temp tables in memory + await this.db`PRAGMA synchronous = NORMAL`; // Slightly less safe but faster + this.ready = true; + } catch (e) { + console.error("error starting db", e); + } + } + + async fetchExpressionById(id: number) { + const query = await this.db` + SELECT * FROM expressions WHERE id = ${id} + `; + return query; + } + // TODO word_phonetics is MANY TO ONE vs expressions, so it's possible for word_phonetics to lack entries for syllables + async fetchExpressionBySpelling( + spelling: string, + lang: string, + ): Promise<FullWordDataDB | null> { + const data = await this.db` +SELECT + json_object( + 'id', e.id, + 'spelling', e.spelling, + 'frequency', e.frequency, + 'phonetic', ( + SELECT json_object( + 'id', wp.id, + 'tone_sequence', wp.tone_sequence, + 'syl_seq', wp.syllable_sequence, + 'syllable_count', wp.syllable_count, + 'ipa', wp.ipa, + 'syllables', ( + SELECT json_group_array( + json_object( + 'stressed', CASE WHEN sw.stressed = 1 THEN json('true') ELSE json('false') END, + 'long', CASE WHEN s.long = 1 THEN json('true') ELSE json('false') END, + 'spelling', s.text, + 'ipa', s.ipa, + 'onset', ( + SELECT json_object('ipa', o.ipa, 'spelling', o.text) + FROM onsets o WHERE o.id = s.onset + ), + 'nucleus', ( + SELECT json_object('ipa', n.ipa, 'spelling', n.text) + FROM nucleus n WHERE n.id = s.nucleus + ), + 'medial', ( + SELECT json_object('ipa', m.ipa, 'spelling', m.text) + FROM medials m WHERE m.id = s.medial + ), + 'coda', ( + SELECT json_object('ipa', c.ipa, 'spelling', c.text) + FROM codas c WHERE c.id = s.coda + ), + 'rhyme', ( + SELECT json_object('ipa', r.ipa, 'spelling', r.text) + FROM rhymes r WHERE r.id = s.rhyme + ), + 'tone', ( + SELECT json_object('letters', t.ipa, 'numbers', t.nums, 'name', t.name) + FROM tones t WHERE t.id = s.tone + ) + ) + ) + FROM syllables_words sw + JOIN syllables s ON sw.syl_id = s.id + WHERE sw.word_id = wp.id + ORDER BY sw.idx ASC + ) + ) + FROM word_phonetics wp + WHERE wp.word_id = e.id + -- Select the most general pronunciation (sense_id IS NULL) or the first available + ORDER BY (wp.sense_id IS NULL) DESC, wp.id ASC + LIMIT 1 + ), + 'senses', ( + SELECT json_group_array( + json_object( + 'id', sn.id, + 'confidence', sn.confidence, + 'etymology', sn.etymology, + 'pos', sn.pos, + 'glosses', ( + SELECT json_group_array(sub.gloss) + FROM subsenses sub + WHERE sub.sid = sn.id + ), + 'examples', ( + SELECT json_group_array( + json_object( + 'example', ex.example, + 'ref', ex.ref + ) + ) + FROM examples ex + WHERE ex.sid = sn.id + ), + 'categories', ( + SELECT json_group_array(wc.category) + FROM word_categories wc + WHERE wc.word_id = sn.id + ), + 'derivation', ( + SELECT json_group_array( + json_object( + 'type', d.type, + 'text', d.text, + 'tags', json(d.tags) + ) + ) + FROM derivation d + WHERE d.sid = sn.id + ) + ) + ) + FROM senses sn + WHERE sn.parent_id = e.id + ) + ) AS full_word_data +FROM expressions e +WHERE e.spelling = ${spelling} AND e.lang = ${lang}; + `; + + if (data.length > 1) { + console.log({ spelling, lang, data }); + throw new Error("more rows than 1 wtf"); + } + if (data.length === 0) { + console.log({ spelling, lang, data }); + return null; + } + const row = data[0]; + const json = JSON.parse(row.full_word_data); + const phonetic = JSON.parse(json.phonetic); + const obj = { ...json, phonetic }; + return obj; } - fetchExpressionById(id: number) { - const query = this.db.query( - ` - SELECT * FROM expressions WHERE id = ? - `, - ); - return query.get(id) as any; + + // TODO combine with this old query to get both phonetic and semantic data + // Thing is the tables have changed, used to be that the senses table had JSONB columns for senses, forms and related but those are now proper tables now with one-to-many relation to the senses table + + async fetchSenses(spelling: string, lang: string) { + const rows = await this.db` + WITH sense_data AS ( + SELECT + s.*, + GROUP_CONCAT(DISTINCT ss.id || ':' || ss.gloss, '|') as subsenses_data, + GROUP_CONCAT(DISTINCT ex.id || ':' || ex.example || ':' || COALESCE(ex.ref, ''), '|') as examples_data, + GROUP_CONCAT(DISTINCT d.id || ':' || d.type || ':' || d.text, '|') as derivation_data, + GROUP_CONCAT(DISTINCT wc.category, '|') as categories_data + FROM senses s + LEFT JOIN subsenses ss ON ss.sid = s.id + LEFT JOIN examples ex ON ex.sid = s.id + LEFT JOIN derivation d ON d.sid = s.id + LEFT JOIN word_categories wc ON wc.word_id = s.id + GROUP BY s.id + ) + SELECT e.*, + (SELECT + json_group_array(json_object( + 'id', sd.id, + 'pos', sd.pos, + 'etymology', sd.etymology, + 'confidence', sd.confidence, + 'subsenses_data', sd.subsenses_data, + 'examples_data', sd.examples_data, + 'derivation_data', sd.derivation_data, + 'categories_data', sd.categories_data + )) + FROM sense_data sd + WHERE sd.parent_id = e.id + ) as senses_array + FROM expressions e + WHERE e.spelling = ${spelling} AND e.lang = ${lang} + ORDER BY e.frequency DESC`; + + return rows; } - fetchWordsByToneAndSyls1(tones: Array<string | null>) { + + // Tones and syls + async fetchWordsByToneAndSyls1(tones: Array<string | null>) { const toneString = tones .reduce((acc: string, item) => { if (!item) return `${acc},%`; else return `${acc},${item}`; }, "") .slice(1); - const query = this.db.query( - ` + const data = await this.db` WITH word_tone_sequences AS ( SELECT wp.ipa, @@ -48,21 +251,19 @@ export class Queries { ) SELECT * FROM word_tone_sequences - WHERE tone_sequence LIKE ? - AND syllable_count = ? - `, - ); - return query.all(toneString, tones.length) as any[]; + WHERE tone_sequence LIKE ${toneString} + AND syllable_count = ${tones.length} + `; + return data; } - fetchWordsByToneAndSylsO(tones: Array<string | null>) { + async fetchWordsByToneAndSylsO(tones: Array<string | null>) { const toneString = tones .reduce((acc: string, item) => { if (!item) return `${acc},%`; else return `${acc},${item}`; }, "") .slice(1); - const query = this.db.query( - ` + const data = await this.db` WITH word_tone_sequences AS ( SELECT w.id as word_id, @@ -87,25 +288,25 @@ export class Queries { tone_sequence, syllable_count FROM word_tone_sequences - WHERE tone_sequence LIKE ? - AND syllable_count = ? + WHERE tone_sequence LIKE ${toneString} + AND syllable_count = ${tones.length} ORDER BY frequency ASC NULLS LAST; - `, - ); + `; // TODO combine with this old query to get both phonetic and semantic data // Thing is the tables have changed, used to be that the senses table had JSONB columns for senses, forms and related but those are now proper tables now with one-to-many relation to the senses table - return query.all(toneString, tones.length) as any[]; + return data; } - fetchWordsByToneAndSyls(tones: Array<string | null>): FullWordDataDB[] { + async fetchWordsByToneAndSyls( + tones: Array<string | null>, + ): Promise<FullWordDataDB[]> { const toneString = tones .reduce((acc: string, item) => { if (!item) return `${acc},%`; else return `${acc},${item}`; }, "") .slice(1); - const query = this.db.query( - ` + const data = await this.db` WITH word_tone_sequences AS ( SELECT w.id as word_id, @@ -161,54 +362,14 @@ export class Queries { ) SELECT * FROM word_tone_sequences - WHERE tone_sequence LIKE ? - AND syllable_count = ? + WHERE tone_sequence LIKE ${toneString} + AND syllable_count = ${tones.length} ORDER BY frequency ASC NULLS LAST; - `, - ); + `; // TODO combine with this old query to get both phonetic and semantic data // Thing is the tables have changed, used to be that the senses table had JSONB columns for senses, forms and related but those are now proper tables now with one-to-many relation to the senses table - return query.all(toneString, tones.length) as any[]; - } - // TODO combine with this old query to get both phonetic and semantic data - // Thing is the tables have changed, used to be that the senses table had JSONB columns for senses, forms and related but those are now proper tables now with one-to-many relation to the senses table - - fetchSenses(spelling: string, lang: string) { - const query = this.db.query(` - WITH sense_data AS ( - SELECT - s.*, - GROUP_CONCAT(DISTINCT ss.id || ':' || ss.gloss, '|') as subsenses_data, - GROUP_CONCAT(DISTINCT ex.id || ':' || ex.example || ':' || COALESCE(ex.ref, ''), '|') as examples_data, - GROUP_CONCAT(DISTINCT d.id || ':' || d.type || ':' || d.text, '|') as derivation_data, - GROUP_CONCAT(DISTINCT wc.category, '|') as categories_data - FROM senses s - LEFT JOIN subsenses ss ON ss.sid = s.id - LEFT JOIN examples ex ON ex.sid = s.id - LEFT JOIN derivation d ON d.sid = s.id - LEFT JOIN word_categories wc ON wc.word_id = s.id - GROUP BY s.id - ) - SELECT e.*, - (SELECT - json_group_array(json_object( - 'id', sd.id, - 'pos', sd.pos, - 'etymology', sd.etymology, - 'confidence', sd.confidence, - 'subsenses_data', sd.subsenses_data, - 'examples_data', sd.examples_data, - 'derivation_data', sd.derivation_data, - 'categories_data', sd.categories_data - )) - FROM sense_data sd - WHERE sd.parent_id = e.id - ) as senses_array - FROM expressions e - WHERE e.spelling = ? AND e.lang = ? - ORDER BY e.frequency DESC`); - return query.all(spelling, lang); + return data; } } export default Queries; diff --git a/packages/db/src/schema.sql b/packages/db/src/schema.sql index c4a7c76..85d0d77 100644 --- a/packages/db/src/schema.sql +++ b/packages/db/src/schema.sql @@ -1,82 +1,11 @@ -/** - * UNIFIED DATABASE SCHEMA FOR THAI LANGUAGE LEARNING APPLICATION - * - * This file consolidates 3 redundant database schemas: - * - schema.sql (main schema with courses and SRS) - * - prosodyschema.sql (phonetic analysis with syllable breakdown) - * - senseschema.sql (semantic analysis with subsenses and derivations) - * - * REDUNDANCY ANALYSIS: - * ==================== - * - ** MAJOR CONFLICTS RESOLVED: - * 1. Languages table: - * - schema.sql: uses 'code' as PRIMARY KEY - * - prosodyschema.sql: uses 'iso6392' as PRIMARY KEY - * - RESOLVED: Unified with both code systems supported - * - * 2. Senses table: - * - schema.sql: includes ipa/prosody JSONB fields - * - senseschema.sql: missing phonetic fields - * - RESOLVED: Enhanced version with all fields - * - * 3. Word/Expression entities: - * - schema.sql: uses 'expressions' table - * - prosodyschema.sql: uses 'words' table - * - RESOLVED: Standardized on 'expressions' terminology - * - * 4. Categories tables: - * - EXACT DUPLICATES in schema.sql and senseschema.sql - * - RESOLVED: Keep one instance, remove duplicate - * - * UNIQUE FEATURES PRESERVED: - * ======================== - * - * FROM schema.sql (Main Course Learning): - * - User management: users, cookies - * - Course structure: lessons, cards, cards_lessons, cards_expressions - * - SRS tracking: user_progress, attempts - * - Bookmarks and user features - * - * FROM prosodyschema.sql (Phonetic Analysis): - * - Detailed syllable breakdown: tones, onsets, medials, nucleus, codas, rhymes - * - Phonetic patterns: word_phonetics, syllables_words - * - Rhyme analysis: word_rhymes, words_wrhymes - * - * FROM senseschema.sql (Semantic Analysis): - * - Detailed definitions: subsenses - * - Etymology tracking: derivation - * - * FOREIGN KEY RELATIONSHIPS: - * ========================= - * - Updated all references from 'words.id' to 'expressions.id' - * - Unified language references to use languages.code - * - Maintained proper cascade relationships - * - * BENEFITS OF UNIFICATION: - * ======================= - * 1. Single source of truth for database structure - * 2. Eliminated conflicting table definitions - * 3. Preserved all unique functionality - * 4. Improved foreign key consistency - * 5. Better support for comprehensive Thai language analysis - */ --- Enable foreign key support and performance optimizations PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL; PRAGMA cache_size = -2000; PRAGMA mmap_size = 30000000000; +PRAGMA busy_timeout = 5000; + -/** - * UNIFIED LANGUAGES TABLE - * - * RESOLVES CONFLICT between: - * - schema.sql: code (PRIMARY KEY), name, native_name - * - prosodyschema.sql: iso6392 (PRIMARY KEY), english - * - * NOW SUPPORTS multiple language code systems for maximum compatibility - */ CREATE TABLE IF NOT EXISTS languages ( code TEXT PRIMARY KEY, -- Primary language code (ISO 639-1 preferred) name TEXT NOT NULL, -- English name @@ -85,14 +14,7 @@ CREATE TABLE IF NOT EXISTS languages ( CONSTRAINT name_unique UNIQUE (name) ); -/** - * CORE CONTENT TABLES - * Standardized on 'expressions' terminology from schema.sql - * Enhanced with fields from both schemas - */ --- Main expressions table (formerly 'words' in prosodyschema.sql) --- UNIFIED from schema.sql expressions and prosodyschema.sql words CREATE TABLE IF NOT EXISTS expressions ( id INTEGER PRIMARY KEY AUTOINCREMENT, spelling TEXT NOT NULL, @@ -104,8 +26,6 @@ CREATE TABLE IF NOT EXISTS expressions ( CONSTRAINT spell_unique UNIQUE (spelling, lang) ); --- Enhanced senses table with phonetic capabilities --- MERGED from schema.sql senses (with ipa/prosody) and senseschema.sql senses CREATE TABLE IF NOT EXISTS senses ( id INTEGER PRIMARY KEY AUTOINCREMENT, parent_id INTEGER NOT NULL, @@ -153,14 +73,6 @@ CREATE TABLE IF NOT EXISTS derivation ( FOREIGN KEY (sid) REFERENCES senses(id) ON DELETE CASCADE ); -/** - * PHONETIC ANALYSIS TABLES (FROM prosodyschema.sql) - * - * Comprehensive syllable breakdown system for Thai language analysis - * These tables provide detailed phonetic analysis beyond basic JSONB fields - */ - - -- Syllable component tables CREATE TABLE IF NOT EXISTS tones ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -252,10 +164,11 @@ CREATE TABLE IF NOT EXISTS word_phonetics ( syllable_sequence TEXT NOT NULL, -- Comma-separated syllables stressed INTEGER, -- index of stressed syllable tone_sequence TEXT, -- Comma-separated tones - tag JSONB, -- Pattern/usage tag + tags TEXT, -- Usually dialect, array from wikisource, we join it with "/-/" notes TEXT, FOREIGN KEY (word_id) REFERENCES expressions(id), - FOREIGN KEY (sense_id) REFERENCES senses(id) + FOREIGN KEY (sense_id) REFERENCES senses(id), + CONSTRAINT phonetics_unique UNIQUE (ipa, tags) ); -- Rhyme analysis tables (UNIQUE to prosodyschema.sql) diff --git a/packages/db/src/server.ts b/packages/db/src/server.ts new file mode 100644 index 0000000..6479b7f --- /dev/null +++ b/packages/db/src/server.ts @@ -0,0 +1,66 @@ +import DB from "."; + +const db = new DB(); +const ready = db.init(); + +type Route = Partial< + Record< + Bun.Serve.HTTPMethod, + Bun.Serve.Handler<Bun.BunRequest, Bun.Server<undefined>, Response> + > +>; +export const handler: Route = { + async GET(req: Bun.BunRequest) { + await ready; + if (!db.ready) return Response.json({ error: "DB failed to initialize" }); + // const db = new DB(); + console.log("Handling HTTP Request on DB", req.url); + + try { + const url = new URL(req.url); + const params = url.searchParams; + const word = params.get("word"); + const lang = params.get("lang"); + if (!word) return Response.json({ error: "word param is required" }); + if (!lang) return Response.json({ error: "lang param is required" }); + const row = await db.fetchExpressionBySpelling(word, lang); + if (!row) return Response.json({ error: "No data found" }); + else return Response.json({ ok: row }); + } catch (e) { + return Response.json({ error: `${e}` }); + } + }, + async POST(req: Bun.BunRequest) { + await ready; + if (!db.ready) return Response.json({ error: "DB failed to initialize" }); + // const db = new DB(); + console.log("Handling HTTP Request on DB", req.url); + + try { + const reqBody = (await req.json()) as RequestBody; + if (!reqBody) return Response.json({ error: "No request body" }); + const returnData = + "getWordFull" in reqBody + ? db.fetchExpressionBySpelling( + reqBody.getWordFull.spelling, + reqBody.getWordFull.lang, + ) + : null; + const row = await returnData; + console.log({ row }); + if (!returnData || !row) return Response.json({ error: "No data found" }); + else return Response.json({ ok: row }); + } catch (e) { + return Response.json({ error: `${e}` }); + } + }, +}; + +type BySpelling = { spelling: string; lang: string }; +type ByLanguage = string; // iso6393 +type RequestBody = + | { getWordFull: BySpelling } + | { getWordsenses: BySpelling } + | { getWordPhonetics: BySpelling } + | { getLanguages: null } + | { getExpressions: ByLanguage }; diff --git a/packages/lang/CLAUDE.md b/packages/lang/CLAUDE.md deleted file mode 100644 index 1ee6890..0000000 --- a/packages/lang/CLAUDE.md +++ /dev/null @@ -1,106 +0,0 @@ - -Default to using Bun instead of Node.js. - -- Use `bun <file>` instead of `node <file>` or `ts-node <file>` -- Use `bun test` instead of `jest` or `vitest` -- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild` -- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` -- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>` -- Bun automatically loads .env, so don't use dotenv. - -## APIs - -- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`. -- `bun:sqlite` for SQLite. Don't use `better-sqlite3`. -- `Bun.redis` for Redis. Don't use `ioredis`. -- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`. -- `WebSocket` is built-in. Don't use `ws`. -- Prefer `Bun.file` over `node:fs`'s readFile/writeFile -- Bun.$`ls` instead of execa. - -## Testing - -Use `bun test` to run tests. - -```ts#index.test.ts -import { test, expect } from "bun:test"; - -test("hello world", () => { - expect(1).toBe(1); -}); -``` - -## Frontend - -Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind. - -Server: - -```ts#index.ts -import index from "./index.html" - -Bun.serve({ - routes: { - "/": index, - "/api/users/:id": { - GET: (req) => { - return new Response(JSON.stringify({ id: req.params.id })); - }, - }, - }, - // optional websocket support - websocket: { - open: (ws) => { - ws.send("Hello, world!"); - }, - message: (ws, message) => { - ws.send(message); - }, - close: (ws) => { - // handle close - } - }, - development: { - hmr: true, - console: true, - } -}) -``` - -HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle. - -```html#index.html -<html> - <body> - <h1>Hello, world!</h1> - <script type="module" src="./frontend.tsx"></script> - </body> -</html> -``` - -With the following `frontend.tsx`: - -```tsx#frontend.tsx -import React from "react"; - -// import .css files directly and it works -import './index.css'; - -import { createRoot } from "react-dom/client"; - -const root = createRoot(document.body); - -export default function Frontend() { - return <h1>Hello, world!</h1>; -} - -root.render(<Frontend />); -``` - -Then, run index.ts - -```sh -bun --hot ./index.ts -``` - -For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`. diff --git a/packages/lang/README.md b/packages/lang/README.md deleted file mode 100644 index dc185a1..0000000 --- a/packages/lang/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Langlib - -Utils to deal with languages and stuff. - -## Langcodes - -- ISO-639-1 are **two** characters. e.g. "en", "fr", "zh". -- ISO-639-2 are **three** characters. e.g. "eng", "fra", "jpn", "cnm". -- ISO-639-3 are also **three** characters. It's more exhasustive than ISO-639-2, supposedly covers every single language. -### BCP-47 -BCP-47 (_Best Current Practice_) is a wider tag that includes in one string language, script and region. -e.g. -- `en-US` -- `en-Latn-US` -- `zh-Hans-CN` -Format is `language-script-region-variant-extension` - -Region codes are ISO 3166-1 -Script tags are ISO 15924 -Variant tags aren't standardized. - -It may include all or some of these. - diff --git a/packages/lang/bun.lock b/packages/lang/bun.lock deleted file mode 100644 index cb5bacd..0000000 --- a/packages/lang/bun.lock +++ /dev/null @@ -1,64 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "lang", - "dependencies": { - "bcp-47": "^2.1.0", - "countryjs": "^1.8.0", - "franc-all": "^7.2.0", - }, - "devDependencies": { - "@types/bun": "latest", - }, - "peerDependencies": { - "typescript": "^5", - }, - }, - }, - "packages": { - "@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="], - - "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], - - "@types/react": ["@types/react@19.2.6", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w=="], - - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="], - - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - - "bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="], - - "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - - "countryjs": ["countryjs@1.8.0", "", { "dependencies": { "lodash": "^4.17.2", "minimatch": "^3.0.3", "require-all": "^2.2.0" } }, "sha512-dOgtXkpGirsknf/sV3qBGUGzN9Xlb6KlshtXwG4hYT8cPaPjuvsu/aKq88mQlXkLqRAbXwaHs8RlwJgF8FODAA=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "franc-all": ["franc-all@7.2.0", "", { "dependencies": { "trigram-utils": "^2.0.0" } }, "sha512-ZR6ciLQTDBaOvBdkOd8+vqDzaLtmIXRa9GCzcAlaBpqNAKg9QrtClPmqiKac5/xZXfCZGMo1d8dIu1T0BLhHEg=="], - - "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], - - "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], - - "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], - - "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], - - "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "n-gram": ["n-gram@2.0.2", "", {}, "sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ=="], - - "require-all": ["require-all@2.2.0", "", {}, "sha512-YWj/WNCxs+KxppuN3j11Ztqzl8MI/oWj4ERwEwgJ5gsHzWi8OAK7FepPu8MLv/Rn8Pov6aPdpRkaoO2Tb6m+zQ=="], - - "trigram-utils": ["trigram-utils@2.0.1", "", { "dependencies": { "collapse-white-space": "^2.0.0", "n-gram": "^2.0.0" } }, "sha512-nfWIXHEaB+HdyslAfMxSqWKDdmqY9I32jS7GnqpdWQnLH89r6A5sdk3fDVYqGAZ0CrT8ovAFSAo6HRiWcWNIGQ=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - } -} diff --git a/packages/lang/src/index.ts b/packages/lang/src/index.ts deleted file mode 100644 index 5ac22b2..0000000 --- a/packages/lang/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./iso"; - -export { getScriptPredictor, separateScript } from "./unicode"; diff --git a/packages/lang/test.ts b/packages/lang/test.ts deleted file mode 100644 index d1763ca..0000000 --- a/packages/lang/test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { franc, francAll } from "franc-all"; -import { getScriptPredictor } from "./src"; - -const a = "My name is John."; -const a2 = "My name is John"; -const b = "ของไทย"; -const b2 = "ของไทย"; -const c = "こんにちは"; -const c2 = "こんにちは"; -// -// This isn't working must be using some browser APIs to do the work I guess -const fa = franc("My name is John."); -const fa2 = francAll("My name is John"); -const fb = franc("ของไทย"); -const fb2 = francAll("ของไทย"); -const fc = franc("こんにちは"); -const fc2 = franc("こんにちは"); - -console.log({ fa, fa2, fb, fb2, fc, fc2 }); - -const Predictor = getScriptPredictor(); -const ua = Predictor(a); - -console.dir({ ua }, { depth: null }); diff --git a/packages/lang/.gitignore b/packages/langlib/.gitignore index a14702c..a14702c 100644 --- a/packages/lang/.gitignore +++ b/packages/langlib/.gitignore diff --git a/packages/langlib/README.md b/packages/langlib/README.md new file mode 100644 index 0000000..fe083f8 --- /dev/null +++ b/packages/langlib/README.md @@ -0,0 +1,15 @@ +# sortug + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.2.12. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/packages/langlib/index.ts b/packages/langlib/index.ts new file mode 100644 index 0000000..c000f81 --- /dev/null +++ b/packages/langlib/index.ts @@ -0,0 +1,6 @@ +export const wtf = "wtf"; +export * from "./src/unicode"; +export * from "./src/iso"; +export * from "./src/dbtypes"; +export * from "./src/types"; +export * from "./src/lang"; diff --git a/packages/lang/package.json b/packages/langlib/package.json index 49d50e1..d934090 100644 --- a/packages/lang/package.json +++ b/packages/langlib/package.json @@ -1,6 +1,6 @@ { "name": "@sortug/langlib", - "module": "src/index.ts", + "module": "index.ts", "type": "module", "devDependencies": { "@types/bun": "latest" diff --git a/packages/langlib/package.worked b/packages/langlib/package.worked new file mode 100644 index 0000000..4839506 --- /dev/null +++ b/packages/langlib/package.worked @@ -0,0 +1,11 @@ +{ + "name": "@sortug/langlib", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + } +} diff --git a/packages/langlib/src/dbtypes.ts b/packages/langlib/src/dbtypes.ts new file mode 100644 index 0000000..d86d32e --- /dev/null +++ b/packages/langlib/src/dbtypes.ts @@ -0,0 +1,69 @@ +export type ToneQuery = Array<string | null>; +export type MutationType = { change: string } | { keep: string }; +export type MutationOrder = MutationType[]; + +export const thaiTones: Record<string, string> = { + "˧": "mid", + "˨˩": "low", + "˥˩": "falling", + "˦˥": "high", + "˩˩˦": "rising", +}; +export const thaiToneNums: Record<string, number> = { + "˧": 33, + "˨˩": 21, + "˥˩": 41, + "˦˥": 45, + "˩˩˦": 214, +}; + +export type FullWordData = { + id: number; + spelling: string; + frequency: number; + phonetic: PhoneticData; + senses: Sense[]; +}; + +export type PhoneticData = { + word_id: number; + tone_sequence: string; // e.g. "1-4-3" + syl_seq: string; // e.g. "pre-si-dent" + syllable_count: number; + syllables: Syllable[]; + ipa: string; +}; +export type Sense = { + confidence: number; + examples: Example[]; + categories: string[]; + etymology: string; + derivation: Derivation[]; + pos: string; + glosses: string[]; +}; +export type Tone = { + letters: string; + numbers: number; + name: string; +}; + +export type Phoneme = { + ipa: string; + spelling: string; +}; +export type Syllable = { + stressed: boolean; + long: boolean; + spelling: string; + ipa: string; + nucleus: Phoneme; + onset: Phoneme; + medial: Phoneme; + coda: Phoneme; + rhyme: Phoneme; + tone: Tone; +}; + +export type Example = { ref: string; text: string }; +export type Derivation = { type: string; text: string; tags: any }; diff --git a/packages/lang/src/iso/index.ts b/packages/langlib/src/iso/index.ts index 60467e0..60467e0 100644 --- a/packages/lang/src/iso/index.ts +++ b/packages/langlib/src/iso/index.ts diff --git a/packages/lang/src/iso/iso15924.ts b/packages/langlib/src/iso/iso15924.ts index dcaa42d..dcaa42d 100644 --- a/packages/lang/src/iso/iso15924.ts +++ b/packages/langlib/src/iso/iso15924.ts diff --git a/packages/lang/src/iso/iso6393-to-1.js b/packages/langlib/src/iso/iso6393-to-1.js index ed818f9..ed818f9 100644 --- a/packages/lang/src/iso/iso6393-to-1.js +++ b/packages/langlib/src/iso/iso6393-to-1.js diff --git a/packages/lang/src/iso/iso6393-to-2b.js b/packages/langlib/src/iso/iso6393-to-2b.js index c65f0a1..c65f0a1 100644 --- a/packages/lang/src/iso/iso6393-to-2b.js +++ b/packages/langlib/src/iso/iso6393-to-2b.js diff --git a/packages/lang/src/iso/iso6393-to-2t.js b/packages/langlib/src/iso/iso6393-to-2t.js index ab84590..ab84590 100644 --- a/packages/lang/src/iso/iso6393-to-2t.js +++ b/packages/langlib/src/iso/iso6393-to-2t.js diff --git a/packages/lang/src/iso/iso6393.js b/packages/langlib/src/iso/iso6393.js index 6e728de..6e728de 100644 --- a/packages/lang/src/iso/iso6393.js +++ b/packages/langlib/src/iso/iso6393.js diff --git a/packages/lang/src/lang/index.ts b/packages/langlib/src/lang/index.ts index f566672..f566672 100644 --- a/packages/lang/src/lang/index.ts +++ b/packages/langlib/src/lang/index.ts diff --git a/packages/lang/src/types.ts b/packages/langlib/src/types.ts index 57e46b4..b9b163e 100644 --- a/packages/lang/src/types.ts +++ b/packages/langlib/src/types.ts @@ -48,3 +48,5 @@ export type ISO_6393_3 = { iso6392T?: string; iso6391?: string; }; + +export * from "./dbtypes"; diff --git a/packages/lang/src/unicode/index.ts b/packages/langlib/src/unicode/index.ts index bf8821d..71736d4 100644 --- a/packages/lang/src/unicode/index.ts +++ b/packages/langlib/src/unicode/index.ts @@ -1,25 +1,24 @@ // Author: Amir Hossein Kargaran (TypeScript port) // Date: March, 2025 -import { ISO_15924_CODE } from "../iso"; +import type { ISO_15924_CODE } from "../iso"; // Description: This code detects/separates the script(s) (writing system(s)) of the given text. // TypeScript port of the original Python implementation. // Types -type ScriptRange = [number, number]; -type ScriptRanges = Record<string, ScriptRange[]>; - -type ScoredScript = [ISO_15924_CODE | null, number, ScriptDetails]; - interface ScriptDetails { - details: Record<ISO_15924_CODE, number> | null; + details: Map<ISO_15924_CODE, number> | null; tie: boolean | null; interval: number | null; } +type ScriptRange = [number, number]; +type ScriptRanges = Partial<Record<ISO_15924_CODE, ScriptRange[]>>; + +type ScoredScript = [ISO_15924_CODE | null, number, ScriptDetails]; // The SCRIPT_RANGES object contains Unicode ranges for different scripts -export const SCRIPT_RANGES: ScriptRanges = { +const SCRIPT_RANGES: ScriptRanges = { Latn: [ [65, 90], [97, 122], @@ -1226,15 +1225,16 @@ export function getScriptPredictor( replaceDigits: boolean = true, ): (sent: string) => ScoredScript { // Create a map of code points to script names - const histMap: Map<number, Set<string>> = new Map(); + const histMap: Map<number, Set<ISO_15924_CODE>> = new Map(); for (const [key, ranges] of Object.entries(SCRIPT_RANGES)) { + const k = key as ISO_15924_CODE; for (const [start, end] of ranges) { for (let ordinal = start; ordinal <= end; ordinal++) { if (!histMap.has(ordinal)) { histMap.set(ordinal, new Set()); } - histMap.get(ordinal)!.add(key); + histMap.get(ordinal)!.add(k); } } } @@ -1266,7 +1266,7 @@ export function getScriptPredictor( } // Count characters by script - const scriptCount: Map<string, number> = new Map(); + const scriptCount: Map<ISO_15924_CODE, number> = new Map(); for (const char of filteredText) { const ordinal = char.codePointAt(0)!; @@ -1278,14 +1278,14 @@ export function getScriptPredictor( } // Convert to sorted object for details - const sortedScores: Record<string, number> = {}; + const scoreMap: Map<ISO_15924_CODE, number> = new Map(); for (const [script, count] of scriptCount.entries()) { - sortedScores[script] = count / filteredText.length; + scoreMap.set(script, count / filteredText.length); } // Find the script with maximum score let maxScore = 0; - let maxScript: string | null = null; + let maxScript: ISO_15924_CODE | null = null; for (const [script, count] of scriptCount.entries()) { const score = count / filteredText.length; @@ -1296,21 +1296,18 @@ export function getScriptPredictor( } // Sort scores for details - const sortedEntries = Object.entries(sortedScores).sort( + const sortedEntries = Array.from(scoreMap.entries()).sort( (a, b) => b[1] - a[1], ); - const sortedDetails: Record<string, number> = - Object.fromEntries(sortedEntries); - // Calculate interval and check for ties if (sortedEntries.length > 1) { - const secondScore = sortedEntries[1][1]; + const secondScore = sortedEntries[1]![1]; const interval = maxScore - secondScore; return [ maxScript, maxScore, { - details: sortedDetails, + details: scoreMap, tie: interval === 0, interval: interval, }, @@ -1321,7 +1318,7 @@ export function getScriptPredictor( maxScript, maxScore, { - details: sortedDetails, + details: scoreMap, tie: false, interval: 1, }, @@ -1406,12 +1403,10 @@ export function testSeparateScript(): void { `Error: '${key}' script not found in detected scripts.`, ); - const detectedTokens = detected[key] - .split(" ") + const detectedTokens = detected[key]!.split(" ") .map((x) => x.trim()) .filter((x) => x.length > 0); - const groundTruthTokens = groundTruth[key] - .split(" ") + const groundTruthTokens = groundTruth[key]!.split(" ") .map((x) => x.trim()) .filter((x) => x.length > 0); diff --git a/packages/lang/tsconfig.json b/packages/langlib/tsconfig.json index bfa0fea..9c62f74 100644 --- a/packages/lang/tsconfig.json +++ b/packages/langlib/tsconfig.json @@ -3,7 +3,7 @@ // Environment setup & latest features "lib": ["ESNext"], "target": "ESNext", - "module": "Preserve", + "module": "ESNext", "moduleDetection": "force", "jsx": "react-jsx", "allowJs": true, @@ -19,7 +19,6 @@ "skipLibCheck": true, "noFallthroughCasesInSwitch": true, "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, // Some stricter flags (disabled by default) "noUnusedLocals": false, diff --git a/packages/prosody-ui/bun.lock b/packages/prosody-ui/bun.lock deleted file mode 100644 index c276ddd..0000000 --- a/packages/prosody-ui/bun.lock +++ /dev/null @@ -1,318 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "prosody-ui", - "dependencies": { - "franc-all": "^7.2.0", - "glotscript": "file:../glotscript", - "motion": "^12.11.3", - "sortug": "file:../sortug", - "sortug-ai": "file:../models", - }, - "devDependencies": { - "@types/bun": "^1.3.2", - "@types/react": "^19.2.6", - }, - "peerDependencies": { - "react": ">=19.0.0", - "typescript": "^5.0.0", - }, - }, - }, - "packages": { - "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.36.3", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-+c0mMLxL/17yFZ4P5+U6bTWiCSFZUKJddrv01ud2aFBWnTPLdRncYV76D3q1tqfnL7aCnhRtykFnoCFzvr4U3Q=="], - - "@google/genai": ["@google/genai@0.13.0", "", { "dependencies": { "google-auth-library": "^9.14.2", "ws": "^8.18.0", "zod": "^3.22.4", "zod-to-json-schema": "^3.22.4" } }, "sha512-eaEncWt875H7046T04mOpxpHJUM+jLIljEf+5QctRyOeChylE/nhpwm1bZWTRWoOu/t46R9r+PmgsJFhTpE7tQ=="], - - "@google/generative-ai": ["@google/generative-ai@0.21.0", "", {}, "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg=="], - - "@grpc/grpc-js": ["@grpc/grpc-js@1.13.3", "", { "dependencies": { "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-FTXHdOoPbZrBjlVLHuKbDZnsTxXv2BlHF57xw6LuThXacXvtkahEPED0CKMk6obZDf65Hv4k3z62eyPNpvinIg=="], - - "@grpc/proto-loader": ["@grpc/proto-loader@0.7.15", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ=="], - - "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], - - "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], - - "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], - - "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], - - "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], - - "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], - - "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], - - "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], - - "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], - - "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], - - "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], - - "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - - "@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="], - - "@types/node": ["@types/node@22.13.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg=="], - - "@types/node-fetch": ["@types/node-fetch@2.6.12", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.0" } }, "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA=="], - - "@types/react": ["@types/react@19.2.6", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w=="], - - "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], - - "agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="], - - "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], - - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - - "axios": ["axios@1.9.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg=="], - - "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - - "bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="], - - "bignumber.js": ["bignumber.js@9.3.0", "", {}, "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA=="], - - "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - - "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], - - "bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - - "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - - "cross-fetch": ["cross-fetch@4.1.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], - - "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], - - "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], - - "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - - "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], - - "file-type": ["file-type@18.7.0", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.2", "strtok3": "^7.0.0", "token-types": "^5.0.1" } }, "sha512-ihHtXRzXEziMrQ56VSgU7wkxh55iNchFkosu7Y9/S+tXHdKyrGjVK0ujbqNnsxzea+78MaLhN6PGmfYSAv1ACw=="], - - "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], - - "form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="], - - "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="], - - "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="], - - "framer-motion": ["framer-motion@12.11.3", "", { "dependencies": { "motion-dom": "^12.11.2", "motion-utils": "^12.9.4", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-ksUtDFBZtrbQFt4bEMFrFgo7camhmXcLeuylKQxEYSd9czkZ4tZmFROxWczWeu51WqC2m91ifpvgGCBLd0uviQ=="], - - "franc-all": ["franc-all@7.2.0", "", { "dependencies": { "trigram-utils": "^2.0.0" } }, "sha512-ZR6ciLQTDBaOvBdkOd8+vqDzaLtmIXRa9GCzcAlaBpqNAKg9QrtClPmqiKac5/xZXfCZGMo1d8dIu1T0BLhHEg=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "gaxios": ["gaxios@6.7.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" } }, "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ=="], - - "gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="], - - "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "glotscript": ["glotscript@file:../glotscript", { "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5.0.0" } }], - - "google-auth-library": ["google-auth-library@9.15.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", "jws": "^4.0.0" } }, "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng=="], - - "google-logging-utils": ["google-logging-utils@0.0.2", "", {}, "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "groq-sdk": ["groq-sdk@0.15.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-aYDEdr4qczx3cLCRRe+Beb37I7g/9bD5kHF+EEDxcrREWw1vKoRcfP3vHEkJB7Ud/8oOuF0scRwDpwWostTWuQ=="], - - "gtoken": ["gtoken@7.1.0", "", { "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" } }, "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - - "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], - - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], - - "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], - - "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], - - "iso-639-3": ["iso-639-3@3.0.1", "", {}, "sha512-SdljCYXOexv/JmbQ0tvigHN43yECoscVpe2y2hlEqy/CStXQlroPhZLj7zKLRiGqLJfw8k7B973UAMDoQczVgQ=="], - - "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], - - "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], - - "jws": ["jws@4.0.0", "", { "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg=="], - - "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], - - "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - - "motion": ["motion@12.11.3", "", { "dependencies": { "framer-motion": "^12.11.3", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-R9t8IYJ5hSl+Ao5rj6XGS4lJN+fXQstcpwKOcFA5aWjlwjf3IHcHr8DUjPV0My6T/5ZCQ1jqh0pmjggO4zUpEA=="], - - "motion-dom": ["motion-dom@12.11.2", "", { "dependencies": { "motion-utils": "^12.9.4" } }, "sha512-wZ396XNNTI9GOkyrr80wFSbZc1JbIHSHTbLdririSbkEgahWWKmsHzsxyxqBBvuBU/iaQWVu1YCjdpXYNfo2yQ=="], - - "motion-utils": ["motion-utils@12.9.4", "", {}, "sha512-BW3I65zeM76CMsfh3kHid9ansEJk9Qvl+K5cu4DVHKGsI52n76OJ4z2CUJUV+Mn3uEP9k1JJA3tClG0ggSrRcg=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "n-gram": ["n-gram@2.0.2", "", {}, "sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ=="], - - "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], - - "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], - - "openai": ["openai@4.98.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-TmDKur1WjxxMPQAtLG5sgBSCJmX7ynTsGmewKzoDwl1fRxtbLOsiR0FA/AOAAtYUmP6azal+MYQuOENfdU+7yg=="], - - "peek-readable": ["peek-readable@5.4.2", "", {}, "sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg=="], - - "playht": ["playht@0.16.0", "", { "dependencies": { "@grpc/grpc-js": "^1.9.4", "axios": "^1.4.0", "cross-fetch": "^4.0.0", "file-type": "^18.5.0", "protobufjs": "^7.2.5", "tslib": "^2.1.0" } }, "sha512-gwKqGcmUwrd3NaG6B2z5RZCjxPM0CI915Bmej+GXWZU2PSdN2g4hXsDMnjts+uakLaqGEY8YaIqNokyYH7SnvQ=="], - - "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], - - "protobufjs": ["protobufjs@7.5.2", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-f2ls6rpO6G153Cy+o2XQ+Y0sARLOZ17+OGVLHrc3VUKcLHYKEKWbkSujdBWQXM7gKn5NTfp0XnRPZn1MIu8n9w=="], - - "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - - "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], - - "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], - - "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], - - "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="], - - "replicate": ["replicate@1.0.1", "", { "optionalDependencies": { "readable-stream": ">=4.0.0" } }, "sha512-EY+rK1YR5bKHcM9pd6WyaIbv6m2aRIvHfHDh51j/LahlHTLKemTYXF6ptif2sLa+YospupAsIoxw8Ndt5nI3vg=="], - - "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], - - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - - "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "sortug": ["sortug@file:../sortug", { "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }], - - "sortug-ai": ["models@file:../models", { "dependencies": { "@anthropic-ai/sdk": "^0.36.3", "@google/genai": "^0.13.0", "@google/generative-ai": "^0.21.0", "bcp-47": "^2.1.0", "franc-all": "^7.2.0", "groq-sdk": "^0.15.0", "iso-639-3": "^3.0.1", "openai": "^4.84.0", "playht": "^0.16.0", "replicate": "^1.0.1", "sortug": "file://home/y/code/npm/sortug" }, "devDependencies": { "@types/bun": "^1.2.12" }, "peerDependencies": { "typescript": "^5.7.3" } }], - - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], - - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strtok3": ["strtok3@7.1.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^5.1.3" } }, "sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg=="], - - "token-types": ["token-types@5.0.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg=="], - - "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], - - "trigram-utils": ["trigram-utils@2.0.1", "", { "dependencies": { "collapse-white-space": "^2.0.0", "n-gram": "^2.0.0" } }, "sha512-nfWIXHEaB+HdyslAfMxSqWKDdmqY9I32jS7GnqpdWQnLH89r6A5sdk3fDVYqGAZ0CrT8ovAFSAo6HRiWcWNIGQ=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - - "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], - - "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - - "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], - - "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - - "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - - "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="], - - "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - - "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], - - "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - - "zod": ["zod@3.24.4", "", {}, "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg=="], - - "zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="], - - "@anthropic-ai/sdk/@types/node": ["@types/node@18.19.100", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA=="], - - "groq-sdk/@types/node": ["@types/node@18.19.100", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA=="], - - "openai/@types/node": ["@types/node@18.19.100", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA=="], - - "sortug-ai/@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="], - - "sortug-ai/sortug": ["sortug@file:../../../npm/sortug", { "devDependencies": { "@types/bun": "latest" }, "peerDependencies": { "typescript": "^5" } }], - - "@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], - - "groq-sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], - - "openai/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], - - "sortug-ai/@types/bun/bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="], - } -} diff --git a/packages/prosody-ui/index.ts b/packages/prosody-ui/index.ts index f7339a2..2c00fbe 100644 --- a/packages/prosody-ui/index.ts +++ b/packages/prosody-ui/index.ts @@ -1,3 +1,6 @@ +import "./src/styles/styles.css"; +import "@sortug/lib/src/styles.css"; + import LangText from "./src/LangText.tsx"; import FontChanger from "./src/fonts/FontChanger.tsx"; import Paragraph from "./src/Paragraph.tsx"; diff --git a/packages/prosody-ui/package.json b/packages/prosody-ui/package.json index cf9ad48..0902fbd 100644 --- a/packages/prosody-ui/package.json +++ b/packages/prosody-ui/package.json @@ -8,11 +8,12 @@ "react": ">=19.0.0" }, "dependencies": { - "franc-all": "^7.2.0", - "motion": "^12.11.3", - "@sortug/lib": "workspace:*", + "@sortug/ai": "workspace:*", "@sortug/langlib": "workspace:*", - "@sortug/ai": "workspace:*" + "@sortug/lib": "workspace:*", + "@tabler/icons-react": "^3.35.0", + "franc-all": "^7.2.0", + "motion": "^12.11.3" }, "private": true, "devDependencies": { diff --git a/packages/prosody-ui/src/LangText.tsx b/packages/prosody-ui/src/LangText.tsx index 790c499..ab9d4f4 100644 --- a/packages/prosody-ui/src/LangText.tsx +++ b/packages/prosody-ui/src/LangText.tsx @@ -2,27 +2,31 @@ import { franc } from "franc-all"; import React, { useEffect, useState } from "react"; import ThaiText from "./thai/ThaiText"; import { ColoredText } from "./components/Sentence"; -import type { AnalyzeRes, WordData } from "./logic/types"; +import type { AnalyzeRes, ColorTheme, WordData } from "./logic/types"; import { detectScript, scriptFromLang } from "./logic/utils"; import LatinText from "./latin/LatinText"; import { buildWiktionaryURL, parseWiktionary } from "./logic/wiki"; -import type { Result } from "sortug"; +import FullWord from "./components/word/FullWordData"; +import type { AsyncRes, Result } from "@sortug/lib"; +import type { FullWordData } from "@sortug/langlib"; export default function LangText({ text, lang, theme, - fetchWiki, handleWord, + handleError, }: { text: string; + theme?: ColorTheme; lang?: string; - theme?: string; - fetchWiki?: (url: string) => Promise<string>; - handleWord?: (wd: Result<WordData>) => any; + handleWord?: (word: AnalyzeRes) => any; + handleError?: (error: string) => any; }) { + const background: ColorTheme = theme ? theme : "light"; const [llang, setLang] = useState(""); const [script, setScript] = useState(scriptFromLang(lang || "", text)); + const [modal, setWordModal] = useState<FullWordData | null>(null); useEffect(() => { if (!lang) { const res = franc(text); @@ -31,7 +35,27 @@ export default function LangText({ }, [text]); console.log("langtext", { text, llang, script }); - async function openWord(word: string) { + async function openWord(word: AnalyzeRes) { + if (handleWord) handleWord(word); + else { + const body = JSON.stringify({ + getWordFull: { spelling: word.word, lang: llang }, + }); + const opts = { + method: "POST", + body, + headers: { "Content-type": "application/json" }, + }; + const res = await fetch("/api/db", opts); + const j = (await res.json()) as Result<FullWordData>; + console.log({ j }); + if ("error" in j) { + if (handleError) handleError(j.error); + else console.error("error opening word", j.error); + } else { + setWordModal(j.ok); + } + } // console.log("looking up", word); // const url = buildWiktionaryURL(word); // const html = await fetchWiki(url); @@ -56,12 +80,20 @@ export default function LangText({ return ( <div className="lang-text-container"> {script === "Thai" ? ( - <ThaiText text={text} openWord={openWord} /> + <ThaiText text={text} theme={background} openWord={openWord} /> ) : script === "Latin" ? ( <LatinText text={text} lang={llang} openWord={openWord} /> ) : ( <Generic text={text} lang={llang} /> )} + {modal && ( + <WordModal + word={modal} + lang={llang} + theme={background} + onClose={() => setWordModal(null)} + /> + )} </div> ); } @@ -76,3 +108,35 @@ function Generic({ text, lang }: { text: string; lang: string }) { // {data && <ColoredText frags={Object.keys(data)} />} return <div className="lang-text-div"></div>; } + +function WordModal({ + word, + lang, + theme, + onClose, +}: { + word: FullWordData; + lang: string; + theme: ColorTheme; + onClose: () => void; +}) { + return ( + <div + id="modal-bg" + role="dialog" + aria-modal="true" + onMouseDown={(event) => { + if (event.target === event.currentTarget) { + onClose(); + } + }} + > + <div + id="modal-fg" + style={{ backgroundColor: "white", border: "5px solid black" }} + > + <FullWord data={word} lang={lang} theme={"light"} /> + </div> + </div> + ); +} diff --git a/packages/prosody-ui/src/Paragraph.tsx b/packages/prosody-ui/src/Paragraph.tsx index 72c43a7..b911fa0 100644 --- a/packages/prosody-ui/src/Paragraph.tsx +++ b/packages/prosody-ui/src/Paragraph.tsx @@ -6,7 +6,7 @@ import type { AnalyzeRes, WordData } from "./logic/types"; import { detectScript, langFromScript } from "./logic/utils"; import LatinText from "./latin/LatinText"; import { buildWiktionaryURL, parseWiktionary } from "./logic/wiki"; -import type { Result } from "sortug"; +import type { Result } from "@sortug/lib"; import * as Stanza from "./logic/stanza"; import { iso6393To1 } from "./logic/iso6393to1"; diff --git a/packages/prosody-ui/src/assets/fonts/Hani/dingliezhuhaifont-20240831GengXinBan)-2.ttf b/packages/prosody-ui/src/assets/fonts/Hani/dingliezhuhaifont-20240831GengXinBan-2.ttf Binary files differindex b387fc5..b387fc5 100644 --- a/packages/prosody-ui/src/assets/fonts/Hani/dingliezhuhaifont-20240831GengXinBan)-2.ttf +++ b/packages/prosody-ui/src/assets/fonts/Hani/dingliezhuhaifont-20240831GengXinBan-2.ttf diff --git a/packages/prosody-ui/src/components/Colors.tsx b/packages/prosody-ui/src/components/Colors.tsx new file mode 100644 index 0000000..d98838f --- /dev/null +++ b/packages/prosody-ui/src/components/Colors.tsx @@ -0,0 +1,198 @@ +import React from "react"; +import { notRandomFromArray, randomFromArrayMany } from "@sortug/lib"; +import "./sentence.css"; +import type { AnalyzeRes, ColorTheme, LangToColor } from "../logic/types"; +import type { POS_CODE } from "../thai/logic/thainlp"; + +export function assignColors(keys: string[], theme?: ColorTheme): string[] { + const background = theme ? theme : "light"; + const colors = colorPalette[background]; + const reduced = randomFromArrayMany(colors, keys.length, false); + const assigned: string[] = []; + for (const key of keys) { + const color = notRandomFromArray(key, reduced); + assigned.push(color); + } + return assigned; +} + +export function ColoredText({ + frags, + fn, + lang, + theme, +}: { + frags: LangToColor<unknown>[]; + fn?: (s: any) => void; + lang?: string; + theme: ColorTheme; +}) { + const colors = colorPalette[theme]; + console.log("coloredText", theme); + + // function getStyle(frags: AnalyzeRes[], i: number) { + // const prev = frags[i - 1]; + // const prevC = prev ? notRandomFromArray(prev.word, colors) : "lol"; + // const color = notRandomFromArray(s, colors); + // const opacity = prev && prevC === color ? 0.8 : 1; + // const style = { color, opacity }; + // return style; + // } + + return ( + <> + {frags.map((s, i) => { + // old code + const prev = frags[i - 1]; + const prevC = prev ? notRandomFromArray(prev.colorBy, colors) : "lol"; + const color = notRandomFromArray(s.colorBy, colors); + const style = !prev ? { color } : { color }; + return ( + <CTInner + lang={lang} + key={s.display + i} + s={s} + style={style} + fn={fn} + /> + ); + })} + </> + ); +} + +export function CTInner({ + s, + style, + fn, + lang, +}: { + s: LangToColor<unknown>; + style: any; + fn?: (s: any) => void; + lang?: string; +}) { + function handleClick(e: React.MouseEvent<HTMLSpanElement>) { + if (fn) { + e.stopPropagation(); + fn(s.data); + } + } + return ( + <span lang={lang} onClick={handleClick} className="word cp" style={style}> + {s.display} + </span> + ); +} + +export const colorPalette: Record<ColorTheme, string[]> = { + light: [ + // Black Standard high contrast + "#000000", + // Charcoal Softer than pure black + "#36454F", + // Slate Grey Cool, dark grey-green + "#2F4F4F", + // Navy Blue Classic professional blue + "#000080", + // Midnight Blue Very deep, rich blue + "#191970", + // Cobalt Vivid, highly legible blue + "#0047AB", + // Teal Distinct blue-green + "#008080", + // Forest Green Nature-inspired dark green + "#006400", + // Pine Green Cooler, bluish green + "#01796F", + // Olive Drab Dark brownish-green + "#4B5320", + // Bronze Metallic brown-orange + "#CD7F32", + // Saddle Brown Robust earthy tone + "#8B4513", + // Chocolate Warm, readable orange-brown + "#D2691E", + // Burnt Sienna Reddish-orange earth tone + "#E97451", + // Firebrick Muted dark red + "#B22222", + // Crimson Vivid, alarming red + "#DC143C", + // Maroon Deep, serious red + "#800000", + // Burgundy Purple-leaning red + "#800020", + // Deep Pink High contrast magenta-pink + "#C71585", + // Dark Violet Vivid purple + "#9400D3", + // Indigo Deep blue-purple + "#4B0082", + // Purple Standard distinct purple + "#800080", + // Rebecca Purple Web-standard bluish purple + "#663399", + // Dim Gray Neutral, medium-dark gray + "#696969", + ], + dark: [ + // White Standard high contrast + "#FFFFFF", + // Silver Soft readable grey + "#C0C0C0", + // Cream Warm white, easier on eyes + "#FFFDD0", + // Cyan The standard terminal blue-green + "#00FFFF", + // Sky Blue Pleasant, airy blue + "#87CEEB", + // Powder Blue Very pale, soft blue + "#B0E0E6", + // Aquamarine Bright neon blue-green + "#7FFFD4", + // Mint Green Soft, pastel green + "#98FB98", + // Lime Classic high-vis terminal green + "#00FF00", + // Chartreuse Yellow-green neon + "#7FFF00", + // Gold Bright yellow-orange + "#FFD700", + // Yellow Standard high-vis yellow + "#FFFF00", + // Khaki Muted, sandy yellow + "#F0E68C", + // Wheat Soft beige/earth tone + "#F5DEB3", + // Orange Standard distinctive orange + "#FFA500", + // Coral Pinkish-orange + "#FF7F50", + // Salmon Soft reddish-pink + "#FA8072", + // Hot Pink Vivid, high-energy pink + "#FF69B4", + // Magenta Pure, digital pink-purple + "#FF00FF", + // Plum Muted, readable purple + "#DDA0DD", + // Violet Bright, distinct purple + "#EE82EE", + // Lavender Very light purple-blue + "#E6E6FA", + // Periwinkle Soft indigo-blue + "#CCCCFF", + // Thistle Desaturated light purple + "#D8BFD8", + ], +}; + +// export const colors = [ +// "#8c2c2c", +// "#000000", +// "#ffd400", +// "#1513a0", +// "#7e7e7e", +// "1eb52d", +// ]; diff --git a/packages/prosody-ui/src/components/Sentence.tsx b/packages/prosody-ui/src/components/Sentence.tsx index 33144ac..1986ba8 100644 --- a/packages/prosody-ui/src/components/Sentence.tsx +++ b/packages/prosody-ui/src/components/Sentence.tsx @@ -1,26 +1,49 @@ import React from "react"; -import { notRandomFromArray } from "sortug"; +import { notRandomFromArray } from "@sortug/lib"; import "./sentence.css"; +import type { AnalyzeRes, ColorTheme, LangToColor } from "../logic/types"; +import type { POS_CODE } from "../thai/logic/thainlp"; export function ColoredText({ frags, fn, lang, + theme, }: { - frags: string[]; - fn?: (s: string) => void; + frags: LangToColor<unknown>[]; + fn?: (s: any) => void; lang?: string; + theme: ColorTheme; }) { + const colors = colorPalette[theme]; + console.log("coloredText", theme); + + // function getStyle(frags: AnalyzeRes[], i: number) { + // const prev = frags[i - 1]; + // const prevC = prev ? notRandomFromArray(prev.word, colors) : "lol"; + // const color = notRandomFromArray(s, colors); + // const opacity = prev && prevC === color ? 0.8 : 1; + // const style = { color, opacity }; + // return style; + // } + return ( <> {frags.map((s, i) => { + // old code const prev = frags[i - 1]; - const prevC = prev ? notRandomFromArray(prev, colors) : "lol"; - const color = notRandomFromArray(s, colors); - const opacity = prev && prevC === color ? 0.8 : 1; - const style = { color, opacity }; - console.log({ style }); - return <CTInner lang={lang} key={s + i} s={s} style={style} fn={fn} />; + const prevC = prev ? notRandomFromArray(prev.colorBy, colors) : "lol"; + const color = notRandomFromArray(s.colorBy, colors); + const style = !prev ? { color } : { color }; + return ( + <CTInner + lang={lang} + key={s.display + i} + s={s} + style={style} + fn={fn} + /> + ); })} </> ); @@ -32,26 +55,132 @@ export function CTInner({ fn, lang, }: { - s: string; + s: LangToColor<unknown>; style: any; - fn?: (s: string) => void; + fn?: (s: any) => void; lang?: string; }) { function handleClick(e: React.MouseEvent<HTMLSpanElement>) { - console.log(!!fn, "fn"); - if (fn) fn(e.currentTarget.innerText.trim()); + if (fn) { + e.stopPropagation(); + fn(s.data); + } } return ( <span lang={lang} onClick={handleClick} className="word cp" style={style}> - {s} + {s.display} </span> ); } -export const colors = [ - "#8c2c2c", - "#000000", - "#ffd400", - "#1513a0", - "#7e7e7e", - "1eb52d", -]; + +export const colorPalette: Record<ColorTheme, string[]> = { + light: [ + // Black Standard high contrast + "#000000", + // Charcoal Softer than pure black + "#36454F", + // Slate Grey Cool, dark grey-green + "#2F4F4F", + // Navy Blue Classic professional blue + "#000080", + // Midnight Blue Very deep, rich blue + "#191970", + // Cobalt Vivid, highly legible blue + "#0047AB", + // Teal Distinct blue-green + "#008080", + // Forest Green Nature-inspired dark green + "#006400", + // Pine Green Cooler, bluish green + "#01796F", + // Olive Drab Dark brownish-green + "#4B5320", + // Bronze Metallic brown-orange + "#CD7F32", + // Saddle Brown Robust earthy tone + "#8B4513", + // Chocolate Warm, readable orange-brown + "#D2691E", + // Burnt Sienna Reddish-orange earth tone + "#E97451", + // Firebrick Muted dark red + "#B22222", + // Crimson Vivid, alarming red + "#DC143C", + // Maroon Deep, serious red + "#800000", + // Burgundy Purple-leaning red + "#800020", + // Deep Pink High contrast magenta-pink + "#C71585", + // Dark Violet Vivid purple + "#9400D3", + // Indigo Deep blue-purple + "#4B0082", + // Purple Standard distinct purple + "#800080", + // Rebecca Purple Web-standard bluish purple + "#663399", + // Dim Gray Neutral, medium-dark gray + "#696969", + ], + dark: [ + // White Standard high contrast + "#FFFFFF", + // Silver Soft readable grey + "#C0C0C0", + // Cream Warm white, easier on eyes + "#FFFDD0", + // Cyan The standard terminal blue-green + "#00FFFF", + // Sky Blue Pleasant, airy blue + "#87CEEB", + // Powder Blue Very pale, soft blue + "#B0E0E6", + // Aquamarine Bright neon blue-green + "#7FFFD4", + // Mint Green Soft, pastel green + "#98FB98", + // Lime Classic high-vis terminal green + "#00FF00", + // Chartreuse Yellow-green neon + "#7FFF00", + // Gold Bright yellow-orange + "#FFD700", + // Yellow Standard high-vis yellow + "#FFFF00", + // Khaki Muted, sandy yellow + "#F0E68C", + // Wheat Soft beige/earth tone + "#F5DEB3", + // Orange Standard distinctive orange + "#FFA500", + // Coral Pinkish-orange + "#FF7F50", + // Salmon Soft reddish-pink + "#FA8072", + // Hot Pink Vivid, high-energy pink + "#FF69B4", + // Magenta Pure, digital pink-purple + "#FF00FF", + // Plum Muted, readable purple + "#DDA0DD", + // Violet Bright, distinct purple + "#EE82EE", + // Lavender Very light purple-blue + "#E6E6FA", + // Periwinkle Soft indigo-blue + "#CCCCFF", + // Thistle Desaturated light purple + "#D8BFD8", + ], +}; + +// export const colors = [ +// "#8c2c2c", +// "#000000", +// "#ffd400", +// "#1513a0", +// "#7e7e7e", +// "1eb52d", +// ]; diff --git a/packages/prosody-ui/src/components/word/FullWordData.tsx b/packages/prosody-ui/src/components/word/FullWordData.tsx new file mode 100644 index 0000000..9b1fc69 --- /dev/null +++ b/packages/prosody-ui/src/components/word/FullWordData.tsx @@ -0,0 +1,156 @@ +import React, { useCallback, useEffect, useState } from "react"; +import spinner from "../assets/icons/spinner.svg"; +import likeIcon from "../assets/icons/heart.svg"; +import commentsIcon from "../assets/icons/quote.svg"; +import shareIcon from "../assets/icons/share.svg"; +import fontIcon from "../assets/icons/font.svg"; +import bookmarkIcon from "@/assets/icons/bookmark.svg"; +import speakerIcon from "@/assets/icons/speaker.svg"; +import type { AnalyzeRes, ColorTheme, Meaning } from "@/logic/types"; +import { ColoredText } from "../Sentence.tsx"; +import { P, Span, useSpeechSynthesis } from "@/hooks/useLang.tsx"; +import type { FullWordData } from "@sortug/langlib"; +import { cycleNext } from "@sortug/lib"; +import FontChanger from "@/fonts/FontChanger.tsx"; +import Phonetic from "./Phonetic.tsx"; + +function Word({ + data, + lang, + theme, +}: { + data: FullWordData; + lang: string; + theme: ColorTheme; +}) { + async function load() { + // const wiki = await fetchWiki(data.word); + // console.log(wiki, "wiki res"); + // if ("ok" in wiki) setM(wiki.ok.meanings); + // else setError(wiki.error); + // setLoading(false); + } + useEffect(() => { + load(); + }, []); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const [meanings, setM] = useState<Meaning[]>([]); + const [fontIdx, setFont] = useState(0); + + const { voices, speaking, speak, stop } = useSpeechSynthesis(); + function playAudio() { + console.log({ voices, speaking }); + console.log("word", data); + speak(data.spelling); + } + console.log({ data }); + + async function saveW() {} + + return ( + <div id="word-modal" title={data.spelling}> + <FontChanger text={data.spelling}> + <img className="save-icon cp" onClick={saveW} src={bookmarkIcon} /> + <div className="original"> + <ColoredText + frags={data.phonetic.syllables.map((s) => ({ + data: s, + display: s.spelling, + colorBy: s.tone.name, + }))} + theme={theme} + /> + </div> + <Phonetic data={data} lang={lang} theme={theme} /> + <div className="pronunciation IPA flex1 flex-center"> + <P>{data.phonetic.ipa}</P> + <img onClick={playAudio} className="icon cp" src={speakerIcon} /> + </div> + <div className="meanings"> + {loading ? ( + <img src={spinner} className="spinner bc" /> + ) : ( + data.senses.map((m) => ( + <div key={JSON.stringify(m)} className="meaning"> + <div className="pos"> + <Span>{m.pos}</Span> + </div> + <ol> + {m.glosses.map((t, i) => ( + <li key={t + i} className="translation"> + <P>{t}</P> + </li> + ))} + </ol> + </div> + )) + )} + {error && <div className="error">{error}</div>} + </div> + </FontChanger> + </div> + ); +} + +export default Word; + +<Card className="absolute inset-0 backface-hidden rotate-y-180 flex flex-col overflow-hidden border-slate-200 dark:border-slate-800 shadow-lg bg-slate-50/50"> + <div className="flex-1 overflow-hidden flex flex-col"> + <Tabs defaultValue="meanings" className="flex-1 flex flex-col"> + <div className="px-6 pt-6 pb-2 bg-white border-b"> + <TabsList className="grid w-full grid-cols-3"> + <TabsTrigger value="meanings">Meanings</TabsTrigger> + <TabsTrigger value="grammar">Grammar</TabsTrigger> + <TabsTrigger value="examples">Examples</TabsTrigger> + </TabsList> + </div> + + <div className="flex-1 overflow-y-auto p-6"> + <TabsContent value="meanings" className="mt-0 space-y-4"> + <EnhancedWordMeanings word={word} /> + </TabsContent> + + <TabsContent value="grammar" className="mt-0 space-y-6"> + <div className="space-y-4"> + <div className="bg-blue-50 p-4 rounded-lg border border-blue-100"> + <h3 className="font-semibold text-blue-900 mb-2"> + Tone Analysis + </h3> + <div className="flex flex-wrap gap-2"> + {tones.map((tone, idx) => ( + <Badge key={idx} variant="outline" className="bg-white"> + Syl {idx + 1}:{" "} + <span + className={cn("ml-1 font-bold", getColorByTone(tone))} + > + {tone} + </span> + </Badge> + ))} + </div> + </div> + + <div className="bg-slate-100 p-4 rounded-lg border border-slate-200"> + <h3 className="font-semibold text-slate-900 mb-2"> + Word Structure + </h3> + <p className="text-sm text-slate-600"> + This word consists of {syls.length} syllable + {syls.length > 1 ? "s" : ""}. The tone pattern is essential for + conveying the correct meaning. + </p> + </div> + </div> + </TabsContent> + + <TabsContent value="examples" className="mt-0 space-y-4"> + <ExamplesTab + word={word} + moreExamples={word.senses?.flatMap((s) => s.examples || [])} + /> + </TabsContent> + </div> + </Tabs> + </div> +</Card>; diff --git a/packages/prosody-ui/src/components/word/Phonetic.tsx b/packages/prosody-ui/src/components/word/Phonetic.tsx new file mode 100644 index 0000000..db3d0cb --- /dev/null +++ b/packages/prosody-ui/src/components/word/Phonetic.tsx @@ -0,0 +1,92 @@ +import React, { useCallback, useEffect, useState } from "react"; +import spinner from "../assets/icons/spinner.svg"; +import likeIcon from "../assets/icons/heart.svg"; +import commentsIcon from "../assets/icons/quote.svg"; +import shareIcon from "../assets/icons/share.svg"; +import fontIcon from "../assets/icons/font.svg"; +import bookmarkIcon from "../assets/icons/bookmark.svg"; +import type { AnalyzeRes, ColorTheme, Meaning } from "@/logic/types"; +import { P, Span, useSpeechSynthesis } from "@/hooks/useLang.tsx"; +import type { FullWordData, Syllable, Tone } from "@sortug/langlib"; +import { cycleNext } from "@sortug/lib"; +import FontChanger from "../fonts/FontChanger.tsx"; +import { assignColors } from "../Colors.tsx"; +import { IconBadgeFilled, IconSpeakerphone } from "@tabler/icons-react"; + +function Phonetic({ + data, + lang, + theme, +}: { + data: FullWordData; + lang: string; + theme: ColorTheme; +}) { + async function load() { + // const wiki = await fetchWiki(data.word); + // console.log(wiki, "wiki res"); + // if ("ok" in wiki) setM(wiki.ok.meanings); + // else setError(wiki.error); + // setLoading(false); + } + useEffect(() => { + load(); + }, []); + const [loading, setLoading] = useState(false); + + const { voices, speaking, speak, stop } = useSpeechSynthesis(); + function playAudio() { + setLoading(true); + console.log({ voices, speaking }); + console.log("word", data); + speak(data.spelling); + setLoading(false); + } + console.log({ data }); + + async function saveW() {} + + return ( + <div className="phonetic-data"> + <div className="pronunciation IPA flex1 flex-center"> + <P>{data.phonetic.ipa}</P> + {loading ? ( + <img src={spinner} className="spinner bc" /> + ) : ( + <IconSpeakerphone onClick={playAudio} /> + )} + </div> + <Syllables data={data} /> + </div> + ); +} + +export default Phonetic; + +function Syllables({ data }: { data: FullWordData }) { + const syllables = data.phonetic.syllables; + + console.log(data.phonetic.tone_sequence); + const isTonal = !!data.phonetic.tone_sequence; + const colorMap = isTonal + ? (s: Syllable) => s.tone.name + : (s: Syllable) => (s.stressed ? "stressed" : "neuter"); + const colors = assignColors(syllables.map(colorMap)); + return ( + <div className="syllables"> + {data.phonetic.syllables.map((syl) => ( + <div className="syllable"> + {syl.tone.letters && <Tone tone={syl.tone} />} + <span>{syl.spelling}</span> + </div> + ))} + </div> + ); +} +function Tone({ tone }: { tone: Tone }) { + return ( + <div className="tone"> + <IconBadgeFilled>{tone.letters}</IconBadgeFilled> + </div> + ); +} diff --git a/packages/prosody-ui/src/components/word/Semantic.tsx b/packages/prosody-ui/src/components/word/Semantic.tsx new file mode 100644 index 0000000..059194c --- /dev/null +++ b/packages/prosody-ui/src/components/word/Semantic.tsx @@ -0,0 +1,184 @@ +import { useEffect, useState } from "react"; +import type { Example, FullWordData } from "@sortug/langlib"; +import { IconBadgeFilled, IconSparkles } from "@tabler/icons-react"; + +type Tab = "meanings" | "grammar" | "examples"; +function Semantic({ data }: { data: FullWordData }) { + return ( + <div className=""> + <div className="flex-col"> + <div className="tab-container"> + {data.senses.map((sense, i) => ( + <div> + <div key={data.spelling + sense.etymology + i} className=""> + {sense.pos && <div className="">{sense.pos}</div>} + + <ul className=""> + {sense.glosses.map((gloss, idx: number) => ( + <li key={idx} className="text-gray-700"> + {gloss} + </li> + ))} + </ul> + + {sense.etymology && ( + <div className=""> + <strong>Etymology:</strong> {sense.etymology} + </div> + )} + + {sense.categories.length > 0 && ( + <div className=""> + <strong>Categories:</strong> {sense.categories.join(", ")} + </div> + )} + {sense.derivation.length > 0 && ( + <div className=""> + <strong>Derived forms:</strong> + {sense.derivation.map((dr, i) => ( + <div key={dr.text + i}> + {dr.type}: {dr.text} - {dr.tags} + </div> + ))} + </div> + )} + {sense.examples.length > 0 && ( + <Examples data={data} examples={sense.examples} /> + )} + </div> + </div> + ))} + </div> + </div> + </div> + ); +} + +export default Semantic; + +function ExamplesTab({ + data, + examples, +}: { + data: FullWordData; + examples: Example[]; +}) { + const [isGenerating, setIsGenerating] = useState(false); + const [generatedExamples, setGeneratedExamples] = useState<any[]>([]); + + const generateExamples = async () => { + setIsGenerating(true); + + try { + // Get the primary meaning from the first sense + const primaryMeaning = + data.senses?.[0]?.glosses?.[0] || "unknown meaning"; + + const response = await fetch("/api/generate-examples", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + word: data.spelling, + meaning: primaryMeaning, + examples, + }), + }); + + if (!response.ok) { + throw new Error("Failed to generate examples"); + } + + const j = await response.json(); + setGeneratedExamples(j.examples || []); + } catch (err) { + console.error("Error generating examples:", err); + } finally { + setIsGenerating(false); + } + }; + return ( + <div className=""> + <div className=""> + <h4 className="">Usage Examples</h4> + + {/* Generate More Button */} + <div className=""> + <button + onClick={generateExamples} + disabled={isGenerating} + className="" + > + {isGenerating ? ( + <> + <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white" /> + Generating Examples... + </> + ) : ( + <> + <IconSparkles size={16} /> + Generate More Example Sentences + </> + )} + </button> + </div> + + {/* Examples Display */} + <div className=""> + {examples.map((example, idx) => ( + <div + key={`original-${idx}`} + className="p-3 bg-white rounded border-l-4 border-blue-400" + > + <p className="text-sm text-gray-700 italic"> + {example?.text || ""} + </p> + {example.ref && ( + <p className="text-xs text-gray-500 mt-1"> + Source: {example.ref} + </p> + )} + </div> + ))} + + {generatedExamples.length > 0 && ( + <> + <h5 className="text-sm font-medium text-gray-700 mb-2 mt-4"> + AI-Generated Examples: + </h5> + {generatedExamples.map((example, idx) => ( + <div + key={`generated-${idx}`} + className="p-3 bg-white rounded border-l-4 border-green-400" + > + <p className="text-sm text-gray-800 font-medium mb-1"> + {example.thai} + </p> + <p className="text-sm text-gray-600 mb-1"> + {example.english} + </p> + {example.context && ( + <p className="text-xs text-gray-500 italic"> + Context: {example.context} + </p> + )} + </div> + ))} + </> + )} + + {/* No Examples */} + {!moreExamples?.length && !generatedExamples.length && ( + <div className="p-3 bg-white rounded border-l-4 border-orange-400"> + <p className="text-sm text-gray-600 italic"> + No examples available for this word. Click "Generate More + Example Sentences" to get AI-generated examples. + </p> + </div> + )} + </div> + </div> + </div> + ); +} diff --git a/packages/prosody-ui/src/fonts/FontChanger.tsx b/packages/prosody-ui/src/fonts/FontChanger.tsx index 15c932e..9ae9c84 100644 --- a/packages/prosody-ui/src/fonts/FontChanger.tsx +++ b/packages/prosody-ui/src/fonts/FontChanger.tsx @@ -1,64 +1,68 @@ import React, { useEffect, useState, type ReactNode } from "react"; import fontIcon from "../assets/icons/font.svg"; -import { getScriptPredictor } from "glotscript"; +import { getScriptPredictor, type ISO_15924_CODE } from "@sortug/langlib"; import ThaiFontLoader from "./Thai"; +import HanFontLoader from "./Hani"; +import LatnFontLoader from "./Latn"; +import JpanFontLoader from "./Jpan"; -function FontChanger({ text }: { text: string }) { - const [script, setScript] = useState<string | null>(null); +function findFontCount(lang: ISO_15924_CODE): number { + if (lang === "Thai") return 7; + if (lang === "Jpan") return 6; + // TODO get more latin fonts + if (lang === "Latn") return 1; + if ((lang as any) === "IPA") return 6; + if (lang.startsWith("Han")) return 23; + return 0; +} + +function FontChanger({ + text, + script, + children, +}: { + text: string; + script?: ISO_15924_CODE; + lang?: string; + children: ReactNode; +}) { + const [script2, setScript] = useState<ISO_15924_CODE | null>(script || null); useEffect(() => { + if (script) return; const predictor = getScriptPredictor(); const res = predictor(text); console.log("script predicted", res); - setScript(res[0]); + const rescript: ISO_15924_CODE | null = res[0]; + if (!rescript) { + console.error("script undetected", text); + return; + } + setScript(rescript); + setFontCount(findFontCount(rescript)); }, [text]); - useEffect(() => { - if (script === "Hani") setFontCount(12); - else if (script === "Thai") setFontCount(6); - else if (script === "Jpan") setFontCount(5); - // else if (script === "Latn") setFontCount(6) - }, [script]); + const [fontIdx, setFont] = useState(0); const [fontCount, setFontCount] = useState(0); function changeFont() { if (fontIdx === fontCount) setFont(0); else setFont((prev) => prev + 1); } + if (!script2) + return <div className="error">Couldn't detect script of {text}</div>; return ( - <div - className={`font-changer font-${script}-${fontIdx}`} - lang={script || ""} - > - <img - className="font-icon cp" - style={{ width: 25 }} - onClick={changeFont} - src={fontIcon} - /> - {script === "Thai" ? <ThaiFontLoader text={text} /> : null} + <div className={`font-changer font-${script}-${fontIdx}`}> + <img className="font-icon cp" onClick={changeFont} src={fontIcon} /> + {script2 === "Thai" ? ( + <ThaiFontLoader>{children}</ThaiFontLoader> + ) : script2.startsWith("Han") ? ( + <HanFontLoader>{children}</HanFontLoader> + ) : script2 === "Jpan" ? ( + <JpanFontLoader>{children}</JpanFontLoader> + ) : script2 === "Latn" ? ( + <LatnFontLoader>{children}</LatnFontLoader> + ) : null} </div> ); } -// function FontChanger({ -// lang, -// children, -// }: { -// lang: string; -// children: ReactNode; -// }) { -// useEffect(() => {}, []); -// const [script, setScript] = useState("Latn"); -// const [fontIdx, setFont] = useState(0); -// const fontCount = 6; -// function changeFont() { -// if (fontIdx === fontCount) setFont(0); -// else setFont((prev) => prev + 1); -// } -// return ( -// <div className="font-changer" lang={script}> -// <img className="font-icon cp" onClick={changeFont} src={fontIcon} /> -// {children} -// </div> -// ); -// } export default FontChanger; diff --git a/packages/prosody-ui/src/fonts/Jpan.tsx b/packages/prosody-ui/src/fonts/Jpan.tsx new file mode 100644 index 0000000..f9cc602 --- /dev/null +++ b/packages/prosody-ui/src/fonts/Jpan.tsx @@ -0,0 +1,14 @@ +import React, { useState, type ReactNode } from "react"; +import "../assets/fonts/Hani/style.css"; + +function ChineseFontLoader({ children }: { children: ReactNode }) { + const [fontIdx, setFont] = useState(0); + const fontCount = 12; + function changeFont() { + if (fontIdx === fontCount) setFont(0); + else setFont((prev) => prev + 1); + } + return <div>{children}</div>; +} + +export default ChineseFontLoader; diff --git a/packages/prosody-ui/src/fonts/Latn.tsx b/packages/prosody-ui/src/fonts/Latn.tsx new file mode 100644 index 0000000..f9cc602 --- /dev/null +++ b/packages/prosody-ui/src/fonts/Latn.tsx @@ -0,0 +1,14 @@ +import React, { useState, type ReactNode } from "react"; +import "../assets/fonts/Hani/style.css"; + +function ChineseFontLoader({ children }: { children: ReactNode }) { + const [fontIdx, setFont] = useState(0); + const fontCount = 12; + function changeFont() { + if (fontIdx === fontCount) setFont(0); + else setFont((prev) => prev + 1); + } + return <div>{children}</div>; +} + +export default ChineseFontLoader; diff --git a/packages/prosody-ui/src/fonts/Thai.tsx b/packages/prosody-ui/src/fonts/Thai.tsx index 0048316..62b886b 100644 --- a/packages/prosody-ui/src/fonts/Thai.tsx +++ b/packages/prosody-ui/src/fonts/Thai.tsx @@ -1,8 +1,8 @@ -import React, { useState, type ReactNode } from "react"; +import { type ReactNode } from "react"; import "../assets/fonts/Thai/style.css"; -function ThaiFontLoader({ text }: { text: string }) { - return <div>{text}</div>; +function ThaiFontLoader({ children }: { children: ReactNode }) { + return <>{children}</>; } export default ThaiFontLoader; diff --git a/packages/prosody-ui/src/fonts/useLangFont.tsx b/packages/prosody-ui/src/fonts/useLangFont.tsx index 36fa603..5467b18 100644 --- a/packages/prosody-ui/src/fonts/useLangFont.tsx +++ b/packages/prosody-ui/src/fonts/useLangFont.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, type ReactNode } from "react"; import fontIcon from "../assets/icons/font.svg"; -import { getScriptPredictor } from "glotscript"; +import { getScriptPredictor } from "@sortug/langlib"; function useLangFont({ text }: { text: string }) { useEffect(() => { diff --git a/packages/prosody-ui/src/latin/LatinText.tsx b/packages/prosody-ui/src/latin/LatinText.tsx index e5b13ff..073baff 100644 --- a/packages/prosody-ui/src/latin/LatinText.tsx +++ b/packages/prosody-ui/src/latin/LatinText.tsx @@ -30,7 +30,7 @@ export default function LatinText({ }: { text: string; lang: string; - openWord?: (word: string) => void; + openWord?: (word: AnalyzeRes) => void; }) { useEffect(() => { const sentences = segmentate(text, lang, "sentence"); @@ -55,7 +55,7 @@ function Sentence({ text: string; lang: string; - openWord?: (word: string) => void; + openWord?: (word: AnalyzeRes) => void; }) { useEffect(() => { const w = segmentate(text, lang, "word"); diff --git a/packages/prosody-ui/src/logic/stanza.ts b/packages/prosody-ui/src/logic/stanza.ts index 9e59450..b74a064 100644 --- a/packages/prosody-ui/src/logic/stanza.ts +++ b/packages/prosody-ui/src/logic/stanza.ts @@ -1,4 +1,4 @@ -import type { AsyncRes, Result } from "sortug"; +import type { AsyncRes, Result } from "@sortug/lib"; const ENDPOINT = "http://localhost:8102"; export async function segmenter(text: string, lang: string) { diff --git a/packages/prosody-ui/src/logic/types.ts b/packages/prosody-ui/src/logic/types.ts index ac308cf..cdae30e 100644 --- a/packages/prosody-ui/src/logic/types.ts +++ b/packages/prosody-ui/src/logic/types.ts @@ -46,3 +46,6 @@ export type WordData = { meanings: Meaning[]; references?: any; }; + +export type ColorTheme = "light" | "dark"; +export type LangToColor<T> = { display: string; colorBy: string; data: T }; diff --git a/packages/prosody-ui/src/logic/utils.ts b/packages/prosody-ui/src/logic/utils.ts index 737a6ec..90b2e1e 100644 --- a/packages/prosody-ui/src/logic/utils.ts +++ b/packages/prosody-ui/src/logic/utils.ts @@ -1,4 +1,4 @@ -import type { Result } from "sortug"; +import type { Result } from "@sortug/lib"; export function detectScript(text: string): Result<string> { const scripts = { diff --git a/packages/prosody-ui/src/logic/wiki.ts b/packages/prosody-ui/src/logic/wiki.ts index 1325c0f..d3c56ee 100644 --- a/packages/prosody-ui/src/logic/wiki.ts +++ b/packages/prosody-ui/src/logic/wiki.ts @@ -1,4 +1,4 @@ -import type { AsyncRes, Result } from "sortug"; +import type { AsyncRes, Result } from "@sortug/lib"; import type { Meaning } from "./types"; export function buildWiktionaryURL(word: string) { diff --git a/packages/prosody-ui/src/styles/styles.css b/packages/prosody-ui/src/styles/styles.css index 69351f1..e98d1f0 100644 --- a/packages/prosody-ui/src/styles/styles.css +++ b/packages/prosody-ui/src/styles/styles.css @@ -149,6 +149,7 @@ #word-modal { position: relative; + height: 80vh; & .font-icon { position: absolute; @@ -225,42 +226,6 @@ img { justify-content: center; } -/* p { */ -/* position: absolute; */ -/* top: 50%; */ -/* left: 50%; */ -/* transform: translate(-50%, -50%); */ -/* color: white; */ -/* background-color: rgba(0, 0, 0, 0.5); */ -/* padding: 10px; */ -/* border-radius: 5px; */ -/* } */ -#modal-bg { - height: 100vh; - width: 100vw; - background-color: rgb(0, 0, 0, 0.9); - position: fixed; - top: 0; - left: 0; - z-index: 100; -} - -#modal-fg { - position: fixed; - top: 50%; - left: 50%; - width: 80%; - z-index: 101; - transform: translate(-50%, -50%); - /* background-color: var(--background-color); */ - background-color: lightgrey; - font-size: 1.2rem; - padding: 1rem; - max-height: 80%; - overflow-y: scroll; -} - - .text-ipa { font-size: 1.5rem; } diff --git a/packages/prosody-ui/src/thai/ThaiText.tsx b/packages/prosody-ui/src/thai/ThaiText.tsx index fc1e1e6..794804a 100644 --- a/packages/prosody-ui/src/thai/ThaiText.tsx +++ b/packages/prosody-ui/src/thai/ThaiText.tsx @@ -1,49 +1,49 @@ import React, { useCallback, useEffect, useState } from "react"; import "../assets/fonts/Thai/style.css"; import { segmentateThai } from "./logic/thainlp"; -import type { AnalyzeRes } from "../logic/types"; +import type { AnalyzeRes, ColorTheme, LangToColor } from "../logic/types"; import { ColoredText } from "../components/Sentence"; import Word from "../components/Word"; export default function ThaiText({ text, openWord, + theme, }: { text: string; - openWord: (s: string) => void; + openWord: (s: AnalyzeRes) => void; + theme: ColorTheme; }) { useEffect(() => { pythonseg(); }, [text]); - const [data, setData] = useState<Record<string, AnalyzeRes>>({}); + const [data, setData] = useState<Array<LangToColor<AnalyzeRes>>>([]); const [modal, setModal] = useState<any>(); const pythonseg = useCallback(async () => { const s2 = await segmentateThai(text.trim()); if ("ok" in s2) { - const ob = s2.ok.reduce((acc, item) => { - acc[item.word] = item; - return acc; - }, {} as any); - setData(ob); + const ob = s2.ok.reduce( + (acc, item) => { + acc[item.word] = item; + return acc; + }, + {} as Record<string, AnalyzeRes>, + ); + const d = Object.values(ob).map((w) => ({ + data: w, + colorBy: w.pos, + display: w.word, + })); + setData(d); console.log(s2, "s2"); } else console.error(s2.error); }, [text]); - // function openWord(e: React.MouseEvent<any>) { - // const s = e.currentTarget.innerText; - // const d = data[s]; - // setModal(d); - // // setModal(<WordModal data={d} lang={lang} />); - // } return ( <div className="thaitext"> - <ColoredText lang="tha" frags={Object.keys(data)} fn={openWord} /> + <ColoredText lang="tha" theme={theme} frags={data} fn={openWord} /> {modal && <Word data={modal} lang={"tha"} />} </div> ); } - -function ThaiWord() { - return <div />; -} diff --git a/packages/prosody-ui/src/thai/logic/thainlp.ts b/packages/prosody-ui/src/thai/logic/thainlp.ts index 031bf4c..dc6ed23 100644 --- a/packages/prosody-ui/src/thai/logic/thainlp.ts +++ b/packages/prosody-ui/src/thai/logic/thainlp.ts @@ -1,4 +1,4 @@ -import type { AsyncRes } from "sortug"; +import type { AsyncRes } from "@sortug/lib"; import type { AnalyzeRes } from "../../logic/types"; const ENDPOINT = "http://192.168.1.110:8001"; @@ -24,7 +24,7 @@ export async function segmentateThai(sentence: string): AsyncRes<AnalyzeRes[]> { return await call("/segmentate", { word: sentence }); } -export const POSMAP: Record<string, string> = { +export const POSMAP = { ADJ: "Adjective", ADP: "Adposition", ADV: "Adverb", @@ -87,4 +87,8 @@ export const POSMAP: Record<string, string> = { EITT: "Ending for interrogative sentence", NEG: "Negator", PUNC: "Punctuation", -}; +} as const; +type POSTYPE = typeof POSMAP; + +export type POS_CODE = keyof POSTYPE; +export type POS = POSTYPE[POS_CODE]; diff --git a/packages/prosody-ui/src/themes/ThemeSwitcher.tsx b/packages/prosody-ui/src/themes/ThemeSwitcher.tsx new file mode 100644 index 0000000..bae617f --- /dev/null +++ b/packages/prosody-ui/src/themes/ThemeSwitcher.tsx @@ -0,0 +1,130 @@ +import React, { + createContext, + useContext, + useEffect, + useState, + type ReactNode, +} from "react"; +import { themes, type Theme, type ThemeName } from "./themes"; + +interface ThemeContextType { + theme: Theme; + themeName: ThemeName; + setTheme: (name: ThemeName) => void; + availableThemes: ThemeName[]; +} + +const ThemeContext = createContext<ThemeContextType | undefined>(undefined); + +interface ThemeProviderProps { + children: ReactNode; + defaultTheme?: ThemeName; +} + +export const ThemeProvider: React.FC<ThemeProviderProps> = ({ + children, + defaultTheme = "light", +}) => { + const [themeName, setThemeName] = useState<ThemeName>(() => { + const savedTheme = localStorage.getItem("theme") as ThemeName; + if (savedTheme && themes[savedTheme]) { + return savedTheme; + } + + if ( + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + return "dark"; + } + + return defaultTheme; + }); + + const theme = themes[themeName]; + + useEffect(() => { + const root = document.documentElement; + + root.setAttribute("data-theme", themeName); + + // Set color variables + Object.entries(theme.colors).forEach(([key, value]) => { + const cssVarName = `--color-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`; + root.style.setProperty(cssVarName, value); + }); + + // Set typography variables + Object.entries(theme.typography).forEach(([key, value]) => { + const cssVarName = `--${key + .replace(/([A-Z])/g, "-$1") + .toLowerCase() + .replace("font-", "font-") + .replace("size", "") + .replace("weight", "")}`; + root.style.setProperty(cssVarName, value); + }); + + // Set spacing variables + Object.entries(theme.spacing).forEach(([key, value]) => { + const cssVarName = `--${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`; + root.style.setProperty(cssVarName, value); + }); + + // Set radius variables + Object.entries(theme.radius).forEach(([key, value]) => { + const cssVarName = `--${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`; + root.style.setProperty(cssVarName, value); + }); + + // Set transition variables + Object.entries(theme.transitions).forEach(([key, value]) => { + const cssVarName = `--${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`; + root.style.setProperty(cssVarName, value); + }); + + // Legacy variables for backward compatibility + root.style.setProperty("--text-color", theme.colors.text); + root.style.setProperty("--background-color", theme.colors.background); + + localStorage.setItem("theme", themeName); + }, [themeName, theme]); + + useEffect(() => { + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const handleChange = (e: MediaQueryListEvent) => { + const savedTheme = localStorage.getItem("theme"); + if (!savedTheme) { + setThemeName(e.matches ? "dark" : "light"); + } + }; + + mediaQuery.addEventListener("change", handleChange); + return () => mediaQuery.removeEventListener("change", handleChange); + }, []); + + const setTheme = (name: ThemeName) => { + if (themes[name]) { + setThemeName(name); + } + }; + + const value: ThemeContextType = { + theme, + themeName, + setTheme, + availableThemes: Object.keys(themes) as ThemeName[], + }; + + return ( + <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider> + ); +}; + +export const useTheme = (): ThemeContextType => { + const context = useContext(ThemeContext); + if (context === undefined) { + throw new Error("useTheme must be used within a ThemeProvider"); + } + return context; +}; diff --git a/packages/prosody-ui/src/themes/themes.ts b/packages/prosody-ui/src/themes/themes.ts new file mode 100644 index 0000000..5637c97 --- /dev/null +++ b/packages/prosody-ui/src/themes/themes.ts @@ -0,0 +1,321 @@ +export type ThemeName = + | "light" + | "dark" + | "sepia" + | "noir" + | "ocean" + | "forest" + | "gruvbox"; + +export interface ThemeColors { + primary: string; + primaryHover: string; + secondary: string; + accent: string; + accentHover: string; + background: string; + surface: string; + surfaceHover: string; + text: string; + textSecondary: string; + textMuted: string; + border: string; + borderLight: string; + success: string; + warning: string; + error: string; + info: string; + link: string; + linkHover: string; + shadow: string; + overlay: string; +} + +export interface ThemeTypography { + fontSizeXs: string; + fontSizeSm: string; + fontSizeMd: string; + fontSizeLg: string; + fontSizeXl: string; + fontWeightNormal: string; + fontWeightMedium: string; + fontWeightSemibold: string; + fontWeightBold: string; +} + +export interface ThemeSpacing { + spacingXs: string; + spacingSm: string; + spacingMd: string; + spacingLg: string; + spacingXl: string; +} + +export interface ThemeRadius { + radiusSm: string; + radiusMd: string; + radiusLg: string; + radiusFull: string; +} + +export interface ThemeTransitions { + transitionFast: string; + transitionNormal: string; + transitionSlow: string; +} + +export interface Theme { + name: ThemeName; + colors: ThemeColors; + typography: ThemeTypography; + spacing: ThemeSpacing; + radius: ThemeRadius; + transitions: ThemeTransitions; +} + +// Common theme properties +const commonTypography: ThemeTypography = { + fontSizeXs: "0.75rem", + fontSizeSm: "0.875rem", + fontSizeMd: "1rem", + fontSizeLg: "1.125rem", + fontSizeXl: "1.25rem", + fontWeightNormal: "400", + fontWeightMedium: "500", + fontWeightSemibold: "600", + fontWeightBold: "700", +}; + +const commonSpacing: ThemeSpacing = { + spacingXs: "0.25rem", + spacingSm: "0.5rem", + spacingMd: "1rem", + spacingLg: "1.5rem", + spacingXl: "2rem", +}; + +const commonRadius: ThemeRadius = { + radiusSm: "0.25rem", + radiusMd: "0.5rem", + radiusLg: "0.75rem", + radiusFull: "9999px", +}; + +const commonTransitions: ThemeTransitions = { + transitionFast: "150ms ease", + transitionNormal: "250ms ease", + transitionSlow: "350ms ease", +}; + +export const themes: Record<ThemeName, Theme> = { + light: { + name: "light", + colors: { + primary: "#543fd7", + primaryHover: "#4532b8", + secondary: "#f39c12", + accent: "#2a9d8f", + accentHover: "#238b7f", + background: "#ffffff", + surface: "#f8f9fa", + surfaceHover: "#e9ecef", + text: "#212529", + textSecondary: "#495057", + textMuted: "#6c757d", + border: "#dee2e6", + borderLight: "#e9ecef", + success: "#28a745", + warning: "#ffc107", + error: "#dc3545", + info: "#17a2b8", + link: "#543fd7", + linkHover: "#4532b8", + shadow: "rgba(0, 0, 0, 0.1)", + overlay: "rgba(0, 0, 0, 0.5)", + }, + typography: commonTypography, + spacing: commonSpacing, + radius: commonRadius, + transitions: commonTransitions, + }, + dark: { + name: "dark", + colors: { + primary: "#7c6ef7", + primaryHover: "#9085f9", + secondary: "#f39c12", + accent: "#2a9d8f", + accentHover: "#238b7f", + background: "#0d1117", + surface: "#161b22", + surfaceHover: "#21262d", + text: "#c9d1d9", + textSecondary: "#8b949e", + textMuted: "#6e7681", + border: "#30363d", + borderLight: "#21262d", + success: "#3fb950", + warning: "#d29922", + error: "#f85149", + info: "#58a6ff", + link: "#58a6ff", + linkHover: "#79b8ff", + shadow: "rgba(0, 0, 0, 0.3)", + overlay: "rgba(0, 0, 0, 0.7)", + }, + typography: commonTypography, + spacing: commonSpacing, + radius: commonRadius, + transitions: commonTransitions, + }, + sepia: { + name: "sepia", + colors: { + primary: "#8b4513", + primaryHover: "#6b3410", + secondary: "#d2691e", + accent: "#2a9d8f", + accentHover: "#238b7f", + background: "#f4e8d0", + surface: "#ede0c8", + surfaceHover: "#e6d9c0", + text: "#3e2723", + textSecondary: "#5d4037", + textMuted: "#6d4c41", + border: "#d7ccc8", + borderLight: "#e0d5d0", + success: "#689f38", + warning: "#ff9800", + error: "#d32f2f", + info: "#0288d1", + link: "#8b4513", + linkHover: "#6b3410", + shadow: "rgba(62, 39, 35, 0.1)", + overlay: "rgba(62, 39, 35, 0.5)", + }, + typography: commonTypography, + spacing: commonSpacing, + radius: commonRadius, + transitions: commonTransitions, + }, + noir: { + name: "noir", + colors: { + primary: "#ffffff", + primaryHover: "#e0e0e0", + secondary: "#808080", + accent: "#2a9d8f", + accentHover: "#238b7f", + background: "#000000", + surface: "#0a0a0a", + surfaceHover: "#1a1a1a", + text: "#ffffff", + textSecondary: "#b0b0b0", + textMuted: "#808080", + border: "#333333", + borderLight: "#1a1a1a", + success: "#4caf50", + warning: "#ff9800", + error: "#f44336", + info: "#2196f3", + link: "#b0b0b0", + linkHover: "#ffffff", + shadow: "rgba(255, 255, 255, 0.1)", + overlay: "rgba(0, 0, 0, 0.9)", + }, + typography: commonTypography, + spacing: commonSpacing, + radius: commonRadius, + transitions: commonTransitions, + }, + ocean: { + name: "ocean", + colors: { + primary: "#006994", + primaryHover: "#005577", + secondary: "#00acc1", + accent: "#2a9d8f", + accentHover: "#238b7f", + background: "#e1f5fe", + surface: "#b3e5fc", + surfaceHover: "#81d4fa", + text: "#01579b", + textSecondary: "#0277bd", + textMuted: "#4fc3f7", + border: "#81d4fa", + borderLight: "#b3e5fc", + success: "#00c853", + warning: "#ffab00", + error: "#d50000", + info: "#00b0ff", + link: "#0277bd", + linkHover: "#01579b", + shadow: "rgba(1, 87, 155, 0.1)", + overlay: "rgba(1, 87, 155, 0.5)", + }, + typography: commonTypography, + spacing: commonSpacing, + radius: commonRadius, + transitions: commonTransitions, + }, + forest: { + name: "forest", + colors: { + primary: "#2e7d32", + primaryHover: "#1b5e20", + secondary: "#689f38", + accent: "#2a9d8f", + accentHover: "#238b7f", + background: "#f1f8e9", + surface: "#dcedc8", + surfaceHover: "#c5e1a5", + text: "#1b5e20", + textSecondary: "#33691e", + textMuted: "#558b2f", + border: "#aed581", + borderLight: "#c5e1a5", + success: "#4caf50", + warning: "#ff9800", + error: "#f44336", + info: "#03a9f4", + link: "#388e3c", + linkHover: "#2e7d32", + shadow: "rgba(27, 94, 32, 0.1)", + overlay: "rgba(27, 94, 32, 0.5)", + }, + typography: commonTypography, + spacing: commonSpacing, + radius: commonRadius, + transitions: commonTransitions, + }, + gruvbox: { + name: "gruvbox", + colors: { + primary: "#fe8019", + primaryHover: "#d65d0e", + secondary: "#fabd2f", + accent: "#2a9d8f", + accentHover: "#238b7f", + background: "#282828", + surface: "#3c3836", + surfaceHover: "#504945", + text: "#ebdbb2", + textSecondary: "#d5c4a1", + textMuted: "#bdae93", + border: "#665c54", + borderLight: "#504945", + success: "#b8bb26", + warning: "#fabd2f", + error: "#fb4934", + info: "#83a598", + link: "#8ec07c", + linkHover: "#b8bb26", + shadow: "rgba(0, 0, 0, 0.3)", + overlay: "rgba(40, 40, 40, 0.8)", + }, + typography: commonTypography, + spacing: commonSpacing, + radius: commonRadius, + transitions: commonTransitions, + }, +}; diff --git a/packages/prosody-ui/src/zoom/FullText.tsx b/packages/prosody-ui/src/zoom/FullText.tsx index ec85f09..9b7fe63 100644 --- a/packages/prosody-ui/src/zoom/FullText.tsx +++ b/packages/prosody-ui/src/zoom/FullText.tsx @@ -3,7 +3,7 @@ import { motion, AnimatePresence } from "motion/react"; import Paragraph from "./Paragraph"; import { useZoom } from "./hooks/useZoom"; import { containerVariants, buttonVariants } from "./animations"; -import { NLP } from "sortug-ai"; +import { NLP } from "@sortug/ai"; interface TextFocusMorphProps { text: string; diff --git a/packages/prosody-ui/src/zoom/Paragraph.tsx b/packages/prosody-ui/src/zoom/Paragraph.tsx index c26f806..b149468 100644 --- a/packages/prosody-ui/src/zoom/Paragraph.tsx +++ b/packages/prosody-ui/src/zoom/Paragraph.tsx @@ -1,7 +1,7 @@ import React, { memo, useCallback, useEffect, useState } from "react"; import { motion } from "motion/react"; import type { ViewProps, LoadingStatus } from "./logic/types"; -import { NLP } from "sortug-ai"; +import { NLP } from "@sortug/ai"; import Sentence from "./Sentence"; import { paragraphVariants, createHoverEffect } from "./animations"; import { useZoom } from "./hooks/useZoom"; diff --git a/packages/prosody-ui/src/zoom/Sentence.tsx b/packages/prosody-ui/src/zoom/Sentence.tsx index 1d90346..fc75773 100644 --- a/packages/prosody-ui/src/zoom/Sentence.tsx +++ b/packages/prosody-ui/src/zoom/Sentence.tsx @@ -1,7 +1,7 @@ import React, { memo } from "react"; import { motion } from "motion/react"; import type { ViewProps, LoadingStatus } from "./logic/types"; -import { NLP } from "sortug-ai"; +import { NLP } from "@sortug/ai"; import SpacyClause from "./SpacyClause"; import { sentenceVariants, createHoverEffect } from "./animations"; import { useZoom } from "./hooks/useZoom"; diff --git a/packages/prosody-ui/src/zoom/SpacyClause.tsx b/packages/prosody-ui/src/zoom/SpacyClause.tsx index 6b6f178..c08a291 100644 --- a/packages/prosody-ui/src/zoom/SpacyClause.tsx +++ b/packages/prosody-ui/src/zoom/SpacyClause.tsx @@ -1,7 +1,7 @@ import React, { memo, useState } from "react"; import { motion } from "motion/react"; import "./spacy.css"; -import { NLP } from "sortug-ai"; +import { NLP } from "@sortug/ai"; // import { clauseVariants, createHoverEffect } from "./animations"; // import { useZoom } from "./hooks/useZoom"; diff --git a/packages/prosody-ui/src/zoom/logic/types.ts b/packages/prosody-ui/src/zoom/logic/types.ts index bea68ff..fd72601 100644 --- a/packages/prosody-ui/src/zoom/logic/types.ts +++ b/packages/prosody-ui/src/zoom/logic/types.ts @@ -1,4 +1,4 @@ -import type { NLP } from "sortug-ai"; +import type { NLP } from "@sortug/ai"; export type ViewLevel = | "text" diff --git a/packages/prosody-ui/tsconfig.json b/packages/prosody-ui/tsconfig.json index 238655f..5a512ba 100644 --- a/packages/prosody-ui/tsconfig.json +++ b/packages/prosody-ui/tsconfig.json @@ -22,6 +22,9 @@ // Some stricter flags (disabled by default) "noUnusedLocals": false, "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false + "noPropertyAccessFromIndexSignature": false, + "paths": { + "@/*": ["./src/*"] + } } } diff --git a/packages/sortug/bun.lock b/packages/sortug/bun.lock deleted file mode 100644 index d50feb5..0000000 --- a/packages/sortug/bun.lock +++ /dev/null @@ -1,25 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "sortug", - "devDependencies": { - "@types/bun": "latest", - }, - "peerDependencies": { - "typescript": "^5", - }, - }, - }, - "packages": { - "@types/bun": ["@types/bun@1.2.16", "", { "dependencies": { "bun-types": "1.2.16" } }, "sha512-1aCZJ/6nSiViw339RsaNhkNoEloLaPzZhxMOYEa7OzRzO41IGg5n/7I43/ZIAW/c+Q6cT12Vf7fOZOoVIzb5BQ=="], - - "@types/node": ["@types/node@24.0.1", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw=="], - - "bun-types": ["bun-types@1.2.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A=="], - - "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], - - "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], - } -} diff --git a/packages/sortug/index.ts b/packages/sortug/index.ts index 039454f..0ab7ee4 100644 --- a/packages/sortug/index.ts +++ b/packages/sortug/index.ts @@ -1,4 +1,2 @@ export type * from "./src/types"; export * from "./src/utils"; -import styles from "./src/styles.module.css"; -export { styles }; diff --git a/packages/prosody-ui/src/sortug.css b/packages/sortug/src/styles.css index c6280c0..375e620 100644 --- a/packages/prosody-ui/src/sortug.css +++ b/packages/sortug/src/styles.css @@ -1,4 +1,3 @@ - /* SORTUG CSS */ /* variables */ :root { @@ -234,7 +233,7 @@ button, /* Modal Content */ #modal-fg { - background-color: var(--bg); + background-color: var(--modal-bg); position: fixed; top: 50%; left: 50%; @@ -245,4 +244,4 @@ button, min-height: 20vh; max-width: 90vw; overflow: auto; -} +}
\ No newline at end of file diff --git a/packages/sortug/src/utils.ts b/packages/sortug/src/utils.ts index 687b8db..43fac8a 100644 --- a/packages/sortug/src/utils.ts +++ b/packages/sortug/src/utils.ts @@ -1,25 +1,45 @@ +export function cycleNext(current: number, max: number) { + if (current + 1 === max) return 0; + else return current + 1; +} +export function cyclePrev(current: number, max: number) { + if (current === 0) return max; + else return current - 1; +} + export function randomFromArray<T>(a: Array<T>): T { const l = a.length; - const ind = Math.floor(Math.random() * l); - if (ind === l) return a[ind - 1]; - else return a[ind]; + if (l === 0) throw new Error("Empty array!"); + const randomIdx = Math.floor(Math.random() * l); + const idx = randomIdx === l ? randomIdx - 1 : randomIdx; + const el = a[idx]!; + return el; } -export function randomFromArrayAcc<T>(a: Array<T>, s?: Set<T>): T { - const st = s ? s : new Set(a); - const l = a.length; - const ind = Math.floor(Math.random() * l); - const res = ind === l ? a[ind - 1] : a[ind]; - if (st.has(res)) return randomFromArrayAcc(a, st); - else { - st.add(res); - // TODO have to return this too? - return res; - } +export function randomFromArrayMany<T>( + a: Array<T>, + count: number, + canRepeat = false, +): Array<T> { + if (canRepeat) return [...new Array(count)].map((s) => randomFromArray(a)); + else return randomFromArrayNoRepeat(a, count); +} +export function randomFromArrayNoRepeat<T>( + a: Array<T>, + count: number, + acc?: Set<T>, +): T[] { + const all = new Set(a); + const st = acc ? acc : new Set<T>(); + if (all.size === st.size || st.size === count) return [...st]; + const el = randomFromArray(a); + if (!st.has(el)) st.add(el); + return randomFromArrayNoRepeat(a, count, st); } + export function notRandomFromArray<T>(data: string, a: Array<T>): T { const l = a.length; const ind = hashTextToNumber(data, l - 1); - return a[ind]; + return a[ind]!; } function hashTextToNumber(text: string, max: number): number { diff --git a/packages/tweetdeck/package.json b/packages/tweetdeck/package.json index bc9a71c..b7ce11a 100644 --- a/packages/tweetdeck/package.json +++ b/packages/tweetdeck/package.json @@ -9,14 +9,16 @@ "start": "NODE_ENV=production bun src/index.ts" }, "dependencies": { + "@sortug/ai": "workspace:*", + "@sortug/lib": "workspace:*", + "@sortug/prosody-ui": "workspace:*", + "@sortug/sorlang-db": "workspace:*", "bun_python": "^0.1.10", "lucide-react": "latest", "node-html-parser": "^7.0.1", "react": "^19", "react-dom": "^19", - "@sortug/lib": "workspce:*", - "@sortug/prosody-ui": "workspce:*", - "@sortug/ai": "workspce:*" + "react-hot-toast": "^2.6.0" }, "devDependencies": { "@types/react": "^19", diff --git a/packages/tweetdeck/src/App.tsx b/packages/tweetdeck/src/App.tsx index 924ff9a..44b6405 100644 --- a/packages/tweetdeck/src/App.tsx +++ b/packages/tweetdeck/src/App.tsx @@ -1,310 +1,15 @@ -import { useCallback, useEffect, useMemo, useState } from "react"; -import "./styles/normalize.css"; import "./styles/index.css"; -import { Sidebar, type NewAccountInput } from "./components/Sidebar"; -import { ColumnBoard } from "./components/ColumnBoard"; -import { AddColumnModal } from "./components/AddColumnModal"; -import { usePersistentState } from "./hooks/usePersistentState"; -import type { - ColumnSnapshot, - ColumnState, - DeckAccount, - DeckColumn, - DeckListsCache, - FullscreenState, -} from "./types/app"; -import type { Tweet } from "./lib/fetching/types"; -import { generateId } from "./lib/utils/id"; -import { twitterClient } from "./lib/client/twitterClient"; -import { FullscreenColumn } from "./components/FullscreenColumn"; - -const ACCOUNTS_KEY = "tweetdeck.accounts"; -const COLUMNS_KEY = "tweetdeck.columns"; +import "./styles/normalize.css"; +import { Toaster } from "react-hot-toast"; +import Deck from "./pages/Deck"; +import Test from "./Test"; export function App() { - const [accounts, setAccounts] = usePersistentState<DeckAccount[]>( - ACCOUNTS_KEY, - [], - ); - const [columns, setColumns] = usePersistentState<DeckColumn[]>( - COLUMNS_KEY, - [], - ); - const [listsCache, setListsCache] = useState<DeckListsCache>({}); - const [activeAccountId, setActiveAccountId] = useState<string | undefined>( - () => accounts[0]?.id, - ); - const [isModalOpen, setModalOpen] = useState(false); - const [toast, setToast] = useState<string | null>(null); - const [fullscreen, setFullscreen] = useState<FullscreenState | null>(null); - const [columnSnapshots, setColumnSnapshots] = useState< - Record<string, ColumnSnapshot> - >({}); - - useEffect(() => { - if (!activeAccountId) { - const firstAccount = accounts[0]; - if (firstAccount) { - setActiveAccountId(firstAccount.id); - } - } - }, [accounts, activeAccountId]); - - useEffect(() => { - const acs = accounts.filter((a) => !a.avatar || !a.username); - console.log({ acs }); - const nacs = acs.map(async (acc) => { - const our = await twitterClient.own({ cookie: acc.cookie }); - const nacc = { - ...acc, - handle: our.username, - label: our.name, - avatar: our.avatar, - }; - return nacc; - }); - Promise.all(nacs) - .then((acs) => setAccounts(acs)) - .catch((err) => console.error(err)); - }, []); - - const handleAddAccount = useCallback( - (payload: NewAccountInput) => { - const label = `Session ${accounts.length + 1}`; - const account: DeckAccount = { - id: generateId(), - label, - accent: randomAccent(), - cookie: payload.cookie.trim(), - createdAt: Date.now(), - }; - setAccounts((prev) => [...prev, account]); - setActiveAccountId(account.id); - setToast(`${account.label} is ready`); - }, - [accounts.length, setAccounts], - ); - - const handleRemoveAccount = useCallback( - (accountId: string) => { - setAccounts((prev) => prev.filter((account) => account.id !== accountId)); - setColumns((prev) => - prev.filter((column) => column.accountId !== accountId), - ); - setListsCache((prev) => { - const next = { ...prev }; - delete next[accountId]; - return next; - }); - if (activeAccountId === accountId) { - setActiveAccountId(undefined); - } - }, - [activeAccountId, setAccounts, setColumns], - ); - - const handleAddColumn = useCallback( - (column: Omit<DeckColumn, "id">) => { - const nextColumn = { ...column, id: generateId() }; - setColumns((prev) => [...prev, nextColumn]); - setToast(`${nextColumn.title} added to deck`); - }, - [setColumns], - ); - - const handleRemoveColumn = useCallback( - (id: string) => { - setColumns((prev) => prev.filter((column) => column.id !== id)); - }, - [setColumns], - ); - - const fetchLists = useCallback( - async (accountId: string) => { - const account = accounts.find((acc) => acc.id === accountId); - if (!account) throw new Error("Account not found"); - if (listsCache[accountId]) return listsCache[accountId]; - console.log({ listsCache }); - const lists = await twitterClient.lists({ cookie: account.cookie }); - console.log({ lists }); - setListsCache((prev) => ({ ...prev, [accountId]: lists })); - return lists; - }, - [accounts, listsCache], - ); - - const handleColumnStateChange = useCallback( - (columnId: string, state: ColumnState) => { - setColumns((prev) => - prev.map((column) => - column.id === columnId ? { ...column, state } : column, - ), - ); - }, - [setColumns], - ); - - const handleColumnSnapshot = useCallback( - (columnId: string, snapshot: ColumnSnapshot) => { - setColumnSnapshots((prev) => { - const existing = prev[columnId]; - if ( - existing && - existing.tweets === snapshot.tweets && - existing.label === snapshot.label - ) { - return prev; - } - return { - ...prev, - [columnId]: { tweets: snapshot.tweets, label: snapshot.label }, - }; - }); - }, - [], - ); - - const openFullscreen = useCallback( - (payload: FullscreenState) => { - const snapshot = columnSnapshots[payload.column.id]; - setFullscreen({ - ...payload, - tweets: snapshot?.tweets ?? payload.tweets, - columnLabel: snapshot?.label ?? payload.columnLabel, - }); - }, - [columnSnapshots], - ); - - useEffect(() => { - if (!fullscreen) return; - const snapshot = columnSnapshots[fullscreen.column.id]; - if (!snapshot) return; - if ( - snapshot.tweets === fullscreen.tweets && - snapshot.label === fullscreen.columnLabel - ) { - return; - } - setFullscreen((prev) => { - if (!prev) return prev; - if (prev.column.id !== fullscreen.column.id) return prev; - const tweets = snapshot.tweets; - const index = Math.min(prev.index, Math.max(tweets.length - 1, 0)); - return { - ...prev, - tweets, - columnTitle: snapshot.label, - index, - }; - }); - }, [columnSnapshots, fullscreen]); - - const content = useMemo( - () => ( - <ColumnBoard - columns={columns} - accounts={accounts} - onRemove={handleRemoveColumn} - onStateChange={handleColumnStateChange} - onSnapshot={handleColumnSnapshot} - onEnterFullscreen={openFullscreen} - /> - ), - [ - accounts, - columns, - handleRemoveColumn, - handleColumnStateChange, - handleColumnSnapshot, - openFullscreen, - ], - ); - return ( - <div className="app-shell"> - <Sidebar - accounts={accounts} - activeAccountId={activeAccountId} - onActivate={(id) => setActiveAccountId(id)} - onAddAccount={handleAddAccount} - onRemoveAccount={handleRemoveAccount} - onAddColumn={() => setModalOpen(true)} - /> - - <main>{content}</main> - - <AddColumnModal - accounts={accounts} - activeAccountId={activeAccountId} - isOpen={isModalOpen} - onClose={() => setModalOpen(false)} - onAdd={handleAddColumn} - fetchLists={fetchLists} - listsCache={listsCache} - /> - - {toast && ( - <div className="toast" onAnimationEnd={() => setToast(null)}> - {toast} - </div> - )} - - {fullscreen && ( - <FullscreenColumn - state={fullscreen} - onExit={() => setFullscreen(null)} - onNavigate={(step) => { - setFullscreen((prev) => { - if (!prev) return prev; - if (!prev.tweets.length) return prev; - const nextIndex = Math.min( - prev.tweets.length - 1, - Math.max(0, prev.index + step), - ); - if (nextIndex === prev.index) return prev; - return { ...prev, index: nextIndex }; - }); - }} - hasPrevColumn={fullscreen.columnIndex > 0} - hasNextColumn={fullscreen.columnIndex < columns.length - 1} - onSwitchColumn={(direction) => { - setFullscreen((prev) => { - if (!prev) return prev; - const nextIndex = prev.columnIndex + direction; - if (nextIndex < 0) return prev; - if (nextIndex >= columns.length) { - setModalOpen(true); - return prev; - } - const nextColumn = columns[nextIndex]; - if (!nextColumn) return prev; - const snapshot = columnSnapshots[nextColumn.id]; - const account = accounts.find( - (acc) => acc.id === nextColumn.accountId, - ); - const tweets = snapshot?.tweets ?? []; - return { - column: nextColumn, - columnIndex: nextIndex, - columnLabel: snapshot?.label ?? nextColumn.title, - accent: account?.accent ?? prev.accent, - tweets, - index: 0, - }; - }); - }} - onAddColumn={() => setModalOpen(true)} - /> - )} - </div> + <> + <Test /> + <Toaster position="top-center" /> + </> ); } - -function randomAccent(): string { - const palette = ["#7f5af0", "#2cb67d", "#f25f4c", "#f0a500", "#19a7ce"]; - const pick = palette[Math.floor(Math.random() * palette.length)]; - return pick ?? "#7f5af0"; -} - export default App; diff --git a/packages/tweetdeck/src/Test.tsx b/packages/tweetdeck/src/Test.tsx new file mode 100644 index 0000000..28de9b9 --- /dev/null +++ b/packages/tweetdeck/src/Test.tsx @@ -0,0 +1,19 @@ +import "./styles/normalize.css"; +import "./styles/index.css"; +import { LangText } from "@sortug/prosody-ui"; +import toast from "react-hot-toast"; + +export function Test() { + const text = `อุตุฯ ฉบับ 16 เช็กจังหวัดภาคใต้เจอฝนตกหนักถึงหนักมาก`; + return ( + <div className="app-shell"> + <LangText + lang="th" + text={text} + theme="dark" + handleError={(e) => toast.error(e)} + /> + </div> + ); +} +export default Test; diff --git a/packages/tweetdeck/src/components/TweetCard.tsx b/packages/tweetdeck/src/components/TweetCard.tsx index 7cd2936..c9e6219 100644 --- a/packages/tweetdeck/src/components/TweetCard.tsx +++ b/packages/tweetdeck/src/components/TweetCard.tsx @@ -2,7 +2,7 @@ import { useCallback, useState } from "react"; import { Bookmark, Heart, Link2, MessageCircle, Repeat2 } from "lucide-react"; import type { Tweet } from "../lib/fetching/types"; import { timeAgo } from "../lib/utils/time"; -import { LangText } from "prosody-ui"; +import { LangText } from "@sortug/prosody-ui"; interface TweetCardProps { tweet: Tweet; diff --git a/packages/tweetdeck/src/index.ts b/packages/tweetdeck/src/index.ts index ccc86e7..9daa973 100644 --- a/packages/tweetdeck/src/index.ts +++ b/packages/tweetdeck/src/index.ts @@ -1,3 +1,4 @@ +import { handler } from "@sortug/sorlang-db"; import { serve } from "bun"; import index from "./index.html"; import { TwitterApiService } from "./lib/fetching/twitter-api"; @@ -39,7 +40,7 @@ const server = serve({ routes: { // Serve index.html for all unmatched routes. "/*": index, - + "/api/db": handler, "/api/hello": { async GET(req) { return Response.json({ diff --git a/packages/tweetdeck/src/pages/Deck.tsx b/packages/tweetdeck/src/pages/Deck.tsx new file mode 100644 index 0000000..c6fa41a --- /dev/null +++ b/packages/tweetdeck/src/pages/Deck.tsx @@ -0,0 +1,310 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import "../styles/normalize.css"; +import "../styles/index.css"; +import { Sidebar, type NewAccountInput } from "../components/Sidebar"; +import { ColumnBoard } from "../components/ColumnBoard"; +import { AddColumnModal } from "../components/AddColumnModal"; +import { usePersistentState } from "../hooks/usePersistentState"; +import type { + ColumnSnapshot, + ColumnState, + DeckAccount, + DeckColumn, + DeckListsCache, + FullscreenState, +} from "../types/app"; +import type { Tweet } from "../lib/fetching/types"; +import { generateId } from "../lib/utils/id"; +import { twitterClient } from "../lib/client/twitterClient"; +import { FullscreenColumn } from "../components/FullscreenColumn"; + +const ACCOUNTS_KEY = "tweetdeck.accounts"; +const COLUMNS_KEY = "tweetdeck.columns"; + +export function App() { + const [accounts, setAccounts] = usePersistentState<DeckAccount[]>( + ACCOUNTS_KEY, + [], + ); + const [columns, setColumns] = usePersistentState<DeckColumn[]>( + COLUMNS_KEY, + [], + ); + const [listsCache, setListsCache] = useState<DeckListsCache>({}); + const [activeAccountId, setActiveAccountId] = useState<string | undefined>( + () => accounts[0]?.id, + ); + const [isModalOpen, setModalOpen] = useState(false); + const [toast, setToast] = useState<string | null>(null); + const [fullscreen, setFullscreen] = useState<FullscreenState | null>(null); + const [columnSnapshots, setColumnSnapshots] = useState< + Record<string, ColumnSnapshot> + >({}); + + useEffect(() => { + if (!activeAccountId) { + const firstAccount = accounts[0]; + if (firstAccount) { + setActiveAccountId(firstAccount.id); + } + } + }, [accounts, activeAccountId]); + + useEffect(() => { + const acs = accounts.filter((a) => !a.avatar || !a.username); + console.log({ acs }); + const nacs = acs.map(async (acc) => { + const our = await twitterClient.own({ cookie: acc.cookie }); + const nacc = { + ...acc, + handle: our.username, + label: our.name, + avatar: our.avatar, + }; + return nacc; + }); + Promise.all(nacs) + .then((acs) => setAccounts(acs)) + .catch((err) => console.error(err)); + }, []); + + const handleAddAccount = useCallback( + (payload: NewAccountInput) => { + const label = `Session ${accounts.length + 1}`; + const account: DeckAccount = { + id: generateId(), + label, + accent: randomAccent(), + cookie: payload.cookie.trim(), + createdAt: Date.now(), + }; + setAccounts((prev) => [...prev, account]); + setActiveAccountId(account.id); + setToast(`${account.label} is ready`); + }, + [accounts.length, setAccounts], + ); + + const handleRemoveAccount = useCallback( + (accountId: string) => { + setAccounts((prev) => prev.filter((account) => account.id !== accountId)); + setColumns((prev) => + prev.filter((column) => column.accountId !== accountId), + ); + setListsCache((prev) => { + const next = { ...prev }; + delete next[accountId]; + return next; + }); + if (activeAccountId === accountId) { + setActiveAccountId(undefined); + } + }, + [activeAccountId, setAccounts, setColumns], + ); + + const handleAddColumn = useCallback( + (column: Omit<DeckColumn, "id">) => { + const nextColumn = { ...column, id: generateId() }; + setColumns((prev) => [...prev, nextColumn]); + setToast(`${nextColumn.title} added to deck`); + }, + [setColumns], + ); + + const handleRemoveColumn = useCallback( + (id: string) => { + setColumns((prev) => prev.filter((column) => column.id !== id)); + }, + [setColumns], + ); + + const fetchLists = useCallback( + async (accountId: string) => { + const account = accounts.find((acc) => acc.id === accountId); + if (!account) throw new Error("Account not found"); + if (listsCache[accountId]) return listsCache[accountId]; + console.log({ listsCache }); + const lists = await twitterClient.lists({ cookie: account.cookie }); + console.log({ lists }); + setListsCache((prev) => ({ ...prev, [accountId]: lists })); + return lists; + }, + [accounts, listsCache], + ); + + const handleColumnStateChange = useCallback( + (columnId: string, state: ColumnState) => { + setColumns((prev) => + prev.map((column) => + column.id === columnId ? { ...column, state } : column, + ), + ); + }, + [setColumns], + ); + + const handleColumnSnapshot = useCallback( + (columnId: string, snapshot: ColumnSnapshot) => { + setColumnSnapshots((prev) => { + const existing = prev[columnId]; + if ( + existing && + existing.tweets === snapshot.tweets && + existing.label === snapshot.label + ) { + return prev; + } + return { + ...prev, + [columnId]: { tweets: snapshot.tweets, label: snapshot.label }, + }; + }); + }, + [], + ); + + const openFullscreen = useCallback( + (payload: FullscreenState) => { + const snapshot = columnSnapshots[payload.column.id]; + setFullscreen({ + ...payload, + tweets: snapshot?.tweets ?? payload.tweets, + columnLabel: snapshot?.label ?? payload.columnLabel, + }); + }, + [columnSnapshots], + ); + + useEffect(() => { + if (!fullscreen) return; + const snapshot = columnSnapshots[fullscreen.column.id]; + if (!snapshot) return; + if ( + snapshot.tweets === fullscreen.tweets && + snapshot.label === fullscreen.columnLabel + ) { + return; + } + setFullscreen((prev) => { + if (!prev) return prev; + if (prev.column.id !== fullscreen.column.id) return prev; + const tweets = snapshot.tweets; + const index = Math.min(prev.index, Math.max(tweets.length - 1, 0)); + return { + ...prev, + tweets, + columnTitle: snapshot.label, + index, + }; + }); + }, [columnSnapshots, fullscreen]); + + const content = useMemo( + () => ( + <ColumnBoard + columns={columns} + accounts={accounts} + onRemove={handleRemoveColumn} + onStateChange={handleColumnStateChange} + onSnapshot={handleColumnSnapshot} + onEnterFullscreen={openFullscreen} + /> + ), + [ + accounts, + columns, + handleRemoveColumn, + handleColumnStateChange, + handleColumnSnapshot, + openFullscreen, + ], + ); + + return ( + <div className="app-shell"> + <Sidebar + accounts={accounts} + activeAccountId={activeAccountId} + onActivate={(id) => setActiveAccountId(id)} + onAddAccount={handleAddAccount} + onRemoveAccount={handleRemoveAccount} + onAddColumn={() => setModalOpen(true)} + /> + + <main>{content}</main> + + <AddColumnModal + accounts={accounts} + activeAccountId={activeAccountId} + isOpen={isModalOpen} + onClose={() => setModalOpen(false)} + onAdd={handleAddColumn} + fetchLists={fetchLists} + listsCache={listsCache} + /> + + {toast && ( + <div className="toast" onAnimationEnd={() => setToast(null)}> + {toast} + </div> + )} + + {fullscreen && ( + <FullscreenColumn + state={fullscreen} + onExit={() => setFullscreen(null)} + onNavigate={(step) => { + setFullscreen((prev) => { + if (!prev) return prev; + if (!prev.tweets.length) return prev; + const nextIndex = Math.min( + prev.tweets.length - 1, + Math.max(0, prev.index + step), + ); + if (nextIndex === prev.index) return prev; + return { ...prev, index: nextIndex }; + }); + }} + hasPrevColumn={fullscreen.columnIndex > 0} + hasNextColumn={fullscreen.columnIndex < columns.length - 1} + onSwitchColumn={(direction) => { + setFullscreen((prev) => { + if (!prev) return prev; + const nextIndex = prev.columnIndex + direction; + if (nextIndex < 0) return prev; + if (nextIndex >= columns.length) { + setModalOpen(true); + return prev; + } + const nextColumn = columns[nextIndex]; + if (!nextColumn) return prev; + const snapshot = columnSnapshots[nextColumn.id]; + const account = accounts.find( + (acc) => acc.id === nextColumn.accountId, + ); + const tweets = snapshot?.tweets ?? []; + return { + column: nextColumn, + columnIndex: nextIndex, + columnLabel: snapshot?.label ?? nextColumn.title, + accent: account?.accent ?? prev.accent, + tweets, + index: 0, + }; + }); + }} + onAddColumn={() => setModalOpen(true)} + /> + )} + </div> + ); +} + +function randomAccent(): string { + const palette = ["#7f5af0", "#2cb67d", "#f25f4c", "#f0a500", "#19a7ce"]; + const pick = palette[Math.floor(Math.random() * palette.length)]; + return pick ?? "#7f5af0"; +} + +export default App; |
