feat: center change

This commit is contained in:
xion 2025-04-06 23:25:46 +08:00
parent bfe8463212
commit 226f9a6896
23 changed files with 521 additions and 119 deletions

View File

@ -10,7 +10,8 @@
"type": "system-app" "type": "system-app"
}, },
"files": [ "files": [
"dist" "dist",
"types"
], ],
"scripts": { "scripts": {
"watch": "rollup -c rollup.config.mjs -w", "watch": "rollup -c rollup.config.mjs -w",
@ -29,7 +30,8 @@
"dependencies": { "dependencies": {
"@kevisual/cache": "^0.0.2", "@kevisual/cache": "^0.0.2",
"@kevisual/permission": "^0.0.1", "@kevisual/permission": "^0.0.1",
"@kevisual/router": "0.0.10" "@kevisual/router": "0.0.10",
"pino-pretty": "^13.0.0"
}, },
"devDependencies": { "devDependencies": {
"@kevisual/code-center-module": "0.0.18", "@kevisual/code-center-module": "0.0.18",

74
pnpm-lock.yaml generated
View File

@ -17,6 +17,9 @@ importers:
'@kevisual/router': '@kevisual/router':
specifier: 0.0.10 specifier: 0.0.10
version: 0.0.10 version: 0.0.10
pino-pretty:
specifier: ^13.0.0
version: 13.0.0
devDependencies: devDependencies:
'@kevisual/code-center-module': '@kevisual/code-center-module':
specifier: 0.0.18 specifier: 0.0.18
@ -1197,6 +1200,9 @@ packages:
colorette@1.4.0: colorette@1.4.0:
resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
combined-stream@1.0.8: combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -1291,6 +1297,9 @@ packages:
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dateformat@4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
dayjs@1.11.13: dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
@ -1400,6 +1409,9 @@ packages:
emoji-regex@9.2.2: emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
engine.io-parser@5.2.3: engine.io-parser@5.2.3:
resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
@ -1523,6 +1535,9 @@ packages:
fast-content-type-parse@2.0.1: fast-content-type-parse@2.0.1:
resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==}
fast-copy@3.0.2:
resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==}
fast-deep-equal@3.1.3: fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@ -1537,6 +1552,9 @@ packages:
resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==}
engines: {node: '>=6'} engines: {node: '>=6'}
fast-safe-stringify@2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
fast-uri@3.0.6: fast-uri@3.0.6:
resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==}
@ -1736,6 +1754,9 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
help-me@5.0.0:
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
hexoid@2.0.0: hexoid@2.0.0:
resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -2326,6 +2347,10 @@ packages:
pino-abstract-transport@2.0.0: pino-abstract-transport@2.0.0:
resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
pino-pretty@13.0.0:
resolution: {integrity: sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==}
hasBin: true
pino-std-serializers@7.0.0: pino-std-serializers@7.0.0:
resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==}
@ -2433,6 +2458,9 @@ packages:
pstree.remy@1.1.8: pstree.remy@1.1.8:
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
pump@3.0.2:
resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==}
punycode@2.3.1: punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -2579,6 +2607,9 @@ packages:
sax@1.4.1: sax@1.4.1:
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
secure-json-parse@2.7.0:
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
selfsigned@2.4.1: selfsigned@2.4.1:
resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -2789,6 +2820,10 @@ packages:
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
engines: {node: '>=18'} engines: {node: '>=18'}
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
stubborn-fs@1.2.5: stubborn-fs@1.2.5:
resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==} resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==}
@ -4244,6 +4279,8 @@ snapshots:
colorette@1.4.0: {} colorette@1.4.0: {}
colorette@2.0.20: {}
combined-stream@1.0.8: combined-stream@1.0.8:
dependencies: dependencies:
delayed-stream: 1.0.0 delayed-stream: 1.0.0
@ -4337,6 +4374,8 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
is-data-view: 1.0.2 is-data-view: 1.0.2
dateformat@4.6.3: {}
dayjs@1.11.13: {} dayjs@1.11.13: {}
dayjs@1.8.36: {} dayjs@1.8.36: {}
@ -4443,6 +4482,10 @@ snapshots:
emoji-regex@9.2.2: {} emoji-regex@9.2.2: {}
end-of-stream@1.4.4:
dependencies:
once: 1.4.0
engine.io-parser@5.2.3: {} engine.io-parser@5.2.3: {}
engine.io@6.6.4: engine.io@6.6.4:
@ -4651,6 +4694,8 @@ snapshots:
fast-content-type-parse@2.0.1: {} fast-content-type-parse@2.0.1: {}
fast-copy@3.0.2: {}
fast-deep-equal@3.1.3: {} fast-deep-equal@3.1.3: {}
fast-glob@3.3.3: fast-glob@3.3.3:
@ -4665,6 +4710,8 @@ snapshots:
fast-redact@3.5.0: {} fast-redact@3.5.0: {}
fast-safe-stringify@2.1.1: {}
fast-uri@3.0.6: {} fast-uri@3.0.6: {}
fastq@1.19.1: fastq@1.19.1:
@ -4885,6 +4932,8 @@ snapshots:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
help-me@5.0.0: {}
hexoid@2.0.0: {} hexoid@2.0.0: {}
http-proxy-agent@7.0.2: http-proxy-agent@7.0.2:
@ -5514,6 +5563,22 @@ snapshots:
dependencies: dependencies:
split2: 4.2.0 split2: 4.2.0
pino-pretty@13.0.0:
dependencies:
colorette: 2.0.20
dateformat: 4.6.3
fast-copy: 3.0.2
fast-safe-stringify: 2.1.1
help-me: 5.0.0
joycon: 3.1.1
minimist: 1.2.8
on-exit-leak-free: 2.1.2
pino-abstract-transport: 2.0.0
pump: 3.0.2
secure-json-parse: 2.7.0
sonic-boom: 4.2.0
strip-json-comments: 3.1.1
pino-std-serializers@7.0.0: {} pino-std-serializers@7.0.0: {}
pino@9.6.0: pino@9.6.0:
@ -5664,6 +5729,11 @@ snapshots:
pstree.remy@1.1.8: {} pstree.remy@1.1.8: {}
pump@3.0.2:
dependencies:
end-of-stream: 1.4.4
once: 1.4.0
punycode@2.3.1: {} punycode@2.3.1: {}
queue-microtask@1.2.3: {} queue-microtask@1.2.3: {}
@ -5845,6 +5915,8 @@ snapshots:
sax@1.4.1: {} sax@1.4.1: {}
secure-json-parse@2.7.0: {}
selfsigned@2.4.1: selfsigned@2.4.1:
dependencies: dependencies:
'@types/node-forge': 1.3.11 '@types/node-forge': 1.3.11
@ -6082,6 +6154,8 @@ snapshots:
strip-final-newline@4.0.0: {} strip-final-newline@4.0.0: {}
strip-json-comments@3.1.1: {}
stubborn-fs@1.2.5: {} stubborn-fs@1.2.5: {}
sucrase@3.35.0: sucrase@3.35.0:

View File

@ -71,6 +71,9 @@ const config = {
'sequelize', // 数据库 orm 'sequelize', // 数据库 orm
'ioredis', // redis 'ioredis', // redis
'pg', // pg 'pg', // pg
'pino', // pino
'pino-pretty', // pino-pretty
], ],
}; };
export default config; export default config;

37
src/logger/index.ts Normal file
View File

@ -0,0 +1,37 @@
import { pino } from 'pino';
import { useConfig } from '@kevisual/use-config/env';
const config = useConfig();
export const logger = pino({
level: config.LOG_LEVEL || 'info',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'SYS:standard',
ignore: 'pid,hostname',
},
},
serializers: {
error: pino.stdSerializers.err,
req: pino.stdSerializers.req,
res: pino.stdSerializers.res,
},
base: {
app: 'ai-chat',
env: process.env.NODE_ENV || 'development',
},
});
export const logError = (message: string, data?: any) => logger.error({ data }, message);
export const logWarning = (message: string, data?: any) => logger.warn({ data }, message);
export const logInfo = (message: string, data?: any) => logger.info({ data }, message);
export const logDebug = (message: string, data?: any) => logger.debug({ data }, message);
export const log = {
error: logError,
warn: logWarning,
info: logInfo,
debug: logDebug,
};

View File

@ -7,6 +7,7 @@ export type OllamaOptions = BaseChatOptions;
*/ */
export class Custom extends BaseChat { export class Custom extends BaseChat {
constructor(options: OllamaOptions) { constructor(options: OllamaOptions) {
super(options); const baseURL = options.baseURL || 'https://api.deepseek.com/v1/';
super({ ...(options as BaseChatOptions), baseURL: baseURL });
} }
} }

View File

@ -3,6 +3,7 @@ import { BaseChat, BaseChatOptions } from '../core/chat.ts';
export type DeepSeekOptions = Partial<BaseChatOptions>; export type DeepSeekOptions = Partial<BaseChatOptions>;
export class DeepSeek extends BaseChat { export class DeepSeek extends BaseChat {
constructor(options: DeepSeekOptions) { constructor(options: DeepSeekOptions) {
super({ baseURL: 'https://api.deepseek.com/v1/', ...options } as any); const baseURL = options.baseURL || 'https://api.deepseek.com/v1/';
super({ ...(options as BaseChatOptions), baseURL: baseURL });
} }
} }

View File

@ -4,6 +4,7 @@ import { BaseChat, BaseChatOptions } from '../core/chat.ts';
export type ModelScopeOptions = Partial<BaseChatOptions>; export type ModelScopeOptions = Partial<BaseChatOptions>;
export class ModelScope extends BaseChat { export class ModelScope extends BaseChat {
constructor(options: ModelScopeOptions) { constructor(options: ModelScopeOptions) {
super({ baseURL: 'https://api-inference.modelscope.cn/v1/', ...options } as any); const baseURL = options.baseURL || 'https://api-inference.modelscope.cn/v1/';
super({ ...options, baseURL: baseURL } as any);
} }
} }

View File

@ -21,7 +21,8 @@ type OllamaModel = {
}; };
export class Ollama extends BaseChat { export class Ollama extends BaseChat {
constructor(options: OllamaOptions) { constructor(options: OllamaOptions) {
super({ baseURL: 'http://localhost:11434/v1', ...(options as BaseChatOptions) }); const baseURL = options.baseURL || 'http://localhost:11434/v1';
super({ ...(options as BaseChatOptions), baseURL: baseURL });
} }
async chat(messages: ChatMessage[], options?: ChatMessageOptions) { async chat(messages: ChatMessage[], options?: ChatMessageOptions) {
const res = await super.chat(messages, options); const res = await super.chat(messages, options);

View File

@ -25,7 +25,8 @@ type SiliconFlowUsageResponse = {
}; };
export class SiliconFlow extends BaseChat { export class SiliconFlow extends BaseChat {
constructor(options: SiliconFlowOptions) { constructor(options: SiliconFlowOptions) {
super({ baseURL: 'https://api.siliconflow.com/v1', ...(options as BaseChatOptions) }); const baseURL = options.baseURL || 'https://api.siliconflow.com/v1';
super({ ...(options as BaseChatOptions), baseURL: baseURL });
} }
async getUsageInfo(): Promise<SiliconFlowUsageResponse> { async getUsageInfo(): Promise<SiliconFlowUsageResponse> {
return this.openai.get('/user/info'); return this.openai.get('/user/info');

View File

@ -3,6 +3,7 @@ import { BaseChat, BaseChatOptions } from '../core/chat.ts';
export type VolcesOptions = Partial<BaseChatOptions>; export type VolcesOptions = Partial<BaseChatOptions>;
export class Volces extends BaseChat { export class Volces extends BaseChat {
constructor(options: VolcesOptions) { constructor(options: VolcesOptions) {
super({ baseURL: 'https://ark.cn-beijing.volces.com/api/v3/', ...options } as any); const baseURL = options.baseURL || 'https://ark.cn-beijing.volces.com/api/v3/';
super({ ...(options as BaseChatOptions), baseURL: baseURL });
} }
} }

View File

@ -1,6 +1,6 @@
import OpenAI from 'openai'; import OpenAI from 'openai';
export type ChatMessage = OpenAI.Chat.Completions.ChatCompletionMessageParam; export type ChatMessage = OpenAI.Chat.Completions.ChatCompletionMessageParam ;
export type ChatMessageOptions = Partial<OpenAI.Chat.Completions.ChatCompletionCreateParams>; export type ChatMessageOptions = Partial<OpenAI.Chat.Completions.ChatCompletionCreateParams>;
export type ChatMessageComplete = OpenAI.Chat.Completions.ChatCompletion; export type ChatMessageComplete = OpenAI.Chat.Completions.ChatCompletion;
export type ChatMessageStream = OpenAI.Chat.Completions.ChatCompletion; export type ChatMessageStream = OpenAI.Chat.Completions.ChatCompletion;

View File

@ -40,6 +40,8 @@ export class ProviderManager {
if (!Provider) { if (!Provider) {
throw new Error(`Provider ${provider} not found`); throw new Error(`Provider ${provider} not found`);
} }
console.log('pm', 'Provider', ProviderMap[provider]);
this.provider = new Provider({ this.provider = new Provider({
model, model,
apiKey, apiKey,

View File

@ -0,0 +1,52 @@
import type { Permission } from '@kevisual/permission';
export type AIModel = {
/**
*
*/
provider: string;
/**
*
*/
model: string;
/**
*
*/
group: string;
/**
*
*/
dayLimit?: number;
/**
* token限制
*/
tokenLimit?: number;
};
export type SecretKey = {
/**
*
*/
group: string;
/**
* API密钥
*/
apiKey: string;
/**
*
*/
decryptKey?: string;
};
export type AIConfig = {
title?: string;
description?: string;
models: AIModel[];
secretKeys: SecretKey[];
permission?: Permission;
filter?: {
objectKey: string;
type: 'array' | 'object';
operate: 'removeAttribute' | 'remove';
attribute: string[];
}[];
};

View File

@ -88,7 +88,11 @@ export class AIConfigParser {
constructor(config: AIConfig) { constructor(config: AIConfig) {
this.config = config; this.config = config;
} }
/**
*
* @param opts
* @returns
*/
getProvider(opts: GetProviderOpts): ProviderResult { getProvider(opts: GetProviderOpts): ProviderResult {
const { model, group, decryptKey } = opts; const { model, group, decryptKey } = opts;
const modelConfig = this.config.models.find((m) => m.model === model && m.group === group); const modelConfig = this.config.models.find((m) => m.model === model && m.group === group);
@ -117,16 +121,17 @@ export class AIConfigParser {
this.result = mergeConfig; this.result = mergeConfig;
return mergeConfig; return mergeConfig;
} }
/**
async getSecretKey({ *
getCache, * @param opts
setCache, * @returns
providerResult, */
}: { async getSecretKey(opts?: {
getCache?: (key: string) => Promise<string>; getCache?: (key: string) => Promise<string>;
setCache?: (key: string, value: string) => Promise<void>; setCache?: (key: string, value: string) => Promise<void>;
providerResult?: ProviderResult; providerResult?: ProviderResult;
}) { }) {
const { getCache, setCache, providerResult } = opts || {};
const { apiKey, decryptKey, group = '', model } = providerResult || this.result; const { apiKey, decryptKey, group = '', model } = providerResult || this.result;
const cacheKey = `${group}--${model}`; const cacheKey = `${group}--${model}`;
if (!decryptKey) { if (!decryptKey) {
@ -144,11 +149,38 @@ export class AIConfigParser {
} }
return secretKey; return secretKey;
} }
/**
*
* @param plainText
* @param secretKey
* @returns
*/
encrypt(plainText: string, secretKey: string) { encrypt(plainText: string, secretKey: string) {
return encryptAES(plainText, secretKey); return encryptAES(plainText, secretKey);
} }
/**
*
* @param cipherText
* @param secretKey
* @returns
*/
decrypt(cipherText: string, secretKey: string) { decrypt(cipherText: string, secretKey: string) {
return decryptAES(cipherText, secretKey); return decryptAES(cipherText, secretKey);
} }
/**
*
* @returns
*/
getSelectOpts() {
const { models, secretKeys = [] } = this.config;
return models.map((model) => {
const selectOpts = secretKeys.find((m) => m.group === model.group);
return {
...model,
...selectOpts,
};
});
}
} }

View File

@ -0,0 +1,43 @@
import { app } from '@/app.ts';
import { ChatConfigServices } from './services/chat-config-srevices.ts';
import { log } from '@/logger/index.ts';
import { ChatServices } from './services/chat-services.ts';
/**
*
*/
// https://localhost:4000/api/router?path=ai&key=clear-cache
app
.route({
path: 'ai',
key: 'clear-cache',
description: '清除缓存',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
const username = tokenUser.username;
const services = new ChatConfigServices(username, username);
await services.clearCache();
log.info('清除缓存成功', { username });
ctx.body = 'success';
})
.addTo(app);
app
.route({
path: 'ai',
key: 'clear-chat-limit',
description: '清除chat使用情况',
middleware: ['auth'],
})
.define(async (ctx) => {
const tokenUser = ctx.state.tokenUser;
const username = tokenUser.username;
const cache = await ChatServices.clearChatLimit(username);
log.debug('清除chat使用情况成功', { username, cache });
ctx.body = {
cache,
};
})
.addTo(app);

View File

@ -3,6 +3,8 @@ import { ChatServices } from './services/chat-services.ts';
import { ChatConfigServices } from './services/chat-config-srevices.ts'; import { ChatConfigServices } from './services/chat-config-srevices.ts';
import { AiChatHistoryModel } from './models/ai-chat-history.ts'; import { AiChatHistoryModel } from './models/ai-chat-history.ts';
import { UserPermission } from '@kevisual/permission'; import { UserPermission } from '@kevisual/permission';
import { AIConfigParser } from '@/provider/utils/parse-config.ts';
import { log } from '@/logger/index.ts';
app app
.route({ .route({
path: 'ai', path: 'ai',
@ -11,10 +13,13 @@ app
}) })
.define(async (ctx) => { .define(async (ctx) => {
const data = ctx.query.data || {}; const data = ctx.query.data || {};
const { id, messages = [], options = {}, title, hook, getFull = false } = data; const { id, messages = [], title, type } = data;
let { username, model, group } = ctx.query; const hook = data.data?.hook;
let { username, model, group, getFull = false } = ctx.query;
const tokenUser = ctx.state.tokenUser || {}; const tokenUser = ctx.state.tokenUser || {};
const tokenUsername = tokenUser.username; const tokenUsername = tokenUser.username;
const options = ctx.query.options || {};
let aiChatHistory: AiChatHistoryModel; let aiChatHistory: AiChatHistoryModel;
if (id) { if (id) {
aiChatHistory = await AiChatHistoryModel.findByPk(id); aiChatHistory = await AiChatHistoryModel.findByPk(id);
@ -58,46 +63,65 @@ app
if (pickMessages.length === 0) { if (pickMessages.length === 0) {
ctx.throw(400, 'chat messages is empty'); ctx.throw(400, 'chat messages is empty');
} }
const res = await chatServices.chat(pickMessages, options);
if (!aiChatHistory) { if (!aiChatHistory) {
aiChatHistory = await AiChatHistoryModel.create({ aiChatHistory = await AiChatHistoryModel.create({
username, username,
model, model,
group, group,
title, title,
type: type || 'keep',
}); });
if (!title) { if (!title) {
// TODO: 创建标题 // TODO: 创建标题
} }
} }
const message = res.choices[0].message; let message;
const newMessage = await chatServices.createNewMessage([...messages, message]); try {
const res = await chatServices.chat(pickMessages, options);
const usage = chatServices.chatProvider.getChatUsage(); message = res.choices[0].message;
await chatServices.updateChatLimit(usage.total_tokens); } catch (error) {
await chatConfigServices.updateUserChatLimit(tokenUsername, usage.total_tokens); log.error('chat error', {
errorMessage: error.message,
const needUpdateData: any = { });
messages: newMessage, ctx.throw(500, error.message);
prompt_tokens: aiChatHistory.prompt_tokens + usage.prompt_tokens, }
completion_tokens: aiChatHistory.completion_tokens + usage.completion_tokens, try {
total_tokens: aiChatHistory.total_tokens + usage.total_tokens, const newMessage = await chatServices.createNewMessage([...messages, message]);
version: aiChatHistory.version + 1,
model: model, const usage = chatServices.chatProvider.getChatUsage();
group: group, await chatServices.updateChatLimit(usage.total_tokens);
username: username, await chatConfigServices.updateUserChatLimit(tokenUsername, usage.total_tokens);
};
if (hook) { const needUpdateData: any = {
needUpdateData.data = { messages: newMessage,
...aiChatHistory.data, prompt_tokens: aiChatHistory.prompt_tokens + usage.prompt_tokens,
hook, completion_tokens: aiChatHistory.completion_tokens + usage.completion_tokens,
}; total_tokens: aiChatHistory.total_tokens + usage.total_tokens,
version: aiChatHistory.version + 1,
model: model,
group: group,
username: username,
};
if (hook) {
needUpdateData.data = {
...aiChatHistory.data,
hook,
};
}
if (type) {
needUpdateData.type = type;
}
await AiChatHistoryModel.update(needUpdateData, { where: { id: aiChatHistory.id } });
ctx.body = {
message: newMessage[newMessage.length - 1],
updatedAt: aiChatHistory.updatedAt,
version: aiChatHistory.version,
aiChatHistory: getFull || !id ? aiChatHistory : undefined,
};
} catch (error) {
console.error('create new message error', error);
ctx.throw(500, error.message);
} }
await AiChatHistoryModel.update(needUpdateData, { where: { id: aiChatHistory.id } });
ctx.body = {
message: newMessage[newMessage.length - 1],
aiChatHistory: getFull || !id ? aiChatHistory : undefined,
};
}) })
.addTo(app); .addTo(app);
@ -136,50 +160,69 @@ app
path: 'ai', path: 'ai',
key: 'get-model-list', key: 'get-model-list',
middleware: ['auth'], middleware: ['auth'],
description: '获取模型列表',
isDebug: true,
}) })
.define(async (ctx) => { .define(async (ctx) => {
const username = ctx.query.username || 'root'; const username = ctx.query.username || 'root';
const tokenUser = ctx.state.tokenUser; const tokenUser = ctx.state.tokenUser;
const usernames = ctx.query.data?.usernames || []; const usernames = ctx.query.data?.usernames || [];
const keepSecret = ctx.query.keepSecret || false;
const tokenUsername = tokenUser.username; const tokenUsername = tokenUser.username;
const isSameUser = username === tokenUser.username; const isSameUser = username === tokenUser.username;
const configArray: any[] = []; const configArray: any[] = [];
const services = new ChatConfigServices(username, tokenUser.username); try {
const res = await services.getChatConfig(true, ctx.query.token);
configArray.push({
username,
config: res,
});
if (!isSameUser) {
const selfServices = new ChatConfigServices(tokenUser.username, tokenUser.username);
const selfRes = await selfServices.getChatConfig(true, ctx.query.token);
configArray.push({
username: tokenUser.username,
self: true,
config: selfRes,
});
}
for (const username of usernames) {
const services = new ChatConfigServices(username, tokenUser.username); const services = new ChatConfigServices(username, tokenUser.username);
const res = await services.getChatConfig(true, ctx.query.token); const res = await services.getChatConfig(services.isOwner && keepSecret, ctx.query.token);
const aiConfig = services.aiConfig; const selectOpts = await services.getSelectOpts(res);
const permission = new UserPermission({ permission: aiConfig.permission, owner: username }); configArray.push({
const checkPermission = permission.checkPermissionSuccess({ username: tokenUsername, password: '-----------------' }); username,
if (!checkPermission.success) { config: res,
// ctx.throw(403, `[${username}] ${checkPermission.message}`); selectOpts,
self: isSameUser,
});
if (!isSameUser) {
const selfServices = new ChatConfigServices(tokenUser.username, tokenUser.username);
const selfRes = await selfServices.getChatConfig(services.isOwner && keepSecret, ctx.query.token);
const selfSelectOpts = await selfServices.getSelectOpts(selfRes);
configArray.push({ configArray.push({
username, username: tokenUser.username,
config: null, self: true,
error: checkPermission.message, config: selfRes,
}); selectOpts: selfSelectOpts,
} else {
configArray.push({
username,
config: res,
}); });
} }
for (const username of usernames) {
const services = new ChatConfigServices(username, tokenUser.username);
const res = await services.getChatConfig(services.isOwner && keepSecret, ctx.query.token);
const aiConfig = services.aiConfig;
const permission = new UserPermission({ permission: aiConfig.permission, owner: username });
const checkPermission = permission.checkPermissionSuccess({ username: tokenUsername, password: '-----------------' });
if (!checkPermission.success) {
configArray.push({
username,
config: null,
error: checkPermission.message,
});
} else {
const selectOpts = await services.getSelectOpts(res);
configArray.push({
username,
config: res,
selectOpts,
self: username === tokenUser.username,
});
}
}
ctx.body = { list: configArray };
} catch (error) {
log.error('get model list error', {
username,
errorMessage: error.message,
errorStack: error.stack,
});
ctx.throw(500, error.message);
} }
ctx.body = configArray;
}) })
.addTo(app); .addTo(app);

View File

@ -10,9 +10,11 @@ app
}) })
.define(async (ctx) => { .define(async (ctx) => {
const tokenUser = ctx.state.tokenUser; const tokenUser = ctx.state.tokenUser;
const type = ctx.query.type || 'keep';
const aiChatList = await AiChatHistoryModel.findAll({ const aiChatList = await AiChatHistoryModel.findAll({
where: { where: {
uid: tokenUser.id, uid: tokenUser.id,
type,
}, },
order: [['updatedAt', 'DESC']], order: [['updatedAt', 'DESC']],
}); });

View File

@ -34,6 +34,7 @@ export class AiChatHistoryModel extends Model {
declare completion_tokens: number; declare completion_tokens: number;
declare version: number; declare version: number;
declare type: string;
declare createdAt: Date; declare createdAt: Date;
declare updatedAt: Date; declare updatedAt: Date;
@ -87,6 +88,11 @@ AiChatHistoryModel.init(
type: DataTypes.JSONB, type: DataTypes.JSONB,
defaultValue: {}, defaultValue: {},
}, },
type: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: 'keep', // keep 保留 temp 临时
},
version: { version: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
defaultValue: 0, defaultValue: 0,

View File

@ -1,7 +1,8 @@
import type { AIConfig } from '@/provider/utils/parse-config.ts'; import { AIConfigParser, type AIConfig } from '@/provider/utils/parse-config.ts';
import { redis } from '@/modules/db.ts'; import { redis } from '@/modules/db.ts';
import { CustomError } from '@kevisual/router'; import { CustomError } from '@kevisual/router';
import { queryConfig } from '@/modules/query.ts'; import { queryConfig } from '@/modules/query.ts';
import { log } from '@/logger/index.ts';
export class ChatConfigServices { export class ChatConfigServices {
cachePrefix = 'ai:chat:config'; cachePrefix = 'ai:chat:config';
// 使用谁的模型 // 使用谁的模型
@ -9,7 +10,7 @@ export class ChatConfigServices {
// 使用者 // 使用者
username: string; username: string;
aiConfig?: AIConfig; aiConfig?: AIConfig;
isOwner: boolean;
/** /**
* username 使使 * username 使使
* @param username * @param username
@ -17,16 +18,17 @@ export class ChatConfigServices {
constructor(owner: string, username: string, token?: string) { constructor(owner: string, username: string, token?: string) {
this.owner = owner; this.owner = owner;
this.username = username; this.username = username;
this.isOwner = owner === username;
} }
getKey() { getKey() {
return `${this.cachePrefix}:${this.owner}`; return `${this.cachePrefix}:${this.owner}`;
} }
/** /**
* chat配置 * chat配置
* @param needClearSecret secret false * @param keepSecret secret true
* @returns * @returns
*/ */
async getChatConfig(needClearSecret = false, token?: string) { async getChatConfig(keepSecret = true, token?: string) {
const key = this.getKey(); const key = this.getKey();
const cache = await redis.get(key); const cache = await redis.get(key);
let modelConfig = null; let modelConfig = null;
@ -35,7 +37,9 @@ export class ChatConfigServices {
} }
if (!modelConfig) { if (!modelConfig) {
if (this.owner !== this.username) { 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`); throw new CustomError(
`the owner [${this.owner}] config, [${this.username}] not permission to init config, only owner can init config, place connect owner`,
);
} else { } else {
const res = await queryConfig.getConfigByKey('ai.json', { token }); const res = await queryConfig.getConfigByKey('ai.json', { token });
if (res.code === 200 && res.data?.data) { if (res.code === 200 && res.data?.data) {
@ -53,14 +57,26 @@ export class ChatConfigServices {
await redis.set(key, JSON.stringify(modelConfig), 'EX', cacheTime); await redis.set(key, JSON.stringify(modelConfig), 'EX', cacheTime);
} }
this.aiConfig = modelConfig; this.aiConfig = modelConfig;
if (needClearSecret) { if (!keepSecret) {
modelConfig = this.filterApiKey(modelConfig); modelConfig = this.filterApiKey(modelConfig);
} }
return modelConfig; return modelConfig;
} }
async clearCache() {
const key = this.getKey();
await redis.set(key, JSON.stringify({}), 'EX', 1);
}
/**
*
* @returns
*/
async getSelectOpts(config?: AIConfig) {
const aiConfigParser = new AIConfigParser(config || this.aiConfig);
return aiConfigParser.getSelectOpts();
}
async filterApiKey(chatConfig: AIConfig) { async filterApiKey(chatConfig: AIConfig) {
// 过滤掉secret中的所有apiKey移除掉并返回chatConfig // 过滤掉secret中的所有apiKey移除掉并返回chatConfig
const { secretKeys, ...rest } = chatConfig; const { secretKeys = [], ...rest } = chatConfig;
return { return {
...rest, ...rest,
secretKeys: secretKeys.map((item) => { secretKeys: secretKeys.map((item) => {
@ -128,4 +144,9 @@ export class ChatConfigServices {
await redis.set(userCacheKey, JSON.stringify({ token }), 'EX', 60 * 60 * 24 * 30); // 30天 await redis.set(userCacheKey, JSON.stringify({ token }), 'EX', 60 * 60 * 24 * 30); // 30天
} }
} }
async clearChatLimit() {
if (this.owner !== 'root') return;
// const userCacheKey = `${this.cachePrefix}:root:chat-limit:${this.username}`;
// await redis.del(userCacheKey);
}
} }

View File

@ -7,6 +7,7 @@ import { pick } from 'lodash-es';
import { ChastHistoryMessage } from '../models/ai-chat-history.ts'; import { ChastHistoryMessage } from '../models/ai-chat-history.ts';
import { nanoid } from '@/utils/uuid.ts'; import { nanoid } from '@/utils/uuid.ts';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { log } from '@/logger/index.ts';
export type ChatServicesConfig = { export type ChatServicesConfig = {
owner: string; owner: string;
@ -78,22 +79,49 @@ export class ChatServices {
const owner = this.owner; const owner = this.owner;
return `${this.cachePrefix}${owner}:${key}`; return `${this.cachePrefix}${owner}:${key}`;
} }
static chatLimitKey(owner: string, key = 'chat-limit') {
return `ai-chat:model:${owner}:${key}`;
}
static async clearChatLimit(owner: string) {
const key = ChatServices.chatLimitKey(owner);
const cache = await redis.get(key);
if (cache) {
await redis.expire(key, 2);
}
return cache;
}
async getConfig(username: string) { async getConfig(username: string) {
const services = new ChatConfigServices(this.owner, username); const services = new ChatConfigServices(this.owner, username);
return services.getChatConfig(); return services.getChatConfig();
} }
async chat(messages: ChatMessage[], options?: ChatMessageOptions) { async chat(messages: ChatMessage[], options?: ChatMessageOptions, customOptions?: { clearThink?: boolean }) {
const { model, provider, apiKey, baseURL } = this.modelConfig; const { model, provider, apiKey, baseURL } = this.modelConfig;
const providerManager = await ProviderManager.createProvider({ try {
provider: provider, const providerManager = await ProviderManager.createProvider({
model: model, provider: provider,
apiKey: apiKey, model: model,
baseURL: baseURL, apiKey: apiKey,
}); baseURL: baseURL,
this.chatProvider = providerManager; });
const result = await providerManager.chat(messages, options); this.chatProvider = providerManager;
return result; const result = await providerManager.chat(messages, options);
const { clearThink = true } = customOptions || {};
if (clearThink) {
result.choices[0].message.content = result.choices[0].message.content.replace(/<think>[\s\S]*?<\/think>/g, '');
}
return result;
} catch (error) {
log.error('chat error', {
errorMessage: error.message,
errorStack: error.stack,
provider,
model,
apiKey,
baseURL,
});
throw error;
}
} }
async createTitle(messages: ChastHistoryMessage[]) { async createTitle(messages: ChastHistoryMessage[]) {
return nanoid(); return nanoid();
@ -135,21 +163,31 @@ export class ChatServices {
const { modelConfig } = this; const { modelConfig } = this;
const { tokenLimit, dayLimit, group, model } = modelConfig; const { tokenLimit, dayLimit, group, model } = modelConfig;
const key = this.wrapperKey(`chat-limit`); const key = this.wrapperKey(`chat-limit`);
const cache = await redis.get(key); try {
if (cache) { const cache = await redis.get(key);
const cacheData = JSON.parse(cache); if (cache) {
const today = dayjs().format('YYYY-MM-DD'); const cacheData = JSON.parse(cache);
const current = cacheData.find((item) => item.group === group && item.model === model); const today = dayjs().format('YYYY-MM-DD');
const day = current[today] || 0; log.debug('checkCanChat', { cacheData });
const token = current.token || 0; let current = cacheData.find((item) => item.group === group && item.model === model);
if (tokenLimit && token >= tokenLimit) { if (current) {
throw new CustomError(400, 'token limit exceeded'); const day = current[today] || 0;
} const token = current.token || 0;
if (dayLimit && day >= dayLimit) { if (tokenLimit && token >= tokenLimit) {
throw new CustomError(400, 'day limit exceeded'); throw new CustomError(400, 'token limit exceeded');
}
if (dayLimit && day >= dayLimit) {
throw new CustomError(400, 'day limit exceeded');
}
}
} }
return true;
} catch (error) {
console.error('checkCanChat error', error);
// 如果获取失败则设置一个空的缓存2秒后删除
await redis.set(key, '', 'EX', 2); // 2秒
throw new CustomError(500, 'checkCanChat error, please try again later');
} }
return true;
} }
/** /**
* 使 * 使
@ -184,19 +222,27 @@ export class ChatServices {
const key = this.wrapperKey(`chat-limit`); const key = this.wrapperKey(`chat-limit`);
const cache = await redis.get(key); const cache = await redis.get(key);
const today = dayjs().format('YYYY-MM-DD'); const today = dayjs().format('YYYY-MM-DD');
if (cache) { try {
const cacheData = JSON.parse(cache); if (cache) {
const current = cacheData.find((item) => item.group === group && item.model === model); const cacheData = JSON.parse(cache);
if (current) { const current = cacheData.find((item) => item.group === group && item.model === model);
const day = current[today] || 0; if (current) {
current[today] = day + 1; const day = current[today] || 0;
current.token = current.token + token; 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 { } else {
cacheData.push({ group, model, token: token, [today]: 1 }); const cacheData = { group, model, token: token, [today]: 1 };
await redis.set(key, JSON.stringify([cacheData]), 'EX', 60 * 60 * 24 * 30); // 30天
} }
await redis.set(key, JSON.stringify(cacheData), 'EX', 60 * 60 * 24 * 30); // 30天 } catch (error) {
} else { console.error('updateChatLimit error', error);
await redis.set(key, JSON.stringify({ group, model, token: token, [today]: 1 }), 'EX', 60 * 60 * 24 * 30); // 30天 // 如果更新失败则设置一个空的缓存2秒后删除
await redis.set(key, '', 'EX', 2); // 2秒
throw new CustomError(500, 'updateChatLimit error, please try again later');
} }
} }
} }

View File

@ -1,2 +1,3 @@
import './ai-chat/index.ts'; import './ai-chat/index.ts';
import './ai-chat/list.ts'; import './ai-chat/list.ts';
import './ai-chat/cache.ts';

View File

@ -0,0 +1,26 @@
import { ModelScope } from '../../provider/chat-adapter/model-scope.ts';
import { logInfo } from '../../logger/index.ts';
import util from 'util';
import { config } from 'dotenv';
config();
const chat = new ModelScope({
apiKey: process.env.MODEL_SCOPE_API_KEY,
model: 'Qwen/Qwen2.5-Coder-32B-Instruct',
});
// chat.chat([{ role: 'user', content: 'Hello, world! 1 + 1 equals ?' }]);
const chatMessage = [{ role: 'user', content: 'Hello, world! 1 + 1 equals ?' }];
const main = async () => {
const res = await chat.test();
logInfo('test', res);
};
// main();
const mainChat = async () => {
const res = await chat.chat(chatMessage as any);
logInfo('chat', res);
};
mainChat();

View File

@ -0,0 +1,6 @@
import { ProviderManager } from '../../provider/index.ts';
const providerConfig = { provider: 'ModelScope', model: 'Qwen/Qwen2.5-Coder-32B-Instruct', apiKey: 'a4cc0e94-3633-4374-85a6-06f455e17bea' };
const provider = await ProviderManager.createProvider(providerConfig);
const result = await provider.chat([{ role: 'user', content: '你好' }]);
console.log(result);