From 2ff8590cebe3a9acff1cc0785868929c067ce0fc Mon Sep 17 00:00:00 2001 From: xion Date: Sat, 5 Apr 2025 14:48:01 +0800 Subject: [PATCH] temp --- .gitignore | 2 + .gitmodules | 3 + package.json | 38 +- pnpm-lock.yaml | 434 ++++++++++++++++-- pnpm-workspace.yaml | 2 + rollup.config.mjs | 9 +- src/demo-route.ts | 18 +- src/dev.ts | 9 +- src/index.ts | 4 +- src/modules/db.ts | 15 +- src/modules/query.ts | 12 + src/modules/sequelize.ts | 30 ++ src/provider/core/type.ts | 3 - src/provider/utils/parse-config.ts | 21 +- src/routes/ai-chat/index.ts | 158 ++++++- src/routes/ai-chat/list.ts | 23 + src/routes/ai-chat/models/ai-chat-history.ts | 99 ++++ .../ai-chat/services/chat-config-srevices.ts | 128 ++++++ src/routes/ai-chat/services/chat-services.ts | 149 +++++- src/routes/index.ts | 2 + src/test/encrypt/index.ts | 9 + src/utils/uuid.ts | 8 + submodules/query-config | 1 + 23 files changed, 1079 insertions(+), 98 deletions(-) create mode 100644 .gitmodules create mode 100644 pnpm-workspace.yaml create mode 100644 src/modules/query.ts create mode 100644 src/modules/sequelize.ts create mode 100644 src/routes/ai-chat/list.ts create mode 100644 src/routes/ai-chat/models/ai-chat-history.ts create mode 100644 src/routes/ai-chat/services/chat-config-srevices.ts create mode 100644 src/routes/index.ts create mode 100644 src/test/encrypt/index.ts create mode 100644 src/utils/uuid.ts create mode 160000 submodules/query-config diff --git a/.gitignore b/.gitignore index b1d32a4..97b62d3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ logs !.env.example config.json + +pack-dist \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0dab4d2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/query-config"] + path = submodules/query-config + url = git@git.xiongxiao.me:kevisual/kevisual-query-config.git diff --git a/package.json b/package.json index e8d9f95..99ba59c 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,17 @@ { - "name": "@kevisual/ai-center", + "name": "@kevisual/ai-center-services", "version": "0.0.1", "description": "", "main": "index.js", + "basename": "/root/ai-center-services", + "app": { + "entry": "dist/app.mjs", + "key": "ai-center-services", + "type": "system-app" + }, + "files": [ + "dist" + ], "scripts": { "watch": "rollup -c rollup.config.mjs -w", "build": "rollup -c rollup.config.mjs", @@ -10,8 +19,7 @@ "test": "tsx test/**/*.ts", "dev:watch": "cross-env NODE_ENV=development concurrently -n \"Watch,Dev\" -c \"green,blue\" \"npm run watch\" \"sleep 1 && npm run dev\" ", "clean": "rm -rf dist", - "prepub": "envision switch root", - "pub": "npm run build && envision pack -p -u" + "pub": "envision pack -p -u" }, "keywords": [], "author": "abearxiong (https://www.xiongxiao.me)", @@ -19,18 +27,13 @@ "packageManager": "pnpm@10.7.1", "type": "module", "dependencies": { - "@kevisual/code-center-module": "0.0.18", - "@kevisual/mark": "0.0.7", - "@kevisual/router": "0.0.10", - "cookie": "^1.0.2", - "crypto-js": "^4.2.0", - "dayjs": "^1.11.13", - "formidable": "^3.5.2", - "json5": "^2.2.3", - "lodash-es": "^4.17.21", - "openai": "^4.91.1" + "@kevisual/router": "0.0.10" }, "devDependencies": { + "@kevisual/code-center-module": "0.0.18", + "@kevisual/mark": "0.0.7", + "@kevisual/query": "^0.0.15", + "@kevisual/query-config": "workspace:*", "@kevisual/types": "^0.0.6", "@kevisual/use-config": "^1.0.10", "@rollup/plugin-alias": "^5.1.1", @@ -45,11 +48,20 @@ "@types/node": "^22.14.0", "@vitejs/plugin-basic-ssl": "^2.0.0", "concurrently": "^9.1.2", + "cookie": "^1.0.2", "cross-env": "^7.0.3", + "crypto-js": "^4.2.0", + "dayjs": "^1.11.13", "dotenv": "^16.4.7", + "formidable": "^3.5.2", "ioredis": "^5.6.0", "jsrepo": "^1.45.3", + "lodash-es": "^4.17.21", + "nanoid": "^5.1.5", "nodemon": "^3.1.9", + "openai": "^4.91.1", + "pg": "^8.14.1", + "pg-hstore": "^2.3.4", "pino": "^9.6.0", "pm2": "^6.0.5", "rimraf": "^6.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ee24a79..7b6355d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,37 +8,22 @@ importers: .: dependencies: - '@kevisual/code-center-module': - specifier: 0.0.18 - version: 0.0.18(@kevisual/auth@1.0.5)(@kevisual/router@0.0.10)(@kevisual/use-config@1.0.10(dotenv@16.4.7))(ioredis@5.6.0)(pg@8.14.1)(sequelize@6.37.7(pg@8.14.1)) - '@kevisual/mark': - specifier: 0.0.7 - version: 0.0.7(dotenv@16.4.7)(esbuild@0.25.2) '@kevisual/router': specifier: 0.0.10 version: 0.0.10 - cookie: - specifier: ^1.0.2 - version: 1.0.2 - crypto-js: - specifier: ^4.2.0 - version: 4.2.0 - dayjs: - specifier: ^1.11.13 - version: 1.11.13 - formidable: - specifier: ^3.5.2 - version: 3.5.2 - json5: - specifier: ^2.2.3 - version: 2.2.3 - lodash-es: - specifier: ^4.17.21 - version: 4.17.21 - openai: - specifier: ^4.91.1 - version: 4.91.1(ws@8.18.1)(zod@3.24.2) devDependencies: + '@kevisual/code-center-module': + specifier: 0.0.18 + version: 0.0.18(@kevisual/auth@1.0.5)(@kevisual/router@0.0.10)(@kevisual/use-config@1.0.10(dotenv@16.4.7))(ioredis@5.6.0)(pg@8.14.1)(sequelize@6.37.7(pg-hstore@2.3.4)(pg@8.14.1)) + '@kevisual/mark': + specifier: 0.0.7 + version: 0.0.7(dotenv@16.4.7)(esbuild@0.25.2)(pg-hstore@2.3.4) + '@kevisual/query': + specifier: ^0.0.15 + version: 0.0.15(ws@8.18.1)(zod@3.24.2) + '@kevisual/query-config': + specifier: workspace:* + version: link:submodules/query-config '@kevisual/types': specifier: ^0.0.6 version: 0.0.6 @@ -81,21 +66,48 @@ importers: concurrently: specifier: ^9.1.2 version: 9.1.2 + cookie: + specifier: ^1.0.2 + version: 1.0.2 cross-env: specifier: ^7.0.3 version: 7.0.3 + crypto-js: + specifier: ^4.2.0 + version: 4.2.0 + dayjs: + specifier: ^1.11.13 + version: 1.11.13 dotenv: specifier: ^16.4.7 version: 16.4.7 + formidable: + specifier: ^3.5.2 + version: 3.5.2 ioredis: specifier: ^5.6.0 version: 5.6.0 jsrepo: specifier: ^1.45.3 version: 1.45.3(typescript@5.8.2)(ws@8.18.1)(zod@3.24.2) + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 + nanoid: + specifier: ^5.1.5 + version: 5.1.5 nodemon: specifier: ^3.1.9 version: 3.1.9 + openai: + specifier: ^4.91.1 + version: 4.91.1(ws@8.18.1)(zod@3.24.2) + pg: + specifier: ^8.14.1 + version: 8.14.1 + pg-hstore: + specifier: ^2.3.4 + version: 2.3.4 pino: specifier: ^9.6.0 version: 9.6.0 @@ -119,7 +131,7 @@ importers: version: 6.2.1(esbuild@0.25.2)(rollup@4.39.0) sequelize: specifier: ^6.37.7 - version: 6.37.7(pg@8.14.1) + version: 6.37.7(pg-hstore@2.3.4)(pg@8.14.1) tape: specifier: ^5.9.0 version: 5.9.0 @@ -136,6 +148,16 @@ importers: specifier: ^6.2.5 version: 6.2.5(@types/node@22.14.0)(tsx@4.19.3) + submodules/query-config: + dependencies: + '@kevisual/query': + specifier: ^0.0.13 + version: 0.0.13(ws@8.18.1)(zod@3.24.2) + devDependencies: + tsup: + specifier: ^8.4.0 + version: 8.4.0(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.2) + packages: '@ampproject/remapping@2.3.0': @@ -392,6 +414,12 @@ packages: '@kevisual/mark@0.0.7': resolution: {integrity: sha512-PiEEy4yvWEpixw76PzgrIWeNelzm+FrhtzFmqJU92o5GkgawaFwighcvIxqcVZRKeEFF4uvlTjFrGeQvXw6F4A==} + '@kevisual/query@0.0.13': + resolution: {integrity: sha512-gSEIDiCvwSaLLAFZv4vam4wSrMsaCuQ3VGjE3kwRwZ8urlVH1TOA+NUO908A22p9m1Iij7Y1Q/JlfSJi2QzuKQ==} + + '@kevisual/query@0.0.15': + resolution: {integrity: sha512-DK41qvyOiJMmlj70QyVP/48M0gszA39DdnBLtgU94YwAe6OqKrr9tYXHLjZrOROmUVMezIIBQuWMLedSAvb54A==} + '@kevisual/rollup-tools@0.0.1': resolution: {integrity: sha512-TdCN+IU0fyHudiiqYvobXQ8r5MltfM/cKmSS59iopyL8YYwXwcipOS4S24NWA79g7uwJfSUNk5lg3yVhom79fQ==} hasBin: true @@ -602,6 +630,10 @@ packages: '@oxc-project/types@0.60.0': resolution: {integrity: sha512-prhfNnb3ATFHOCv7mzKFfwLij5RzoUz6Y1n525ZhCEqfq5wreCXL+DyVoq3ShukPo7q45ZjYIdjFUgjj+WKzng==} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@pm2/agent@2.1.1': resolution: {integrity: sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==} @@ -963,6 +995,9 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1071,6 +1106,16 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1106,6 +1151,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} @@ -1151,6 +1200,10 @@ packages: commander@2.15.1: resolution: {integrity: sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==} + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -1166,6 +1219,10 @@ packages: resolution: {integrity: sha512-Bi6v586cy1CoTFViVO4lGTtx780lfF96fUmS1lSX6wpZf6330NvHUu6fReVuDP1de8Mg0nkZb01c8tAQdz1o3w==} engines: {node: '>=18'} + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} @@ -1604,6 +1661,10 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + glob@11.0.1: resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==} engines: {node: 20 || >=22} @@ -1862,10 +1923,17 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.1.0: resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==} engines: {node: 20 || >=22} + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-git@0.7.8: resolution: {integrity: sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==} @@ -1888,11 +1956,6 @@ packages: json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -1900,6 +1963,17 @@ packages: resolution: {integrity: sha512-+eHkKSyrFJcJ0N/H57QAH3OsDja0T3zPN8DEti2ZyBL/4CZq/iR/BcEgyNgV2bFUvbR9/nUYbA33cgtztiQRsA==} hasBin: true + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + locate-character@3.0.0: resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} @@ -1912,9 +1986,15 @@ packages: lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.1.0: resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} engines: {node: 20 || >=22} @@ -1964,6 +2044,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -1995,6 +2079,9 @@ packages: mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2145,6 +2232,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.0: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} @@ -2166,6 +2257,10 @@ packages: pg-connection-string@2.7.0: resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + pg-hstore@2.3.4: + resolution: {integrity: sha512-N3SGs/Rf+xA1M2/n0JBiXFDVMzdekwLZLAO0g7mpDY9ouX+fDI7jS6kTq3JujmYbtNSJ53TJ0q4G98KVZSM4EA==} + engines: {node: '>= 0.8.x'} + pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -2223,6 +2318,10 @@ packages: resolution: {integrity: sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==} hasBin: true + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + pm2-axon-rpc@0.7.1: resolution: {integrity: sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==} engines: {node: '>=5'} @@ -2250,6 +2349,24 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + postcss@8.5.3: resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} @@ -2301,6 +2418,10 @@ packages: pstree.remy@1.1.8: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2315,6 +2436,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} @@ -2347,6 +2472,10 @@ packages: resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==} engines: {node: '>=6'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -2584,6 +2713,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead @@ -2644,6 +2777,11 @@ packages: stubborn-fs@1.2.5: resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==} + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -2674,12 +2812,26 @@ packages: resolution: {integrity: sha512-czbGgxSVwRlbB3Ly/aqQrNwrDAzKHDW/kVXegp4hSFmR2c8qqm3hCgZbUy1+3QAQFGhPDG7J56UsV1uNilBFCA==} hasBin: true + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thread-stream@3.1.0: resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} tiktoken@1.0.20: resolution: {integrity: sha512-zVIpXp84kth/Ni2me1uYlJgl2RZ2EjxwDaWLeDY/s6fZiyO9n1QoTOM5P7ZSYfToPvAvwYNMbg5LETVYVKyzfQ==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.12: + resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} + engines: {node: '>=12.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2698,16 +2850,41 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tslib@1.9.3: resolution: {integrity: sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==} tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsup@8.4.0: + resolution: {integrity: sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + tsx@4.19.3: resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} engines: {node: '>=18.0.0'} @@ -2756,6 +2933,9 @@ packages: undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + underscore@1.13.7: + resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} + undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -2867,12 +3047,18 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + whatwg-fetch@3.6.20: resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + when-exit@2.1.4: resolution: {integrity: sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==} @@ -3161,7 +3347,7 @@ snapshots: '@kevisual/auth@1.0.5': {} - '@kevisual/code-center-module@0.0.18(@kevisual/auth@1.0.5)(@kevisual/router@0.0.10)(@kevisual/use-config@1.0.10(dotenv@16.4.7))(ioredis@5.6.0)(pg@8.14.1)(sequelize@6.37.7(pg@8.14.1))': + '@kevisual/code-center-module@0.0.18(@kevisual/auth@1.0.5)(@kevisual/router@0.0.10)(@kevisual/use-config@1.0.10(dotenv@16.4.7))(ioredis@5.6.0)(pg@8.14.1)(sequelize@6.37.7(pg-hstore@2.3.4)(pg@8.14.1))': dependencies: '@kevisual/auth': 1.0.5 '@kevisual/router': 0.0.10 @@ -3169,7 +3355,7 @@ snapshots: ioredis: 5.6.0 nanoid: 5.1.5 pg: 8.14.1 - sequelize: 6.37.7(pg@8.14.1) + sequelize: 6.37.7(pg-hstore@2.3.4)(pg@8.14.1) socket.io: 4.8.1 zod: 3.24.2 transitivePeerDependencies: @@ -3181,7 +3367,7 @@ snapshots: dependencies: eventemitter3: 5.0.1 - '@kevisual/mark@0.0.7(dotenv@16.4.7)(esbuild@0.25.2)': + '@kevisual/mark@0.0.7(dotenv@16.4.7)(esbuild@0.25.2)(pg-hstore@2.3.4)': dependencies: '@kevisual/auth': 1.0.5 '@kevisual/rollup-tools': 0.0.1(esbuild@0.25.2) @@ -3190,7 +3376,7 @@ snapshots: cookie: 1.0.2 nanoid: 5.1.5 pg: 8.14.1 - sequelize: 6.37.7(pg@8.14.1) + sequelize: 6.37.7(pg-hstore@2.3.4)(pg@8.14.1) transitivePeerDependencies: - bufferutil - dotenv @@ -3207,6 +3393,22 @@ snapshots: - tedious - utf-8-validate + '@kevisual/query@0.0.13(ws@8.18.1)(zod@3.24.2)': + dependencies: + openai: 4.91.1(ws@8.18.1)(zod@3.24.2) + transitivePeerDependencies: + - encoding + - ws + - zod + + '@kevisual/query@0.0.15(ws@8.18.1)(zod@3.24.2)': + dependencies: + openai: 4.91.1(ws@8.18.1)(zod@3.24.2) + transitivePeerDependencies: + - encoding + - ws + - zod + '@kevisual/rollup-tools@0.0.1(esbuild@0.25.2)': dependencies: '@rollup/plugin-alias': 5.1.1(rollup@4.39.0) @@ -3464,6 +3666,9 @@ snapshots: '@oxc-project/types@0.60.0': {} + '@pkgjs/parseargs@0.11.0': + optional: true + '@pm2/agent@2.1.1': dependencies: async: 3.2.6 @@ -3823,6 +4028,8 @@ snapshots: ansi-styles@6.2.1: {} + any-promise@1.3.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -3928,6 +4135,13 @@ snapshots: buffer-from@1.1.2: {} + bundle-require@5.1.0(esbuild@0.25.2): + dependencies: + esbuild: 0.25.2 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -3973,6 +4187,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + cli-boxes@3.0.0: {} cli-tableau@2.0.1: @@ -4007,6 +4225,8 @@ snapshots: commander@2.15.1: {} + commander@4.1.1: {} + commondir@1.0.1: {} concat-map@0.0.1: {} @@ -4033,6 +4253,8 @@ snapshots: semver: 7.7.1 uint8array-extras: 1.4.0 + consola@3.4.2: {} + cookie@0.7.2: {} cookie@1.0.2: {} @@ -4557,6 +4779,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@11.0.1: dependencies: foreground-child: 3.3.1 @@ -4831,10 +5062,18 @@ snapshots: isexe@2.0.0: {} + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jackspeak@4.1.0: dependencies: '@isaacs/cliui': 8.0.2 + joycon@3.1.1: {} + js-git@0.7.8: dependencies: bodec: 0.1.0 @@ -4858,8 +5097,6 @@ snapshots: json-stringify-safe@5.0.1: optional: true - json5@2.2.3: {} - jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -4906,6 +5143,12 @@ snapshots: - ws - zod + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + locate-character@3.0.0: {} lodash-es@4.17.21: {} @@ -4914,8 +5157,12 @@ snapshots: lodash.isarguments@3.1.0: {} + lodash.sortby@4.7.0: {} + lodash@4.17.21: {} + lru-cache@10.4.3: {} + lru-cache@11.1.0: {} lru-cache@6.0.0: @@ -4957,6 +5204,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + minimist@1.2.8: {} minipass@7.1.2: {} @@ -4985,6 +5236,12 @@ snapshots: mute-stream@0.0.8: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nanoid@3.3.11: {} nanoid@5.1.5: {} @@ -5152,6 +5409,11 @@ snapshots: path-parse@1.0.7: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + path-scurry@2.0.0: dependencies: lru-cache: 11.1.0 @@ -5168,6 +5430,10 @@ snapshots: pg-connection-string@2.7.0: {} + pg-hstore@2.3.4: + dependencies: + underscore: 1.13.7 + pg-int8@1.0.1: {} pg-pool@3.8.0(pg@8.14.1): @@ -5233,6 +5499,8 @@ snapshots: sonic-boom: 4.2.0 thread-stream: 3.1.0 + pirates@4.0.7: {} + pm2-axon-rpc@0.7.1: dependencies: debug: 4.4.0(supports-color@5.5.0) @@ -5308,6 +5576,13 @@ snapshots: possible-typed-array-names@1.1.0: {} + postcss-load-config@6.0.1(postcss@8.5.3)(tsx@4.19.3): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.5.3 + tsx: 4.19.3 + postcss@8.5.3: dependencies: nanoid: 3.3.11 @@ -5358,6 +5633,8 @@ snapshots: pstree.remy@1.1.8: {} + punycode@2.3.1: {} + queue-microtask@1.2.3: {} quick-format-unescaped@4.0.4: {} @@ -5370,6 +5647,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.1.2: {} + real-require@0.2.0: {} redis-errors@1.2.0: {} @@ -5410,6 +5689,8 @@ snapshots: transitivePeerDependencies: - supports-color + resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} resolve@1.22.10: @@ -5546,7 +5827,7 @@ snapshots: sequelize-pool@7.1.0: {} - sequelize@6.37.7(pg@8.14.1): + sequelize@6.37.7(pg-hstore@2.3.4)(pg@8.14.1): dependencies: '@types/debug': 4.1.12 '@types/validator': 13.12.3 @@ -5566,6 +5847,7 @@ snapshots: wkx: 0.5.0 optionalDependencies: pg: 8.14.1 + pg-hstore: 2.3.4 transitivePeerDependencies: - supports-color @@ -5699,6 +5981,10 @@ snapshots: source-map@0.6.1: {} + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + sourcemap-codec@1.4.8: {} split2@4.2.0: {} @@ -5767,6 +6053,16 @@ snapshots: stubborn-fs@1.2.5: {} + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + ts-interface-checker: 0.1.13 + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -5826,12 +6122,27 @@ snapshots: resolve: 2.0.0-next.5 string.prototype.trim: 1.2.10 + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + thread-stream@3.1.0: dependencies: real-require: 0.2.0 tiktoken@1.0.20: {} + tinyexec@0.3.2: {} + + tinyglobby@0.2.12: + dependencies: + fdir: 6.4.3(picomatch@4.0.2) + picomatch: 4.0.2 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -5844,12 +6155,45 @@ snapshots: tr46@0.0.3: {} + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} + ts-interface-checker@0.1.13: {} + tslib@1.9.3: {} tslib@2.8.1: {} + tsup@8.4.0(postcss@8.5.3)(tsx@4.19.3)(typescript@5.8.2): + dependencies: + bundle-require: 5.1.0(esbuild@0.25.2) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.0(supports-color@5.5.0) + esbuild: 0.25.2 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.5.3)(tsx@4.19.3) + resolve-from: 5.0.0 + rollup: 4.39.0 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.12 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.3 + typescript: 5.8.2 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + tsx@4.19.3: dependencies: esbuild: 0.25.2 @@ -5912,6 +6256,8 @@ snapshots: undefsafe@2.0.5: {} + underscore@1.13.7: {} + undici-types@5.26.5: {} undici-types@6.21.0: {} @@ -5974,6 +6320,8 @@ snapshots: webidl-conversions@3.0.1: {} + webidl-conversions@4.0.2: {} + whatwg-fetch@3.6.20: {} whatwg-url@5.0.0: @@ -5981,6 +6329,12 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + when-exit@2.1.4: {} which-boxed-primitive@1.1.1: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..cbe79b2 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'submodules/**' diff --git a/rollup.config.mjs b/rollup.config.mjs index d807dc7..04898d3 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -24,7 +24,7 @@ const config = { replace({ preventAssignment: true, // 防止意外赋值 DEV_SERVER: JSON.stringify(isDev), // 替换 process.env.NODE_ENV - VERSION: JSON.stringify(pkgs.version), + // VERSION: JSON.stringify(pkgs.version), }), alias({ // only esbuild needs to be configured @@ -54,6 +54,7 @@ const config = { }), resolve({ preferBuiltins: true, // 强制优先使用内置模块 + browser: false, }), commonjs(), esbuild({ @@ -67,9 +68,9 @@ const config = { /@kevisual\/router(\/.*)?/, //, // 路由 /@kevisual\/use-config(\/.*)?/, // - // 'sequelize', // 数据库 orm - // 'ioredis', // redis - // 'pg', // pg + 'sequelize', // 数据库 orm + 'ioredis', // redis + 'pg', // pg ], }; export default config; diff --git a/src/demo-route.ts b/src/demo-route.ts index aabbc93..6ea3731 100644 --- a/src/demo-route.ts +++ b/src/demo-route.ts @@ -1,5 +1,21 @@ import { app } from './app.ts'; import { useConfig } from '@kevisual/use-config/env'; +const config = useConfig(); + +app + .route({ + path: 'auth', + key: 'auth', + id: 'auth', + }) + .define(async (ctx) => { + ctx.state.tokenUser = { + id: 'abcdefff', + username: 'root', + }; + ctx.query.token = config.ROOT_TEST_TOKEN; + }) + .addTo(app); app .route({ @@ -11,6 +27,4 @@ app }) .addTo(app); -const config = useConfig(); - console.log('run demo: http://localhost:' + config.PORT + '/api/router?path=demo&key=demo'); diff --git a/src/dev.ts b/src/dev.ts index d089c88..ef7737d 100644 --- a/src/dev.ts +++ b/src/dev.ts @@ -1,10 +1,7 @@ import { useConfig } from '@kevisual/use-config/env'; -import { useContextKey } from '@kevisual/use-config/context'; -import { Redis } from 'ioredis'; -export const redis = useContextKey('redis', () => { - return new Redis(); -}); -import { app } from './index.ts'; // 开发环境 +import { app } from './app.ts'; +import './demo-route.ts'; +import './routes/index.ts'; const config = useConfig(); diff --git a/src/index.ts b/src/index.ts index d7c7c49..f2dc5d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ import { app } from './app.ts'; -import './demo-route.ts'; +import './routes/index.ts'; -export { app }; \ No newline at end of file +export { app }; diff --git a/src/modules/db.ts b/src/modules/db.ts index 0ec1fb5..64381ea 100644 --- a/src/modules/db.ts +++ b/src/modules/db.ts @@ -1,3 +1,16 @@ import { useContextKey } from '@kevisual/use-config/context'; +import { Redis } from 'ioredis'; +export const redis = useContextKey('redis', () => { + const redis = new Redis({ + host: 'localhost', + port: 6379, + }); + return redis; +}); -export const redis = useContextKey('redis'); +const checkConnection = async () => { + const res = await redis.ping(); + console.log('redis ping', res); +}; + +// checkConnection(); diff --git a/src/modules/query.ts b/src/modules/query.ts new file mode 100644 index 0000000..23bba1f --- /dev/null +++ b/src/modules/query.ts @@ -0,0 +1,12 @@ +import { Query } from '@kevisual/query/query'; +import { QueryConfig } from '@kevisual/query-config'; +import { config } from './config.ts'; + +const baseURL = new URL(config.path, config.host); +export const query = new Query({ + url: baseURL.toString(), +}); + +export const queryConfig = new QueryConfig({ + query: query as any, +}); diff --git a/src/modules/sequelize.ts b/src/modules/sequelize.ts new file mode 100644 index 0000000..38d8f0d --- /dev/null +++ b/src/modules/sequelize.ts @@ -0,0 +1,30 @@ +import { Sequelize } from 'sequelize'; +import { useConfig } from '@kevisual/use-config/env'; +export const config = useConfig() as any; + +export type PostgresConfig = { + postgres: { + username: string; + password: string; + host: string; + port: number; + database: string; + }; +}; +if (!config.POSTGRES_PASSWORD || !config.POSTGRES_USER) { + console.error('postgres config is required password and user'); + process.exit(1); +} +const postgresConfig = { + username: config.POSTGRES_USER, + password: config.POSTGRES_PASSWORD, + host: config.POSTGRES_HOST || 'localhost', + port: parseInt(config.POSTGRES_PORT || '5432'), + database: config.POSTGRES_DB || 'postgres', +}; +// connect to db +export const sequelize = new Sequelize({ + dialect: 'postgres', + ...postgresConfig, + // logging: false, +}); diff --git a/src/provider/core/type.ts b/src/provider/core/type.ts index 1edfc02..8866ca3 100644 --- a/src/provider/core/type.ts +++ b/src/provider/core/type.ts @@ -1,7 +1,4 @@ import OpenAI from 'openai'; -import { APIPromise } from 'openai/core.mjs'; -import { ChatCompletionChunk } from 'openai/resources.mjs'; -import { Stream } from 'openai/streaming.mjs'; export type ChatMessage = OpenAI.Chat.Completions.ChatCompletionMessageParam; export type ChatMessageOptions = Partial; diff --git a/src/provider/utils/parse-config.ts b/src/provider/utils/parse-config.ts index d74bc1a..e8d78df 100644 --- a/src/provider/utils/parse-config.ts +++ b/src/provider/utils/parse-config.ts @@ -1,14 +1,14 @@ -import { AES, enc } from 'crypto-js'; +import CryptoJS from 'crypto-js'; // 加密函数 export function encryptAES(plainText: string, secretKey: string) { - return AES.encrypt(plainText, secretKey).toString(); + return CryptoJS.AES.encrypt(plainText, secretKey).toString(); } // 解密函数 export function decryptAES(cipherText: string, secretKey: string) { - const bytes = AES.decrypt(cipherText, secretKey); - return bytes.toString(enc.Utf8); + const bytes = CryptoJS.AES.decrypt(cipherText, secretKey); + return bytes.toString(CryptoJS.enc.Utf8); } type AIModel = { @@ -25,9 +25,13 @@ type AIModel = { */ group: string; /** - * 每日限制 + * 每日请求频率限制 */ dayLimit?: number; + /** + * 总的token限制 + */ + tokenLimit?: number; }; type SecretKey = { @@ -56,6 +60,7 @@ export type ProviderResult = { group: string; apiKey: string; dayLimit?: number; + tokenLimit?: number; baseURL?: string; /** * 解密密钥 @@ -68,6 +73,12 @@ export type AIConfig = { description?: string; models: AIModel[]; secretKeys: SecretKey[]; + filter?: { + objectKey: string; + type: 'array' | 'object'; + operate: 'removeAttribute' | 'remove'; + attribute: string[]; + }[]; }; export class AIConfigParser { private config: AIConfig; diff --git a/src/routes/ai-chat/index.ts b/src/routes/ai-chat/index.ts index 2c891c2..082e08b 100644 --- a/src/routes/ai-chat/index.ts +++ b/src/routes/ai-chat/index.ts @@ -1,11 +1,165 @@ import { app } from '@/app.ts'; +import { ChatServices } from './services/chat-services.ts'; +import { ChatConfigServices } from './services/chat-config-srevices.ts'; +import { AiChatHistoryModel } from './models/ai-chat-history.ts'; app .route({ path: 'ai', key: 'chat', + middleware: ['auth'], }) - .define(async () => { - // + .define(async (ctx) => { + const data = ctx.query.data || {}; + const { id, messages = [], options = {}, title, hook, getFull = false } = data; + let { username, model, group } = ctx.query; + const tokenUser = ctx.state.tokenUser || {}; + const tokenUsername = tokenUser.username; + let aiChatHistory: AiChatHistoryModel; + if (id) { + aiChatHistory = await AiChatHistoryModel.findByPk(id); + if (!aiChatHistory) { + ctx.throw(400, 'aiChatHistory not found'); + } + if (aiChatHistory.uid !== tokenUser.uid) { + ctx.throw(403, 'not permission'); + } + username = username || aiChatHistory.username; + model = model || aiChatHistory.model; + group = group || aiChatHistory.group; + } else { + username = username || tokenUsername; + } + if (!Array.isArray(messages)) { + ctx.throw(400, 'chat messages is not array'); + } + + // 初始化服务 + const chatServices = await ChatServices.createServices({ + owner: username, + model, + group, + username: tokenUsername, + }); + const chatConfigServices = new ChatConfigServices(username, tokenUsername); + await chatConfigServices.checkUserCanChat(tokenUsername); + await chatServices.checkCanChat(); + const pickMessages = await chatServices.chatMessagePick(messages); + if (pickMessages.length === 0) { + ctx.throw(400, 'chat messages is empty'); + } + const res = await chatServices.chat(pickMessages, options); + if (!aiChatHistory) { + aiChatHistory = await AiChatHistoryModel.create({ + username, + model, + group, + title, + }); + if (!title) { + // TODO: 创建标题 + } + } + const message = res.choices[0].message; + const newMessage = await chatServices.createNewMessage([...messages, message]); + + const usage = chatServices.chatProvider.getChatUsage(); + await chatServices.updateChatLimit(usage.total_tokens); + await chatConfigServices.updateUserChatLimit(tokenUsername, usage.total_tokens); + + const needUpdateData: any = { + messages: newMessage, + prompt_tokens: aiChatHistory.prompt_tokens + usage.prompt_tokens, + completion_tokens: aiChatHistory.completion_tokens + usage.completion_tokens, + total_tokens: aiChatHistory.total_tokens + usage.total_tokens, + }; + if (hook) { + needUpdateData.data = { + ...aiChatHistory.data, + hook, + }; + } + await AiChatHistoryModel.update(needUpdateData, { where: { id: aiChatHistory.id } }); + ctx.body = { + message: newMessage[newMessage.length - 1], + aiChatHistory: getFull || !id ? aiChatHistory : undefined, + }; + }) + .addTo(app); + +// http://localhost:4010/api/router?path=ai&key=question&question="1 and 1 equals" +app + .route({ + path: 'ai', + key: 'question', + middleware: ['auth'], + isDebug: true, + }) + .define(async (ctx) => { + const data = ctx.query; + const model = data.model || 'qwq:latest'; + const group = data.group || 'ollama'; + const tokenUser = ctx.state.tokenUser; + const chatServices = await ChatServices.createServices({ + owner: data.username || 'root', + model, + group, + username: tokenUser.username, + }); + const res = await chatServices.chat([ + { + role: 'user', + content: data.question, + }, + ]); + ctx.body = res; + }) + .addTo(app); + +// http://localhost:4010/api/router?path=ai&key=get-model-list +app + .route({ + path: 'ai', + key: 'get-model-list', + middleware: ['auth'], + }) + .define(async (ctx) => { + const username = ctx.query.username || 'root'; + const tokenUser = ctx.state.tokenUser; + const isSameUser = username === tokenUser.username; + const configObject: Record = {}; + const services = new ChatConfigServices(username, tokenUser.username); + const res = await services.getChatConfig(true, ctx.query.token); + configObject[username] = res; + if (!isSameUser) { + const selfServices = new ChatConfigServices(tokenUser.username, tokenUser.username); + const selfRes = await selfServices.getChatConfig(true, ctx.query.token); + configObject['self'] = selfRes; + } else { + configObject['self'] = res; + } + ctx.body = configObject; + }) + .addTo(app); + +app + .route({ + path: 'ai', + key: 'get-chat-usage', + description: '获取chat使用情况', + middleware: ['auth'], + }) + .define(async (ctx) => { + const tokenUser = ctx.state.tokenUser || {}; + const username = tokenUser.username; + const services = new ChatConfigServices('root', username); + const chatServices = await ChatServices.createServices({ owner: username, username }); + const rootUsage = await services.getUserChatLimit(username); + const selfUsage = await chatServices.getChatLimit(); + + ctx.body = { + rootUsage, + selfUsage, + }; }) .addTo(app); diff --git a/src/routes/ai-chat/list.ts b/src/routes/ai-chat/list.ts new file mode 100644 index 0000000..b270d2f --- /dev/null +++ b/src/routes/ai-chat/list.ts @@ -0,0 +1,23 @@ +import { app } from '@/app.ts'; +import { AiChatHistoryModel } from './models/ai-chat-history.ts'; + +app + .route({ + path: 'ai', + key: 'get-chat-list', + description: '获取chat列表', + middleware: ['auth'], + }) + .define(async (ctx) => { + const tokenUser = ctx.state.tokenUser; + const aiChatList = await AiChatHistoryModel.findAll({ + where: { + uid: tokenUser.uid, + }, + order: [['createdAt', 'DESC']], + }); + ctx.body = { + list: aiChatList, + }; + }) + .addTo(app); diff --git a/src/routes/ai-chat/models/ai-chat-history.ts b/src/routes/ai-chat/models/ai-chat-history.ts new file mode 100644 index 0000000..ce7b915 --- /dev/null +++ b/src/routes/ai-chat/models/ai-chat-history.ts @@ -0,0 +1,99 @@ +import { sequelize } from '@/modules/sequelize.ts'; +import { DataTypes, Model } from 'sequelize'; + +export type AiChatHistory = Partial>; + +export type ChastHistoryMessage = { + role: string; + content: string; + name: string; + id?: string; + createdAt?: number; + updatedAt?: number; + hide?: boolean; + noUse?: boolean; +}; +type AiChatHistoryData = { + hook?: { + [key: string]: any; + }; +}; +export class AiChatHistoryModel extends Model { + declare id: string; + declare username: string; + declare model: string; + declare group: string; + + declare title: string; + + declare messages: ChastHistoryMessage[]; + declare uid: string; + declare data: AiChatHistoryData; + declare prompt_tokens: number; + declare total_tokens: number; + declare completion_tokens: number; + + declare createdAt: Date; + declare updatedAt: Date; +} + +AiChatHistoryModel.init( + { + id: { + type: DataTypes.UUID, + primaryKey: true, + defaultValue: DataTypes.UUIDV4, + }, + username: { + type: DataTypes.STRING, + allowNull: false, + }, + model: { + type: DataTypes.STRING, + allowNull: false, + }, + group: { + type: DataTypes.STRING, + allowNull: false, + }, + title: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: '', + }, + messages: { + type: DataTypes.JSONB, + allowNull: false, + defaultValue: [], + }, + prompt_tokens: { + type: DataTypes.INTEGER, + defaultValue: 0, + }, + total_tokens: { + type: DataTypes.INTEGER, + defaultValue: 0, + }, + completion_tokens: { + type: DataTypes.INTEGER, + defaultValue: 0, + }, + data: { + type: DataTypes.JSONB, + defaultValue: {}, + }, + uid: { + type: DataTypes.UUID, + allowNull: true, + }, + }, + { + sequelize, + tableName: 'kv_ai_chat_history', + paranoid: false, + }, +); + +AiChatHistoryModel.sync({ alter: true, logging: false }).catch((e) => { + console.error('AiChatHistoryModel sync', e); +}); diff --git a/src/routes/ai-chat/services/chat-config-srevices.ts b/src/routes/ai-chat/services/chat-config-srevices.ts new file mode 100644 index 0000000..9994f83 --- /dev/null +++ b/src/routes/ai-chat/services/chat-config-srevices.ts @@ -0,0 +1,128 @@ +import type { AIConfig } from '@/provider/utils/parse-config.ts'; +import { redis } from '@/modules/db.ts'; +import { CustomError } from '@kevisual/router'; +import { queryConfig } from '@/modules/query.ts'; +export class ChatConfigServices { + cachePrefix = 'ai:chat:config'; + // 使用谁的模型 + owner: string; + // 使用者 + username: string; + /** + * username 是使用的模型的用户名,使用谁的模型 + * @param username + */ + constructor(owner: string, username: string, token?: string) { + this.owner = owner; + this.username = username; + } + getKey() { + return `${this.cachePrefix}:${this.owner}`; + } + /** + * 获取chat配置 + * @param needClearSecret 是否需要清除secret 默认false + * @returns + */ + async getChatConfig(needClearSecret = false, token?: string) { + const key = this.getKey(); + const cache = await redis.get(key); + let modelConfig = null; + if (cache) { + modelConfig = JSON.parse(cache); + } + if (!modelConfig) { + if (this.owner !== this.username) { + throw new CustomError(`the owner [${this.owner}] config, [${this.username}] not permission to init config, only owner can init config, place connect owner`); + } else { + const res = await queryConfig.getConfigByKey('ai.json', { token }); + if (res.code === 200 && res.data?.data) { + modelConfig = res.data.data; + } else { + throw new CustomError(400, 'get config failed'); + } + } + } + if (!modelConfig) { + throw new CustomError(`${this.owner} modelConfig is null`); + } + if (!cache) { + const cacheTime = 60 * 60 * 24 * 40; // 1天 + await redis.set(key, JSON.stringify(modelConfig), 'EX', cacheTime); + } + if (needClearSecret) { + modelConfig = this.filterApiKey(modelConfig); + } + return modelConfig; + } + async filterApiKey(chatConfig: AIConfig) { + // 过滤掉secret中的所有apiKey,移除掉并返回chatConfig + const { secretKeys, ...rest } = chatConfig; + return { + ...rest, + secretKeys: secretKeys.map((item) => { + return { + ...item, + apiKey: undefined, + decryptKey: undefined, + }; + }), + }; + } + /** + * 获取和检测当前用户的额度, 当使用 root 账号的时候,才需要检测 + * username是当前使用用户 + * @param username + */ + async checkUserCanChat(username: string) { + if (this.owner !== 'root') return true; + const maxToken = 100000; + const userCacheKey = `${this.cachePrefix}:root:chat-limit:${username}`; + const cache = await redis.get(userCacheKey); + if (cache) { + const cacheData = JSON.parse(cache); + if (cacheData.token >= maxToken) { + throw new CustomError(400, 'use root account token limit exceeded'); + } + } + return true; + } + /** + * 获取用户的使用情况 + * username是当前使用用户 + * @param username + * @returns + */ + async getUserChatLimit(username: string) { + if (this.owner !== 'root') return; + const userCacheKey = `${this.cachePrefix}:root:chat-limit:${username}`; + const cache = await redis.get(userCacheKey); + if (cache) { + const cacheData = JSON.parse(cache); + return cacheData; + } + return { + token: 0, + day: 0, + }; + } + + /** + * 更新用户的使用情况 + * username是当前使用用户 + * @param username + * @param token + */ + async updateUserChatLimit(username: string, token: number) { + if (this.owner !== 'root') return; + const userCacheKey = `${this.cachePrefix}:root:chat-limit:${username}`; + const cache = await redis.get(userCacheKey); + if (cache) { + const cacheData = JSON.parse(cache); + cacheData.token = cacheData.token + token; + await redis.set(userCacheKey, JSON.stringify(cacheData), 'EX', 60 * 60 * 24 * 30); // 30天 + } else { + await redis.set(userCacheKey, JSON.stringify({ token }), 'EX', 60 * 60 * 24 * 30); // 30天 + } + } +} diff --git a/src/routes/ai-chat/services/chat-services.ts b/src/routes/ai-chat/services/chat-services.ts index d98292b..e4969b4 100644 --- a/src/routes/ai-chat/services/chat-services.ts +++ b/src/routes/ai-chat/services/chat-services.ts @@ -1,9 +1,15 @@ import { AIConfigParser, ProviderResult } from '@/provider/utils/parse-config.ts'; -import { ProviderManager, ChatMessage, BaseChat } from '@/provider/index.ts'; -import { getChatConfig } from '@/modules/chat-config.ts'; +import { ProviderManager, ChatMessage, BaseChat, ChatMessageOptions } from '@/provider/index.ts'; import { redis } from '@/modules/db.ts'; +import { CustomError } from '@kevisual/router'; +import { ChatConfigServices } from './chat-config-srevices.ts'; +import { pick } from 'lodash-es'; +import { ChastHistoryMessage } from '../models/ai-chat-history.ts'; +import { nanoid } from '@/utils/uuid.ts'; +import dayjs from 'dayjs'; + export type ChatServicesConfig = { - username: string; + owner: string; model: string; group: string; decryptKey?: string; @@ -13,7 +19,7 @@ export class ChatServices { /** * 用户名 */ - username: string; + owner: string; /** * 模型 */ @@ -32,7 +38,7 @@ export class ChatServices { modelConfig?: ProviderResult; chatProvider?: BaseChat; constructor(opts: ChatServicesConfig) { - this.username = opts.username; + this.owner = opts.owner; this.model = opts.model; this.group = opts.group; this.decryptKey = opts.decryptKey; @@ -41,8 +47,8 @@ export class ChatServices { * 初始化 * @returns */ - async init() { - const config = await this.getConfig(); + async init(username: string) { + const config = await this.getConfig(username); const aiConfigParser = new AIConfigParser(config); const model = this.model; const group = this.group; @@ -52,24 +58,30 @@ export class ChatServices { const apiKey = await aiConfigParser.getSecretKey({ getCache: async (key) => { const cache = await redis.get(that.wrapperKey(key)); - return cache; + return cache ? JSON.parse(cache) : null; }, setCache: async (key, value) => { - await redis.set(that.wrapperKey(key), value); + await redis.set(that.wrapperKey(key), JSON.stringify(value), 'EX', 60 * 60 * 24 * 1); // 1天 }, }); that.modelConfig = { ...providerResult, apiKey }; return that.modelConfig; } - async wrapperKey(key: string) { - const username = this.username; - return `${this.cachePrefix}${username}:${key}`; + /** + * 包装key , 默认了username + * @param key + * @returns + */ + wrapperKey(key: string) { + const owner = this.owner; + return `${this.cachePrefix}${owner}:${key}`; } - async getConfig() { - return getChatConfig(); + async getConfig(username: string) { + const services = new ChatConfigServices(this.owner, username); + return services.getChatConfig(); } - async chat(messages: ChatMessage[]) { + async chat(messages: ChatMessage[], options?: ChatMessageOptions) { const { model, provider, apiKey, baseURL } = this.modelConfig; const providerManager = await ProviderManager.createProvider({ provider: provider, @@ -78,14 +90,111 @@ export class ChatServices { baseURL: baseURL, }); this.chatProvider = providerManager; - const result = await providerManager.chat(messages); + const result = await providerManager.chat(messages, options); return result; } - static async createServices(opts: Partial) { - const username = opts.username || 'root'; - const model = opts.model || 'deepseek-r1-250120'; + async createTitle(messages: ChastHistoryMessage[]) { + return nanoid(); + } + /** + * 过滤消息,只保留对话需要的内容,name,role,content + * @param messages + * @returns + */ + async chatMessagePick(messages: ChastHistoryMessage[]) { + let newMessages = messages.filter((item) => !item.hide && !item.noUse); + return newMessages.map((item) => pick(item, ['role', 'content', 'name'])) as ChatMessage[]; + } + async createNewMessage(messages: ChastHistoryMessage[]) { + return messages.map((item) => { + if (!item.id) { + item.id = 'chat' + nanoid(); + item.createdAt = Date.now(); + item.updatedAt = Date.now(); + } + return item; + }); + } + static async createServices(opts: Partial & { username: string }) { + const owner = opts.owner || 'root'; + const model = opts.model || 'deepseek-chat'; const group = opts.group || 'deepseek'; const decryptKey = opts.decryptKey; - return new ChatServices({ username, model, group, decryptKey }); + const chatServices = new ChatServices({ owner, model, group, decryptKey }); + await chatServices.init(opts.username); + return chatServices; + } + + /** + * 检查模型的余量 + * @returns + */ + async checkCanChat() { + const { modelConfig } = this; + const { tokenLimit, dayLimit, group, model } = modelConfig; + const key = this.wrapperKey(`chat-limit`); + const cache = await redis.get(key); + if (cache) { + const cacheData = JSON.parse(cache); + const today = dayjs().format('YYYY-MM-DD'); + const current = cacheData.find((item) => item.group === group && item.model === model); + const day = current[today] || 0; + const token = current.token || 0; + if (tokenLimit && token >= tokenLimit) { + throw new CustomError(400, 'token limit exceeded'); + } + if (dayLimit && day >= dayLimit) { + throw new CustomError(400, 'day limit exceeded'); + } + } + return true; + } + /** + * 获取模型的使用情况 + * @returns + */ + async getChatLimit() { + const { modelConfig } = this; + const { group, model } = modelConfig; + const key = this.wrapperKey(`chat-limit`); + const cache = await redis.get(key); + const today = dayjs().format('YYYY-MM-DD'); + if (cache) { + const cacheData = JSON.parse(cache); + return cacheData; + } + return [ + { + group: group, + model: model, + token: 0, + [today]: 0, + }, + ]; + } + /** + * 更新模型的使用情况 + * @param token + */ + async updateChatLimit(token: number) { + const { modelConfig } = this; + const { group, model } = modelConfig; + const key = this.wrapperKey(`chat-limit`); + const cache = await redis.get(key); + const today = dayjs().format('YYYY-MM-DD'); + if (cache) { + const cacheData = JSON.parse(cache); + const current = cacheData.find((item) => item.group === group && item.model === model); + if (current) { + const day = current[today] || 0; + current[today] = day + 1; + current.token = current.token + token; + } else { + cacheData.push({ group, model, token: token, [today]: 1 }); + } + await redis.set(key, JSON.stringify(cacheData), 'EX', 60 * 60 * 24 * 30); // 30天 + } else { + await redis.set(key, JSON.stringify({ group, model, token: token, [today]: 1 }), 'EX', 60 * 60 * 24 * 30); // 30天 + } } } diff --git a/src/routes/index.ts b/src/routes/index.ts new file mode 100644 index 0000000..24dda09 --- /dev/null +++ b/src/routes/index.ts @@ -0,0 +1,2 @@ +import './ai-chat/index.ts'; +import './ai-chat/list.ts'; diff --git a/src/test/encrypt/index.ts b/src/test/encrypt/index.ts new file mode 100644 index 0000000..7771358 --- /dev/null +++ b/src/test/encrypt/index.ts @@ -0,0 +1,9 @@ +import { encryptAES, decryptAES } from '../../provider/utils/parse-config.ts'; + +const plainx = process.env.API_KEY; +const decryptKey = process.env.DECRYPT_KEY; +const encrypt = encryptAES(plainx, decryptKey); +console.log('encrypt', encrypt); + +const decrypt = decryptAES(encrypt, decryptKey); +console.log(decrypt); diff --git a/src/utils/uuid.ts b/src/utils/uuid.ts new file mode 100644 index 0000000..0f2f595 --- /dev/null +++ b/src/utils/uuid.ts @@ -0,0 +1,8 @@ +import { customAlphabet } from 'nanoid'; + +export const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz'; +export const nanoid = customAlphabet(alphabet, 16); + +export function uuid() { + return nanoid(); +} diff --git a/submodules/query-config b/submodules/query-config new file mode 160000 index 0000000..53cd974 --- /dev/null +++ b/submodules/query-config @@ -0,0 +1 @@ +Subproject commit 53cd97454dc4300a1a1040c25dd26d86c390f1c8