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 /packages/prosody-ui | |
| parent | cb1b56f5a0eddbf77446f415f2beda57c8305f85 (diff) | |
Diffstat (limited to 'packages/prosody-ui')
33 files changed, 1436 insertions, 717 deletions
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/sortug.css b/packages/prosody-ui/src/sortug.css deleted file mode 100644 index c6280c0..0000000 --- a/packages/prosody-ui/src/sortug.css +++ /dev/null @@ -1,248 +0,0 @@ - -/* SORTUG CSS */ -/* variables */ -:root { - --bai: rgba(255, 255, 255, 1); - --baizi: rgba(230, 230, 230); - --hui: rgba(130, 130, 130, 1); - --hei: rgba(0, 0, 0, 1); - --hong: rgb(141, 15, 15, 1); - --huang: rgb(230, 180, 60, 1); - --lan: rgb(30, 60, 80, 1); -} - -[data-theme="dark"] { - --bg: hei; - --fg: baizi; -} - -[data-theme="light"] { - --bg: white; - --fg: black; -} - -* { - box-sizing: border-box; -} - -html, -body, -#root { - height: 100%; - min-height: 100%; - overscroll-behavior: none; - color: var(--fg); - -webkit-font-smoothing: antialiased; - margin: 0; -} - -/* tailwindy classes */ -.card { - padding: 1rem; - max-width: max-content; -} - -button, -.button { - max-width: max-content; - padding: 0.5rem; - border: 1px solid var(--fg); -} - -/* borders */ -.nb { - border: none; -} - -/* widths */ -.hw { - width: 50%; -} - -.qw { - width: 25%; -} - -.tqw { - width: 75%; -} - -/* flex */ -.row { - display: flex; - align-items: center; -} - -.sy { - overflow-y: scroll; -} - -.fsy { - overflow-y: scroll; - height: 100%; -} - -.fxc { - display: flex; - justify-content: center; - align-items: baseline; -} - -/* flex spread */ -.fs { - display: flex; - justify-content: space-between; -} - -.fsc { - display: flex; - justify-content: space-between; - align-items: center; -} - -.g1 { - gap: 0.5rem; -} - -.g2 { - gap: 1rem; -} - -.address { - font-family: "Courier New", Courier, monospace; -} - -.spread { - justify-content: space-between; -} - -.even { - justify-content: space-evenly; -} - -.flexc { - justify-content: center; -} - -.cp { - cursor: pointer; -} - -/* centering */ -.gc { - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.agc { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.ac { - position: absolute; - left: 50%; - transform: translateX(-50%); -} - -.xc { - position: fixed; - left: 50%; - transform: translateX(-50%); - z-index: 20; -} - -.tc { - text-align: center; -} - -.bc { - display: block; - margin-left: auto; - margin-right: auto; -} - -.blocks { - & * { - display: block; - } -} - -.bold { - font-weight: 700; -} - -.weak { - opacity: 0.7; -} - -.all-c { - & * { - margin-left: auto; - margin-right: auto; - } -} - -.mb-1 { - margin-bottom: 1rem; -} - -.error { - color: red; - text-align: center; -} - -.tabs { - display: flex; - justify-content: space-evenly; - align-items: center; - - & .tab { - cursor: pointer; - opacity: 0.5; - } - - & .tab.active { - opacity: 1; - } -} - -.disabled { - opacity: 0.5; -} - -.smol { - font-size: 0.9rem; -} - -/* The Modal (background) */ -#modal-bg { - position: fixed; - z-index: 1; - left: 0; - top: 0; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgba(0, 0, 0, 0.4); - z-index: 998; -} - -/* Modal Content */ -#modal-fg { - background-color: var(--bg); - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - padding: 20px; - z-index: 999; - max-height: 90vh; - min-height: 20vh; - max-width: 90vw; - overflow: auto; -} 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/*"] + } } } |
