generated from tailored/app-template
	Merge branch 'main' of git.xiongxiao.me:kevisual/ai-center
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -17,3 +17,5 @@ logs
 | 
				
			|||||||
!.env.example
 | 
					!.env.example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
config.json
 | 
					config.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pack-dist
 | 
				
			||||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					[submodule "submodules/query-config"]
 | 
				
			||||||
 | 
						path = submodules/query-config
 | 
				
			||||||
 | 
						url = git@git.xiongxiao.me:kevisual/kevisual-query-config.git
 | 
				
			||||||
							
								
								
									
										15
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								package.json
									
									
									
									
									
								
							@@ -1,8 +1,18 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "@kevisual/ai-center",
 | 
					  "name": "@kevisual/ai-center-services",
 | 
				
			||||||
  "version": "0.0.1",
 | 
					  "version": "0.0.1",
 | 
				
			||||||
  "description": "",
 | 
					  "description": "",
 | 
				
			||||||
  "main": "index.js",
 | 
					  "main": "index.js",
 | 
				
			||||||
 | 
					  "basename": "/root/ai-center-services",
 | 
				
			||||||
 | 
					  "app": {
 | 
				
			||||||
 | 
					    "entry": "dist/app.mjs",
 | 
				
			||||||
 | 
					    "key": "ai-center-services",
 | 
				
			||||||
 | 
					    "type": "system-app"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "files": [
 | 
				
			||||||
 | 
					    "dist",
 | 
				
			||||||
 | 
					    "types"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "build": "npm run clean && bun bun.config.mjs",
 | 
					    "build": "npm run clean && bun bun.config.mjs",
 | 
				
			||||||
    "dev": "bun run --watch bun.config.mjs",
 | 
					    "dev": "bun run --watch bun.config.mjs",
 | 
				
			||||||
@@ -29,9 +39,6 @@
 | 
				
			|||||||
      "types": "./dist/ai-provider.d.ts"
 | 
					      "types": "./dist/ai-provider.d.ts"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "files": [
 | 
					 | 
				
			||||||
    "dist"
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "dependencies": {},
 | 
					  "dependencies": {},
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@kevisual/code-center-module": "0.0.18",
 | 
					    "@kevisual/code-center-module": "0.0.18",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										289
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										289
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							@@ -53,6 +53,12 @@ importers:
 | 
				
			|||||||
      dayjs:
 | 
					      dayjs:
 | 
				
			||||||
        specifier: ^1.11.13
 | 
					        specifier: ^1.11.13
 | 
				
			||||||
        version: 1.11.13
 | 
					        version: 1.11.13
 | 
				
			||||||
 | 
					      crypto-js:
 | 
				
			||||||
 | 
					        specifier: ^4.2.0
 | 
				
			||||||
 | 
					        version: 4.2.0
 | 
				
			||||||
 | 
					      dayjs:
 | 
				
			||||||
 | 
					        specifier: ^1.11.13
 | 
				
			||||||
 | 
					        version: 1.11.13
 | 
				
			||||||
      dotenv:
 | 
					      dotenv:
 | 
				
			||||||
        specifier: ^16.5.0
 | 
					        specifier: ^16.5.0
 | 
				
			||||||
        version: 16.5.0
 | 
					        version: 16.5.0
 | 
				
			||||||
@@ -88,7 +94,7 @@ importers:
 | 
				
			|||||||
        version: 6.2.1(rollup@4.40.0)(typescript@5.8.3)
 | 
					        version: 6.2.1(rollup@4.40.0)(typescript@5.8.3)
 | 
				
			||||||
      sequelize:
 | 
					      sequelize:
 | 
				
			||||||
        specifier: ^6.37.7
 | 
					        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:
 | 
					      tape:
 | 
				
			||||||
        specifier: ^5.9.0
 | 
					        specifier: ^5.9.0
 | 
				
			||||||
        version: 5.9.0
 | 
					        version: 5.9.0
 | 
				
			||||||
@@ -275,6 +281,9 @@ packages:
 | 
				
			|||||||
  '@kevisual/auth@1.0.5':
 | 
					  '@kevisual/auth@1.0.5':
 | 
				
			||||||
    resolution: {integrity: sha512-GwsLj7unKXi7lmMiIIgdig4LwwLiDJnOy15HHZR5gMbyK6s5/uJiMY5RXPB2+onGzTNDqFo/hXjsD2wkerHPVg==}
 | 
					    resolution: {integrity: sha512-GwsLj7unKXi7lmMiIIgdig4LwwLiDJnOy15HHZR5gMbyK6s5/uJiMY5RXPB2+onGzTNDqFo/hXjsD2wkerHPVg==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  '@kevisual/cache@0.0.2':
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-2Cl5KF2Gi27uLfhO6CdTMFnRzx9vYnqevAo7d9ab3rOaqTgF8tLeAXglXyRbaWW3WUbHU2XaOb4r98uUsqIQQw==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  '@kevisual/code-center-module@0.0.18':
 | 
					  '@kevisual/code-center-module@0.0.18':
 | 
				
			||||||
    resolution: {integrity: sha512-BfANmxLEO1AwVmqpa6VDgxk//YN8asf1r5jIPpyKDQm12kyyrYgHND9AgGCDRH8lvq6rYVe0svCZXD5b06UPWQ==}
 | 
					    resolution: {integrity: sha512-BfANmxLEO1AwVmqpa6VDgxk//YN8asf1r5jIPpyKDQm12kyyrYgHND9AgGCDRH8lvq6rYVe0svCZXD5b06UPWQ==}
 | 
				
			||||||
    peerDependencies:
 | 
					    peerDependencies:
 | 
				
			||||||
@@ -291,6 +300,15 @@ packages:
 | 
				
			|||||||
  '@kevisual/mark@0.0.7':
 | 
					  '@kevisual/mark@0.0.7':
 | 
				
			||||||
    resolution: {integrity: sha512-PiEEy4yvWEpixw76PzgrIWeNelzm+FrhtzFmqJU92o5GkgawaFwighcvIxqcVZRKeEFF4uvlTjFrGeQvXw6F4A==}
 | 
					    resolution: {integrity: sha512-PiEEy4yvWEpixw76PzgrIWeNelzm+FrhtzFmqJU92o5GkgawaFwighcvIxqcVZRKeEFF4uvlTjFrGeQvXw6F4A==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  '@kevisual/permission@0.0.1':
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-nSX2LzbPkU3YAMegbUFGU8tfmtFb7dcF5edqzm+gI6crcyCL1JzIB9HAYNEeEVIljLxuREwM/vVg9aFmF4cz9Q==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  '@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':
 | 
					  '@kevisual/rollup-tools@0.0.1':
 | 
				
			||||||
    resolution: {integrity: sha512-TdCN+IU0fyHudiiqYvobXQ8r5MltfM/cKmSS59iopyL8YYwXwcipOS4S24NWA79g7uwJfSUNk5lg3yVhom79fQ==}
 | 
					    resolution: {integrity: sha512-TdCN+IU0fyHudiiqYvobXQ8r5MltfM/cKmSS59iopyL8YYwXwcipOS4S24NWA79g7uwJfSUNk5lg3yVhom79fQ==}
 | 
				
			||||||
    hasBin: true
 | 
					    hasBin: true
 | 
				
			||||||
@@ -625,6 +643,9 @@ packages:
 | 
				
			|||||||
    resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
 | 
					    resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
 | 
				
			||||||
    engines: {node: '>=12'}
 | 
					    engines: {node: '>=12'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  any-promise@1.3.0:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  anymatch@3.1.3:
 | 
					  anymatch@3.1.3:
 | 
				
			||||||
    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
 | 
					    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
 | 
				
			||||||
    engines: {node: '>= 8'}
 | 
					    engines: {node: '>= 8'}
 | 
				
			||||||
@@ -760,6 +781,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'}
 | 
				
			||||||
@@ -771,6 +795,10 @@ packages:
 | 
				
			|||||||
  commander@2.15.1:
 | 
					  commander@2.15.1:
 | 
				
			||||||
    resolution: {integrity: sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==}
 | 
					    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:
 | 
					  commondir@1.0.1:
 | 
				
			||||||
    resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
 | 
					    resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -823,6 +851,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==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -917,6 +948,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'}
 | 
				
			||||||
@@ -1137,6 +1171,10 @@ packages:
 | 
				
			|||||||
    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
 | 
					    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
 | 
				
			||||||
    engines: {node: '>= 6'}
 | 
					    engines: {node: '>= 6'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  glob@10.4.5:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
 | 
				
			||||||
 | 
					    hasBin: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  glob@11.0.1:
 | 
					  glob@11.0.1:
 | 
				
			||||||
    resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==}
 | 
					    resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==}
 | 
				
			||||||
    engines: {node: 20 || >=22}
 | 
					    engines: {node: 20 || >=22}
 | 
				
			||||||
@@ -1361,10 +1399,17 @@ packages:
 | 
				
			|||||||
  isexe@2.0.0:
 | 
					  isexe@2.0.0:
 | 
				
			||||||
    resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
 | 
					    resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  jackspeak@3.4.3:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  jackspeak@4.1.0:
 | 
					  jackspeak@4.1.0:
 | 
				
			||||||
    resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==}
 | 
					    resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==}
 | 
				
			||||||
    engines: {node: 20 || >=22}
 | 
					    engines: {node: 20 || >=22}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  joycon@3.1.1:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
 | 
				
			||||||
 | 
					    engines: {node: '>=10'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  js-git@0.7.8:
 | 
					  js-git@0.7.8:
 | 
				
			||||||
    resolution: {integrity: sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==}
 | 
					    resolution: {integrity: sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1381,11 +1426,6 @@ packages:
 | 
				
			|||||||
  json-stringify-safe@5.0.1:
 | 
					  json-stringify-safe@5.0.1:
 | 
				
			||||||
    resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
 | 
					    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:
 | 
					  jsonfile@4.0.0:
 | 
				
			||||||
    resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
 | 
					    resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1398,9 +1438,15 @@ packages:
 | 
				
			|||||||
  lodash.isarguments@3.1.0:
 | 
					  lodash.isarguments@3.1.0:
 | 
				
			||||||
    resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
 | 
					    resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lodash.sortby@4.7.0:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  lodash@4.17.21:
 | 
					  lodash@4.17.21:
 | 
				
			||||||
    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
 | 
					    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lru-cache@10.4.3:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  lru-cache@11.1.0:
 | 
					  lru-cache@11.1.0:
 | 
				
			||||||
    resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==}
 | 
					    resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==}
 | 
				
			||||||
    engines: {node: 20 || >=22}
 | 
					    engines: {node: 20 || >=22}
 | 
				
			||||||
@@ -1446,6 +1492,10 @@ packages:
 | 
				
			|||||||
  minimatch@3.1.2:
 | 
					  minimatch@3.1.2:
 | 
				
			||||||
    resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
 | 
					    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:
 | 
					  minimist@1.2.8:
 | 
				
			||||||
    resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
 | 
					    resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1477,6 +1527,9 @@ packages:
 | 
				
			|||||||
  mute-stream@0.0.8:
 | 
					  mute-stream@0.0.8:
 | 
				
			||||||
    resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
 | 
					    resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  mz@2.7.0:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  nanoid@3.3.11:
 | 
					  nanoid@3.3.11:
 | 
				
			||||||
    resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
 | 
					    resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
 | 
				
			||||||
    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
 | 
					    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
 | 
				
			||||||
@@ -1589,6 +1642,10 @@ packages:
 | 
				
			|||||||
  path-parse@1.0.7:
 | 
					  path-parse@1.0.7:
 | 
				
			||||||
    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
 | 
					    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:
 | 
					  path-scurry@2.0.0:
 | 
				
			||||||
    resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
 | 
					    resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
 | 
				
			||||||
    engines: {node: 20 || >=22}
 | 
					    engines: {node: 20 || >=22}
 | 
				
			||||||
@@ -1610,6 +1667,10 @@ packages:
 | 
				
			|||||||
  pg-connection-string@2.7.0:
 | 
					  pg-connection-string@2.7.0:
 | 
				
			||||||
    resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==}
 | 
					    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:
 | 
					  pg-int8@1.0.1:
 | 
				
			||||||
    resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
 | 
					    resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
 | 
				
			||||||
    engines: {node: '>=4.0.0'}
 | 
					    engines: {node: '>=4.0.0'}
 | 
				
			||||||
@@ -1660,6 +1721,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==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1667,6 +1732,10 @@ packages:
 | 
				
			|||||||
    resolution: {integrity: sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==}
 | 
					    resolution: {integrity: sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==}
 | 
				
			||||||
    hasBin: true
 | 
					    hasBin: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pirates@4.0.7:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
 | 
				
			||||||
 | 
					    engines: {node: '>= 6'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pm2-axon-rpc@0.7.1:
 | 
					  pm2-axon-rpc@0.7.1:
 | 
				
			||||||
    resolution: {integrity: sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==}
 | 
					    resolution: {integrity: sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==}
 | 
				
			||||||
    engines: {node: '>=5'}
 | 
					    engines: {node: '>=5'}
 | 
				
			||||||
@@ -1694,6 +1763,24 @@ packages:
 | 
				
			|||||||
    resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
 | 
					    resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
 | 
				
			||||||
    engines: {node: '>= 0.4'}
 | 
					    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:
 | 
					  postcss@8.5.3:
 | 
				
			||||||
    resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
 | 
					    resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
 | 
				
			||||||
    engines: {node: ^10 || ^12 || >=14}
 | 
					    engines: {node: ^10 || ^12 || >=14}
 | 
				
			||||||
@@ -1741,6 +1828,10 @@ packages:
 | 
				
			|||||||
    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
 | 
					    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
 | 
				
			||||||
    engines: {node: '>=8.10.0'}
 | 
					    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:
 | 
					  real-require@0.2.0:
 | 
				
			||||||
    resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
 | 
					    resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
 | 
				
			||||||
    engines: {node: '>= 12.13.0'}
 | 
					    engines: {node: '>= 12.13.0'}
 | 
				
			||||||
@@ -1765,6 +1856,10 @@ packages:
 | 
				
			|||||||
    resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==}
 | 
					    resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==}
 | 
				
			||||||
    engines: {node: '>=6'}
 | 
					    engines: {node: '>=6'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  resolve-from@5.0.0:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
 | 
				
			||||||
 | 
					    engines: {node: '>=8'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  resolve-pkg-maps@1.0.0:
 | 
					  resolve-pkg-maps@1.0.0:
 | 
				
			||||||
    resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
 | 
					    resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1850,6 +1945,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'}
 | 
				
			||||||
@@ -1988,6 +2086,10 @@ packages:
 | 
				
			|||||||
    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
 | 
					    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
 | 
				
			||||||
    engines: {node: '>=0.10.0'}
 | 
					    engines: {node: '>=0.10.0'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  source-map@0.8.0-beta.0:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
 | 
				
			||||||
 | 
					    engines: {node: '>= 8'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sourcemap-codec@1.4.8:
 | 
					  sourcemap-codec@1.4.8:
 | 
				
			||||||
    resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
 | 
					    resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
 | 
				
			||||||
    deprecated: Please use @jridgewell/sourcemap-codec instead
 | 
					    deprecated: Please use @jridgewell/sourcemap-codec instead
 | 
				
			||||||
@@ -2055,6 +2157,13 @@ packages:
 | 
				
			|||||||
    resolution: {integrity: sha512-czbGgxSVwRlbB3Ly/aqQrNwrDAzKHDW/kVXegp4hSFmR2c8qqm3hCgZbUy1+3QAQFGhPDG7J56UsV1uNilBFCA==}
 | 
					    resolution: {integrity: sha512-czbGgxSVwRlbB3Ly/aqQrNwrDAzKHDW/kVXegp4hSFmR2c8qqm3hCgZbUy1+3QAQFGhPDG7J56UsV1uNilBFCA==}
 | 
				
			||||||
    hasBin: true
 | 
					    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:
 | 
					  thread-stream@3.1.0:
 | 
				
			||||||
    resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
 | 
					    resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2081,6 +2190,25 @@ packages:
 | 
				
			|||||||
  tslib@2.8.1:
 | 
					  tslib@2.8.1:
 | 
				
			||||||
    resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
 | 
					    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:
 | 
					  tsx@4.19.3:
 | 
				
			||||||
    resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==}
 | 
					    resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==}
 | 
				
			||||||
    engines: {node: '>=18.0.0'}
 | 
					    engines: {node: '>=18.0.0'}
 | 
				
			||||||
@@ -2389,7 +2517,7 @@ snapshots:
 | 
				
			|||||||
      ioredis: 5.6.1
 | 
					      ioredis: 5.6.1
 | 
				
			||||||
      nanoid: 5.1.5
 | 
					      nanoid: 5.1.5
 | 
				
			||||||
      pg: 8.14.1
 | 
					      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
 | 
					      socket.io: 4.8.1
 | 
				
			||||||
      zod: 3.24.2
 | 
					      zod: 3.24.2
 | 
				
			||||||
    transitivePeerDependencies:
 | 
					    transitivePeerDependencies:
 | 
				
			||||||
@@ -2410,7 +2538,7 @@ snapshots:
 | 
				
			|||||||
      cookie: 1.0.2
 | 
					      cookie: 1.0.2
 | 
				
			||||||
      nanoid: 5.1.5
 | 
					      nanoid: 5.1.5
 | 
				
			||||||
      pg: 8.14.1
 | 
					      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:
 | 
					    transitivePeerDependencies:
 | 
				
			||||||
      - bufferutil
 | 
					      - bufferutil
 | 
				
			||||||
      - dotenv
 | 
					      - dotenv
 | 
				
			||||||
@@ -2427,6 +2555,24 @@ snapshots:
 | 
				
			|||||||
      - tedious
 | 
					      - tedious
 | 
				
			||||||
      - utf-8-validate
 | 
					      - utf-8-validate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  '@kevisual/permission@0.0.1': {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  '@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)':
 | 
					  '@kevisual/rollup-tools@0.0.1(esbuild@0.25.2)':
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      '@rollup/plugin-alias': 5.1.1(rollup@4.40.0)
 | 
					      '@rollup/plugin-alias': 5.1.1(rollup@4.40.0)
 | 
				
			||||||
@@ -2765,6 +2911,8 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  ansi-styles@6.2.1: {}
 | 
					  ansi-styles@6.2.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  any-promise@1.3.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  anymatch@3.1.3:
 | 
					  anymatch@3.1.3:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      normalize-path: 3.0.0
 | 
					      normalize-path: 3.0.0
 | 
				
			||||||
@@ -2902,6 +3050,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
 | 
				
			||||||
@@ -2910,6 +3060,8 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  commander@2.15.1: {}
 | 
					  commander@2.15.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  commander@4.1.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  commondir@1.0.1: {}
 | 
					  commondir@1.0.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  concat-map@0.0.1: {}
 | 
					  concat-map@0.0.1: {}
 | 
				
			||||||
@@ -2959,6 +3111,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: {}
 | 
				
			||||||
@@ -3051,6 +3205,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:
 | 
				
			||||||
@@ -3359,6 +3517,15 @@ snapshots:
 | 
				
			|||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      is-glob: 4.0.3
 | 
					      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:
 | 
					  glob@11.0.1:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      foreground-child: 3.3.1
 | 
					      foreground-child: 3.3.1
 | 
				
			||||||
@@ -3613,10 +3780,18 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  isexe@2.0.0: {}
 | 
					  isexe@2.0.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  jackspeak@3.4.3:
 | 
				
			||||||
 | 
					    dependencies:
 | 
				
			||||||
 | 
					      '@isaacs/cliui': 8.0.2
 | 
				
			||||||
 | 
					    optionalDependencies:
 | 
				
			||||||
 | 
					      '@pkgjs/parseargs': 0.11.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  jackspeak@4.1.0:
 | 
					  jackspeak@4.1.0:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      '@isaacs/cliui': 8.0.2
 | 
					      '@isaacs/cliui': 8.0.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  joycon@3.1.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  js-git@0.7.8:
 | 
					  js-git@0.7.8:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      bodec: 0.1.0
 | 
					      bodec: 0.1.0
 | 
				
			||||||
@@ -3636,8 +3811,6 @@ snapshots:
 | 
				
			|||||||
  json-stringify-safe@5.0.1:
 | 
					  json-stringify-safe@5.0.1:
 | 
				
			||||||
    optional: true
 | 
					    optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  json5@2.2.3: {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  jsonfile@4.0.0:
 | 
					  jsonfile@4.0.0:
 | 
				
			||||||
    optionalDependencies:
 | 
					    optionalDependencies:
 | 
				
			||||||
      graceful-fs: 4.2.11
 | 
					      graceful-fs: 4.2.11
 | 
				
			||||||
@@ -3648,8 +3821,12 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  lodash.isarguments@3.1.0: {}
 | 
					  lodash.isarguments@3.1.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lodash.sortby@4.7.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  lodash@4.17.21: {}
 | 
					  lodash@4.17.21: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  lru-cache@10.4.3: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  lru-cache@11.1.0: {}
 | 
					  lru-cache@11.1.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  lru-cache@6.0.0:
 | 
					  lru-cache@6.0.0:
 | 
				
			||||||
@@ -3689,6 +3866,10 @@ snapshots:
 | 
				
			|||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      brace-expansion: 1.1.11
 | 
					      brace-expansion: 1.1.11
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  minimatch@9.0.5:
 | 
				
			||||||
 | 
					    dependencies:
 | 
				
			||||||
 | 
					      brace-expansion: 2.0.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  minimist@1.2.8: {}
 | 
					  minimist@1.2.8: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  minipass@7.1.2: {}
 | 
					  minipass@7.1.2: {}
 | 
				
			||||||
@@ -3717,6 +3898,12 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  mute-stream@0.0.8: {}
 | 
					  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@3.3.11: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  nanoid@5.1.5: {}
 | 
					  nanoid@5.1.5: {}
 | 
				
			||||||
@@ -3818,6 +4005,11 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  path-parse@1.0.7: {}
 | 
					  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:
 | 
					  path-scurry@2.0.0:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      lru-cache: 11.1.0
 | 
					      lru-cache: 11.1.0
 | 
				
			||||||
@@ -3834,6 +4026,10 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  pg-connection-string@2.7.0: {}
 | 
					  pg-connection-string@2.7.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pg-hstore@2.3.4:
 | 
				
			||||||
 | 
					    dependencies:
 | 
				
			||||||
 | 
					      underscore: 1.13.7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pg-int8@1.0.1: {}
 | 
					  pg-int8@1.0.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pg-pool@3.8.0(pg@8.14.1):
 | 
					  pg-pool@3.8.0(pg@8.14.1):
 | 
				
			||||||
@@ -3883,6 +4079,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:
 | 
				
			||||||
@@ -3899,6 +4111,8 @@ snapshots:
 | 
				
			|||||||
      sonic-boom: 4.2.0
 | 
					      sonic-boom: 4.2.0
 | 
				
			||||||
      thread-stream: 3.1.0
 | 
					      thread-stream: 3.1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pirates@4.0.7: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pm2-axon-rpc@0.7.1:
 | 
					  pm2-axon-rpc@0.7.1:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      debug: 4.4.0
 | 
					      debug: 4.4.0
 | 
				
			||||||
@@ -3974,6 +4188,13 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  possible-typed-array-names@1.1.0: {}
 | 
					  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:
 | 
					  postcss@8.5.3:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      nanoid: 3.3.11
 | 
					      nanoid: 3.3.11
 | 
				
			||||||
@@ -4023,6 +4244,8 @@ snapshots:
 | 
				
			|||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      picomatch: 2.3.1
 | 
					      picomatch: 2.3.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  readdirp@4.1.2: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  real-require@0.2.0: {}
 | 
					  real-require@0.2.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  redis-errors@1.2.0: {}
 | 
					  redis-errors@1.2.0: {}
 | 
				
			||||||
@@ -4059,6 +4282,8 @@ snapshots:
 | 
				
			|||||||
    transitivePeerDependencies:
 | 
					    transitivePeerDependencies:
 | 
				
			||||||
      - supports-color
 | 
					      - supports-color
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  resolve-from@5.0.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  resolve-pkg-maps@1.0.0: {}
 | 
					  resolve-pkg-maps@1.0.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  resolve@1.22.10:
 | 
					  resolve@1.22.10:
 | 
				
			||||||
@@ -4178,6 +4403,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
 | 
				
			||||||
@@ -4191,7 +4418,7 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  sequelize-pool@7.1.0: {}
 | 
					  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:
 | 
					    dependencies:
 | 
				
			||||||
      '@types/debug': 4.1.12
 | 
					      '@types/debug': 4.1.12
 | 
				
			||||||
      '@types/validator': 13.12.3
 | 
					      '@types/validator': 13.12.3
 | 
				
			||||||
@@ -4211,6 +4438,7 @@ snapshots:
 | 
				
			|||||||
      wkx: 0.5.0
 | 
					      wkx: 0.5.0
 | 
				
			||||||
    optionalDependencies:
 | 
					    optionalDependencies:
 | 
				
			||||||
      pg: 8.14.1
 | 
					      pg: 8.14.1
 | 
				
			||||||
 | 
					      pg-hstore: 2.3.4
 | 
				
			||||||
    transitivePeerDependencies:
 | 
					    transitivePeerDependencies:
 | 
				
			||||||
      - supports-color
 | 
					      - supports-color
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -4336,6 +4564,10 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  source-map@0.6.1: {}
 | 
					  source-map@0.6.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  source-map@0.8.0-beta.0:
 | 
				
			||||||
 | 
					    dependencies:
 | 
				
			||||||
 | 
					      whatwg-url: 7.1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  sourcemap-codec@1.4.8: {}
 | 
					  sourcemap-codec@1.4.8: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  split2@4.2.0: {}
 | 
					  split2@4.2.0: {}
 | 
				
			||||||
@@ -4428,6 +4660,14 @@ snapshots:
 | 
				
			|||||||
      resolve: 2.0.0-next.5
 | 
					      resolve: 2.0.0-next.5
 | 
				
			||||||
      string.prototype.trim: 1.2.10
 | 
					      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:
 | 
					  thread-stream@3.1.0:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      real-require: 0.2.0
 | 
					      real-require: 0.2.0
 | 
				
			||||||
@@ -4451,6 +4691,33 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  tslib@2.8.1: {}
 | 
					  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:
 | 
					  tsx@4.19.3:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      esbuild: 0.25.2
 | 
					      esbuild: 0.25.2
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								pnpm-workspace.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								pnpm-workspace.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					packages:
 | 
				
			||||||
 | 
					  - 'submodules/**'
 | 
				
			||||||
@@ -1,5 +1,21 @@
 | 
				
			|||||||
import { app } from './app.ts';
 | 
					import { app } from './app.ts';
 | 
				
			||||||
import { useConfig } from '@kevisual/use-config/env';
 | 
					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
 | 
					app
 | 
				
			||||||
  .route({
 | 
					  .route({
 | 
				
			||||||
@@ -11,6 +27,4 @@ app
 | 
				
			|||||||
  })
 | 
					  })
 | 
				
			||||||
  .addTo(app);
 | 
					  .addTo(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const config = useConfig();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
console.log('run demo: http://localhost:' + config.PORT + '/api/router?path=demo&key=demo');
 | 
					console.log('run demo: http://localhost:' + config.PORT + '/api/router?path=demo&key=demo');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,7 @@
 | 
				
			|||||||
import { useConfig } from '@kevisual/use-config/env';
 | 
					import { useConfig } from '@kevisual/use-config/env';
 | 
				
			||||||
import { useContextKey } from '@kevisual/use-config/context';
 | 
					import { app } from './app.ts';
 | 
				
			||||||
import { Redis } from 'ioredis';
 | 
					import './demo-route.ts';
 | 
				
			||||||
export const redis = useContextKey('redis', () => {
 | 
					import './routes/index.ts';
 | 
				
			||||||
  return new Redis();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
import { app } from './index.ts'; // 开发环境
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const config = useConfig();
 | 
					const config = useConfig();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { app } from './app.ts';
 | 
					import { app } from './app.ts';
 | 
				
			||||||
import './demo-route.ts';
 | 
					import './routes/index.ts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { app };
 | 
					export { app };
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										37
									
								
								src/logger/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/logger/index.ts
									
									
									
									
									
										Normal 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,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -1,3 +1,16 @@
 | 
				
			|||||||
import { useContextKey } from '@kevisual/use-config/context';
 | 
					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();
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								src/modules/query.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/modules/query.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										30
									
								
								src/modules/sequelize.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/modules/sequelize.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -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 });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,6 @@
 | 
				
			|||||||
import OpenAI from 'openai';
 | 
					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 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;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										52
									
								
								src/provider/utils/ai-config-type.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/provider/utils/ai-config-type.ts
									
									
									
									
									
										Normal 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[];
 | 
				
			||||||
 | 
					  }[];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -1,14 +1,15 @@
 | 
				
			|||||||
import { AES, enc } from 'crypto-js';
 | 
					import { Permission } from '@kevisual/permission';
 | 
				
			||||||
 | 
					import CryptoJS from 'crypto-js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 加密函数
 | 
					// 加密函数
 | 
				
			||||||
export function encryptAES(plainText: string, secretKey: string) {
 | 
					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) {
 | 
					export function decryptAES(cipherText: string, secretKey: string) {
 | 
				
			||||||
  const bytes = AES.decrypt(cipherText, secretKey);
 | 
					  const bytes = CryptoJS.AES.decrypt(cipherText, secretKey);
 | 
				
			||||||
  return bytes.toString(enc.Utf8);
 | 
					  return bytes.toString(CryptoJS.enc.Utf8);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type AIModel = {
 | 
					type AIModel = {
 | 
				
			||||||
@@ -25,9 +26,13 @@ type AIModel = {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  group: string;
 | 
					  group: string;
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * 每日限制
 | 
					   * 每日请求频率限制
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  dayLimit?: number;
 | 
					  dayLimit?: number;
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 总的token限制
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  tokenLimit?: number;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SecretKey = {
 | 
					type SecretKey = {
 | 
				
			||||||
@@ -56,6 +61,7 @@ export type ProviderResult = {
 | 
				
			|||||||
  group: string;
 | 
					  group: string;
 | 
				
			||||||
  apiKey: string;
 | 
					  apiKey: string;
 | 
				
			||||||
  dayLimit?: number;
 | 
					  dayLimit?: number;
 | 
				
			||||||
 | 
					  tokenLimit?: number;
 | 
				
			||||||
  baseURL?: string;
 | 
					  baseURL?: string;
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * 解密密钥
 | 
					   * 解密密钥
 | 
				
			||||||
@@ -68,6 +74,13 @@ export type AIConfig = {
 | 
				
			|||||||
  description?: string;
 | 
					  description?: string;
 | 
				
			||||||
  models: AIModel[];
 | 
					  models: AIModel[];
 | 
				
			||||||
  secretKeys: SecretKey[];
 | 
					  secretKeys: SecretKey[];
 | 
				
			||||||
 | 
					  permission?: Permission;
 | 
				
			||||||
 | 
					  filter?: {
 | 
				
			||||||
 | 
					    objectKey: string;
 | 
				
			||||||
 | 
					    type: 'array' | 'object';
 | 
				
			||||||
 | 
					    operate: 'removeAttribute' | 'remove';
 | 
				
			||||||
 | 
					    attribute: string[];
 | 
				
			||||||
 | 
					  }[];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
export class AIConfigParser {
 | 
					export class AIConfigParser {
 | 
				
			||||||
  private config: AIConfig;
 | 
					  private config: AIConfig;
 | 
				
			||||||
@@ -75,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);
 | 
				
			||||||
@@ -104,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) {
 | 
				
			||||||
@@ -131,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,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										43
									
								
								src/routes/ai-chat/cache.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/routes/ai-chat/cache.ts
									
									
									
									
									
										Normal 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);
 | 
				
			||||||
@@ -1,11 +1,249 @@
 | 
				
			|||||||
import { app } from '@/app.ts';
 | 
					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';
 | 
				
			||||||
 | 
					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',
 | 
				
			||||||
    key: 'chat',
 | 
					    key: 'chat',
 | 
				
			||||||
 | 
					    middleware: ['auth'],
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
  .define(async () => {
 | 
					  .define(async (ctx) => {
 | 
				
			||||||
    //
 | 
					    const data = ctx.query.data || {};
 | 
				
			||||||
 | 
					    const { id, messages = [], title, type } = data;
 | 
				
			||||||
 | 
					    const hook = data.data?.hook;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let { username, model, group, getFull = false } = ctx.query;
 | 
				
			||||||
 | 
					    const tokenUser = ctx.state.tokenUser || {};
 | 
				
			||||||
 | 
					    const tokenUsername = tokenUser.username;
 | 
				
			||||||
 | 
					    const options = ctx.query.options || {};
 | 
				
			||||||
 | 
					    let aiChatHistory: AiChatHistoryModel;
 | 
				
			||||||
 | 
					    if (id) {
 | 
				
			||||||
 | 
					      aiChatHistory = await AiChatHistoryModel.findByPk(id);
 | 
				
			||||||
 | 
					      if (!aiChatHistory) {
 | 
				
			||||||
 | 
					        ctx.throw(400, 'aiChatHistory not found');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (aiChatHistory.uid !== tokenUser.id) {
 | 
				
			||||||
 | 
					        ctx.throw(403, 'not permission');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      username = username || aiChatHistory.username || tokenUsername;
 | 
				
			||||||
 | 
					      model = model || aiChatHistory.model;
 | 
				
			||||||
 | 
					      group = group || aiChatHistory.group;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      username = username || tokenUsername;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const isSelf = 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,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    if (!isSelf && username !== 'root') {
 | 
				
			||||||
 | 
					      const aiConfig = chatServices.aiConfig;
 | 
				
			||||||
 | 
					      const permission = new UserPermission({ permission: aiConfig.permission, owner: username });
 | 
				
			||||||
 | 
					      const checkPermission = permission.checkPermissionSuccess({ username: tokenUsername, password: options.password });
 | 
				
			||||||
 | 
					      if (!checkPermission.success) {
 | 
				
			||||||
 | 
					        ctx.throw(403, checkPermission.message);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!aiChatHistory) {
 | 
				
			||||||
 | 
					      aiChatHistory = await AiChatHistoryModel.create({
 | 
				
			||||||
 | 
					        username,
 | 
				
			||||||
 | 
					        model,
 | 
				
			||||||
 | 
					        group,
 | 
				
			||||||
 | 
					        title,
 | 
				
			||||||
 | 
					        type: type || 'keep',
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      if (!title) {
 | 
				
			||||||
 | 
					        // TODO: 创建标题
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let message;
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const res = await chatServices.chat(pickMessages, options);
 | 
				
			||||||
 | 
					      message = res.choices[0].message;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      log.error('chat error', {
 | 
				
			||||||
 | 
					        errorMessage: error.message,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      ctx.throw(500, error.message);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      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,
 | 
				
			||||||
 | 
					        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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .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'],
 | 
				
			||||||
 | 
					    description: '获取模型列表',
 | 
				
			||||||
 | 
					    isDebug: true,
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .define(async (ctx) => {
 | 
				
			||||||
 | 
					    const username = ctx.query.username || 'root';
 | 
				
			||||||
 | 
					    const tokenUser = ctx.state.tokenUser;
 | 
				
			||||||
 | 
					    const usernames = ctx.query.data?.usernames || [];
 | 
				
			||||||
 | 
					    const keepSecret = ctx.query.keepSecret || false;
 | 
				
			||||||
 | 
					    const tokenUsername = tokenUser.username;
 | 
				
			||||||
 | 
					    const isSameUser = username === tokenUser.username;
 | 
				
			||||||
 | 
					    const configArray: any[] = [];
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const services = new ChatConfigServices(username, tokenUser.username);
 | 
				
			||||||
 | 
					      const res = await services.getChatConfig(services.isOwner && keepSecret, ctx.query.token);
 | 
				
			||||||
 | 
					      const selectOpts = await services.getSelectOpts(res);
 | 
				
			||||||
 | 
					      configArray.push({
 | 
				
			||||||
 | 
					        username,
 | 
				
			||||||
 | 
					        config: res,
 | 
				
			||||||
 | 
					        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({
 | 
				
			||||||
 | 
					          username: tokenUser.username,
 | 
				
			||||||
 | 
					          self: true,
 | 
				
			||||||
 | 
					          config: selfRes,
 | 
				
			||||||
 | 
					          selectOpts: selfSelectOpts,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .addTo(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app
 | 
				
			||||||
 | 
					  .route({
 | 
				
			||||||
 | 
					    path: 'ai',
 | 
				
			||||||
 | 
					    key: 'get-chat-usage',
 | 
				
			||||||
 | 
					    description: '获取chat使用情况, 只获取root的使用情况',
 | 
				
			||||||
 | 
					    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);
 | 
					  .addTo(app);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										139
									
								
								src/routes/ai-chat/list.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/routes/ai-chat/list.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,139 @@
 | 
				
			|||||||
 | 
					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 type = ctx.query.type || 'keep';
 | 
				
			||||||
 | 
					    const all = ctx.query.all || false;
 | 
				
			||||||
 | 
					    let search: any = {};
 | 
				
			||||||
 | 
					    if (type && !all) {
 | 
				
			||||||
 | 
					      search.type = type;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const aiChatList = await AiChatHistoryModel.findAll({
 | 
				
			||||||
 | 
					      where: {
 | 
				
			||||||
 | 
					        uid: tokenUser.id,
 | 
				
			||||||
 | 
					        ...search,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      order: [['updatedAt', 'DESC']],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    ctx.body = {
 | 
				
			||||||
 | 
					      list: aiChatList,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .addTo(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app
 | 
				
			||||||
 | 
					  .route({
 | 
				
			||||||
 | 
					    path: 'ai',
 | 
				
			||||||
 | 
					    key: 'update-chat',
 | 
				
			||||||
 | 
					    description: '更新chat',
 | 
				
			||||||
 | 
					    middleware: ['auth'],
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .define(async (ctx) => {
 | 
				
			||||||
 | 
					    const tokenUser = ctx.state.tokenUser;
 | 
				
			||||||
 | 
					    const uid = tokenUser.id;
 | 
				
			||||||
 | 
					    const { id, data, prompt_tokens, total_tokens, completion_tokens, createdAt, updatedAt, ...rest } = ctx.query.data || {};
 | 
				
			||||||
 | 
					    let aiChat: AiChatHistoryModel | null = null;
 | 
				
			||||||
 | 
					    if (id) {
 | 
				
			||||||
 | 
					      aiChat = await AiChatHistoryModel.findByPk(id);
 | 
				
			||||||
 | 
					      if (!aiChat) {
 | 
				
			||||||
 | 
					        ctx.throw(404, 'chat not found');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (aiChat.uid !== uid) {
 | 
				
			||||||
 | 
					        ctx.throw(403, 'no permission');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      await aiChat.update({ data: { ...aiChat.data, ...data }, ...rest, version: aiChat.version + 1 });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      aiChat = await AiChatHistoryModel.create({
 | 
				
			||||||
 | 
					        ...rest,
 | 
				
			||||||
 | 
					        uid: uid,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ctx.body = aiChat;
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .addTo(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app
 | 
				
			||||||
 | 
					  .route({
 | 
				
			||||||
 | 
					    path: 'ai',
 | 
				
			||||||
 | 
					    key: 'get-chat',
 | 
				
			||||||
 | 
					    description: '获取chat',
 | 
				
			||||||
 | 
					    middleware: ['auth'],
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .define(async (ctx) => {
 | 
				
			||||||
 | 
					    const tokenUser = ctx.state.tokenUser;
 | 
				
			||||||
 | 
					    const { id } = ctx.query.data || {};
 | 
				
			||||||
 | 
					    if (!id) {
 | 
				
			||||||
 | 
					      ctx.throw(400, 'id is required');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const aiChat = await AiChatHistoryModel.findByPk(id);
 | 
				
			||||||
 | 
					    if (!aiChat) {
 | 
				
			||||||
 | 
					      ctx.throw(404, 'chat not found');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (aiChat.uid !== tokenUser.id) {
 | 
				
			||||||
 | 
					      ctx.throw(403, 'no permission');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ctx.body = aiChat;
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .addTo(app);
 | 
				
			||||||
 | 
					app
 | 
				
			||||||
 | 
					  .route({
 | 
				
			||||||
 | 
					    path: 'ai',
 | 
				
			||||||
 | 
					    key: 'get-chat-version',
 | 
				
			||||||
 | 
					    description: '获取chat版本',
 | 
				
			||||||
 | 
					    middleware: ['auth'],
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .define(async (ctx) => {
 | 
				
			||||||
 | 
					    const tokenUser = ctx.state.tokenUser;
 | 
				
			||||||
 | 
					    const { id } = ctx.query.data || {};
 | 
				
			||||||
 | 
					    if (!id) {
 | 
				
			||||||
 | 
					      ctx.throw(400, 'id is required');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const aiChat = await AiChatHistoryModel.findByPk(id);
 | 
				
			||||||
 | 
					    if (!aiChat) {
 | 
				
			||||||
 | 
					      ctx.throw(404, 'chat not found');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (aiChat.uid !== tokenUser.id) {
 | 
				
			||||||
 | 
					      ctx.throw(403, 'no permission');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ctx.body = {
 | 
				
			||||||
 | 
					      id: aiChat.id,
 | 
				
			||||||
 | 
					      version: aiChat.version,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .addTo(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app
 | 
				
			||||||
 | 
					  .route({
 | 
				
			||||||
 | 
					    path: 'ai',
 | 
				
			||||||
 | 
					    key: 'delete-chat',
 | 
				
			||||||
 | 
					    description: '删除chat',
 | 
				
			||||||
 | 
					    middleware: ['auth'],
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .define(async (ctx) => {
 | 
				
			||||||
 | 
					    const tokenUser = ctx.state.tokenUser;
 | 
				
			||||||
 | 
					    const { id } = ctx.query.data || {};
 | 
				
			||||||
 | 
					    if (!id) {
 | 
				
			||||||
 | 
					      ctx.throw(400, 'id is required');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const aiChat = await AiChatHistoryModel.findByPk(id);
 | 
				
			||||||
 | 
					    if (!aiChat) {
 | 
				
			||||||
 | 
					      ctx.throw(404, 'chat not found');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (aiChat.uid !== tokenUser.id) {
 | 
				
			||||||
 | 
					      ctx.throw(403, 'no permission');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await aiChat.destroy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ctx.body = {
 | 
				
			||||||
 | 
					      success: true,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  .addTo(app);
 | 
				
			||||||
							
								
								
									
										116
									
								
								src/routes/ai-chat/models/ai-chat-history.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/routes/ai-chat/models/ai-chat-history.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					import { sequelize } from '@/modules/sequelize.ts';
 | 
				
			||||||
 | 
					import { DataTypes, Model } from 'sequelize';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type AiChatHistory = Partial<InstanceType<typeof AiChatHistoryModel>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 const aiChatTypes = ['keep', 'temp', 'archive'] as const;
 | 
				
			||||||
 | 
					export type AIChatType = (typeof aiChatTypes)[number];
 | 
				
			||||||
 | 
					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 version: number;
 | 
				
			||||||
 | 
					  declare type: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  declare createdAt: Date;
 | 
				
			||||||
 | 
					  declare updatedAt: Date;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AiChatHistoryModel.init(
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    id: {
 | 
				
			||||||
 | 
					      type: DataTypes.UUID,
 | 
				
			||||||
 | 
					      primaryKey: true,
 | 
				
			||||||
 | 
					      defaultValue: DataTypes.UUIDV4,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    username: {
 | 
				
			||||||
 | 
					      type: DataTypes.STRING,
 | 
				
			||||||
 | 
					      allowNull: false,
 | 
				
			||||||
 | 
					      defaultValue: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    model: {
 | 
				
			||||||
 | 
					      type: DataTypes.STRING,
 | 
				
			||||||
 | 
					      allowNull: false,
 | 
				
			||||||
 | 
					      defaultValue: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    group: {
 | 
				
			||||||
 | 
					      type: DataTypes.STRING,
 | 
				
			||||||
 | 
					      allowNull: false,
 | 
				
			||||||
 | 
					      defaultValue: '',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    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: {},
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    type: {
 | 
				
			||||||
 | 
					      type: DataTypes.STRING,
 | 
				
			||||||
 | 
					      allowNull: false,
 | 
				
			||||||
 | 
					      defaultValue: 'keep', // keep 保留  temp 临时
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    version: {
 | 
				
			||||||
 | 
					      type: DataTypes.INTEGER,
 | 
				
			||||||
 | 
					      defaultValue: 0,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										152
									
								
								src/routes/ai-chat/services/chat-config-srevices.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								src/routes/ai-chat/services/chat-config-srevices.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,152 @@
 | 
				
			|||||||
 | 
					import { AIConfigParser, 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';
 | 
				
			||||||
 | 
					import { log } from '@/logger/index.ts';
 | 
				
			||||||
 | 
					export class ChatConfigServices {
 | 
				
			||||||
 | 
					  cachePrefix = 'ai:chat:config';
 | 
				
			||||||
 | 
					  // 使用谁的模型
 | 
				
			||||||
 | 
					  owner: string;
 | 
				
			||||||
 | 
					  // 使用者
 | 
				
			||||||
 | 
					  username: string;
 | 
				
			||||||
 | 
					  aiConfig?: AIConfig;
 | 
				
			||||||
 | 
					  isOwner: boolean;
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * username 是使用的模型的用户名,使用谁的模型
 | 
				
			||||||
 | 
					   * @param username
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  constructor(owner: string, username: string, token?: string) {
 | 
				
			||||||
 | 
					    this.owner = owner;
 | 
				
			||||||
 | 
					    this.username = username;
 | 
				
			||||||
 | 
					    this.isOwner = owner === username;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  getKey() {
 | 
				
			||||||
 | 
					    return `${this.cachePrefix}:${this.owner}`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 获取chat配置
 | 
				
			||||||
 | 
					   * @param keepSecret 是否需要清除secret 默认 不清除 为true
 | 
				
			||||||
 | 
					   * @returns
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  async getChatConfig(keepSecret = true, 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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.aiConfig = modelConfig;
 | 
				
			||||||
 | 
					    if (!keepSecret) {
 | 
				
			||||||
 | 
					      modelConfig = this.filterApiKey(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) {
 | 
				
			||||||
 | 
					    // 过滤掉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天
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  async clearChatLimit() {
 | 
				
			||||||
 | 
					    if (this.owner !== 'root') return;
 | 
				
			||||||
 | 
					    // const userCacheKey = `${this.cachePrefix}:root:chat-limit:${this.username}`;
 | 
				
			||||||
 | 
					    // await redis.del(userCacheKey);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,9 +1,16 @@
 | 
				
			|||||||
import { AIConfigParser, ProviderResult } from '@/provider/utils/parse-config.ts';
 | 
					import { AIConfig, AIConfigParser, ProviderResult } from '@/provider/utils/parse-config.ts';
 | 
				
			||||||
import { ProviderManager, ChatMessage, BaseChat } from '@/provider/index.ts';
 | 
					import { ProviderManager, ChatMessage, BaseChat, ChatMessageOptions } from '@/provider/index.ts';
 | 
				
			||||||
import { getChatConfig } from '@/modules/chat-config.ts';
 | 
					 | 
				
			||||||
import { redis } from '@/modules/db.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';
 | 
				
			||||||
 | 
					import { log } from '@/logger/index.ts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ChatServicesConfig = {
 | 
					export type ChatServicesConfig = {
 | 
				
			||||||
  username: string;
 | 
					  owner: string;
 | 
				
			||||||
  model: string;
 | 
					  model: string;
 | 
				
			||||||
  group: string;
 | 
					  group: string;
 | 
				
			||||||
  decryptKey?: string;
 | 
					  decryptKey?: string;
 | 
				
			||||||
@@ -13,7 +20,7 @@ export class ChatServices {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * 用户名
 | 
					   * 用户名
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  username: string;
 | 
					  owner: string;
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * 模型
 | 
					   * 模型
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
@@ -30,9 +37,10 @@ export class ChatServices {
 | 
				
			|||||||
   * 模型配置
 | 
					   * 模型配置
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  modelConfig?: ProviderResult;
 | 
					  modelConfig?: ProviderResult;
 | 
				
			||||||
 | 
					  aiConfig?: AIConfig;
 | 
				
			||||||
  chatProvider?: BaseChat;
 | 
					  chatProvider?: BaseChat;
 | 
				
			||||||
  constructor(opts: ChatServicesConfig) {
 | 
					  constructor(opts: ChatServicesConfig) {
 | 
				
			||||||
    this.username = opts.username;
 | 
					    this.owner = opts.owner;
 | 
				
			||||||
    this.model = opts.model;
 | 
					    this.model = opts.model;
 | 
				
			||||||
    this.group = opts.group;
 | 
					    this.group = opts.group;
 | 
				
			||||||
    this.decryptKey = opts.decryptKey;
 | 
					    this.decryptKey = opts.decryptKey;
 | 
				
			||||||
@@ -41,8 +49,8 @@ export class ChatServices {
 | 
				
			|||||||
   * 初始化
 | 
					   * 初始化
 | 
				
			||||||
   * @returns
 | 
					   * @returns
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  async init() {
 | 
					  async init(username: string) {
 | 
				
			||||||
    const config = await this.getConfig();
 | 
					    const config = await this.getConfig(username);
 | 
				
			||||||
    const aiConfigParser = new AIConfigParser(config);
 | 
					    const aiConfigParser = new AIConfigParser(config);
 | 
				
			||||||
    const model = this.model;
 | 
					    const model = this.model;
 | 
				
			||||||
    const group = this.group;
 | 
					    const group = this.group;
 | 
				
			||||||
@@ -52,40 +60,189 @@ export class ChatServices {
 | 
				
			|||||||
    const apiKey = await aiConfigParser.getSecretKey({
 | 
					    const apiKey = await aiConfigParser.getSecretKey({
 | 
				
			||||||
      getCache: async (key) => {
 | 
					      getCache: async (key) => {
 | 
				
			||||||
        const cache = await redis.get(that.wrapperKey(key));
 | 
					        const cache = await redis.get(that.wrapperKey(key));
 | 
				
			||||||
        return cache;
 | 
					        return cache ? JSON.parse(cache) : null;
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      setCache: async (key, value) => {
 | 
					      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 };
 | 
					    that.modelConfig = { ...providerResult, apiKey };
 | 
				
			||||||
 | 
					    that.aiConfig = config;
 | 
				
			||||||
    return that.modelConfig;
 | 
					    return that.modelConfig;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  async wrapperKey(key: string) {
 | 
					  /**
 | 
				
			||||||
    const username = this.username;
 | 
					   * 包装key , 默认了username
 | 
				
			||||||
    return `${this.cachePrefix}${username}:${key}`;
 | 
					   * @param key
 | 
				
			||||||
 | 
					   * @returns
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  wrapperKey(key: string) {
 | 
				
			||||||
 | 
					    const owner = this.owner;
 | 
				
			||||||
 | 
					    return `${this.cachePrefix}${owner}:${key}`;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  async getConfig() {
 | 
					  static chatLimitKey(owner: string, key = 'chat-limit') {
 | 
				
			||||||
    return getChatConfig();
 | 
					    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) {
 | 
				
			||||||
 | 
					    const services = new ChatConfigServices(this.owner, username);
 | 
				
			||||||
 | 
					    return services.getChatConfig();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async chat(messages: ChatMessage[]) {
 | 
					  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);
 | 
					      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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  static async createServices(opts: Partial<ChatServicesConfig>) {
 | 
					  async createTitle(messages: ChastHistoryMessage[]) {
 | 
				
			||||||
    const username = opts.username || 'root';
 | 
					    return nanoid();
 | 
				
			||||||
    const model = opts.model || 'deepseek-r1-250120';
 | 
					  }
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 过滤消息,只保留对话需要的内容,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<ChatServicesConfig> & { username: string }) {
 | 
				
			||||||
 | 
					    const owner = opts.owner || 'root';
 | 
				
			||||||
 | 
					    const model = opts.model || 'deepseek-chat';
 | 
				
			||||||
    const group = opts.group || 'deepseek';
 | 
					    const group = opts.group || 'deepseek';
 | 
				
			||||||
    const decryptKey = opts.decryptKey;
 | 
					    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`);
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const cache = await redis.get(key);
 | 
				
			||||||
 | 
					      if (cache) {
 | 
				
			||||||
 | 
					        const cacheData = JSON.parse(cache);
 | 
				
			||||||
 | 
					        const today = dayjs().format('YYYY-MM-DD');
 | 
				
			||||||
 | 
					        log.debug('checkCanChat', { cacheData });
 | 
				
			||||||
 | 
					        let current = cacheData.find((item) => item.group === group && item.model === model);
 | 
				
			||||||
 | 
					        if (current) {
 | 
				
			||||||
 | 
					          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;
 | 
				
			||||||
 | 
					    } 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');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * 获取模型的使用情况
 | 
				
			||||||
 | 
					   * @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');
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      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 {
 | 
				
			||||||
 | 
					        const cacheData = { group, model, token: token, [today]: 1 };
 | 
				
			||||||
 | 
					        await redis.set(key, JSON.stringify([cacheData]), 'EX', 60 * 60 * 24 * 30); // 30天
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      console.error('updateChatLimit error', error);
 | 
				
			||||||
 | 
					      // 如果更新失败,则设置一个空的缓存,2秒后删除
 | 
				
			||||||
 | 
					      await redis.set(key, '', 'EX', 2); // 2秒
 | 
				
			||||||
 | 
					      throw new CustomError(500, 'updateChatLimit error, please try again later');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								src/routes/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/routes/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					import './ai-chat/index.ts';
 | 
				
			||||||
 | 
					import './ai-chat/list.ts';
 | 
				
			||||||
 | 
					import './ai-chat/cache.ts';
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/test/encrypt/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/test/encrypt/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/test/model-scope/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/test/model-scope/index.ts
									
									
									
									
									
										Normal 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();
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/test/provider/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/test/provider/index.ts
									
									
									
									
									
										Normal 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);
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/utils/uuid.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/utils/uuid.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					import { customAlphabet } from 'nanoid';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
 | 
				
			||||||
 | 
					export const nanoid = customAlphabet(alphabet, 16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function uuid() {
 | 
				
			||||||
 | 
					  return nanoid();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								submodules/query-config
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								submodules/query-config
									
									
									
									
									
										Submodule
									
								
							 Submodule submodules/query-config added at 53cd97454d
									
								
							
		Reference in New Issue
	
	Block a user