feat: optimize ui
This commit is contained in:
@@ -42,7 +42,7 @@ The `config.json` file has several key sections:
|
||||
|
||||
- **`PROXY_URL`** (optional): You can set a proxy for API requests, for example: `"PROXY_URL": "http://127.0.0.1:7890"`.
|
||||
- **`LOG`** (optional): You can enable logging by setting it to `true`. When set to `false`, no log files will be created. Default is `true`.
|
||||
- **`LOG_LEVEL`** (optional): Set the logging level. Available options are: `"fatal"`, `"error"`, `"warn"`, `"info"`, `"debug"`, `"trace"`. Default is `"info"`.
|
||||
- **`LOG_LEVEL`** (optional): Set the logging level. Available options are: `"fatal"`, `"error"`, `"warn"`, `"info"`, `"debug"`, `"trace"`. Default is `"debug"`.
|
||||
- **Logging Systems**: The Claude Code Router uses two separate logging systems:
|
||||
- **Server-level logs**: HTTP requests, API calls, and server events are logged using pino in the `~/.claude-code-router/logs/` directory with filenames like `ccr-*.log`
|
||||
- **Application-level logs**: Routing decisions and business logic events are logged in `~/.claude-code-router/claude-code-router.log`
|
||||
|
||||
@@ -38,7 +38,7 @@ npm install -g @musistudio/claude-code-router
|
||||
`config.json` 文件有几个关键部分:
|
||||
- **`PROXY_URL`** (可选): 您可以为 API 请求设置代理,例如:`"PROXY_URL": "http://127.0.0.1:7890"`。
|
||||
- **`LOG`** (可选): 您可以通过将其设置为 `true` 来启用日志记录。当设置为 `false` 时,将不会创建日志文件。默认值为 `true`。
|
||||
- **`LOG_LEVEL`** (可选): 设置日志级别。可用选项包括:`"fatal"`、`"error"`、`"warn"`、`"info"`、`"debug"`、`"trace"`。默认值为 `"info"`。
|
||||
- **`LOG_LEVEL`** (可选): 设置日志级别。可用选项包括:`"fatal"`、`"error"`、`"warn"`、`"info"`、`"debug"`、`"trace"`。默认值为 `"debug"`。
|
||||
- **日志系统**: Claude Code Router 使用两个独立的日志系统:
|
||||
- **服务器级别日志**: HTTP 请求、API 调用和服务器事件使用 pino 记录在 `~/.claude-code-router/logs/` 目录中,文件名类似于 `ccr-*.log`
|
||||
- **应用程序级别日志**: 路由决策和业务逻辑事件记录在 `~/.claude-code-router/claude-code-router.log` 文件中
|
||||
|
||||
345
pnpm-lock.yaml
generated
345
pnpm-lock.yaml
generated
@@ -13,7 +13,7 @@ importers:
|
||||
version: 8.2.0
|
||||
'@musistudio/llms':
|
||||
specifier: ^1.0.25
|
||||
version: 1.0.25(ws@8.18.3)(zod@3.25.67)
|
||||
version: 1.0.25(ws@8.18.3)
|
||||
dotenv:
|
||||
specifier: ^16.4.7
|
||||
version: 16.6.1
|
||||
@@ -28,26 +28,26 @@ importers:
|
||||
version: 0.0.2
|
||||
tiktoken:
|
||||
specifier: ^1.0.21
|
||||
version: 1.0.21
|
||||
version: 1.0.22
|
||||
uuid:
|
||||
specifier: ^11.1.0
|
||||
version: 11.1.0
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^24.0.15
|
||||
version: 24.0.15
|
||||
version: 24.3.0
|
||||
esbuild:
|
||||
specifier: ^0.25.1
|
||||
version: 0.25.5
|
||||
version: 0.25.9
|
||||
fastify:
|
||||
specifier: ^5.4.0
|
||||
version: 5.4.0
|
||||
version: 5.5.0
|
||||
shx:
|
||||
specifier: ^0.4.0
|
||||
version: 0.4.0
|
||||
typescript:
|
||||
specifier: ^5.8.2
|
||||
version: 5.8.3
|
||||
version: 5.9.2
|
||||
|
||||
packages:
|
||||
|
||||
@@ -55,152 +55,158 @@ packages:
|
||||
resolution: {integrity: sha512-xyoCtHJnt/qg5GG6IgK+UJEndz8h8ljzt/caKXmq3LfBF81nC/BW6E4x2rOWCZcvsLyVW+e8U5mtIr6UCE/kJw==}
|
||||
hasBin: true
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.5':
|
||||
resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
|
||||
'@esbuild/aix-ppc64@0.25.9':
|
||||
resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==}
|
||||
'@esbuild/android-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.25.5':
|
||||
resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==}
|
||||
'@esbuild/android-arm@0.25.9':
|
||||
resolution: {integrity: sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.25.5':
|
||||
resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==}
|
||||
'@esbuild/android-x64@0.25.9':
|
||||
resolution: {integrity: sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==}
|
||||
'@esbuild/darwin-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.25.5':
|
||||
resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==}
|
||||
'@esbuild/darwin-x64@0.25.9':
|
||||
resolution: {integrity: sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==}
|
||||
'@esbuild/freebsd-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.25.5':
|
||||
resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==}
|
||||
'@esbuild/freebsd-x64@0.25.9':
|
||||
resolution: {integrity: sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==}
|
||||
'@esbuild/linux-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.25.5':
|
||||
resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==}
|
||||
'@esbuild/linux-arm@0.25.9':
|
||||
resolution: {integrity: sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.25.5':
|
||||
resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==}
|
||||
'@esbuild/linux-ia32@0.25.9':
|
||||
resolution: {integrity: sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.25.5':
|
||||
resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==}
|
||||
'@esbuild/linux-loong64@0.25.9':
|
||||
resolution: {integrity: sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.25.5':
|
||||
resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==}
|
||||
'@esbuild/linux-mips64el@0.25.9':
|
||||
resolution: {integrity: sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.25.5':
|
||||
resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==}
|
||||
'@esbuild/linux-ppc64@0.25.9':
|
||||
resolution: {integrity: sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.25.5':
|
||||
resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==}
|
||||
'@esbuild/linux-riscv64@0.25.9':
|
||||
resolution: {integrity: sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.25.5':
|
||||
resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==}
|
||||
'@esbuild/linux-s390x@0.25.9':
|
||||
resolution: {integrity: sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.25.5':
|
||||
resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==}
|
||||
'@esbuild/linux-x64@0.25.9':
|
||||
resolution: {integrity: sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==}
|
||||
'@esbuild/netbsd-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.25.5':
|
||||
resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==}
|
||||
'@esbuild/netbsd-x64@0.25.9':
|
||||
resolution: {integrity: sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==}
|
||||
'@esbuild/openbsd-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.25.5':
|
||||
resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==}
|
||||
'@esbuild/openbsd-x64@0.25.9':
|
||||
resolution: {integrity: sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/sunos-x64@0.25.5':
|
||||
resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==}
|
||||
'@esbuild/openharmony-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.25.9':
|
||||
resolution: {integrity: sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.25.5':
|
||||
resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==}
|
||||
'@esbuild/win32-arm64@0.25.9':
|
||||
resolution: {integrity: sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.25.5':
|
||||
resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==}
|
||||
'@esbuild/win32-ia32@0.25.9':
|
||||
resolution: {integrity: sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.25.5':
|
||||
resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==}
|
||||
'@esbuild/win32-x64@0.25.9':
|
||||
resolution: {integrity: sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -211,8 +217,8 @@ packages:
|
||||
'@fastify/ajv-compiler@4.0.2':
|
||||
resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==}
|
||||
|
||||
'@fastify/cors@11.0.1':
|
||||
resolution: {integrity: sha512-dmZaE7M1f4SM8ZZuk5RhSsDJ+ezTgI7v3HHRj8Ow9CneczsPLZV6+2j2uwdaSLn8zhTv6QV0F4ZRcqdalGx1pQ==}
|
||||
'@fastify/cors@11.1.0':
|
||||
resolution: {integrity: sha512-sUw8ed8wP2SouWZTIbA7V2OQtMNpLj2W6qJOYhNdcmINTu6gsxVYXjQiM9mdi8UUDlcoDDJ/W2syPo1WB2QjYA==}
|
||||
|
||||
'@fastify/error@4.2.0':
|
||||
resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==}
|
||||
@@ -235,8 +241,8 @@ packages:
|
||||
'@fastify/static@8.2.0':
|
||||
resolution: {integrity: sha512-PejC/DtT7p1yo3p+W7LiUtLMsV8fEvxAK15sozHy9t8kwo5r0uLYmhV/inURmGz1SkHZFz/8CNtHLPyhKcx4SQ==}
|
||||
|
||||
'@google/genai@1.8.0':
|
||||
resolution: {integrity: sha512-n3KiMFesQCy2R9iSdBIuJ0JWYQ1HZBJJkmt4PPZMGZKvlgHhBAGw1kUMyX+vsAIzprN3lK45DI755lm70wPOOg==}
|
||||
'@google/genai@1.14.0':
|
||||
resolution: {integrity: sha512-jirYprAAJU1svjwSDVCzyVq+FrJpJd5CSxR/g2Ga/gZ0ZYZpcWjMS75KJl9y71K1mDN+tcx6s21CzCbB2R840g==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
'@modelcontextprotocol/sdk': ^1.11.0
|
||||
@@ -275,14 +281,14 @@ packages:
|
||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
'@types/node@24.0.15':
|
||||
resolution: {integrity: sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==}
|
||||
'@types/node@24.3.0':
|
||||
resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==}
|
||||
|
||||
abstract-logging@2.0.1:
|
||||
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
|
||||
|
||||
agent-base@7.1.3:
|
||||
resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
|
||||
agent-base@7.1.4:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
ajv-formats@3.0.1:
|
||||
@@ -322,8 +328,8 @@ packages:
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
bignumber.js@9.3.0:
|
||||
resolution: {integrity: sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==}
|
||||
bignumber.js@9.3.1:
|
||||
resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
|
||||
|
||||
braces@3.0.3:
|
||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
||||
@@ -395,8 +401,8 @@ packages:
|
||||
end-of-stream@1.4.5:
|
||||
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||
|
||||
esbuild@0.25.5:
|
||||
resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==}
|
||||
esbuild@0.25.9:
|
||||
resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
@@ -436,8 +442,8 @@ packages:
|
||||
fastify-plugin@5.0.1:
|
||||
resolution: {integrity: sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==}
|
||||
|
||||
fastify@5.4.0:
|
||||
resolution: {integrity: sha512-I4dVlUe+WNQAhKSyv15w+dwUh2EPiEl4X2lGYMmNSgF83WzTMAPKGdWEv5tPsCQOb+SOZwz8Vlta2vF+OeDgRw==}
|
||||
fastify@5.5.0:
|
||||
resolution: {integrity: sha512-ZWSWlzj3K/DcULCnCjEiC2zn2FBPdlZsSA/pnPa/dbUfLvxkD/Nqmb0XXMXLrWkeM4uQPUvjdJpwtXmTfriXqw==}
|
||||
|
||||
fastq@1.19.1:
|
||||
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
|
||||
@@ -494,8 +500,8 @@ packages:
|
||||
engines: {node: 20 || >=22}
|
||||
hasBin: true
|
||||
|
||||
google-auth-library@10.2.0:
|
||||
resolution: {integrity: sha512-gy/0hRx8+Ye0HlUm3GrfpR4lbmJQ6bJ7F44DmN7GtMxxzWSojLzx0Bhv/hj7Wlj7a2On0FcT8jrz8Y1c1nxCyg==}
|
||||
google-auth-library@10.2.1:
|
||||
resolution: {integrity: sha512-HMxFl2NfeHYnaL1HoRIN1XgorKS+6CDaM+z9LSSN+i/nKDDL4KFFEWogMXu7jV4HZQy2MsxpY+wA5XIf3w410A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
google-auth-library@9.15.1:
|
||||
@@ -666,8 +672,8 @@ packages:
|
||||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
openai@5.8.2:
|
||||
resolution: {integrity: sha512-8C+nzoHYgyYOXhHGN6r0fcb4SznuEn1R7YZMvlqDbnCuE0FM2mm3T1HiYW6WIcMS/F1Of2up/cSPjLPaWt0X9Q==}
|
||||
openai@5.12.2:
|
||||
resolution: {integrity: sha512-xqzHHQch5Tws5PcKR2xsZGX9xtch+JQFz5zb14dGqlshmmDAFBFEWmeIpf7wVqWV+w7Emj7jRgkNJakyKE0tYQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
ws: ^8.18.0
|
||||
@@ -716,8 +722,8 @@ packages:
|
||||
pino-std-serializers@7.0.0:
|
||||
resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==}
|
||||
|
||||
pino@9.7.0:
|
||||
resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==}
|
||||
pino@9.9.0:
|
||||
resolution: {integrity: sha512-zxsRIQG9HzG+jEljmvmZupOMDUQ0Jpj0yAgE28jQvvrdYTlEaiGwelJpdndMl/MBuRr70heIj83QyqJUWaU8mQ==}
|
||||
hasBin: true
|
||||
|
||||
process-warning@4.0.1:
|
||||
@@ -869,8 +875,8 @@ packages:
|
||||
thread-stream@3.1.0:
|
||||
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
|
||||
|
||||
tiktoken@1.0.21:
|
||||
resolution: {integrity: sha512-/kqtlepLMptX0OgbYD9aMYbM7EFrMZCL7EoHM8Psmg2FuhXoo/bH64KqOiZGGwa6oS9TPdSEDKBnV2LuB8+5vQ==}
|
||||
tiktoken@1.0.22:
|
||||
resolution: {integrity: sha512-PKvy1rVF1RibfF3JlXBSP0Jrcw2uq3yXdgcEXtKTYn3QJ/cBRBHDnrJ5jHky+MENZ6DIPwNUGWpkVx+7joCpNA==}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
@@ -887,16 +893,16 @@ packages:
|
||||
tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
|
||||
typescript@5.8.3:
|
||||
resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==}
|
||||
typescript@5.9.2:
|
||||
resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.8.0:
|
||||
resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==}
|
||||
undici-types@7.10.0:
|
||||
resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==}
|
||||
|
||||
undici@7.11.0:
|
||||
resolution: {integrity: sha512-heTSIac3iLhsmZhUCjyS3JQEkZELateufzZuBaVM5RHXdSBMb1LPMQf5x+FH7qjsZYDP0ttAc3nnVpUB+wYbOg==}
|
||||
undici@7.13.0:
|
||||
resolution: {integrity: sha512-l+zSMssRqrzDcb3fjMkjjLGmuiiK2pMIcV++mJaAc9vhjSGpvM7h43QgP+OAMb1GImHmbPyG2tBXeuyG5iY4gA==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
uuid@11.1.0:
|
||||
@@ -949,91 +955,86 @@ packages:
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
zod-to-json-schema@3.24.6:
|
||||
resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==}
|
||||
peerDependencies:
|
||||
zod: ^3.24.1
|
||||
|
||||
zod@3.25.67:
|
||||
resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@anthropic-ai/sdk@0.54.0': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.5':
|
||||
'@esbuild/aix-ppc64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.25.5':
|
||||
'@esbuild/android-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.25.5':
|
||||
'@esbuild/android-arm@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.25.5':
|
||||
'@esbuild/android-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.25.5':
|
||||
'@esbuild/darwin-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.25.5':
|
||||
'@esbuild/darwin-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.25.5':
|
||||
'@esbuild/freebsd-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.25.5':
|
||||
'@esbuild/freebsd-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.25.5':
|
||||
'@esbuild/linux-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.25.5':
|
||||
'@esbuild/linux-arm@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.25.5':
|
||||
'@esbuild/linux-ia32@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.25.5':
|
||||
'@esbuild/linux-loong64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.25.5':
|
||||
'@esbuild/linux-mips64el@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.25.5':
|
||||
'@esbuild/linux-ppc64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.25.5':
|
||||
'@esbuild/linux-riscv64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.25.5':
|
||||
'@esbuild/linux-s390x@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.25.5':
|
||||
'@esbuild/linux-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.25.5':
|
||||
'@esbuild/netbsd-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.25.5':
|
||||
'@esbuild/netbsd-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.25.5':
|
||||
'@esbuild/openbsd-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.25.5':
|
||||
'@esbuild/openbsd-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.25.5':
|
||||
'@esbuild/openharmony-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.25.5':
|
||||
'@esbuild/sunos-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.25.5':
|
||||
'@esbuild/win32-arm64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.25.5':
|
||||
'@esbuild/win32-ia32@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.25.9':
|
||||
optional: true
|
||||
|
||||
'@fastify/accept-negotiator@2.0.1': {}
|
||||
@@ -1044,7 +1045,7 @@ snapshots:
|
||||
ajv-formats: 3.0.1(ajv@8.17.1)
|
||||
fast-uri: 3.0.6
|
||||
|
||||
'@fastify/cors@11.0.1':
|
||||
'@fastify/cors@11.1.0':
|
||||
dependencies:
|
||||
fastify-plugin: 5.0.1
|
||||
toad-cache: 3.7.0
|
||||
@@ -1083,12 +1084,10 @@ snapshots:
|
||||
fastq: 1.19.1
|
||||
glob: 11.0.3
|
||||
|
||||
'@google/genai@1.8.0':
|
||||
'@google/genai@1.14.0':
|
||||
dependencies:
|
||||
google-auth-library: 9.15.1
|
||||
ws: 8.18.3
|
||||
zod: 3.25.67
|
||||
zod-to-json-schema: 3.24.6(zod@3.25.67)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- encoding
|
||||
@@ -1112,18 +1111,18 @@ snapshots:
|
||||
|
||||
'@lukeed/ms@2.0.2': {}
|
||||
|
||||
'@musistudio/llms@1.0.25(ws@8.18.3)(zod@3.25.67)':
|
||||
'@musistudio/llms@1.0.25(ws@8.18.3)':
|
||||
dependencies:
|
||||
'@anthropic-ai/sdk': 0.54.0
|
||||
'@fastify/cors': 11.0.1
|
||||
'@google/genai': 1.8.0
|
||||
'@fastify/cors': 11.1.0
|
||||
'@google/genai': 1.14.0
|
||||
dotenv: 16.6.1
|
||||
fastify: 5.4.0
|
||||
google-auth-library: 10.2.0
|
||||
fastify: 5.5.0
|
||||
google-auth-library: 10.2.1
|
||||
json5: 2.2.3
|
||||
jsonrepair: 3.13.0
|
||||
openai: 5.8.2(ws@8.18.3)(zod@3.25.67)
|
||||
undici: 7.11.0
|
||||
openai: 5.12.2(ws@8.18.3)
|
||||
undici: 7.13.0
|
||||
uuid: 11.1.0
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
@@ -1146,13 +1145,13 @@ snapshots:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.19.1
|
||||
|
||||
'@types/node@24.0.15':
|
||||
'@types/node@24.3.0':
|
||||
dependencies:
|
||||
undici-types: 7.8.0
|
||||
undici-types: 7.10.0
|
||||
|
||||
abstract-logging@2.0.1: {}
|
||||
|
||||
agent-base@7.1.3: {}
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
ajv-formats@3.0.1(ajv@8.17.1):
|
||||
optionalDependencies:
|
||||
@@ -1184,7 +1183,7 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
bignumber.js@9.3.0: {}
|
||||
bignumber.js@9.3.1: {}
|
||||
|
||||
braces@3.0.3:
|
||||
dependencies:
|
||||
@@ -1244,33 +1243,34 @@ snapshots:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
|
||||
esbuild@0.25.5:
|
||||
esbuild@0.25.9:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.25.5
|
||||
'@esbuild/android-arm': 0.25.5
|
||||
'@esbuild/android-arm64': 0.25.5
|
||||
'@esbuild/android-x64': 0.25.5
|
||||
'@esbuild/darwin-arm64': 0.25.5
|
||||
'@esbuild/darwin-x64': 0.25.5
|
||||
'@esbuild/freebsd-arm64': 0.25.5
|
||||
'@esbuild/freebsd-x64': 0.25.5
|
||||
'@esbuild/linux-arm': 0.25.5
|
||||
'@esbuild/linux-arm64': 0.25.5
|
||||
'@esbuild/linux-ia32': 0.25.5
|
||||
'@esbuild/linux-loong64': 0.25.5
|
||||
'@esbuild/linux-mips64el': 0.25.5
|
||||
'@esbuild/linux-ppc64': 0.25.5
|
||||
'@esbuild/linux-riscv64': 0.25.5
|
||||
'@esbuild/linux-s390x': 0.25.5
|
||||
'@esbuild/linux-x64': 0.25.5
|
||||
'@esbuild/netbsd-arm64': 0.25.5
|
||||
'@esbuild/netbsd-x64': 0.25.5
|
||||
'@esbuild/openbsd-arm64': 0.25.5
|
||||
'@esbuild/openbsd-x64': 0.25.5
|
||||
'@esbuild/sunos-x64': 0.25.5
|
||||
'@esbuild/win32-arm64': 0.25.5
|
||||
'@esbuild/win32-ia32': 0.25.5
|
||||
'@esbuild/win32-x64': 0.25.5
|
||||
'@esbuild/aix-ppc64': 0.25.9
|
||||
'@esbuild/android-arm': 0.25.9
|
||||
'@esbuild/android-arm64': 0.25.9
|
||||
'@esbuild/android-x64': 0.25.9
|
||||
'@esbuild/darwin-arm64': 0.25.9
|
||||
'@esbuild/darwin-x64': 0.25.9
|
||||
'@esbuild/freebsd-arm64': 0.25.9
|
||||
'@esbuild/freebsd-x64': 0.25.9
|
||||
'@esbuild/linux-arm': 0.25.9
|
||||
'@esbuild/linux-arm64': 0.25.9
|
||||
'@esbuild/linux-ia32': 0.25.9
|
||||
'@esbuild/linux-loong64': 0.25.9
|
||||
'@esbuild/linux-mips64el': 0.25.9
|
||||
'@esbuild/linux-ppc64': 0.25.9
|
||||
'@esbuild/linux-riscv64': 0.25.9
|
||||
'@esbuild/linux-s390x': 0.25.9
|
||||
'@esbuild/linux-x64': 0.25.9
|
||||
'@esbuild/netbsd-arm64': 0.25.9
|
||||
'@esbuild/netbsd-x64': 0.25.9
|
||||
'@esbuild/openbsd-arm64': 0.25.9
|
||||
'@esbuild/openbsd-x64': 0.25.9
|
||||
'@esbuild/openharmony-arm64': 0.25.9
|
||||
'@esbuild/sunos-x64': 0.25.9
|
||||
'@esbuild/win32-arm64': 0.25.9
|
||||
'@esbuild/win32-ia32': 0.25.9
|
||||
'@esbuild/win32-x64': 0.25.9
|
||||
|
||||
escape-html@1.0.3: {}
|
||||
|
||||
@@ -1317,7 +1317,7 @@ snapshots:
|
||||
|
||||
fastify-plugin@5.0.1: {}
|
||||
|
||||
fastify@5.4.0:
|
||||
fastify@5.5.0:
|
||||
dependencies:
|
||||
'@fastify/ajv-compiler': 4.0.2
|
||||
'@fastify/error': 4.2.0
|
||||
@@ -1328,7 +1328,7 @@ snapshots:
|
||||
fast-json-stringify: 6.0.1
|
||||
find-my-way: 9.3.0
|
||||
light-my-request: 6.6.0
|
||||
pino: 9.7.0
|
||||
pino: 9.9.0
|
||||
process-warning: 5.0.0
|
||||
rfdc: 1.4.1
|
||||
secure-json-parse: 4.0.0
|
||||
@@ -1418,7 +1418,7 @@ snapshots:
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 2.0.0
|
||||
|
||||
google-auth-library@10.2.0:
|
||||
google-auth-library@10.2.1:
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
ecdsa-sig-formatter: 1.0.11
|
||||
@@ -1475,7 +1475,7 @@ snapshots:
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
dependencies:
|
||||
agent-base: 7.1.3
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -1512,7 +1512,7 @@ snapshots:
|
||||
|
||||
json-bigint@1.0.0:
|
||||
dependencies:
|
||||
bignumber.js: 9.3.0
|
||||
bignumber.js: 9.3.1
|
||||
|
||||
json-schema-ref-resolver@2.0.1:
|
||||
dependencies:
|
||||
@@ -1586,10 +1586,9 @@ snapshots:
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
openai@5.8.2(ws@8.18.3)(zod@3.25.67):
|
||||
openai@5.12.2(ws@8.18.3):
|
||||
optionalDependencies:
|
||||
ws: 8.18.3
|
||||
zod: 3.25.67
|
||||
|
||||
openurl@1.1.1: {}
|
||||
|
||||
@@ -1620,7 +1619,7 @@ snapshots:
|
||||
|
||||
pino-std-serializers@7.0.0: {}
|
||||
|
||||
pino@9.7.0:
|
||||
pino@9.9.0:
|
||||
dependencies:
|
||||
atomic-sleep: 1.0.0
|
||||
fast-redact: 3.5.0
|
||||
@@ -1755,7 +1754,7 @@ snapshots:
|
||||
dependencies:
|
||||
real-require: 0.2.0
|
||||
|
||||
tiktoken@1.0.21: {}
|
||||
tiktoken@1.0.22: {}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
dependencies:
|
||||
@@ -1767,11 +1766,11 @@ snapshots:
|
||||
|
||||
tr46@0.0.3: {}
|
||||
|
||||
typescript@5.8.3: {}
|
||||
typescript@5.9.2: {}
|
||||
|
||||
undici-types@7.8.0: {}
|
||||
undici-types@7.10.0: {}
|
||||
|
||||
undici@7.11.0: {}
|
||||
undici@7.13.0: {}
|
||||
|
||||
uuid@11.1.0: {}
|
||||
|
||||
@@ -1809,9 +1808,3 @@ snapshots:
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
ws@8.18.3: {}
|
||||
|
||||
zod-to-json-schema@3.24.6(zod@3.25.67):
|
||||
dependencies:
|
||||
zod: 3.25.67
|
||||
|
||||
zod@3.25.67: {}
|
||||
|
||||
35
src/index.ts
35
src/index.ts
@@ -15,6 +15,7 @@ import { CONFIG_FILE } from "./constants";
|
||||
import createWriteStream from "pino-rotating-file-stream";
|
||||
import { HOME_DIR } from "./constants";
|
||||
import { configureLogging } from "./utils/log";
|
||||
import { sessionUsageCache } from "./utils/cache";
|
||||
|
||||
async function initializeClaudeConfig() {
|
||||
const homeDir = homedir();
|
||||
@@ -88,7 +89,9 @@ async function run(options: RunOptions = {}) {
|
||||
: port;
|
||||
|
||||
// Configure logger based on config settings
|
||||
const loggerConfig = config.LOG !== false ? {
|
||||
const loggerConfig =
|
||||
config.LOG !== false
|
||||
? {
|
||||
level: config.LOG_LEVEL || "debug",
|
||||
stream: createWriteStream({
|
||||
path: HOME_DIR,
|
||||
@@ -96,7 +99,8 @@ async function run(options: RunOptions = {}) {
|
||||
maxFiles: 3,
|
||||
interval: "1d",
|
||||
}),
|
||||
} : false;
|
||||
}
|
||||
: false;
|
||||
|
||||
const server = createServer({
|
||||
jsonPath: CONFIG_FILE,
|
||||
@@ -129,6 +133,33 @@ async function run(options: RunOptions = {}) {
|
||||
router(req, reply, config);
|
||||
}
|
||||
});
|
||||
server.addHook("onSend", async (req, reply, payload) => {
|
||||
if (req.sessionId && req.url.startsWith("/v1/messages")) {
|
||||
if (payload instanceof ReadableStream) {
|
||||
const [originalStream, clonedStream] = payload.tee();
|
||||
const reader1 = clonedStream.getReader();
|
||||
while (true) {
|
||||
const { done, value } = await reader1.read();
|
||||
if (done) break;
|
||||
// Process the value if needed
|
||||
const dataStr = new TextDecoder().decode(value);
|
||||
if (!dataStr.startsWith("event: message_delta")) {
|
||||
continue;
|
||||
}
|
||||
const str = dataStr.slice(27);
|
||||
try {
|
||||
const message = JSON.parse(str);
|
||||
sessionUsageCache.put(req.sessionId, message.usage);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return originalStream;
|
||||
} else {
|
||||
sessionUsageCache.put(req.sessionId, payload.usage);
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
server.start();
|
||||
}
|
||||
|
||||
|
||||
47
src/utils/cache.ts
Normal file
47
src/utils/cache.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
// LRU cache for session usage
|
||||
|
||||
export interface Usage {
|
||||
input_tokens: number;
|
||||
output_tokens: number;
|
||||
}
|
||||
|
||||
class LRUCache<K, V> {
|
||||
private capacity: number;
|
||||
private cache: Map<K, V>;
|
||||
|
||||
constructor(capacity: number) {
|
||||
this.capacity = capacity;
|
||||
this.cache = new Map<K, V>();
|
||||
}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
if (!this.cache.has(key)) {
|
||||
return undefined;
|
||||
}
|
||||
const value = this.cache.get(key) as V;
|
||||
// Move to end to mark as recently used
|
||||
this.cache.delete(key);
|
||||
this.cache.set(key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
put(key: K, value: V): void {
|
||||
if (this.cache.has(key)) {
|
||||
// If key exists, delete it to update its position
|
||||
this.cache.delete(key);
|
||||
} else if (this.cache.size >= this.capacity) {
|
||||
// If cache is full, delete the least recently used item
|
||||
const leastRecentlyUsedKey = this.cache.keys().next().value;
|
||||
if (leastRecentlyUsedKey !== undefined) {
|
||||
this.cache.delete(leastRecentlyUsedKey);
|
||||
}
|
||||
}
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
|
||||
values(): V[] {
|
||||
return Array.from(this.cache.values());
|
||||
}
|
||||
}
|
||||
|
||||
export const sessionUsageCache = new LRUCache<string, Usage>(100);
|
||||
@@ -11,12 +11,14 @@ export async function executeCodeCommand(args: string[] = []) {
|
||||
const config = await readConfigFile();
|
||||
const env: Record<string, string> = {
|
||||
...process.env,
|
||||
ANTHROPIC_AUTH_TOKEN: "test",
|
||||
ANTHROPIC_AUTH_TOKEN: config?.APIKEY || "test",
|
||||
ANTHROPIC_BASE_URL: `http://127.0.0.1:${config.PORT || 3456}`,
|
||||
API_TIMEOUT_MS: String(config.API_TIMEOUT_MS ?? 600000), // Default to 10 minutes if not set
|
||||
};
|
||||
|
||||
const settingsFlag: Record<string, any> = {};
|
||||
const settingsFlag: Record<string, any> = {
|
||||
env,
|
||||
};
|
||||
if (config?.StatusLine?.enabled) {
|
||||
settingsFlag.statusLine = {
|
||||
type: "command",
|
||||
@@ -41,10 +43,10 @@ export async function executeCodeCommand(args: string[] = []) {
|
||||
env.ANTHROPIC_SMALL_FAST_MODEL = config.ANTHROPIC_SMALL_FAST_MODEL;
|
||||
}
|
||||
|
||||
if (config?.APIKEY) {
|
||||
env.ANTHROPIC_API_KEY = config.APIKEY;
|
||||
delete env.ANTHROPIC_AUTH_TOKEN;
|
||||
}
|
||||
// if (config?.APIKEY) {
|
||||
// env.ANTHROPIC_API_KEY = config.APIKEY;
|
||||
// delete env.ANTHROPIC_AUTH_TOKEN;
|
||||
// }
|
||||
|
||||
// Increment reference count when command starts
|
||||
incrementReferenceCount();
|
||||
|
||||
@@ -16,7 +16,7 @@ let logLevel: string = "info";
|
||||
// Function to configure logging
|
||||
export function configureLogging(config: { LOG?: boolean; LOG_LEVEL?: string }) {
|
||||
isLogEnabled = config.LOG !== false; // Default to true if not explicitly set to false
|
||||
logLevel = config.LOG_LEVEL || "info";
|
||||
logLevel = config.LOG_LEVEL || "debug";
|
||||
}
|
||||
|
||||
export function log(...args: any[]) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from "@anthropic-ai/sdk/resources/messages";
|
||||
import { get_encoding } from "tiktoken";
|
||||
import { log } from "./log";
|
||||
import { sessionUsageCache, Usage } from "./cache";
|
||||
|
||||
const enc = get_encoding("cl100k_base");
|
||||
|
||||
@@ -62,7 +63,12 @@ const calculateTokenCount = (
|
||||
return tokenCount;
|
||||
};
|
||||
|
||||
const getUseModel = async (req: any, tokenCount: number, config: any) => {
|
||||
const getUseModel = async (
|
||||
req: any,
|
||||
tokenCount: number,
|
||||
config: any,
|
||||
lastUsage?: Usage | undefined
|
||||
) => {
|
||||
if (req.body.model.includes(",")) {
|
||||
const [provider, model] = req.body.model.split(",");
|
||||
const finalProvider = config.Providers.find(
|
||||
@@ -78,7 +84,15 @@ const getUseModel = async (req: any, tokenCount: number, config: any) => {
|
||||
}
|
||||
// if tokenCount is greater than the configured threshold, use the long context model
|
||||
const longContextThreshold = config.Router.longContextThreshold || 60000;
|
||||
if (tokenCount > longContextThreshold && config.Router.longContext) {
|
||||
const lastUsageThreshold =
|
||||
lastUsage &&
|
||||
lastUsage.input_tokens > longContextThreshold &&
|
||||
tokenCount > 20000;
|
||||
const tokenCountThreshold = tokenCount > longContextThreshold;
|
||||
if (
|
||||
(lastUsageThreshold || tokenCountThreshold) &&
|
||||
config.Router.longContext
|
||||
) {
|
||||
log(
|
||||
"Using long context model due to token count:",
|
||||
tokenCount,
|
||||
@@ -128,11 +142,12 @@ const getUseModel = async (req: any, tokenCount: number, config: any) => {
|
||||
export const router = async (req: any, _res: any, config: any) => {
|
||||
// Parse sessionId from metadata.user_id
|
||||
if (req.body.metadata?.user_id) {
|
||||
const parts = req.body.metadata.user_id.split('_session_');
|
||||
const parts = req.body.metadata.user_id.split("_session_");
|
||||
if (parts.length > 1) {
|
||||
req.sessionId = parts[1];
|
||||
}
|
||||
}
|
||||
const lastMessageUsage = sessionUsageCache.get(req.sessionId);
|
||||
const { messages, system = [], tools }: MessageCreateParamsBase = req.body;
|
||||
try {
|
||||
const tokenCount = calculateTokenCount(
|
||||
@@ -152,7 +167,7 @@ export const router = async (req: any, _res: any, config: any) => {
|
||||
}
|
||||
}
|
||||
if (!model) {
|
||||
model = await getUseModel(req, tokenCount, config);
|
||||
model = await getUseModel(req, tokenCount, config, lastMessageUsage);
|
||||
}
|
||||
req.body.model = model;
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -331,7 +331,7 @@ function App() {
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex h-[calc(100vh-4rem)] gap-4 p-4">
|
||||
<main className="flex h-[calc(100vh-4rem)] gap-4 p-4 overflow-hidden">
|
||||
<div className="w-3/5">
|
||||
<Providers />
|
||||
</div>
|
||||
@@ -339,7 +339,7 @@ function App() {
|
||||
<div className="h-3/5">
|
||||
<Router />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Transformers />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -69,7 +69,7 @@ export function ConfigProvider({ children }: ConfigProviderProps) {
|
||||
// Validate the received data to ensure it has the expected structure
|
||||
const validConfig = {
|
||||
LOG: typeof data.LOG === 'boolean' ? data.LOG : false,
|
||||
LOG_LEVEL: typeof data.LOG_LEVEL === 'string' ? data.LOG_LEVEL : 'info',
|
||||
LOG_LEVEL: typeof data.LOG_LEVEL === 'string' ? data.LOG_LEVEL : 'debug',
|
||||
CLAUDE_PATH: typeof data.CLAUDE_PATH === 'string' ? data.CLAUDE_PATH : '',
|
||||
HOST: typeof data.HOST === 'string' ? data.HOST : '127.0.0.1',
|
||||
PORT: typeof data.PORT === 'number' ? data.PORT : 3456,
|
||||
@@ -115,7 +115,7 @@ export function ConfigProvider({ children }: ConfigProviderProps) {
|
||||
// Set default empty config when fetch fails
|
||||
setConfig({
|
||||
LOG: false,
|
||||
LOG_LEVEL: 'info',
|
||||
LOG_LEVEL: 'debug',
|
||||
CLAUDE_PATH: '',
|
||||
HOST: '127.0.0.1',
|
||||
PORT: 3456,
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { X, Trash2, Plus, Eye, EyeOff } from "lucide-react";
|
||||
import { X, Trash2, Plus, Eye, EyeOff, Search, XCircle } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { ComboInput } from "@/components/ui/combo-input";
|
||||
@@ -38,6 +38,7 @@ export function Providers() {
|
||||
const [showApiKey, setShowApiKey] = useState<Record<number, boolean>>({});
|
||||
const [apiKeyError, setApiKeyError] = useState<string | null>(null);
|
||||
const [nameError, setNameError] = useState<string | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||
const comboInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -487,15 +488,57 @@ export function Providers() {
|
||||
|
||||
const editingProvider = editingProviderData || (editingProviderIndex !== null ? validProviders[editingProviderIndex] : null);
|
||||
|
||||
// Filter providers based on search term
|
||||
const filteredProviders = validProviders.filter(provider => {
|
||||
if (!searchTerm) return true;
|
||||
const term = searchTerm.toLowerCase();
|
||||
// Check provider name and URL
|
||||
if (
|
||||
(provider.name && provider.name.toLowerCase().includes(term)) ||
|
||||
(provider.api_base_url && provider.api_base_url.toLowerCase().includes(term))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
// Check models
|
||||
if (provider.models && Array.isArray(provider.models)) {
|
||||
return provider.models.some(model =>
|
||||
model && model.toLowerCase().includes(term)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className="flex h-full flex-col rounded-lg border shadow-sm">
|
||||
<CardHeader className="flex flex-row items-center justify-between border-b p-4">
|
||||
<CardTitle className="text-lg">{t("providers.title")} <span className="text-sm font-normal text-gray-500">({validProviders.length})</span></CardTitle>
|
||||
<CardHeader className="flex flex-col border-b p-4 gap-3">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<CardTitle className="text-lg">{t("providers.title")} <span className="text-sm font-normal text-gray-500">({filteredProviders.length}/{validProviders.length})</span></CardTitle>
|
||||
<Button onClick={handleAddProvider}>{t("providers.add")}</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-500" />
|
||||
<Input
|
||||
placeholder={t("providers.search")}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-8"
|
||||
/>
|
||||
</div>
|
||||
{searchTerm && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setSearchTerm("")}
|
||||
>
|
||||
<XCircle className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-grow overflow-y-auto p-4">
|
||||
<ProviderList
|
||||
providers={validProviders}
|
||||
providers={filteredProviders}
|
||||
onEdit={handleEditProvider}
|
||||
onRemove={setDeletingProviderIndex}
|
||||
/>
|
||||
|
||||
@@ -59,11 +59,11 @@ export function SettingsDialog({ isOpen, onOpenChange }: SettingsDialogProps) {
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onOpenChange} >
|
||||
<DialogContent data-testid="settings-dialog">
|
||||
<DialogHeader>
|
||||
<DialogContent data-testid="settings-dialog" className="max-h-[80vh] flex flex-col p-0">
|
||||
<DialogHeader className="p-4 pb-0">
|
||||
<DialogTitle>{t("toplevel.title")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="space-y-4 p-4 px-8 overflow-y-auto flex-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id="log"
|
||||
@@ -213,7 +213,7 @@ export function SettingsDialog({ isOpen, onOpenChange }: SettingsDialogProps) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogFooter className="p-4 pt-0">
|
||||
<Button
|
||||
onClick={() => onOpenChange(false)}
|
||||
className="transition-all-ease hover:scale-[1.02] active:scale-[0.98]"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect, useMemo, useCallback, useRef } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -96,6 +97,207 @@ const ANSI_COLORS: Record<string, string> = {
|
||||
bg_bright_purple: "bg-purple-400",
|
||||
};
|
||||
|
||||
|
||||
// 图标搜索输入组件
|
||||
interface IconData {
|
||||
className: string;
|
||||
unicode: string;
|
||||
char: string;
|
||||
}
|
||||
|
||||
interface IconSearchInputProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
fontFamily: string;
|
||||
t: (key: string) => string;
|
||||
}
|
||||
|
||||
const IconSearchInput = React.memo(({ value, onChange, fontFamily, t }: IconSearchInputProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState(value);
|
||||
const [icons, setIcons] = useState<IconData[]>([]);
|
||||
const [filteredIcons, setFilteredIcons] = useState<IconData[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
// 加载Nerdfonts图标数据
|
||||
const loadIcons = useCallback(async () => {
|
||||
if (icons.length > 0) return; // 已经加载过了
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetch('https://www.nerdfonts.com/assets/css/combo.css');
|
||||
const cssText = await response.text();
|
||||
|
||||
// 解析CSS中的图标类名和Unicode
|
||||
const iconRegex = /\.nf-([a-zA-Z0-9_-]+):before\s*\{\s*content:\s*"\\([0-9a-fA-F]+)";?\s*\}/g;
|
||||
const iconData: IconData[] = [];
|
||||
let match;
|
||||
|
||||
while ((match = iconRegex.exec(cssText)) !== null) {
|
||||
const className = `nf-${match[1]}`;
|
||||
const unicode = match[2];
|
||||
const char = String.fromCharCode(parseInt(unicode, 16));
|
||||
iconData.push({ className, unicode, char });
|
||||
}
|
||||
|
||||
setIcons(iconData);
|
||||
setFilteredIcons(iconData.slice(0, 200));
|
||||
} catch (error) {
|
||||
console.error('Failed to load icons:', error);
|
||||
setIcons([]);
|
||||
setFilteredIcons([]);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [icons.length]);
|
||||
|
||||
// 模糊搜索图标
|
||||
useEffect(() => {
|
||||
if (searchTerm.trim() === '') {
|
||||
setFilteredIcons(icons.slice(0, 100)); // 显示前100个图标
|
||||
return;
|
||||
}
|
||||
|
||||
const term = searchTerm.toLowerCase();
|
||||
let filtered = icons;
|
||||
|
||||
// 如果输入的是特殊字符(可能是粘贴的图标),则搜索对应图标
|
||||
if (term.length === 1 || /[\u{2000}-\u{2FFFF}]/u.test(searchTerm)) {
|
||||
const pastedIcon = icons.find(icon => icon.char === searchTerm);
|
||||
if (pastedIcon) {
|
||||
filtered = [pastedIcon];
|
||||
} else {
|
||||
// 搜索包含该字符的图标
|
||||
filtered = icons.filter(icon => icon.char === searchTerm);
|
||||
}
|
||||
} else {
|
||||
// 模糊搜索:类名、简化后的名称匹配
|
||||
filtered = icons.filter(icon => {
|
||||
const className = icon.className.toLowerCase();
|
||||
const simpleClassName = className.replace(/[_-]/g, '');
|
||||
const simpleTerm = term.replace(/[_-]/g, '');
|
||||
|
||||
return (
|
||||
className.includes(term) ||
|
||||
simpleClassName.includes(simpleTerm) ||
|
||||
// 关键词匹配
|
||||
term.split(' ').every(keyword =>
|
||||
className.includes(keyword) || simpleClassName.includes(keyword)
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
setFilteredIcons(filtered.slice(0, 120)); // 显示更多结果
|
||||
}, [searchTerm, icons]);
|
||||
|
||||
// 处理输入变化
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = e.target.value;
|
||||
setSearchTerm(newValue);
|
||||
onChange(newValue);
|
||||
|
||||
// 始终打开下拉框,让用户搜索或确认粘贴的内容
|
||||
setIsOpen(true);
|
||||
if (icons.length === 0) {
|
||||
loadIcons();
|
||||
}
|
||||
};
|
||||
|
||||
// 处理粘贴事件
|
||||
const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
|
||||
const pastedText = e.clipboardData.getData('text');
|
||||
|
||||
// 如果是单个字符(可能是图标),直接接受并打开下拉框显示相应图标
|
||||
if (pastedText && pastedText.length === 1) {
|
||||
setTimeout(() => {
|
||||
setIsOpen(true);
|
||||
}, 10);
|
||||
}
|
||||
};
|
||||
|
||||
// 选择图标
|
||||
const handleIconSelect = (iconChar: string) => {
|
||||
setSearchTerm(iconChar);
|
||||
onChange(iconChar);
|
||||
setIsOpen(false);
|
||||
inputRef.current?.focus();
|
||||
};
|
||||
|
||||
// 处理焦点事件
|
||||
const handleFocus = () => {
|
||||
setIsOpen(true);
|
||||
if (icons.length === 0) {
|
||||
loadIcons();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 处理失去焦点(延迟关闭以便点击图标)
|
||||
const handleBlur = () => {
|
||||
setTimeout(() => setIsOpen(false), 200);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="relative">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={searchTerm}
|
||||
onChange={handleInputChange}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onPaste={handlePaste}
|
||||
placeholder={t("statusline.icon_placeholder")}
|
||||
style={{ fontFamily: fontFamily + ', monospace' }}
|
||||
className="text-lg pr-2"
|
||||
/>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="absolute z-50 mt-1 w-full max-h-72 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<svg className="animate-spin h-6 w-6 text-primary" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" opacity="0.1"/>
|
||||
<path fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"/>
|
||||
</svg>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid grid-cols-5 gap-2 p-2 max-h-72 overflow-y-auto">
|
||||
{filteredIcons.map((icon) => (
|
||||
<div
|
||||
key={icon.className}
|
||||
className="flex items-center justify-center p-3 text-2xl cursor-pointer hover:bg-secondary rounded transition-colors"
|
||||
onClick={() => handleIconSelect(icon.char)}
|
||||
onMouseDown={(e) => e.preventDefault()} // 防止失去焦点
|
||||
title={`${icon.char} - ${icon.className}`}
|
||||
style={{ fontFamily: fontFamily + ', monospace' }}
|
||||
>
|
||||
{icon.char}
|
||||
</div>
|
||||
))}
|
||||
{filteredIcons.length === 0 && (
|
||||
<div className="col-span-5 flex flex-col items-center justify-center p-8 text-muted-foreground">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="mb-2">
|
||||
<circle cx="11" cy="11" r="8" />
|
||||
<path d="m21 21-4.35-4.35" />
|
||||
</svg>
|
||||
<div className="text-sm">
|
||||
{searchTerm ? `${t("statusline.no_icons_found")} "${searchTerm}"` : t("statusline.no_icons_available")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// 变量替换函数
|
||||
function replaceVariables(
|
||||
text: string,
|
||||
@@ -500,9 +702,57 @@ export function StatusLineConfigDialog({
|
||||
? currentModules[selectedModuleIndex]
|
||||
: null;
|
||||
|
||||
// 删除选中模块的函数
|
||||
const deleteSelectedModule = useCallback(() => {
|
||||
if (selectedModuleIndex === null) return;
|
||||
|
||||
const currentTheme =
|
||||
statusLineConfig.currentStyle as keyof StatusLineConfig;
|
||||
const themeConfig = statusLineConfig[currentTheme];
|
||||
const modules =
|
||||
themeConfig &&
|
||||
typeof themeConfig === "object" &&
|
||||
"modules" in themeConfig
|
||||
? [...((themeConfig as StatusLineThemeConfig).modules || [])]
|
||||
: [];
|
||||
|
||||
if (selectedModuleIndex >= 0 && selectedModuleIndex < modules.length) {
|
||||
modules.splice(selectedModuleIndex, 1);
|
||||
|
||||
setStatusLineConfig((prev) => ({
|
||||
...prev,
|
||||
[currentTheme]: { modules },
|
||||
}));
|
||||
|
||||
setSelectedModuleIndex(null);
|
||||
}
|
||||
}, [selectedModuleIndex, statusLineConfig]);
|
||||
|
||||
// 字体样式
|
||||
const fontStyle = fontFamily ? { fontFamily } : {};
|
||||
|
||||
// 键盘事件监听器,支持删除选中的模块
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// 检查是否选中了模块
|
||||
if (selectedModuleIndex === null) return;
|
||||
|
||||
// 检查是否按下了删除键 (Delete 或 Backspace)
|
||||
if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||
e.preventDefault();
|
||||
deleteSelectedModule();
|
||||
}
|
||||
};
|
||||
|
||||
// 添加事件监听器
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [selectedModuleIndex, deleteSelectedModule]);
|
||||
|
||||
// 当字体或主题变化时强制重新渲染
|
||||
const fontKey = `${fontFamily}-${statusLineConfig.currentStyle}`;
|
||||
|
||||
@@ -558,30 +808,30 @@ export function StatusLineConfigDialog({
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="theme-style" className="text-sm font-medium">
|
||||
主题样式
|
||||
{t("statusline.theme")}
|
||||
</Label>
|
||||
<Combobox
|
||||
options={[
|
||||
{ label: "默认", value: "default" },
|
||||
{ label: "Powerline", value: "powerline" },
|
||||
{ label: t("statusline.theme_default"), value: "default" },
|
||||
{ label: t("statusline.theme_powerline"), value: "powerline" },
|
||||
]}
|
||||
value={statusLineConfig.currentStyle}
|
||||
onChange={handleThemeChange}
|
||||
data-testid="theme-selector"
|
||||
placeholder="选择主题样式"
|
||||
placeholder={t("statusline.theme_placeholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="font-family" className="text-sm font-medium">
|
||||
字体
|
||||
{t("statusline.module_icon")}
|
||||
</Label>
|
||||
<Combobox
|
||||
options={NERD_FONTS}
|
||||
value={fontFamily}
|
||||
onChange={(value) => setFontFamily(value)}
|
||||
data-testid="font-family-selector"
|
||||
placeholder="选择字体"
|
||||
placeholder={t("statusline.font_placeholder")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -590,9 +840,9 @@ export function StatusLineConfigDialog({
|
||||
{/* 三栏布局:组件列表 | 预览区域 | 属性配置 */}
|
||||
<div className="grid grid-cols-5 gap-6 overflow-hidden flex-1">
|
||||
{/* 左侧:支持的组件 */}
|
||||
<div className="border rounded-lg p-4 flex flex-col overflow-hidden col-span-1">
|
||||
<h3 className="text-sm font-medium mb-3">组件</h3>
|
||||
<div className="space-y-2 overflow-y-auto flex-1">
|
||||
<div className="border rounded-lg flex flex-col overflow-hidden col-span-1">
|
||||
<h3 className="text-sm font-medium p-4 pb-0 mb-3">{t("statusline.components")}</h3>
|
||||
<div className="space-y-2 overflow-y-auto px-4 pb-4 flex-1">
|
||||
{MODULE_TYPES_OPTIONS.map((moduleType) => (
|
||||
<div
|
||||
key={moduleType.value}
|
||||
@@ -610,7 +860,7 @@ export function StatusLineConfigDialog({
|
||||
|
||||
{/* 中间:预览区域 */}
|
||||
<div className="border rounded-lg p-4 flex flex-col col-span-3">
|
||||
<h3 className="text-sm font-medium mb-3">预览</h3>
|
||||
<h3 className="text-sm font-medium mb-3">{t("statusline.preview")}</h3>
|
||||
<div
|
||||
key={fontKey}
|
||||
className={`rounded bg-black/90 text-white font-mono text-sm overflow-x-auto flex items-center border border-border p-3 py-5 shadow-inner overflow-hidden ${
|
||||
@@ -793,7 +1043,7 @@ export function StatusLineConfigDialog({
|
||||
<path d="M8 12h8" />
|
||||
</svg>
|
||||
<span className="text-gray-500 text-sm">
|
||||
拖拽组件到此处进行配置
|
||||
{t("statusline.drag_hint")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -801,45 +1051,31 @@ export function StatusLineConfigDialog({
|
||||
</div>
|
||||
|
||||
{/* 右侧:属性配置 */}
|
||||
<div className="border rounded-lg p-4 flex flex-col overflow-hidden col-span-1">
|
||||
<h3 className="text-sm font-medium mb-3">属性</h3>
|
||||
<div className="overflow-y-auto flex-1">
|
||||
<div className="border rounded-lg flex flex-col overflow-hidden col-span-1">
|
||||
<h3 className="text-sm font-medium p-4 pb-0 mb-3">{t("statusline.properties")}</h3>
|
||||
<div className="overflow-y-auto px-4 pb-4 flex-1">
|
||||
{selectedModule && selectedModuleIndex !== null ? (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>{t("statusline.module_type")}</Label>
|
||||
<Combobox
|
||||
options={MODULE_TYPES_OPTIONS}
|
||||
value={selectedModule.type}
|
||||
onChange={(value) =>
|
||||
handleModuleChange(selectedModuleIndex, "type", value)
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
选择模块类型以确定显示的信息
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="module-icon">
|
||||
{t("statusline.module_icon")}
|
||||
</Label>
|
||||
<Input
|
||||
<IconSearchInput
|
||||
key={fontKey}
|
||||
id="module-icon"
|
||||
value={selectedModule.icon || ""}
|
||||
onChange={(e) =>
|
||||
onChange={(value) =>
|
||||
handleModuleChange(
|
||||
selectedModuleIndex,
|
||||
"icon",
|
||||
e.target.value
|
||||
value
|
||||
)
|
||||
}
|
||||
placeholder="例如: "
|
||||
style={fontStyle}
|
||||
fontFamily={fontFamily}
|
||||
t={t}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
输入图标字符或表情符号(可选)
|
||||
{t("statusline.icon_description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -857,10 +1093,10 @@ export function StatusLineConfigDialog({
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
placeholder="例如: {{workDirName}}"
|
||||
placeholder={t("statusline.text_placeholder")}
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
<p>输入显示文本,可使用变量:</p>
|
||||
<p>{t("statusline.module_text_description")}</p>
|
||||
<div className="flex flex-wrap gap-1 mt-1">
|
||||
<Badge
|
||||
variant="secondary"
|
||||
@@ -909,7 +1145,7 @@ export function StatusLineConfigDialog({
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
选择文字颜色
|
||||
{t("statusline.module_color_description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -926,7 +1162,7 @@ export function StatusLineConfigDialog({
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
选择背景颜色(可选)
|
||||
{t("statusline.module_background_description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -934,7 +1170,7 @@ export function StatusLineConfigDialog({
|
||||
{selectedModule.type === "script" && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="module-script-path">
|
||||
脚本路径
|
||||
{t("statusline.module_script_path")}
|
||||
</Label>
|
||||
<Input
|
||||
id="module-script-path"
|
||||
@@ -946,10 +1182,10 @@ export function StatusLineConfigDialog({
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
placeholder="例如: /path/to/your/script.js"
|
||||
placeholder={t("statusline.script_placeholder")}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
输入Node.js脚本文件的绝对路径
|
||||
{t("statusline.module_script_path_description")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -958,36 +1194,15 @@ export function StatusLineConfigDialog({
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const currentTheme =
|
||||
statusLineConfig.currentStyle as keyof StatusLineConfig;
|
||||
const themeConfig = statusLineConfig[currentTheme];
|
||||
const modules =
|
||||
themeConfig &&
|
||||
typeof themeConfig === "object" &&
|
||||
"modules" in themeConfig
|
||||
? [
|
||||
...((themeConfig as StatusLineThemeConfig)
|
||||
.modules || []),
|
||||
]
|
||||
: [];
|
||||
modules.splice(selectedModuleIndex, 1);
|
||||
|
||||
setStatusLineConfig((prev) => ({
|
||||
...prev,
|
||||
[currentTheme]: { modules },
|
||||
}));
|
||||
|
||||
setSelectedModuleIndex(null);
|
||||
}}
|
||||
onClick={deleteSelectedModule}
|
||||
>
|
||||
删除组件
|
||||
{t("statusline.delete_module")}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full min-h-[200px]">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
选择一个组件进行配置
|
||||
{t("statusline.select_hint")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -119,4 +119,38 @@
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
|
||||
/* 美化滚动条 - WebKit浏览器 (Chrome, Safari, Edge) */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-muted-foreground/30;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
@apply bg-muted-foreground/50;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: oklch(0.556 0 0) oklch(0.97 0 0);
|
||||
}
|
||||
|
||||
.dark * {
|
||||
scrollbar-color: oklch(0.708 0 0) oklch(0.269 0 0);
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,8 @@
|
||||
"select_template": "Select a template...",
|
||||
"api_key_required": "API Key is required",
|
||||
"name_required": "Name is required",
|
||||
"name_duplicate": "A provider with this name already exists"
|
||||
"name_duplicate": "A provider with this name already exists",
|
||||
"search": "Search providers..."
|
||||
|
||||
},
|
||||
"router": {
|
||||
@@ -128,9 +129,17 @@
|
||||
"module_text": "Text",
|
||||
"module_color": "Color",
|
||||
"module_background": "Background",
|
||||
"module_text_description": "Enter display text, variables can be used:",
|
||||
"module_color_description": "Select text color",
|
||||
"module_background_description": "Select background color (optional)",
|
||||
"module_script_path": "Script Path",
|
||||
"module_script_path_description": "Enter the absolute path of the Node.js script file",
|
||||
"add_module": "Add Module",
|
||||
"remove_module": "Remove Module",
|
||||
"delete_module": "Delete Module",
|
||||
"preview": "Preview",
|
||||
"components": "Components",
|
||||
"properties": "Properties",
|
||||
"workDir": "Working Directory",
|
||||
"gitBranch": "Git Branch",
|
||||
"model": "Model",
|
||||
@@ -153,6 +162,16 @@
|
||||
"color_bright_magenta": "Bright Magenta",
|
||||
"color_bright_cyan": "Bright Cyan",
|
||||
"color_bright_white": "Bright White",
|
||||
"font_placeholder": "Select Font",
|
||||
"theme_placeholder": "Select Theme Style",
|
||||
"icon_placeholder": "Paste icon or search by name...",
|
||||
"icon_description": "Enter icon character, paste icon, or search icons (optional)",
|
||||
"text_placeholder": "e.g.: {{workDirName}}",
|
||||
"script_placeholder": "e.g.: /path/to/your/script.js",
|
||||
"drag_hint": "Drag components here to configure",
|
||||
"select_hint": "Select a component to configure",
|
||||
"no_icons_found": "No icons found",
|
||||
"no_icons_available": "No icons available",
|
||||
"import_export": "Import/Export",
|
||||
"import": "Import Config",
|
||||
"export": "Export Config",
|
||||
|
||||
@@ -93,7 +93,8 @@
|
||||
"select_template": "选择一个模板...",
|
||||
"api_key_required": "API 密钥为必填项",
|
||||
"name_required": "名称为必填项",
|
||||
"name_duplicate": "已存在同名供应商"
|
||||
"name_duplicate": "已存在同名供应商",
|
||||
"search": "搜索供应商..."
|
||||
|
||||
},
|
||||
"router": {
|
||||
@@ -128,9 +129,17 @@
|
||||
"module_text": "文本",
|
||||
"module_color": "颜色",
|
||||
"module_background": "背景",
|
||||
"module_text_description": "输入显示文本,可使用变量:",
|
||||
"module_color_description": "选择文字颜色",
|
||||
"module_background_description": "选择背景颜色(可选)",
|
||||
"module_script_path": "脚本路径",
|
||||
"module_script_path_description": "输入Node.js脚本文件的绝对路径",
|
||||
"add_module": "添加模块",
|
||||
"remove_module": "移除模块",
|
||||
"delete_module": "删除组件",
|
||||
"preview": "预览",
|
||||
"components": "组件",
|
||||
"properties": "属性",
|
||||
"workDir": "工作目录",
|
||||
"gitBranch": "Git分支",
|
||||
"model": "模型",
|
||||
@@ -153,6 +162,16 @@
|
||||
"color_bright_magenta": "亮品红",
|
||||
"color_bright_cyan": "亮青色",
|
||||
"color_bright_white": "亮白色",
|
||||
"font_placeholder": "选择字体",
|
||||
"theme_placeholder": "选择主题样式",
|
||||
"icon_placeholder": "粘贴图标或输入名称搜索...",
|
||||
"icon_description": "输入图标字符、粘贴图标或搜索图标(可选)",
|
||||
"text_placeholder": "例如: {{workDirName}}",
|
||||
"script_placeholder": "例如: /path/to/your/script.js",
|
||||
"drag_hint": "拖拽组件到此处进行配置",
|
||||
"select_hint": "选择一个组件进行配置",
|
||||
"no_icons_found": "未找到图标",
|
||||
"no_icons_available": "暂无可用图标",
|
||||
"import_export": "导入/导出",
|
||||
"import": "导入配置",
|
||||
"export": "导出配置",
|
||||
|
||||
Reference in New Issue
Block a user