From 2c75559101da502a10c278f920c4eab81a1e9f31 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Thu, 4 Dec 2025 01:55:07 +0800 Subject: [PATCH] feat: add some orhter provider --- package.json | 16 +-- pnpm-lock.yaml | 107 ++++++++-------- src/provider/chat-adapter/kimi.ts | 10 ++ src/provider/chat-adapter/zhipu.ts | 10 ++ src/provider/chat.ts | 6 + src/provider/core/chat.ts | 30 +---- src/provider/core/utils/index.ts | 192 +++++++++++++++++++++++++++++ src/provider/utils/chunk.ts | 86 ------------- 8 files changed, 288 insertions(+), 169 deletions(-) create mode 100644 src/provider/chat-adapter/kimi.ts create mode 100644 src/provider/chat-adapter/zhipu.ts create mode 100644 src/provider/core/utils/index.ts delete mode 100644 src/provider/utils/chunk.ts diff --git a/package.json b/package.json index 4cd4b21..95275c8 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "basename": "/root/ai-center-services", "app": { "entry": "dist/app.mjs", - "key": "ai-center-services", "type": "system-app" }, "files": [ @@ -28,7 +27,7 @@ ], "author": "abearxiong (https://www.xiongxiao.me)", "license": "MIT", - "packageManager": "pnpm@10.23.0", + "packageManager": "pnpm@10.24.0", "type": "module", "publishConfig": { "registry": "https://registry.npmjs.org/", @@ -58,14 +57,14 @@ "@kevisual/mark": "0.0.7", "@kevisual/router": "0.0.33", "@kevisual/types": "^0.0.10", - "@kevisual/use-config": "^1.0.19", + "@kevisual/use-config": "^1.0.21", "@types/bun": "^1.3.3", "@types/crypto-js": "^4.2.2", "@types/formidable": "^3.4.6", "@types/lodash-es": "^4.17.12", "@types/node": "^24.10.1", "@vitejs/plugin-basic-ssl": "^2.1.0", - "cookie": "^1.0.2", + "cookie": "^1.1.1", "cross-env": "^10.1.0", "crypto-js": "^4.2.0", "dayjs": "^1.11.19", @@ -75,17 +74,18 @@ "json5": "^2.2.3", "lodash-es": "^4.17.21", "openai": "6.9.1", - "pm2": "^6.0.13", + "pm2": "^6.0.14", "rimraf": "^6.1.2", "rollup": "^4.53.3", - "rollup-plugin-dts": "^6.2.3", + "rollup-plugin-dts": "^6.3.0", "sequelize": "^6.37.7", "tape": "^5.9.0", "tiktoken": "^1.0.22", "typescript": "^5.9.3", - "vite": "^7.2.4" + "vite": "^7.2.6" }, "dependencies": { - "@kevisual/logger": "^0.0.4" + "@kevisual/logger": "^0.0.4", + "@kevisual/permission": "^0.0.3" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad941cd..ad48ebe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@kevisual/logger': specifier: ^0.0.4 version: 0.0.4 + '@kevisual/permission': + specifier: ^0.0.3 + version: 0.0.3 devDependencies: '@kevisual/code-center-module': specifier: 0.0.24 @@ -25,8 +28,8 @@ importers: specifier: ^0.0.10 version: 0.0.10 '@kevisual/use-config': - specifier: ^1.0.19 - version: 1.0.19(dotenv@17.2.3) + specifier: ^1.0.21 + version: 1.0.21(dotenv@17.2.3) '@types/bun': specifier: ^1.3.3 version: 1.3.3 @@ -44,10 +47,10 @@ importers: version: 24.10.1 '@vitejs/plugin-basic-ssl': specifier: ^2.1.0 - version: 2.1.0(vite@7.2.4(@types/node@24.10.1)) + version: 2.1.0(vite@7.2.6(@types/node@24.10.1)) cookie: - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^1.1.1 + version: 1.1.1 cross-env: specifier: ^10.1.0 version: 10.1.0 @@ -76,8 +79,8 @@ importers: specifier: 6.9.1 version: 6.9.1(ws@8.18.3)(zod@3.25.76) pm2: - specifier: ^6.0.13 - version: 6.0.13 + specifier: ^6.0.14 + version: 6.0.14 rimraf: specifier: ^6.1.2 version: 6.1.2 @@ -85,8 +88,8 @@ importers: specifier: ^4.53.3 version: 4.53.3 rollup-plugin-dts: - specifier: ^6.2.3 - version: 6.2.3(rollup@4.53.3)(typescript@5.9.3) + specifier: ^6.3.0 + version: 6.3.0(rollup@4.53.3)(typescript@5.9.3) sequelize: specifier: ^6.37.7 version: 6.37.7(pg@8.16.3) @@ -100,8 +103,8 @@ importers: specifier: ^5.9.3 version: 5.9.3 vite: - specifier: ^7.2.4 - version: 7.2.4(@types/node@24.10.1) + specifier: ^7.2.6 + version: 7.2.6(@types/node@24.10.1) packages: @@ -290,6 +293,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.4': resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@kevisual/auth@1.0.5': resolution: {integrity: sha512-GwsLj7unKXi7lmMiIIgdig4LwwLiDJnOy15HHZR5gMbyK6s5/uJiMY5RXPB2+onGzTNDqFo/hXjsD2wkerHPVg==} @@ -305,6 +311,9 @@ packages: '@kevisual/mark@0.0.7': resolution: {integrity: sha512-PiEEy4yvWEpixw76PzgrIWeNelzm+FrhtzFmqJU92o5GkgawaFwighcvIxqcVZRKeEFF4uvlTjFrGeQvXw6F4A==} + '@kevisual/permission@0.0.3': + resolution: {integrity: sha512-8JsA/5O5Ax/z+M+MYpFYdlioHE6jNmWMuFSokBWYs9CCAHNiSKMR01YLkoVDoPvncfH/Y8F5K/IEXRCbptuMNA==} + '@kevisual/rollup-tools@0.0.1': resolution: {integrity: sha512-TdCN+IU0fyHudiiqYvobXQ8r5MltfM/cKmSS59iopyL8YYwXwcipOS4S24NWA79g7uwJfSUNk5lg3yVhom79fQ==} hasBin: true @@ -321,10 +330,10 @@ packages: '@kevisual/types@0.0.10': resolution: {integrity: sha512-Q73uzzjk9UidumnmCvOpgzqDDvQxsblz22bIFuoiioUFJWwaparx8bpd8ArRyFojicYL1YJoFDzDZ9j9NN8grA==} - '@kevisual/use-config@1.0.19': - resolution: {integrity: sha512-Q1IH4eMqUe5w6Bq8etoqOSls9FPIy0xwwD3wHf26EsQLZadhccI9qkDuFzP/rFWDa57mwFPEfwbGE5UlqWOCkw==} + '@kevisual/use-config@1.0.21': + resolution: {integrity: sha512-czgy4+tBDBJI6QTnKh2PCwswET6ZpZ4ZqBE/SPkkOivEtlrcPzLs5elwMLZ3goD1XMD4VB3yjumb5WuW/8H8MA==} peerDependencies: - dotenv: ^16.4.7 + dotenv: ^17 '@ljharb/resumer@0.1.3': resolution: {integrity: sha512-d+tsDgfkj9X5QTriqM4lKesCkMMJC3IrbPKHvayP00ELx2axdXvDfWkqjxrLXIzGcQzmj7VAUT1wopqARTvafw==} @@ -473,67 +482,56 @@ packages: resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.3': resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.3': resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.3': resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.3': resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.53.3': resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.53.3': resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.3': resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.3': resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.3': resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.3': resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openharmony-arm64@4.53.3': resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} @@ -799,8 +797,8 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - cookie@1.0.2: - resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} cors@2.8.5: @@ -1410,8 +1408,8 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsbn@1.1.0: @@ -1458,6 +1456,9 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1716,8 +1717,8 @@ packages: pm2-sysmonit@1.2.8: resolution: {integrity: sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==} - pm2@6.0.13: - resolution: {integrity: sha512-1hS/adMgKoDpX4S1ichJW8SiGpex+oBSZK31dP1FSYOOGtaeuemXzhXPOCefmddgIY4K6v7uu+7xNPnmEnK3ag==} + pm2@6.0.14: + resolution: {integrity: sha512-wX1FiFkzuT2H/UUEA8QNXDAA9MMHDsK/3UHj6Dkd5U7kxyigKDA5gyDw78ycTQZAuGCLWyUX5FiXEuVQWafukA==} engines: {node: '>=16.0.0'} hasBin: true @@ -1818,8 +1819,8 @@ packages: resolution: {integrity: sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==} engines: {node: '>=8.3'} - rollup-plugin-dts@6.2.3: - resolution: {integrity: sha512-UgnEsfciXSPpASuOelix7m4DrmyQgiaWBnvI0TM4GxuDh5FkqW8E5hu57bCxXB90VvR1WNfLV80yEDN18UogSA==} + rollup-plugin-dts@6.3.0: + resolution: {integrity: sha512-d0UrqxYd8KyZ6i3M2Nx7WOMy708qsV/7fTHMHxCMCBOAe3V/U7OMPu5GkX8hC+cmkHhzGnfeYongl1IgiooddA==} engines: {node: '>=16'} peerDependencies: rollup: ^3.29.4 || ^4 @@ -2174,8 +2175,8 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite@7.2.4: - resolution: {integrity: sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==} + vite@7.2.6: + resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2410,13 +2411,15 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.4': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@kevisual/auth@1.0.5': {} '@kevisual/code-center-module@0.0.24(dotenv@17.2.3)': dependencies: '@kevisual/auth': 1.0.5 '@kevisual/router': 0.0.23 - '@kevisual/use-config': 1.0.19(dotenv@17.2.3) + '@kevisual/use-config': 1.0.21(dotenv@17.2.3) ioredis: 5.8.2 nanoid: 5.1.5 pg: 8.16.3 @@ -2449,8 +2452,8 @@ snapshots: '@kevisual/auth': 1.0.5 '@kevisual/rollup-tools': 0.0.1(esbuild@0.25.8) '@kevisual/router': 0.0.7 - '@kevisual/use-config': 1.0.19(dotenv@17.2.3) - cookie: 1.0.2 + '@kevisual/use-config': 1.0.21(dotenv@17.2.3) + cookie: 1.1.1 nanoid: 5.1.5 pg: 8.16.3 sequelize: 6.37.7(pg@8.16.3) @@ -2470,6 +2473,8 @@ snapshots: - tedious - utf-8-validate + '@kevisual/permission@0.0.3': {} + '@kevisual/rollup-tools@0.0.1(esbuild@0.25.8)': dependencies: '@rollup/plugin-alias': 5.1.1(rollup@4.53.3) @@ -2484,7 +2489,7 @@ snapshots: glob: 11.0.3 rollup: 4.53.3 rollup-plugin-copy: 3.5.0 - rollup-plugin-dts: 6.2.3(rollup@4.53.3)(typescript@5.9.3) + rollup-plugin-dts: 6.3.0(rollup@4.53.3)(typescript@5.9.3) rollup-plugin-esbuild: 6.2.1(esbuild@0.25.8)(rollup@4.53.3) rollup-plugin-inject: 3.0.2 tslib: 2.8.1 @@ -2520,7 +2525,7 @@ snapshots: '@kevisual/types@0.0.10': {} - '@kevisual/use-config@1.0.19(dotenv@17.2.3)': + '@kevisual/use-config@1.0.21(dotenv@17.2.3)': dependencies: '@kevisual/load': 0.0.6 dotenv: 17.2.3 @@ -2787,9 +2792,9 @@ snapshots: '@types/validator@13.15.2': {} - '@vitejs/plugin-basic-ssl@2.1.0(vite@7.2.4(@types/node@24.10.1))': + '@vitejs/plugin-basic-ssl@2.1.0(vite@7.2.6(@types/node@24.10.1))': dependencies: - vite: 7.2.4(@types/node@24.10.1) + vite: 7.2.6(@types/node@24.10.1) accepts@1.3.8: dependencies: @@ -2955,7 +2960,7 @@ snapshots: cookie@0.7.2: {} - cookie@1.0.2: {} + cookie@1.1.1: {} cors@2.8.5: dependencies: @@ -3681,7 +3686,7 @@ snapshots: js-tokens@4.0.0: optional: true - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -3720,6 +3725,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.4 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + math-intrinsics@1.1.0: {} merge2@1.4.1: {} @@ -3966,7 +3975,7 @@ snapshots: - supports-color optional: true - pm2@6.0.13: + pm2@6.0.14: dependencies: '@pm2/agent': 2.1.1 '@pm2/blessed': 0.1.81 @@ -3984,7 +3993,7 @@ snapshots: enquirer: 2.3.6 eventemitter2: 5.0.1 fclone: 1.0.11 - js-yaml: 4.1.0 + js-yaml: 4.1.1 mkdirp: 1.0.4 needle: 2.4.0 pidusage: 3.0.2 @@ -4118,9 +4127,9 @@ snapshots: globby: 10.0.1 is-plain-object: 3.0.1 - rollup-plugin-dts@6.2.3(rollup@4.53.3)(typescript@5.9.3): + rollup-plugin-dts@6.3.0(rollup@4.53.3)(typescript@5.9.3): dependencies: - magic-string: 0.30.17 + magic-string: 0.30.21 rollup: 4.53.3 typescript: 5.9.3 optionalDependencies: @@ -4563,7 +4572,7 @@ snapshots: vary@1.1.2: {} - vite@7.2.4(@types/node@24.10.1): + vite@7.2.6(@types/node@24.10.1): dependencies: esbuild: 0.25.8 fdir: 6.5.0(picomatch@4.0.3) diff --git a/src/provider/chat-adapter/kimi.ts b/src/provider/chat-adapter/kimi.ts new file mode 100644 index 0000000..6dd93fa --- /dev/null +++ b/src/provider/chat-adapter/kimi.ts @@ -0,0 +1,10 @@ +import { BaseChat, BaseChatOptions } from '../core/chat.ts'; + +export type KimiOptions = Partial; +export class Kimi extends BaseChat { + static BASE_URL = 'https://api.moonshot.cn/v1/'; + constructor(options: KimiOptions) { + const baseURL = options.baseURL || Kimi.BASE_URL; + super({ ...(options as BaseChatOptions), baseURL: baseURL }); + } +} diff --git a/src/provider/chat-adapter/zhipu.ts b/src/provider/chat-adapter/zhipu.ts new file mode 100644 index 0000000..453f8d3 --- /dev/null +++ b/src/provider/chat-adapter/zhipu.ts @@ -0,0 +1,10 @@ +import { BaseChat, BaseChatOptions } from '../core/chat.ts'; + +export type ZhipuOptions = Partial; +export class Zhipu extends BaseChat { + static BASE_URL = 'https://open.bigmodel.cn/api/paas/v4/'; + constructor(options: ZhipuOptions) { + const baseURL = options.baseURL || Zhipu.BASE_URL; + super({ ...(options as BaseChatOptions), baseURL: baseURL }); + } +} \ No newline at end of file diff --git a/src/provider/chat.ts b/src/provider/chat.ts index aa6d5aa..c3f7fc4 100644 --- a/src/provider/chat.ts +++ b/src/provider/chat.ts @@ -8,6 +8,8 @@ import { Volces } from './chat-adapter/volces.ts'; import { DeepSeek } from './chat-adapter/deepseek.ts'; import { ModelScope } from './chat-adapter/model-scope.ts'; import { BailianChat } from './chat-adapter/dashscope.ts'; +import { Zhipu } from './chat-adapter/zhipu.ts'; +import { Kimi } from './chat-adapter/kimi.ts'; import { ChatMessage } from './core/type.ts'; @@ -18,6 +20,8 @@ export const VolcesProvider = Volces; export const DeepSeekProvider = DeepSeek; export const ModelScopeProvider = ModelScope; export const BailianProvider = BailianChat; +export const ZhipuProvider = Zhipu; +export const KimiProvider = Kimi; export const ChatProviderMap = { Ollama: OllamaProvider, @@ -28,6 +32,8 @@ export const ChatProviderMap = { ModelScope: ModelScopeProvider, BaseChat: BaseChat, Bailian: BailianProvider, + Zhipu: ZhipuProvider, + Kimi: KimiProvider, }; type ProviderManagerConfig = { diff --git a/src/provider/core/chat.ts b/src/provider/core/chat.ts index e24945a..7072993 100644 --- a/src/provider/core/chat.ts +++ b/src/provider/core/chat.ts @@ -9,6 +9,7 @@ import type { EmbeddingMessage, EmbeddingMessageComplete, } from './type.ts'; +import { AIUtils } from './utils/index.ts'; export type BaseChatOptions> = { /** @@ -32,14 +33,7 @@ export type BaseChatOptions> = { */ stream?: boolean; } & T; -export const getIsBrowser = () => { - try { - // 检查是否存在window对象 - return typeof window !== 'undefined' && typeof window.document !== 'undefined'; - } catch (e) { - return false; - } -}; + export class BaseChat implements BaseChatInterface, BaseChatUsageInterface { /** * 默认baseURL @@ -53,32 +47,16 @@ export class BaseChat implements BaseChatInterface, BaseChatUsageInterface { * 默认apiKey */ apiKey: string; - /** - * 是否在浏览器中使用 - */ - isBrowser: boolean; - /** - * openai实例 - */ - openai: OpenAI; - prompt_tokens: number; total_tokens: number; completion_tokens: number; responseText: string; - + utils: AIUtils; constructor(options: BaseChatOptions) { this.baseURL = options.baseURL; this.model = options.model; this.apiKey = options.apiKey; - // @ts-ignore - const DEFAULT_IS_BROWSER = getIsBrowser(); - this.isBrowser = options.isBrowser ?? DEFAULT_IS_BROWSER; - // this.openai = new OpenAI({ - // apiKey: this.apiKey, - // baseURL: this.baseURL, - // dangerouslyAllowBrowser: options?.dangerouslyAllowBrowser ?? this.isBrowser, - // }); + this.utils = new AIUtils(); } post(url = '', opts: { headers?: Record, data?: any } = {}) { let _url = url.startsWith('http') ? url : this.baseURL + url; diff --git a/src/provider/core/utils/index.ts b/src/provider/core/utils/index.ts new file mode 100644 index 0000000..bc6d6ac --- /dev/null +++ b/src/provider/core/utils/index.ts @@ -0,0 +1,192 @@ +export class AIUtils { + /** + * 从 Markdown 代码块中提取 JSON + * @param str 包含 JSON 的字符串 + * @returns 解析后的对象或 null + */ + extractJsonFromMarkdown(str: string): any | null { + // Try to extract JSON from ```json ... ``` + const jsonRegex = /```json\s*([\s\S]*?)\s*```/; + const match = str.match(jsonRegex); + let jsonStr = match && match[1] ? match[1] : str; + + try { + return JSON.parse(jsonStr); + } catch { + return null; + } + } + + /** + * 从 Markdown 代码块中提取代码 + * @param str Markdown 字符串 + * @param language 语言类型,不指定则返回所有代码块 + * @returns 提取的代码字符串或数组 + */ + extractCodeFromMarkdown(str: string, language?: string): string | string[] | null { + if (language) { + const regex = new RegExp(`\`\`\`${language}\\s*([\\s\\S]*?)\\s*\`\`\``, 'g'); + const matches = str.match(regex); + if (!matches) return null; + return matches.map(m => m.replace(new RegExp(`\`\`\`${language}\\s*|\\s*\`\`\``, 'g'), '').trim()); + } + + const regex = /```[\w]*\s*([\s\S]*?)\s*```/g; + const matches = [...str.matchAll(regex)]; + if (matches.length === 0) return null; + return matches.map(m => m[1].trim()); + } + + /** + * 清理 AI 响应中的多余空白和格式 + * @param str 原始字符串 + * @returns 清理后的字符串 + */ + cleanResponse(str: string): string { + return str + .trim() + .replace(/\n{3,}/g, '\n\n') // 多个换行符替换为两个 + .replace(/[ \t]+$/gm, ''); // 删除行尾空格 + } + + /** + * 从 AI 响应中提取标签 + * @param str 响应字符串 + * @returns 标签数组 + */ + extractTags(str: string): string[] { + const tagPatterns = [ + /#(\w+)/g, // #tag 格式 + /\[(\w+)\]/g, // [tag] 格式 + /tags?:\s*\[([^\]]+)\]/gi, // tags: [...] 格式 + ]; + + const tags = new Set(); + + for (const pattern of tagPatterns) { + const matches = str.matchAll(pattern); + for (const match of matches) { + if (match[1]) { + const extracted = match[1].split(/[,;]/).map(t => t.trim()).filter(Boolean); + extracted.forEach(tag => tags.add(tag)); + } + } + } + + return Array.from(tags); + } + + /** + * 从文本中提取 URL + * @param str 文本字符串 + * @returns URL 数组 + */ + extractUrls(str: string): string[] { + const urlRegex = /(https?:\/\/[^\s]+)/g; + const matches = str.match(urlRegex); + return matches || []; + } + + /** + * 分割长文本为指定 token 数量的块 + * @param text 原始文本 + * @param maxTokens 每块最大 token 数(粗略估算:1 token ≈ 4 字符) + * @returns 文本块数组 + */ + chunkText(text: string, maxTokens: number = 1000): string[] { + const chunkSize = maxTokens * 4; // 粗略估算 + const chunks: string[] = []; + + // 按段落分割 + const paragraphs = text.split(/\n\n+/); + let currentChunk = ''; + + for (const paragraph of paragraphs) { + if ((currentChunk + paragraph).length > chunkSize && currentChunk) { + chunks.push(currentChunk.trim()); + currentChunk = paragraph; + } else { + currentChunk += (currentChunk ? '\n\n' : '') + paragraph; + } + } + + if (currentChunk) { + chunks.push(currentChunk.trim()); + } + + return chunks; + } + + /** + * 移除 AI 响应中的思考过程(thinking 标签) + * @param str 响应字符串 + * @returns 清理后的字符串 + */ + removeThinkingTags(str: string): string { + return str + .replace(/[\s\S]*?<\/thinking>/gi, '') + .replace(/\[thinking\][\s\S]*?\[\/thinking\]/gi, '') + .trim(); + } + + /** + * 转义特殊字符用于 AI 提示词 + * @param str 原始字符串 + * @returns 转义后的字符串 + */ + escapeForPrompt(str: string): string { + return str + .replace(/\\/g, '\\\\') + .replace(/`/g, '\\`') + .replace(/\$/g, '\\$'); + } + + /** + * 统计文本的大致 token 数量 + * @param text 文本 + * @returns 估算的 token 数量 + */ + estimateTokens(text: string): number { + // 简单估算:中文约 1.5 字符/token,英文约 4 字符/token + const chineseChars = (text.match(/[\u4e00-\u9fa5]/g) || []).length; + const otherChars = text.length - chineseChars; + return Math.ceil(chineseChars / 1.5 + otherChars / 4); + } + + /** + * 从响应中提取结构化数据(key: value 格式) + * @param str 响应字符串 + * @returns 键值对对象 + */ + extractKeyValuePairs(str: string): Record { + const result: Record = {}; + const lines = str.split('\n'); + + for (const line of lines) { + const match = line.match(/^([^::]+)[::]\s*(.+)$/); + if (match) { + const key = match[1].trim(); + const value = match[2].trim(); + result[key] = value; + } + } + + return result; + } + + /** + * 验证 AI 响应是否完整(检查截断) + * @param str 响应字符串 + * @returns 是否完整 + */ + isResponseComplete(str: string): boolean { + const incompleteSigns = [ + /```[\w]*\s*[\s\S]*?(? pattern.test(str.trim())); + } +} \ No newline at end of file diff --git a/src/provider/utils/chunk.ts b/src/provider/utils/chunk.ts deleted file mode 100644 index f6d3f88..0000000 --- a/src/provider/utils/chunk.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { numTokensFromString } from './token.ts'; - -// 常量定义 -const CHUNK_SIZE = 512; // 每个chunk的最大token数 -const MAGIC_SEPARATOR = '🦛'; -const DELIMITER = [',', '.', '!', '?', '\n', ',', '。', '!', '?']; -const PARAGRAPH_DELIMITER = '\n\n'; - -export interface Chunk { - chunkId: number; - text: string; - tokens: number; -} - -/** - * 确保每个chunk的大小不超过最大token数 - * @param chunk 输入的文本块 - * @returns 分割后的文本块及其token数的数组 - */ -function ensureChunkSize(chunk: string): Array<[string, number]> { - const tokens = numTokensFromString(chunk); - if (tokens <= CHUNK_SIZE) { - return [[chunk, tokens]]; - } - - // 在分隔符后添加魔法分隔符 - let processedChunk = chunk; - for (const delimiter of DELIMITER) { - // 转义特殊字符 - const escapedDelimiter = delimiter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - processedChunk = processedChunk.replace(new RegExp(escapedDelimiter, 'g'), delimiter + MAGIC_SEPARATOR); - } - - const chunks: Array<[string, number]> = []; - let tail = ''; - - // 按CHUNK_SIZE分割文本 - for (let i = 0; i < processedChunk.length; i += CHUNK_SIZE) { - const sentences = (processedChunk.slice(i, i + CHUNK_SIZE) + ' ').split(MAGIC_SEPARATOR); - const currentChunk = tail + sentences.slice(0, -1).join(''); - if (currentChunk.trim()) { - const tokenCount = numTokensFromString(currentChunk); - chunks.push([currentChunk, tokenCount]); - } - tail = sentences[sentences.length - 1].trim(); - } - - // 处理最后剩余的tail - if (tail) { - const tokenCount = numTokensFromString(tail); - chunks.push([tail, tokenCount]); - } - - return chunks; -} - -/** - * 将文本分割成chunks - * @param text 输入文本 - * @returns 分割后的chunks数组 - */ -export async function getChunks(text: string): Promise { - // 按段落分割文本 - const paragraphs = text - .split(PARAGRAPH_DELIMITER) - .map((p) => p.trim()) - .filter((p) => p); - - const chunks: Chunk[] = []; - let currentIndex = 0; - - // 处理每个段落 - for (const paragraph of paragraphs) { - const splittedParagraph = ensureChunkSize(paragraph); - for (const [text, tokens] of splittedParagraph) { - chunks.push({ - chunkId: currentIndex, - text, - tokens, - }); - currentIndex++; - } - } - - return chunks; -}