mirror of
https://github.com/musistudio/claude-code-router.git
synced 2026-01-30 06:12:06 +00:00
move llms to core package
This commit is contained in:
90
CLAUDE.md
90
CLAUDE.md
@@ -8,7 +8,7 @@ Claude Code Router is a tool that routes Claude Code requests to different LLM p
|
|||||||
|
|
||||||
- **cli** (`@musistudio/claude-code-router-cli`): Command-line tool providing the `ccr` command
|
- **cli** (`@musistudio/claude-code-router-cli`): Command-line tool providing the `ccr` command
|
||||||
- **server** (`@musistudio/claude-code-router-server`): Core server handling API routing and transformations
|
- **server** (`@musistudio/claude-code-router-server`): Core server handling API routing and transformations
|
||||||
- **shared** (`@musistudio/claude-code-router-shared`): Shared constants and utilities
|
- **shared** (`@musistudio/claude-code-router-shared`): Shared constants, utilities, and preset management
|
||||||
- **ui** (`@musistudio/claude-code-router-ui`): Web management interface (React + Vite)
|
- **ui** (`@musistudio/claude-code-router-ui`): Web management interface (React + Vite)
|
||||||
|
|
||||||
## Build Commands
|
## Build Commands
|
||||||
@@ -127,11 +127,22 @@ ccr restart # Restart server
|
|||||||
ccr status # Show status
|
ccr status # Show status
|
||||||
ccr code # Execute claude command
|
ccr code # Execute claude command
|
||||||
ccr model # Interactive model selection and configuration
|
ccr model # Interactive model selection and configuration
|
||||||
|
ccr preset # Manage presets (export, install, list, info, delete)
|
||||||
ccr activate # Output shell environment variables (for integration)
|
ccr activate # Output shell environment variables (for integration)
|
||||||
ccr ui # Open Web UI
|
ccr ui # Open Web UI
|
||||||
ccr statusline # Integrated statusline (reads JSON from stdin)
|
ccr statusline # Integrated statusline (reads JSON from stdin)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Preset Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ccr preset export <name> # Export current configuration as a preset
|
||||||
|
ccr preset install <source> # Install a preset from file, URL, or name
|
||||||
|
ccr preset list # List all installed presets
|
||||||
|
ccr preset info <name> # Show preset information
|
||||||
|
ccr preset delete <name> # Delete a preset
|
||||||
|
```
|
||||||
|
|
||||||
## Subagent Routing
|
## Subagent Routing
|
||||||
|
|
||||||
Use special tags in subagent prompts to specify models:
|
Use special tags in subagent prompts to specify models:
|
||||||
@@ -140,6 +151,83 @@ Use special tags in subagent prompts to specify models:
|
|||||||
Please help me analyze this code...
|
Please help me analyze this code...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Preset System
|
||||||
|
|
||||||
|
The preset system allows users to save, share, and reuse configurations easily.
|
||||||
|
|
||||||
|
### Preset Structure
|
||||||
|
|
||||||
|
Presets are stored in `~/.claude-code-router/presets/<preset-name>/manifest.json`
|
||||||
|
|
||||||
|
Each preset contains:
|
||||||
|
- **Metadata**: name, version, description, author, keywords, etc.
|
||||||
|
- **Configuration**: Providers, Router, transformers, and other settings
|
||||||
|
- **Dynamic Schema** (optional): Input fields for collecting required information during installation
|
||||||
|
- **Required Inputs** (optional): Fields that need to be filled during installation (e.g., API keys)
|
||||||
|
|
||||||
|
### Core Functions
|
||||||
|
|
||||||
|
Located in `packages/shared/src/preset/`:
|
||||||
|
|
||||||
|
- **export.ts**: Export current configuration as a preset (.ccrsets file)
|
||||||
|
- `exportPreset(presetName, config, options)`: Creates ZIP archive with manifest.json
|
||||||
|
- Automatically sanitizes sensitive data (api_key fields become `{{field}}` placeholders)
|
||||||
|
|
||||||
|
- **install.ts**: Install and manage presets
|
||||||
|
- `installPreset(preset, config, options)`: Install preset to config
|
||||||
|
- `loadPreset(source)`: Load preset from file, URL, or directory
|
||||||
|
- `listPresets()`: List all installed presets
|
||||||
|
- `isPresetInstalled(presetName)`: Check if preset is installed
|
||||||
|
- `validatePreset(preset)`: Validate preset structure
|
||||||
|
|
||||||
|
- **merge.ts**: Merge preset configuration with existing config
|
||||||
|
- Handles conflicts using different strategies (ask, overwrite, merge, skip)
|
||||||
|
|
||||||
|
- **sensitiveFields.ts**: Identify and sanitize sensitive fields
|
||||||
|
- Detects api_key, password, secret fields automatically
|
||||||
|
- Creates `requiredInputs` array for installation prompts
|
||||||
|
|
||||||
|
### Preset File Format
|
||||||
|
|
||||||
|
**manifest.json** (in ZIP archive or extracted directory):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "my-preset",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "My configuration",
|
||||||
|
"author": "Author Name",
|
||||||
|
"keywords": ["openai", "production"],
|
||||||
|
"Providers": [...],
|
||||||
|
"Router": {...},
|
||||||
|
"schema": [
|
||||||
|
{
|
||||||
|
"id": "apiKey",
|
||||||
|
"type": "password",
|
||||||
|
"label": "OpenAI API Key",
|
||||||
|
"prompt": "Enter your OpenAI API key"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requiredInputs": [
|
||||||
|
{
|
||||||
|
"field": "Providers[0].api_key",
|
||||||
|
"placeholder": "Enter your API key"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CLI Integration
|
||||||
|
|
||||||
|
The CLI layer (`packages/cli/src/utils/preset/`) handles:
|
||||||
|
- User interaction and prompts
|
||||||
|
- File operations
|
||||||
|
- Display formatting
|
||||||
|
|
||||||
|
Key files:
|
||||||
|
- `commands.ts`: Command handlers for `ccr preset` subcommands
|
||||||
|
- `export.ts`: CLI wrapper for export functionality
|
||||||
|
- `install.ts`: CLI wrapper for install functionality
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -253,7 +253,46 @@ This command provides an interactive interface to:
|
|||||||
|
|
||||||
The CLI tool validates all inputs and provides helpful prompts to guide you through the configuration process, making it easy to manage complex setups without editing JSON files manually.
|
The CLI tool validates all inputs and provides helpful prompts to guide you through the configuration process, making it easy to manage complex setups without editing JSON files manually.
|
||||||
|
|
||||||
### 6. Activate Command (Environment Variables Setup)
|
### 6. Presets Management
|
||||||
|
|
||||||
|
Presets allow you to save, share, and reuse configurations easily. You can export your current configuration as a preset and install presets from files or URLs.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Export current configuration as a preset
|
||||||
|
ccr preset export my-preset
|
||||||
|
|
||||||
|
# Export with metadata
|
||||||
|
ccr preset export my-preset --description "My OpenAI config" --author "Your Name" --tags "openai,production"
|
||||||
|
|
||||||
|
# Install a preset from file, URL, or registry
|
||||||
|
ccr preset install my-preset.ccrsets
|
||||||
|
ccr preset install https://example.com/preset.ccrsets
|
||||||
|
|
||||||
|
# List all installed presets
|
||||||
|
ccr preset list
|
||||||
|
|
||||||
|
# Show preset information
|
||||||
|
ccr preset info my-preset
|
||||||
|
|
||||||
|
# Delete a preset
|
||||||
|
ccr preset delete my-preset
|
||||||
|
```
|
||||||
|
|
||||||
|
**Preset Features:**
|
||||||
|
- **Export**: Save your current configuration as a `.ccrsets` file (ZIP archive with manifest.json)
|
||||||
|
- **Install**: Install presets from local files, URLs, or the preset registry
|
||||||
|
- **Sensitive Data Handling**: API keys and other sensitive data are automatically sanitized during export (marked as `{{field}}` placeholders)
|
||||||
|
- **Dynamic Configuration**: Presets can include input schemas for collecting required information during installation
|
||||||
|
- **Version Control**: Each preset includes version metadata for tracking updates
|
||||||
|
|
||||||
|
**Preset File Structure:**
|
||||||
|
```
|
||||||
|
~/.claude-code-router/presets/
|
||||||
|
├── my-preset/
|
||||||
|
│ └── manifest.json # Contains configuration and metadata
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Activate Command (Environment Variables Setup)
|
||||||
|
|
||||||
The `activate` command allows you to set up environment variables globally in your shell, enabling you to use the `claude` command directly or integrate Claude Code Router with applications built using the Agent SDK.
|
The `activate` command allows you to set up environment variables globally in your shell, enabling you to use the `claude` command directly or integrate Claude Code Router with applications built using the Agent SDK.
|
||||||
|
|
||||||
|
|||||||
66
README_zh.md
66
README_zh.md
@@ -203,7 +203,71 @@ ccr ui
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### 5. Activate 命令(环境变量设置)
|
### 5. CLI 模型管理
|
||||||
|
|
||||||
|
对于偏好终端工作流的用户,可以使用交互式 CLI 模型选择器:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
ccr model
|
||||||
|
```
|
||||||
|
|
||||||
|
该命令提供交互式界面来:
|
||||||
|
|
||||||
|
- 查看当前配置
|
||||||
|
- 查看所有配置的模型(default、background、think、longContext、webSearch、image)
|
||||||
|
- 切换模型:快速更改每个路由器类型使用的模型
|
||||||
|
- 添加新模型:向现有提供商添加模型
|
||||||
|
- 创建新提供商:设置完整的提供商配置,包括:
|
||||||
|
- 提供商名称和 API 端点
|
||||||
|
- API 密钥
|
||||||
|
- 可用模型
|
||||||
|
- Transformer 配置,支持:
|
||||||
|
- 多个转换器(openrouter、deepseek、gemini 等)
|
||||||
|
- Transformer 选项(例如,带自定义限制的 maxtoken)
|
||||||
|
- 特定于提供商的路由(例如,OpenRouter 提供商偏好)
|
||||||
|
|
||||||
|
CLI 工具验证所有输入并提供有用的提示来引导您完成配置过程,使管理复杂的设置变得容易,无需手动编辑 JSON 文件。
|
||||||
|
|
||||||
|
### 6. 预设管理
|
||||||
|
|
||||||
|
预设允许您轻松保存、共享和重用配置。您可以将当前配置导出为预设,并从文件或 URL 安装预设。
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 将当前配置导出为预设
|
||||||
|
ccr preset export my-preset
|
||||||
|
|
||||||
|
# 使用元数据导出
|
||||||
|
ccr preset export my-preset --description "我的 OpenAI 配置" --author "您的名字" --tags "openai,生产环境"
|
||||||
|
|
||||||
|
# 从文件、URL 或注册表安装预设
|
||||||
|
ccr preset install my-preset.ccrsets
|
||||||
|
ccr preset install https://example.com/preset.ccrsets
|
||||||
|
|
||||||
|
# 列出所有已安装的预设
|
||||||
|
ccr preset list
|
||||||
|
|
||||||
|
# 显示预设信息
|
||||||
|
ccr preset info my-preset
|
||||||
|
|
||||||
|
# 删除预设
|
||||||
|
ccr preset delete my-preset
|
||||||
|
```
|
||||||
|
|
||||||
|
**预设功能:**
|
||||||
|
- **导出**:将当前配置保存为 `.ccrsets` 文件(包含 manifest.json 的 ZIP 存档)
|
||||||
|
- **安装**:从本地文件、URL 或预设注册表安装预设
|
||||||
|
- **敏感数据处理**:导出期间自动清理 API 密钥和其他敏感数据(标记为 `{{field}}` 占位符)
|
||||||
|
- **动态配置**:预设可以包含输入架构,用于在安装期间收集所需信息
|
||||||
|
- **版本控制**:每个预设包含版本元数据,用于跟踪更新
|
||||||
|
|
||||||
|
**预设文件结构:**
|
||||||
|
```
|
||||||
|
~/.claude-code-router/presets/
|
||||||
|
├── my-preset/
|
||||||
|
│ └── manifest.json # 包含配置和元数据
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Activate 命令(环境变量设置)
|
||||||
|
|
||||||
`activate` 命令允许您在 shell 中全局设置环境变量,使您能够直接使用 `claude` 命令或将 Claude Code Router 与使用 Agent SDK 构建的应用程序集成。
|
`activate` 命令允许您在 shell 中全局设置环境变量,使您能够直接使用 `claude` 命令或将 Claude Code Router 与使用 Agent SDK 构建的应用程序集成。
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node scripts/build.js",
|
"build": "node scripts/build.js",
|
||||||
|
"build:core": "node scripts/build-core.js",
|
||||||
|
"build:shared": "node scripts/build-shared.js",
|
||||||
"build:cli": "pnpm --filter @CCR/cli build",
|
"build:cli": "pnpm --filter @CCR/cli build",
|
||||||
"build:server": "pnpm --filter @CCR/server build",
|
"build:server": "pnpm --filter @CCR/server build",
|
||||||
"build:ui": "pnpm --filter @CCR/ui build",
|
"build:ui": "pnpm --filter @CCR/ui build",
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ import { activateCommand } from "./utils/activateCommand";
|
|||||||
import { readConfigFile } from "./utils";
|
import { readConfigFile } from "./utils";
|
||||||
import { version } from "../package.json";
|
import { version } from "../package.json";
|
||||||
import { spawn, exec } from "child_process";
|
import { spawn, exec } from "child_process";
|
||||||
import { PID_FILE, REFERENCE_COUNT_FILE } from "@CCR/shared";
|
import {PID_FILE, readPresetFile, REFERENCE_COUNT_FILE} from "@CCR/shared";
|
||||||
import fs, { existsSync, readFileSync } from "fs";
|
import fs, { existsSync, readFileSync } from "fs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { parseStatusLineData, StatusLineInput } from "./utils/statusline";
|
import { parseStatusLineData, StatusLineInput } from "./utils/statusline";
|
||||||
|
import {handlePresetCommand} from "./utils/preset";
|
||||||
|
|
||||||
|
|
||||||
const command = process.argv[2];
|
const command = process.argv[2];
|
||||||
|
|
||||||
@@ -95,7 +97,6 @@ async function main() {
|
|||||||
|
|
||||||
// 如果命令不是已知命令,检查是否是 preset
|
// 如果命令不是已知命令,检查是否是 preset
|
||||||
if (command && !KNOWN_COMMANDS.includes(command)) {
|
if (command && !KNOWN_COMMANDS.includes(command)) {
|
||||||
const { readPresetFile } = await import("./utils");
|
|
||||||
const presetData: any = await readPresetFile(command);
|
const presetData: any = await readPresetFile(command);
|
||||||
|
|
||||||
if (presetData) {
|
if (presetData) {
|
||||||
@@ -248,7 +249,6 @@ async function main() {
|
|||||||
await runModelSelector();
|
await runModelSelector();
|
||||||
break;
|
break;
|
||||||
case "preset":
|
case "preset":
|
||||||
const { handlePresetCommand } = await import("./utils/preset");
|
|
||||||
await handlePresetCommand(process.argv.slice(3));
|
await handlePresetCommand(process.argv.slice(3));
|
||||||
break;
|
break;
|
||||||
case "activate":
|
case "activate":
|
||||||
|
|||||||
@@ -105,20 +105,11 @@ export async function applyPresetCli(
|
|||||||
|
|
||||||
console.log(`${BOLDCYAN}Validating preset...${RESET} ${GREEN}✓${RESET}`);
|
console.log(`${BOLDCYAN}Validating preset...${RESET} ${GREEN}✓${RESET}`);
|
||||||
|
|
||||||
// 检查是否已经配置过(通过检查manifest中是否已有敏感信息)
|
// 检查是否需要配置
|
||||||
const presetDir = getPresetDir(presetName);
|
if (preset.schema && preset.schema.length > 0) {
|
||||||
|
console.log(`\n${BOLDCYAN}Configuration required:${RESET} ${preset.schema.length} field(s)\n`);
|
||||||
try {
|
} else {
|
||||||
const existingManifest = await readManifestFromDir(presetDir);
|
console.log(`\n${DIM}No configuration required for this preset${RESET}\n`);
|
||||||
// 检查是否已经配置了敏感信息(例如api_key)
|
|
||||||
const hasSecrets = existingManifest.Providers?.some((p: any) => p.api_key && p.api_key !== '');
|
|
||||||
if (hasSecrets) {
|
|
||||||
console.log(`\n${GREEN}✓${RESET} Preset already configured`);
|
|
||||||
console.log(`${DIM}You can use this preset with: ccr ${presetName}${RESET}\n`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// manifest不存在,继续配置流程
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收集用户输入
|
// 收集用户输入
|
||||||
|
|||||||
@@ -14,8 +14,13 @@ import {
|
|||||||
getDefaultValue,
|
getDefaultValue,
|
||||||
sortFieldsByDependencies,
|
sortFieldsByDependencies,
|
||||||
getAffectedFields,
|
getAffectedFields,
|
||||||
} from '@musistudio/claude-code-router-shared';
|
} from '@CCR/shared';
|
||||||
import { input, confirm, select, password } from '@inquirer/prompts';
|
import input from '@inquirer/input';
|
||||||
|
import confirm from '@inquirer/confirm';
|
||||||
|
import select from '@inquirer/select';
|
||||||
|
import password from '@inquirer/password';
|
||||||
|
import checkbox from '@inquirer/checkbox';
|
||||||
|
import editor from '@inquirer/editor';
|
||||||
|
|
||||||
// ANSI 颜色代码
|
// ANSI 颜色代码
|
||||||
export const COLORS = {
|
export const COLORS = {
|
||||||
@@ -183,7 +188,6 @@ async function promptField(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @inquirer/prompts 没有多选,使用 checkbox
|
// @inquirer/prompts 没有多选,使用 checkbox
|
||||||
const { checkbox } = await import('@inquirer/prompts');
|
|
||||||
return await checkbox({
|
return await checkbox({
|
||||||
message,
|
message,
|
||||||
choices: options.map(opt => ({
|
choices: options.map(opt => ({
|
||||||
@@ -195,7 +199,6 @@ async function promptField(
|
|||||||
}
|
}
|
||||||
|
|
||||||
case InputType.EDITOR: {
|
case InputType.EDITOR: {
|
||||||
const { editor } = await import('@inquirer/prompts');
|
|
||||||
return await editor({
|
return await editor({
|
||||||
message,
|
message,
|
||||||
default: field.defaultValue,
|
default: field.defaultValue,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/multipart": "^9.0.0",
|
"@fastify/multipart": "^9.0.0",
|
||||||
"@fastify/static": "^8.2.0",
|
"@fastify/static": "^8.2.0",
|
||||||
"@musistudio/llms": "^1.0.51",
|
"@musistudio/llms": "workspace:*",
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { existsSync, writeFileSync, unlinkSync } from "fs";
|
import { existsSync } from "fs";
|
||||||
import { writeFile } from "fs/promises";
|
import { writeFile } from "fs/promises";
|
||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
@@ -6,7 +6,7 @@ import { initConfig, initDir } from "./utils";
|
|||||||
import { createServer } from "./server";
|
import { createServer } from "./server";
|
||||||
import { router } from "./utils/router";
|
import { router } from "./utils/router";
|
||||||
import { apiKeyAuth } from "./middleware/auth";
|
import { apiKeyAuth } from "./middleware/auth";
|
||||||
import { CONFIG_FILE, HOME_DIR } from "@CCR/shared";
|
import {CONFIG_FILE, HOME_DIR, listPresets} from "@CCR/shared";
|
||||||
import { createStream } from 'rotating-file-stream';
|
import { createStream } from 'rotating-file-stream';
|
||||||
import { sessionUsageCache } from "./utils/cache";
|
import { sessionUsageCache } from "./utils/cache";
|
||||||
import {SSEParserTransform} from "./utils/SSEParser.transform";
|
import {SSEParserTransform} from "./utils/SSEParser.transform";
|
||||||
@@ -16,7 +16,6 @@ import JSON5 from "json5";
|
|||||||
import { IAgent, ITool } from "./agents/type";
|
import { IAgent, ITool } from "./agents/type";
|
||||||
import agentsManager from "./agents";
|
import agentsManager from "./agents";
|
||||||
import { EventEmitter } from "node:events";
|
import { EventEmitter } from "node:events";
|
||||||
import {spawn} from "child_process";
|
|
||||||
|
|
||||||
const event = new EventEmitter()
|
const event = new EventEmitter()
|
||||||
|
|
||||||
@@ -121,6 +120,8 @@ async function getServer(options: RunOptions = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const presets = await listPresets();
|
||||||
|
|
||||||
const serverInstance = await createServer({
|
const serverInstance = await createServer({
|
||||||
jsonPath: CONFIG_FILE,
|
jsonPath: CONFIG_FILE,
|
||||||
initialConfig: {
|
initialConfig: {
|
||||||
@@ -137,6 +138,11 @@ async function getServer(options: RunOptions = {}) {
|
|||||||
logger: loggerConfig,
|
logger: loggerConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
presets.forEach(preset => {
|
||||||
|
console.log(preset.name, preset.config);
|
||||||
|
serverInstance.registerNamespace(preset.name, preset.config);
|
||||||
|
})
|
||||||
|
|
||||||
// Add async preHandler hook for authentication
|
// Add async preHandler hook for authentication
|
||||||
serverInstance.addHook("preHandler", async (req: any, reply: any) => {
|
serverInstance.addHook("preHandler", async (req: any, reply: any) => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import {
|
|||||||
readManifestFromDir,
|
readManifestFromDir,
|
||||||
manifestToPresetFile,
|
manifestToPresetFile,
|
||||||
extractPreset,
|
extractPreset,
|
||||||
validatePreset,
|
|
||||||
loadPreset,
|
|
||||||
saveManifest,
|
saveManifest,
|
||||||
isPresetInstalled,
|
isPresetInstalled,
|
||||||
downloadPresetToTemp,
|
downloadPresetToTemp,
|
||||||
@@ -20,16 +18,15 @@ import {
|
|||||||
type PresetFile,
|
type PresetFile,
|
||||||
type ManifestFile,
|
type ManifestFile,
|
||||||
type PresetMetadata,
|
type PresetMetadata,
|
||||||
MergeStrategy
|
|
||||||
} from "@CCR/shared";
|
} from "@CCR/shared";
|
||||||
|
import fastifyMultipart from "@fastify/multipart";
|
||||||
|
import AdmZip from "adm-zip";
|
||||||
|
|
||||||
export const createServer = async (config: any): Promise<any> => {
|
export const createServer = async (config: any): Promise<any> => {
|
||||||
const server = new Server(config);
|
const server = new Server(config);
|
||||||
const app = server.app;
|
const app = server.app;
|
||||||
|
|
||||||
// Register multipart plugin for file uploads (dynamic import)
|
app.register(fastifyMultipart, {
|
||||||
const fastifyMultipart = await import('@fastify/multipart');
|
|
||||||
app.register(fastifyMultipart.default, {
|
|
||||||
limits: {
|
limits: {
|
||||||
fileSize: 50 * 1024 * 1024, // 50MB
|
fileSize: 50 * 1024 * 1024, // 50MB
|
||||||
},
|
},
|
||||||
@@ -171,8 +168,6 @@ export const createServer = async (config: any): Promise<any> => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ========== Preset 相关 API ==========
|
|
||||||
|
|
||||||
// 获取预设列表
|
// 获取预设列表
|
||||||
app.get("/api/presets", async (req: any, reply: any) => {
|
app.get("/api/presets", async (req: any, reply: any) => {
|
||||||
try {
|
try {
|
||||||
@@ -435,8 +430,13 @@ export const createServer = async (config: any): Promise<any> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 解析 GitHub 仓库 URL
|
// 解析 GitHub 仓库 URL
|
||||||
// 支持格式: https://github.com/owner/repo 或 https://github.com/owner/repo.git
|
// 支持格式:
|
||||||
const githubRepoMatch = repo.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
|
// - owner/repo (简短格式,来自市场)
|
||||||
|
// - github.com/owner/repo
|
||||||
|
// - https://github.com/owner/repo
|
||||||
|
// - https://github.com/owner/repo.git
|
||||||
|
// - git@github.com:owner/repo.git
|
||||||
|
const githubRepoMatch = repo.match(/(?:github\.com[:/]|^)([^/]+)\/([^/\s#]+?)(?:\.git)?$/);
|
||||||
if (!githubRepoMatch) {
|
if (!githubRepoMatch) {
|
||||||
reply.status(400).send({ error: "Invalid GitHub repository URL" });
|
reply.status(400).send({ error: "Invalid GitHub repository URL" });
|
||||||
return;
|
return;
|
||||||
@@ -484,7 +484,6 @@ export const createServer = async (config: any): Promise<any> => {
|
|||||||
|
|
||||||
// 辅助函数:从 ZIP 加载预设
|
// 辅助函数:从 ZIP 加载预设
|
||||||
async function loadPresetFromZip(zipFile: string): Promise<PresetFile> {
|
async function loadPresetFromZip(zipFile: string): Promise<PresetFile> {
|
||||||
const AdmZip = (await import('adm-zip')).default;
|
|
||||||
const zip = new AdmZip(zipFile);
|
const zip = new AdmZip(zipFile);
|
||||||
|
|
||||||
// 首先尝试在根目录查找 manifest.json
|
// 首先尝试在根目录查找 manifest.json
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ export * from './preset/install';
|
|||||||
export * from './preset/export';
|
export * from './preset/export';
|
||||||
export * from './preset/readPreset';
|
export * from './preset/readPreset';
|
||||||
export * from './preset/schema';
|
export * from './preset/schema';
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
import * as fsSync from 'fs';
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import JSON5 from 'json5';
|
import JSON5 from 'json5';
|
||||||
import AdmZip from 'adm-zip';
|
import AdmZip from 'adm-zip';
|
||||||
import { PresetFile, MergeStrategy, RequiredInput, ManifestFile } from './types';
|
import { PresetFile, MergeStrategy, RequiredInput, ManifestFile, PresetInfo } from './types';
|
||||||
import { HOME_DIR } from '../constants';
|
import { HOME_DIR, PRESETS_DIR } from '../constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取预设目录的完整路径
|
* 获取预设目录的完整路径
|
||||||
@@ -48,6 +47,58 @@ export async function extractPreset(sourceZip: string, targetDir: string): Promi
|
|||||||
|
|
||||||
// 解压文件
|
// 解压文件
|
||||||
const zip = new AdmZip(sourceZip);
|
const zip = new AdmZip(sourceZip);
|
||||||
|
const entries = zip.getEntries();
|
||||||
|
|
||||||
|
// 检测是否有单一的根目录(GitHub ZIP 文件通常有这个特征)
|
||||||
|
if (entries.length > 0) {
|
||||||
|
// 获取所有顶层目录
|
||||||
|
const rootDirs = new Set<string>();
|
||||||
|
for (const entry of entries) {
|
||||||
|
const parts = entry.entryName.split('/');
|
||||||
|
if (parts.length > 1) {
|
||||||
|
rootDirs.add(parts[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果只有一个根目录,则去除它
|
||||||
|
if (rootDirs.size === 1) {
|
||||||
|
const singleRoot = Array.from(rootDirs)[0];
|
||||||
|
|
||||||
|
// 检查 manifest.json 是否在根目录下
|
||||||
|
const hasManifestInRoot = entries.some(e =>
|
||||||
|
e.entryName === 'manifest.json' || e.entryName.startsWith(`${singleRoot}/manifest.json`)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasManifestInRoot) {
|
||||||
|
// 将所有文件从根目录下提取出来
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isDirectory) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去除根目录前缀
|
||||||
|
let newPath = entry.entryName;
|
||||||
|
if (newPath.startsWith(`${singleRoot}/`)) {
|
||||||
|
newPath = newPath.substring(singleRoot.length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳过根目录本身
|
||||||
|
if (newPath === '' || newPath === singleRoot) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取文件
|
||||||
|
const targetPath = path.join(targetDir, newPath);
|
||||||
|
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
||||||
|
await fs.writeFile(targetPath, entry.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有单一的根目录,直接解压
|
||||||
zip.extractAllTo(targetDir, true);
|
zip.extractAllTo(targetDir, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,11 +116,11 @@ export async function readManifestFromDir(presetDir: string): Promise<ManifestFi
|
|||||||
* 将manifest转换为PresetFile格式
|
* 将manifest转换为PresetFile格式
|
||||||
*/
|
*/
|
||||||
export function manifestToPresetFile(manifest: ManifestFile): PresetFile {
|
export function manifestToPresetFile(manifest: ManifestFile): PresetFile {
|
||||||
const { Providers, Router, PORT, HOST, API_TIMEOUT_MS, PROXY_URL, LOG, LOG_LEVEL, StatusLine, NON_INTERACTIVE_MODE, requiredInputs, ...metadata } = manifest;
|
const { Providers, Router, StatusLine, NON_INTERACTIVE_MODE, schema, ...metadata } = manifest;
|
||||||
return {
|
return {
|
||||||
metadata,
|
metadata,
|
||||||
config: { Providers, Router, PORT, HOST, API_TIMEOUT_MS, PROXY_URL, LOG, LOG_LEVEL, StatusLine, NON_INTERACTIVE_MODE },
|
config: { Providers, Router, StatusLine, NON_INTERACTIVE_MODE },
|
||||||
requiredInputs,
|
schema,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,3 +288,55 @@ export async function isPresetInstalled(presetName: string): Promise<boolean> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列出所有已安装的预设
|
||||||
|
* @returns PresetInfo 数组
|
||||||
|
*/
|
||||||
|
export async function listPresets(): Promise<PresetInfo[]> {
|
||||||
|
const presetsDir = PRESETS_DIR;
|
||||||
|
const presets: PresetInfo[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.access(presetsDir);
|
||||||
|
} catch {
|
||||||
|
return presets;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取目录下的所有子目录
|
||||||
|
const entries = await fs.readdir(presetsDir, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
const presetName = entry.name;
|
||||||
|
const presetDir = path.join(presetsDir, presetName);
|
||||||
|
const manifestPath = path.join(presetDir, 'manifest.json');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查 manifest.json 是否存在
|
||||||
|
await fs.access(manifestPath);
|
||||||
|
|
||||||
|
// 读取 manifest.json
|
||||||
|
const content = await fs.readFile(manifestPath, 'utf-8');
|
||||||
|
const manifest = JSON5.parse(content) as ManifestFile;
|
||||||
|
|
||||||
|
// 获取目录创建时间
|
||||||
|
const stats = await fs.stat(presetDir);
|
||||||
|
|
||||||
|
presets.push({
|
||||||
|
name: manifest.name || presetName,
|
||||||
|
version: manifest.version,
|
||||||
|
description: manifest.description,
|
||||||
|
author: manifest.author,
|
||||||
|
config: manifestToPresetFile(manifest).config,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// 忽略无效的预设目录(没有 manifest.json 或读取失败)
|
||||||
|
// 可以选择跳过或者添加到列表中标记为错误
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return presets;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,72 +2,24 @@
|
|||||||
* 配置合并策略
|
* 配置合并策略
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MergeStrategy, ProviderConfig, RouterConfig, TransformerConfig, ProviderConflictAction } from './types';
|
import { MergeStrategy, ProviderConfig, RouterConfig, TransformerConfig } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 合并 Provider 配置
|
* 合并 Provider 配置
|
||||||
|
* 如果 provider 已存在则直接覆盖,否则添加
|
||||||
*/
|
*/
|
||||||
async function mergeProviders(
|
function mergeProviders(
|
||||||
existing: ProviderConfig[],
|
existing: ProviderConfig[],
|
||||||
incoming: ProviderConfig[],
|
incoming: ProviderConfig[]
|
||||||
strategy: MergeStrategy,
|
): ProviderConfig[] {
|
||||||
onProviderConflict?: (providerName: string) => Promise<ProviderConflictAction>
|
|
||||||
): Promise<ProviderConfig[]> {
|
|
||||||
const result = [...existing];
|
const result = [...existing];
|
||||||
const existingNames = new Set(existing.map(p => p.name));
|
const existingNames = new Map(existing.map(p => [p.name, result.findIndex(x => x.name === p.name)]));
|
||||||
|
|
||||||
for (const provider of incoming) {
|
for (const provider of incoming) {
|
||||||
if (existingNames.has(provider.name)) {
|
const existingIndex = existingNames.get(provider.name);
|
||||||
// Provider 已存在,需要处理冲突
|
if (existingIndex !== undefined) {
|
||||||
let action: ProviderConflictAction;
|
// Provider 已存在,直接覆盖
|
||||||
|
result[existingIndex] = provider;
|
||||||
if (strategy === MergeStrategy.ASK && onProviderConflict) {
|
|
||||||
action = await onProviderConflict(provider.name);
|
|
||||||
} else if (strategy === MergeStrategy.OVERWRITE) {
|
|
||||||
action = 'overwrite';
|
|
||||||
} else if (strategy === MergeStrategy.MERGE) {
|
|
||||||
action = 'merge';
|
|
||||||
} else {
|
|
||||||
action = 'skip';
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (action) {
|
|
||||||
case 'keep':
|
|
||||||
// 保留现有,不做任何操作
|
|
||||||
break;
|
|
||||||
case 'overwrite':
|
|
||||||
const index = result.findIndex(p => p.name === provider.name);
|
|
||||||
result[index] = provider;
|
|
||||||
break;
|
|
||||||
case 'merge':
|
|
||||||
const existingProvider = result.find(p => p.name === provider.name)!;
|
|
||||||
// 合并模型列表,去重
|
|
||||||
const mergedModels = [...new Set([
|
|
||||||
...existingProvider.models,
|
|
||||||
...provider.models,
|
|
||||||
])];
|
|
||||||
existingProvider.models = mergedModels;
|
|
||||||
|
|
||||||
// 合并 transformer 配置
|
|
||||||
if (provider.transformer) {
|
|
||||||
if (!existingProvider.transformer) {
|
|
||||||
existingProvider.transformer = provider.transformer;
|
|
||||||
} else {
|
|
||||||
// 合并 transformer.use
|
|
||||||
if (provider.transformer.use && existingProvider.transformer.use) {
|
|
||||||
const mergedTransformers = [...new Set([
|
|
||||||
...existingProvider.transformer.use,
|
|
||||||
...provider.transformer.use,
|
|
||||||
])];
|
|
||||||
existingProvider.transformer.use = mergedTransformers as any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'skip':
|
|
||||||
// 跳过,不做任何操作
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 新 Provider,直接添加
|
// 新 Provider,直接添加
|
||||||
result.push(provider);
|
result.push(provider);
|
||||||
@@ -216,7 +168,6 @@ async function mergeOtherConfig(
|
|||||||
* 合并交互回调接口
|
* 合并交互回调接口
|
||||||
*/
|
*/
|
||||||
export interface MergeCallbacks {
|
export interface MergeCallbacks {
|
||||||
onProviderConflict?: (providerName: string) => Promise<ProviderConflictAction>;
|
|
||||||
onRouterConflict?: (key: string, existingValue: any, newValue: any) => Promise<boolean>;
|
onRouterConflict?: (key: string, existingValue: any, newValue: any) => Promise<boolean>;
|
||||||
onTransformerConflict?: (transformerPath: string) => Promise<'keep' | 'overwrite' | 'skip'>;
|
onTransformerConflict?: (transformerPath: string) => Promise<'keep' | 'overwrite' | 'skip'>;
|
||||||
onConfigConflict?: (key: string) => Promise<boolean>;
|
onConfigConflict?: (key: string) => Promise<boolean>;
|
||||||
@@ -240,11 +191,9 @@ export async function mergeConfig(
|
|||||||
|
|
||||||
// 合并 Providers
|
// 合并 Providers
|
||||||
if (presetConfig.Providers) {
|
if (presetConfig.Providers) {
|
||||||
result.Providers = await mergeProviders(
|
result.Providers = mergeProviders(
|
||||||
result.Providers || [],
|
result.Providers || [],
|
||||||
presetConfig.Providers,
|
presetConfig.Providers
|
||||||
strategy,
|
|
||||||
callbacks?.onProviderConflict
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,9 +128,9 @@ function sanitizeObject(
|
|||||||
sanitizedObj[key] = value;
|
sanitizedObj[key] = value;
|
||||||
// 仍然需要记录为必需输入,但使用已有环境变量
|
// 仍然需要记录为必需输入,但使用已有环境变量
|
||||||
const envVarName = extractEnvVarName(value);
|
const envVarName = extractEnvVarName(value);
|
||||||
if (envVarName && !requiredInputs.some(input => input.field === currentPath)) {
|
if (envVarName && !requiredInputs.some(input => input.id === currentPath)) {
|
||||||
requiredInputs.push({
|
requiredInputs.push({
|
||||||
field: currentPath,
|
id: currentPath,
|
||||||
prompt: `Enter ${key}`,
|
prompt: `Enter ${key}`,
|
||||||
placeholder: envVarName,
|
placeholder: envVarName,
|
||||||
});
|
});
|
||||||
@@ -163,7 +163,7 @@ function sanitizeObject(
|
|||||||
|
|
||||||
// 记录为必需输入
|
// 记录为必需输入
|
||||||
requiredInputs.push({
|
requiredInputs.push({
|
||||||
field: currentPath,
|
id: currentPath,
|
||||||
prompt: `Enter ${key}`,
|
prompt: `Enter ${key}`,
|
||||||
placeholder: envVarName,
|
placeholder: envVarName,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -140,12 +140,6 @@ export interface PresetConfigSection {
|
|||||||
Providers?: ProviderConfig[];
|
Providers?: ProviderConfig[];
|
||||||
Router?: RouterConfig;
|
Router?: RouterConfig;
|
||||||
transformers?: TransformerConfig[];
|
transformers?: TransformerConfig[];
|
||||||
PORT?: number;
|
|
||||||
HOST?: string;
|
|
||||||
API_TIMEOUT_MS?: number;
|
|
||||||
PROXY_URL?: string;
|
|
||||||
LOG?: boolean;
|
|
||||||
LOG_LEVEL?: string;
|
|
||||||
StatusLine?: any;
|
StatusLine?: any;
|
||||||
NON_INTERACTIVE_MODE?: boolean;
|
NON_INTERACTIVE_MODE?: boolean;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
@@ -243,5 +237,11 @@ export interface SanitizeResult {
|
|||||||
sanitizedCount: number;
|
sanitizedCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provider 冲突处理动作
|
// Preset 信息(用于列表展示)
|
||||||
export type ProviderConflictAction = 'keep' | 'overwrite' | 'merge' | 'skip';
|
export interface PresetInfo {
|
||||||
|
name: string; // 预设名称
|
||||||
|
version?: string; // 版本号
|
||||||
|
description?: string; // 描述
|
||||||
|
author?: string; // 作者
|
||||||
|
config: PresetConfigSection;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@monaco-editor/react": "^4.7.0",
|
"@monaco-editor/react": "^4.7.0",
|
||||||
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.14",
|
"@radix-ui/react-dialog": "^1.1.14",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-popover": "^1.1.14",
|
"@radix-ui/react-popover": "^1.1.14",
|
||||||
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@radix-ui/react-switch": "^1.2.5",
|
"@radix-ui/react-switch": "^1.2.5",
|
||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
|
|||||||
@@ -144,10 +144,45 @@ export function Presets() {
|
|||||||
const handleInstallFromMarket = async (preset: MarketPreset) => {
|
const handleInstallFromMarket = async (preset: MarketPreset) => {
|
||||||
try {
|
try {
|
||||||
setInstallingFromMarket(preset.id);
|
setInstallingFromMarket(preset.id);
|
||||||
|
|
||||||
|
// 第一步:安装预设(解压到目录)
|
||||||
await api.installPresetFromGitHub(preset.repo, preset.name);
|
await api.installPresetFromGitHub(preset.repo, preset.name);
|
||||||
setToast({ message: t('presets.preset_installed'), type: 'success' });
|
|
||||||
setMarketDialogOpen(false);
|
// 第二步:获取预设详情(检查是否需要配置)
|
||||||
await loadPresets();
|
try {
|
||||||
|
const detail = await api.getPreset(preset.name);
|
||||||
|
const presetDetail: PresetDetail = { ...preset, ...detail };
|
||||||
|
|
||||||
|
// 检查是否需要配置
|
||||||
|
if (detail.schema && detail.schema.length > 0) {
|
||||||
|
// 需要配置,打开配置对话框
|
||||||
|
setSelectedPreset(presetDetail);
|
||||||
|
|
||||||
|
// 初始化默认值
|
||||||
|
const initialValues: Record<string, any> = {};
|
||||||
|
for (const input of detail.schema) {
|
||||||
|
initialValues[input.id] = input.defaultValue ?? '';
|
||||||
|
}
|
||||||
|
setSecrets(initialValues);
|
||||||
|
|
||||||
|
// 关闭市场对话框,打开详情对话框
|
||||||
|
setMarketDialogOpen(false);
|
||||||
|
setDetailDialogOpen(true);
|
||||||
|
|
||||||
|
setToast({ message: t('presets.preset_installed_config_required'), type: 'warning' });
|
||||||
|
} else {
|
||||||
|
// 不需要配置,直接完成
|
||||||
|
setToast({ message: t('presets.preset_installed'), type: 'success' });
|
||||||
|
setMarketDialogOpen(false);
|
||||||
|
await loadPresets();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 获取详情失败,但安装成功了,刷新列表
|
||||||
|
console.error('Failed to get preset details after installation:', error);
|
||||||
|
setToast({ message: t('presets.preset_installed'), type: 'success' });
|
||||||
|
setMarketDialogOpen(false);
|
||||||
|
await loadPresets();
|
||||||
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Failed to install preset:', error);
|
console.error('Failed to install preset:', error);
|
||||||
setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' });
|
setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' });
|
||||||
@@ -214,21 +249,81 @@ export function Presets() {
|
|||||||
try {
|
try {
|
||||||
setIsInstalling(true);
|
setIsInstalling(true);
|
||||||
|
|
||||||
if (installMethod === 'url' && installUrl) {
|
// 验证输入
|
||||||
await api.installPresetFromUrl(installUrl, installName || undefined);
|
if (installMethod === 'url' && !installUrl) {
|
||||||
} else if (installMethod === 'file' && installFile) {
|
setToast({ message: t('presets.please_provide_url'), type: 'warning' });
|
||||||
await api.uploadPresetFile(installFile, installName || undefined);
|
return;
|
||||||
} else {
|
}
|
||||||
setToast({ message: t('presets.please_provide_file_or_url'), type: 'warning' });
|
if (installMethod === 'file' && !installFile) {
|
||||||
|
setToast({ message: t('presets.please_provide_file'), type: 'warning' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setToast({ message: t('presets.preset_installed'), type: 'success' });
|
// 确定预设名称
|
||||||
setInstallDialogOpen(false);
|
const presetName = installName || (
|
||||||
setInstallUrl('');
|
installMethod === 'file'
|
||||||
setInstallFile(null);
|
? installFile!.name.replace('.ccrsets', '')
|
||||||
setInstallName('');
|
: installUrl!.split('/').pop()!.replace('.ccrsets', '')
|
||||||
await loadPresets();
|
);
|
||||||
|
|
||||||
|
// 第一步:安装预设(解压到目录)
|
||||||
|
if (installMethod === 'url' && installUrl) {
|
||||||
|
await api.installPresetFromUrl(installUrl, presetName);
|
||||||
|
} else if (installMethod === 'file' && installFile) {
|
||||||
|
await api.uploadPresetFile(installFile, presetName);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二步:获取预设详情(检查是否需要配置)
|
||||||
|
try {
|
||||||
|
const detail = await api.getPreset(presetName);
|
||||||
|
|
||||||
|
// 检查是否需要配置
|
||||||
|
if (detail.schema && detail.schema.length > 0) {
|
||||||
|
// 需要配置,打开配置对话框
|
||||||
|
setSelectedPreset({
|
||||||
|
id: presetName,
|
||||||
|
name: presetName,
|
||||||
|
version: detail.version || '1.0.0',
|
||||||
|
installed: true,
|
||||||
|
...detail
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化默认值
|
||||||
|
const initialValues: Record<string, any> = {};
|
||||||
|
for (const input of detail.schema) {
|
||||||
|
initialValues[input.id] = input.defaultValue ?? '';
|
||||||
|
}
|
||||||
|
setSecrets(initialValues);
|
||||||
|
|
||||||
|
// 关闭安装对话框,打开详情对话框
|
||||||
|
setInstallDialogOpen(false);
|
||||||
|
setInstallUrl('');
|
||||||
|
setInstallFile(null);
|
||||||
|
setInstallName('');
|
||||||
|
setDetailDialogOpen(true);
|
||||||
|
|
||||||
|
setToast({ message: t('presets.preset_installed_config_required'), type: 'warning' });
|
||||||
|
} else {
|
||||||
|
// 不需要配置,直接完成
|
||||||
|
setToast({ message: t('presets.preset_installed'), type: 'success' });
|
||||||
|
setInstallDialogOpen(false);
|
||||||
|
setInstallUrl('');
|
||||||
|
setInstallFile(null);
|
||||||
|
setInstallName('');
|
||||||
|
await loadPresets();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 获取详情失败,但安装成功了,刷新列表
|
||||||
|
console.error('Failed to get preset details after installation:', error);
|
||||||
|
setToast({ message: t('presets.preset_installed'), type: 'success' });
|
||||||
|
setInstallDialogOpen(false);
|
||||||
|
setInstallUrl('');
|
||||||
|
setInstallFile(null);
|
||||||
|
setInstallName('');
|
||||||
|
await loadPresets();
|
||||||
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Failed to install preset:', error);
|
console.error('Failed to install preset:', error);
|
||||||
setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' });
|
setToast({ message: t('presets.preset_install_failed', { error: error.message }), type: 'error' });
|
||||||
@@ -262,6 +357,8 @@ export function Presets() {
|
|||||||
setToast({ message: t('presets.preset_applied'), type: 'success' });
|
setToast({ message: t('presets.preset_applied'), type: 'success' });
|
||||||
setDetailDialogOpen(false);
|
setDetailDialogOpen(false);
|
||||||
setSecrets({});
|
setSecrets({});
|
||||||
|
// 刷新预设列表
|
||||||
|
await loadPresets();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Failed to apply preset:', error);
|
console.error('Failed to apply preset:', error);
|
||||||
setToast({ message: t('presets.preset_apply_failed', { error: error.message }), type: 'error' });
|
setToast({ message: t('presets.preset_apply_failed', { error: error.message }), type: 'error' });
|
||||||
@@ -443,7 +540,7 @@ export function Presets() {
|
|||||||
)}
|
)}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex-1 overflow-y-auto py-4">
|
<div className="flex-1 overflow-y-auto py-4 px-2">
|
||||||
{selectedPreset?.description && (
|
{selectedPreset?.description && (
|
||||||
<p className="text-gray-700 mb-4">{selectedPreset.description}</p>
|
<p className="text-gray-700 mb-4">{selectedPreset.description}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export function DynamicConfigForm({
|
|||||||
const visible = new Set<string>();
|
const visible = new Set<string>();
|
||||||
|
|
||||||
for (const field of schema) {
|
for (const field of schema) {
|
||||||
if (shouldShowField(field, values)) {
|
if (shouldShowField(field)) {
|
||||||
visible.add(field.id);
|
visible.add(field.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,7 +334,7 @@ export function DynamicConfigForm({
|
|||||||
{field.type === 'select' && (
|
{field.type === 'select' && (
|
||||||
<Select
|
<Select
|
||||||
value={values[field.id] || ''}
|
value={values[field.id] || ''}
|
||||||
onValueChange={(value) => updateValue(field.id, value)}
|
onValueChange={(value: string) => updateValue(field.id, value)}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
<SelectTrigger id={`field-${field.id}`}>
|
<SelectTrigger id={`field-${field.id}`}>
|
||||||
@@ -367,9 +367,9 @@ export function DynamicConfigForm({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
id={`field-${field.id}-${option.value}`}
|
id={`field-${field.id}-${option.value}`}
|
||||||
checked={Array.isArray(values[field.id]) && values[field.id].includes(option.value)}
|
checked={Array.isArray(values[field.id]) && values[field.id].includes(option.value)}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked: boolean | 'indeterminate') => {
|
||||||
const current = Array.isArray(values[field.id]) ? values[field.id] : [];
|
const current = Array.isArray(values[field.id]) ? values[field.id] : [];
|
||||||
if (checked) {
|
if (checked === true) {
|
||||||
updateValue(field.id, [...current, option.value]);
|
updateValue(field.id, [...current, option.value]);
|
||||||
} else {
|
} else {
|
||||||
updateValue(field.id, current.filter((v: any) => v !== option.value));
|
updateValue(field.id, current.filter((v: any) => v !== option.value));
|
||||||
@@ -397,7 +397,7 @@ export function DynamicConfigForm({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
id={`field-${field.id}`}
|
id={`field-${field.id}`}
|
||||||
checked={values[field.id] || false}
|
checked={values[field.id] || false}
|
||||||
onCheckedChange={(checked) => updateValue(field.id, checked)}
|
onCheckedChange={(checked: boolean | 'indeterminate') => updateValue(field.id, checked)}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
<Label htmlFor={`field-${field.id}`} className="text-sm font-normal cursor-pointer">
|
<Label htmlFor={`field-${field.id}`} className="text-sm font-normal cursor-pointer">
|
||||||
@@ -412,7 +412,7 @@ export function DynamicConfigForm({
|
|||||||
id={`field-${field.id}`}
|
id={`field-${field.id}`}
|
||||||
placeholder={field.placeholder}
|
placeholder={field.placeholder}
|
||||||
value={values[field.id] || ''}
|
value={values[field.id] || ''}
|
||||||
onChange={(e) => updateValue(field.id, e.target.value)}
|
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => updateValue(field.id, e.target.value)}
|
||||||
rows={field.rows || 5}
|
rows={field.rows || 5}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class ApiClient {
|
|||||||
localStorage.removeItem('apiKey');
|
localStorage.removeItem('apiKey');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update temp API key
|
// Update temp API key
|
||||||
setTempApiKey(tempApiKey: string | null) {
|
setTempApiKey(tempApiKey: string | null) {
|
||||||
this.tempApiKey = tempApiKey;
|
this.tempApiKey = tempApiKey;
|
||||||
@@ -56,25 +56,25 @@ class ApiClient {
|
|||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use temp API key if available, otherwise use regular API key
|
// Use temp API key if available, otherwise use regular API key
|
||||||
if (this.tempApiKey) {
|
if (this.tempApiKey) {
|
||||||
headers['X-Temp-API-Key'] = this.tempApiKey;
|
headers['X-Temp-API-Key'] = this.tempApiKey;
|
||||||
} else if (this.apiKey) {
|
} else if (this.apiKey) {
|
||||||
headers['X-API-Key'] = this.apiKey;
|
headers['X-API-Key'] = this.apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentType) {
|
if (contentType) {
|
||||||
headers['Content-Type'] = contentType;
|
headers['Content-Type'] = contentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic fetch wrapper with base URL and authentication
|
// Generic fetch wrapper with base URL and authentication
|
||||||
private async apiFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
private async apiFetch<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
||||||
const url = `${this.baseUrl}${endpoint}`;
|
const url = `${this.baseUrl}${endpoint}`;
|
||||||
|
|
||||||
const config: RequestInit = {
|
const config: RequestInit = {
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -82,10 +82,10 @@ class ApiClient {
|
|||||||
...options.headers,
|
...options.headers,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, config);
|
const response = await fetch(url, config);
|
||||||
|
|
||||||
// Handle 401 Unauthorized responses
|
// Handle 401 Unauthorized responses
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
// Remove API key when it's invalid
|
// Remove API key when it's invalid
|
||||||
@@ -101,11 +101,11 @@ class ApiClient {
|
|||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.status === 204) {
|
if (response.status === 204) {
|
||||||
return {} as T;
|
return {} as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
return text ? JSON.parse(text) : ({} as T);
|
return text ? JSON.parse(text) : ({} as T);
|
||||||
|
|
||||||
@@ -139,9 +139,10 @@ class ApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DELETE request
|
// DELETE request
|
||||||
async delete<T>(endpoint: string): Promise<T> {
|
async delete<T>(endpoint: string, body?: any): Promise<T> {
|
||||||
return this.apiFetch<T>(endpoint, {
|
return this.apiFetch<T>(endpoint, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
|
body: JSON.stringify(body || {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,87 +151,87 @@ class ApiClient {
|
|||||||
async getConfig(): Promise<Config> {
|
async getConfig(): Promise<Config> {
|
||||||
return this.get<Config>('/config');
|
return this.get<Config>('/config');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update entire configuration
|
// Update entire configuration
|
||||||
async updateConfig(config: Config): Promise<Config> {
|
async updateConfig(config: Config): Promise<Config> {
|
||||||
return this.post<Config>('/config', config);
|
return this.post<Config>('/config', config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get providers
|
// Get providers
|
||||||
async getProviders(): Promise<Provider[]> {
|
async getProviders(): Promise<Provider[]> {
|
||||||
return this.get<Provider[]>('/api/providers');
|
return this.get<Provider[]>('/api/providers');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new provider
|
// Add a new provider
|
||||||
async addProvider(provider: Provider): Promise<Provider> {
|
async addProvider(provider: Provider): Promise<Provider> {
|
||||||
return this.post<Provider>('/api/providers', provider);
|
return this.post<Provider>('/api/providers', provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update a provider
|
// Update a provider
|
||||||
async updateProvider(index: number, provider: Provider): Promise<Provider> {
|
async updateProvider(index: number, provider: Provider): Promise<Provider> {
|
||||||
return this.post<Provider>(`/api/providers/${index}`, provider);
|
return this.post<Provider>(`/api/providers/${index}`, provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete a provider
|
// Delete a provider
|
||||||
async deleteProvider(index: number): Promise<void> {
|
async deleteProvider(index: number): Promise<void> {
|
||||||
return this.delete<void>(`/api/providers/${index}`);
|
return this.delete<void>(`/api/providers/${index}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get transformers
|
// Get transformers
|
||||||
async getTransformers(): Promise<Transformer[]> {
|
async getTransformers(): Promise<Transformer[]> {
|
||||||
return this.get<Transformer[]>('/api/transformers');
|
return this.get<Transformer[]>('/api/transformers');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new transformer
|
// Add a new transformer
|
||||||
async addTransformer(transformer: Transformer): Promise<Transformer> {
|
async addTransformer(transformer: Transformer): Promise<Transformer> {
|
||||||
return this.post<Transformer>('/api/transformers', transformer);
|
return this.post<Transformer>('/api/transformers', transformer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update a transformer
|
// Update a transformer
|
||||||
async updateTransformer(index: number, transformer: Transformer): Promise<Transformer> {
|
async updateTransformer(index: number, transformer: Transformer): Promise<Transformer> {
|
||||||
return this.post<Transformer>(`/api/transformers/${index}`, transformer);
|
return this.post<Transformer>(`/api/transformers/${index}`, transformer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete a transformer
|
// Delete a transformer
|
||||||
async deleteTransformer(index: number): Promise<void> {
|
async deleteTransformer(index: number): Promise<void> {
|
||||||
return this.delete<void>(`/api/transformers/${index}`);
|
return this.delete<void>(`/api/transformers/${index}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get configuration (new endpoint)
|
// Get configuration (new endpoint)
|
||||||
async getConfigNew(): Promise<Config> {
|
async getConfigNew(): Promise<Config> {
|
||||||
return this.get<Config>('/config');
|
return this.get<Config>('/config');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save configuration (new endpoint)
|
// Save configuration (new endpoint)
|
||||||
async saveConfig(config: Config): Promise<unknown> {
|
async saveConfig(config: Config): Promise<unknown> {
|
||||||
return this.post<Config>('/config', config);
|
return this.post<Config>('/config', config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart service
|
// Restart service
|
||||||
async restartService(): Promise<unknown> {
|
async restartService(): Promise<unknown> {
|
||||||
return this.post<void>('/restart', {});
|
return this.post<void>('/restart', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for updates
|
// Check for updates
|
||||||
async checkForUpdates(): Promise<{ hasUpdate: boolean; latestVersion?: string; changelog?: string }> {
|
async checkForUpdates(): Promise<{ hasUpdate: boolean; latestVersion?: string; changelog?: string }> {
|
||||||
return this.get<{ hasUpdate: boolean; latestVersion?: string; changelog?: string }>('/update/check');
|
return this.get<{ hasUpdate: boolean; latestVersion?: string; changelog?: string }>('/update/check');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform update
|
// Perform update
|
||||||
async performUpdate(): Promise<{ success: boolean; message: string }> {
|
async performUpdate(): Promise<{ success: boolean; message: string }> {
|
||||||
return this.post<{ success: boolean; message: string }>('/api/update/perform', {});
|
return this.post<{ success: boolean; message: string }>('/api/update/perform', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get log files list
|
// Get log files list
|
||||||
async getLogFiles(): Promise<Array<{ name: string; path: string; size: number; lastModified: string }>> {
|
async getLogFiles(): Promise<Array<{ name: string; path: string; size: number; lastModified: string }>> {
|
||||||
return this.get<Array<{ name: string; path: string; size: number; lastModified: string }>>('/logs/files');
|
return this.get<Array<{ name: string; path: string; size: number; lastModified: string }>>('/logs/files');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get logs from specific file
|
// Get logs from specific file
|
||||||
async getLogs(filePath: string): Promise<string[]> {
|
async getLogs(filePath: string): Promise<string[]> {
|
||||||
return this.get<string[]>(`/logs?file=${encodeURIComponent(filePath)}`);
|
return this.get<string[]>(`/logs?file=${encodeURIComponent(filePath)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear logs from specific file
|
// Clear logs from specific file
|
||||||
async clearLogs(filePath: string): Promise<void> {
|
async clearLogs(filePath: string): Promise<void> {
|
||||||
return this.delete<void>(`/logs?file=${encodeURIComponent(filePath)}`);
|
return this.delete<void>(`/logs?file=${encodeURIComponent(filePath)}`);
|
||||||
@@ -300,7 +301,7 @@ class ApiClient {
|
|||||||
|
|
||||||
// Delete preset
|
// Delete preset
|
||||||
async deletePreset(name: string): Promise<any> {
|
async deletePreset(name: string): Promise<any> {
|
||||||
return this.delete<any>(`/presets/${encodeURIComponent(name)}`);
|
return this.delete<any>(`/presets/${encodeURIComponent(name)}`, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get market presets
|
// Get market presets
|
||||||
@@ -318,4 +319,4 @@ class ApiClient {
|
|||||||
export const api = new ApiClient();
|
export const api = new ApiClient();
|
||||||
|
|
||||||
// Export the class for creating custom instances
|
// Export the class for creating custom instances
|
||||||
export default ApiClient;
|
export default ApiClient;
|
||||||
|
|||||||
@@ -282,6 +282,9 @@
|
|||||||
"load_presets_failed": "Failed to load presets",
|
"load_presets_failed": "Failed to load presets",
|
||||||
"load_preset_details_failed": "Failed to load preset details",
|
"load_preset_details_failed": "Failed to load preset details",
|
||||||
"please_fill_field": "Please fill in {{field}}",
|
"please_fill_field": "Please fill in {{field}}",
|
||||||
"load_market_failed": "Failed to load market presets"
|
"load_market_failed": "Failed to load market presets",
|
||||||
|
"preset_installed_config_required": "Preset installed, please complete configuration",
|
||||||
|
"please_provide_file": "Please provide a preset file",
|
||||||
|
"please_provide_url": "Please provide a preset URL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,6 +282,9 @@
|
|||||||
"load_presets_failed": "加载预设失败",
|
"load_presets_failed": "加载预设失败",
|
||||||
"load_preset_details_failed": "加载预设详情失败",
|
"load_preset_details_failed": "加载预设详情失败",
|
||||||
"please_fill_field": "请填写 {{field}}",
|
"please_fill_field": "请填写 {{field}}",
|
||||||
"load_market_failed": "加载市场预设失败"
|
"load_market_failed": "加载市场预设失败",
|
||||||
|
"preset_installed_config_required": "预设已安装,请完成配置",
|
||||||
|
"please_provide_file": "请提供预设文件",
|
||||||
|
"please_provide_url": "请提供预设 URL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
216
pnpm-lock.yaml
generated
216
pnpm-lock.yaml
generated
@@ -62,7 +62,7 @@ importers:
|
|||||||
version: 10.4.23(postcss@8.5.6)
|
version: 10.4.23(postcss@8.5.6)
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: '3'
|
specifier: '3'
|
||||||
version: 3.4.19
|
version: 3.4.19(tsx@4.21.0)
|
||||||
|
|
||||||
packages/cli:
|
packages/cli:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -107,6 +107,55 @@ importers:
|
|||||||
specifier: ^5.8.2
|
specifier: ^5.8.2
|
||||||
version: 5.8.3
|
version: 5.8.3
|
||||||
|
|
||||||
|
packages/core:
|
||||||
|
dependencies:
|
||||||
|
'@anthropic-ai/sdk':
|
||||||
|
specifier: ^0.54.0
|
||||||
|
version: 0.54.0
|
||||||
|
'@fastify/cors':
|
||||||
|
specifier: ^11.0.1
|
||||||
|
version: 11.1.0
|
||||||
|
'@google/genai':
|
||||||
|
specifier: ^1.7.0
|
||||||
|
version: 1.24.0
|
||||||
|
dotenv:
|
||||||
|
specifier: ^16.5.0
|
||||||
|
version: 16.6.1
|
||||||
|
fastify:
|
||||||
|
specifier: ^5.4.0
|
||||||
|
version: 5.6.1
|
||||||
|
google-auth-library:
|
||||||
|
specifier: ^10.1.0
|
||||||
|
version: 10.4.0
|
||||||
|
json5:
|
||||||
|
specifier: ^2.2.3
|
||||||
|
version: 2.2.3
|
||||||
|
jsonrepair:
|
||||||
|
specifier: ^3.13.0
|
||||||
|
version: 3.13.1
|
||||||
|
openai:
|
||||||
|
specifier: ^5.6.0
|
||||||
|
version: 5.23.2(ws@8.18.3)
|
||||||
|
undici:
|
||||||
|
specifier: ^7.10.0
|
||||||
|
version: 7.16.0
|
||||||
|
uuid:
|
||||||
|
specifier: ^11.1.0
|
||||||
|
version: 11.1.0
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^24.0.15
|
||||||
|
version: 24.7.0
|
||||||
|
esbuild:
|
||||||
|
specifier: ^0.25.1
|
||||||
|
version: 0.25.10
|
||||||
|
tsx:
|
||||||
|
specifier: ^4.20.3
|
||||||
|
version: 4.21.0
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.8.2
|
||||||
|
version: 5.8.3
|
||||||
|
|
||||||
packages/server:
|
packages/server:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fastify/multipart':
|
'@fastify/multipart':
|
||||||
@@ -116,8 +165,8 @@ importers:
|
|||||||
specifier: ^8.2.0
|
specifier: ^8.2.0
|
||||||
version: 8.2.0
|
version: 8.2.0
|
||||||
'@musistudio/llms':
|
'@musistudio/llms':
|
||||||
specifier: ^1.0.51
|
specifier: workspace:*
|
||||||
version: 1.0.51(ws@8.18.3)
|
version: link:../core
|
||||||
adm-zip:
|
adm-zip:
|
||||||
specifier: ^0.5.16
|
specifier: ^0.5.16
|
||||||
version: 0.5.16
|
version: 0.5.16
|
||||||
@@ -198,6 +247,9 @@ importers:
|
|||||||
'@monaco-editor/react':
|
'@monaco-editor/react':
|
||||||
specifier: ^4.7.0
|
specifier: ^4.7.0
|
||||||
version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
'@radix-ui/react-checkbox':
|
||||||
|
specifier: ^1.3.3
|
||||||
|
version: 1.3.3(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
'@radix-ui/react-dialog':
|
'@radix-ui/react-dialog':
|
||||||
specifier: ^1.1.14
|
specifier: ^1.1.14
|
||||||
version: 1.1.15(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
version: 1.1.15(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
@@ -207,6 +259,9 @@ importers:
|
|||||||
'@radix-ui/react-popover':
|
'@radix-ui/react-popover':
|
||||||
specifier: ^1.1.14
|
specifier: ^1.1.14
|
||||||
version: 1.1.15(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
version: 1.1.15(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
'@radix-ui/react-select':
|
||||||
|
specifier: ^2.2.6
|
||||||
|
version: 2.2.6(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
'@radix-ui/react-slot':
|
'@radix-ui/react-slot':
|
||||||
specifier: ^1.2.3
|
specifier: ^1.2.3
|
||||||
version: 1.2.4(@types/react@18.3.27)(react@19.2.3)
|
version: 1.2.4(@types/react@18.3.27)(react@19.2.3)
|
||||||
@@ -221,7 +276,7 @@ importers:
|
|||||||
version: 1.2.8(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
version: 1.2.8(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.1.11
|
specifier: ^4.1.11
|
||||||
version: 4.1.18(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))
|
version: 4.1.18(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0))
|
||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
@@ -288,7 +343,7 @@ importers:
|
|||||||
version: 19.2.3(@types/react@18.3.27)
|
version: 19.2.3(@types/react@18.3.27)
|
||||||
'@vitejs/plugin-react':
|
'@vitejs/plugin-react':
|
||||||
specifier: ^4.6.0
|
specifier: ^4.6.0
|
||||||
version: 4.7.0(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))
|
version: 4.7.0(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0))
|
||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.4.21
|
specifier: ^10.4.21
|
||||||
version: 10.4.23(postcss@8.5.6)
|
version: 10.4.23(postcss@8.5.6)
|
||||||
@@ -321,10 +376,10 @@ importers:
|
|||||||
version: 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3)
|
version: 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3)
|
||||||
vite:
|
vite:
|
||||||
specifier: ^7.0.4
|
specifier: ^7.0.4
|
||||||
version: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)
|
version: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)
|
||||||
vite-plugin-singlefile:
|
vite-plugin-singlefile:
|
||||||
specifier: ^2.3.0
|
specifier: ^2.3.0
|
||||||
version: 2.3.0(rollup@4.54.0)(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))
|
version: 2.3.0(rollup@4.54.0)(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0))
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -2117,9 +2172,6 @@ packages:
|
|||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
'@musistudio/llms@1.0.51':
|
|
||||||
resolution: {integrity: sha512-38T9NYrL9/Ombwx4YkGZSryYPtyaSMWJb3aGsepY8m9nbwCST60V+68xKVEaWyri6EfN8D3kenU0W8+okfVSmQ==}
|
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -2155,6 +2207,9 @@ packages:
|
|||||||
'@polka/url@1.0.0-next.29':
|
'@polka/url@1.0.0-next.29':
|
||||||
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||||
|
|
||||||
|
'@radix-ui/number@1.1.1':
|
||||||
|
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
|
||||||
|
|
||||||
'@radix-ui/primitive@1.1.3':
|
'@radix-ui/primitive@1.1.3':
|
||||||
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||||
|
|
||||||
@@ -2171,6 +2226,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-checkbox@1.3.3':
|
||||||
|
resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '18'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-collection@1.1.7':
|
'@radix-ui/react-collection@1.1.7':
|
||||||
resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
|
resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2372,6 +2440,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-select@2.2.6':
|
||||||
|
resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '18'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-slot@1.2.3':
|
'@radix-ui/react-slot@1.2.3':
|
||||||
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
|
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4517,6 +4598,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
get-tsconfig@4.13.0:
|
||||||
|
resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
|
||||||
|
|
||||||
github-slugger@1.5.0:
|
github-slugger@1.5.0:
|
||||||
resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==}
|
resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==}
|
||||||
|
|
||||||
@@ -6703,6 +6787,9 @@ packages:
|
|||||||
resolve-pathname@3.0.0:
|
resolve-pathname@3.0.0:
|
||||||
resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==}
|
resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==}
|
||||||
|
|
||||||
|
resolve-pkg-maps@1.0.0:
|
||||||
|
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||||
|
|
||||||
resolve@1.22.10:
|
resolve@1.22.10:
|
||||||
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
|
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -7245,6 +7332,11 @@ 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==}
|
||||||
|
|
||||||
|
tsx@4.21.0:
|
||||||
|
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
tw-animate-css@1.4.0:
|
tw-animate-css@1.4.0:
|
||||||
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
||||||
|
|
||||||
@@ -10217,28 +10309,6 @@ snapshots:
|
|||||||
react: 19.2.3
|
react: 19.2.3
|
||||||
react-dom: 19.2.3(react@19.2.3)
|
react-dom: 19.2.3(react@19.2.3)
|
||||||
|
|
||||||
'@musistudio/llms@1.0.51(ws@8.18.3)':
|
|
||||||
dependencies:
|
|
||||||
'@anthropic-ai/sdk': 0.54.0
|
|
||||||
'@fastify/cors': 11.1.0
|
|
||||||
'@google/genai': 1.24.0
|
|
||||||
dotenv: 16.6.1
|
|
||||||
fastify: 5.6.1
|
|
||||||
google-auth-library: 10.4.0
|
|
||||||
json5: 2.2.3
|
|
||||||
jsonrepair: 3.13.1
|
|
||||||
openai: 5.23.2(ws@8.18.3)
|
|
||||||
undici: 7.16.0
|
|
||||||
uuid: 11.1.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@modelcontextprotocol/sdk'
|
|
||||||
- bufferutil
|
|
||||||
- encoding
|
|
||||||
- supports-color
|
|
||||||
- utf-8-validate
|
|
||||||
- ws
|
|
||||||
- zod
|
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nodelib/fs.stat': 2.0.5
|
'@nodelib/fs.stat': 2.0.5
|
||||||
@@ -10270,6 +10340,8 @@ snapshots:
|
|||||||
|
|
||||||
'@polka/url@1.0.0-next.29': {}
|
'@polka/url@1.0.0-next.29': {}
|
||||||
|
|
||||||
|
'@radix-ui/number@1.1.1': {}
|
||||||
|
|
||||||
'@radix-ui/primitive@1.1.3': {}
|
'@radix-ui/primitive@1.1.3': {}
|
||||||
|
|
||||||
'@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
'@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||||
@@ -10281,6 +10353,22 @@ snapshots:
|
|||||||
'@types/react': 18.3.27
|
'@types/react': 18.3.27
|
||||||
'@types/react-dom': 19.2.3(@types/react@18.3.27)
|
'@types/react-dom': 19.2.3(@types/react@18.3.27)
|
||||||
|
|
||||||
|
'@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
react: 19.2.3
|
||||||
|
react-dom: 19.2.3(react@19.2.3)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.27
|
||||||
|
'@types/react-dom': 19.2.3(@types/react@18.3.27)
|
||||||
|
|
||||||
'@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
'@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
|
||||||
@@ -10475,6 +10563,35 @@ snapshots:
|
|||||||
'@types/react': 18.3.27
|
'@types/react': 18.3.27
|
||||||
'@types/react-dom': 19.2.3(@types/react@18.3.27)
|
'@types/react-dom': 19.2.3(@types/react@18.3.27)
|
||||||
|
|
||||||
|
'@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/number': 1.1.1
|
||||||
|
'@radix-ui/primitive': 1.1.3
|
||||||
|
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
'@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
'@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
aria-hidden: 1.2.6
|
||||||
|
react: 19.2.3
|
||||||
|
react-dom: 19.2.3(react@19.2.3)
|
||||||
|
react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@19.2.3)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 18.3.27
|
||||||
|
'@types/react-dom': 19.2.3(@types/react@18.3.27)
|
||||||
|
|
||||||
'@radix-ui/react-slot@1.2.3(@types/react@18.3.27)(react@19.2.3)':
|
'@radix-ui/react-slot@1.2.3(@types/react@18.3.27)(react@19.2.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.3)
|
||||||
@@ -10877,12 +10994,12 @@ snapshots:
|
|||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
tailwindcss: 4.1.18
|
tailwindcss: 4.1.18
|
||||||
|
|
||||||
'@tailwindcss/vite@4.1.18(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))':
|
'@tailwindcss/vite@4.1.18(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tailwindcss/node': 4.1.18
|
'@tailwindcss/node': 4.1.18
|
||||||
'@tailwindcss/oxide': 4.1.18
|
'@tailwindcss/oxide': 4.1.18
|
||||||
tailwindcss: 4.1.18
|
tailwindcss: 4.1.18
|
||||||
vite: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)
|
vite: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)
|
||||||
|
|
||||||
'@trysound/sax@0.2.0': {}
|
'@trysound/sax@0.2.0': {}
|
||||||
|
|
||||||
@@ -11218,7 +11335,7 @@ snapshots:
|
|||||||
|
|
||||||
'@vercel/oidc@3.0.5': {}
|
'@vercel/oidc@3.0.5': {}
|
||||||
|
|
||||||
'@vitejs/plugin-react@4.7.0(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1))':
|
'@vitejs/plugin-react@4.7.0(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.28.5
|
'@babel/core': 7.28.5
|
||||||
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
|
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5)
|
||||||
@@ -11226,7 +11343,7 @@ snapshots:
|
|||||||
'@rolldown/pluginutils': 1.0.0-beta.27
|
'@rolldown/pluginutils': 1.0.0-beta.27
|
||||||
'@types/babel__core': 7.20.5
|
'@types/babel__core': 7.20.5
|
||||||
react-refresh: 0.17.0
|
react-refresh: 0.17.0
|
||||||
vite: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)
|
vite: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -12818,6 +12935,10 @@ snapshots:
|
|||||||
|
|
||||||
get-stream@6.0.1: {}
|
get-stream@6.0.1: {}
|
||||||
|
|
||||||
|
get-tsconfig@4.13.0:
|
||||||
|
dependencies:
|
||||||
|
resolve-pkg-maps: 1.0.0
|
||||||
|
|
||||||
github-slugger@1.5.0: {}
|
github-slugger@1.5.0: {}
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
@@ -14646,12 +14767,13 @@ snapshots:
|
|||||||
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
'@csstools/utilities': 2.0.0(postcss@8.5.6)
|
||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
|
|
||||||
postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6):
|
postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
lilconfig: 3.1.3
|
lilconfig: 3.1.3
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
jiti: 1.21.7
|
jiti: 1.21.7
|
||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
|
tsx: 4.21.0
|
||||||
|
|
||||||
postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.104.1(esbuild@0.25.10)):
|
postcss-loader@7.3.4(postcss@8.5.6)(typescript@5.9.3)(webpack@5.104.1(esbuild@0.25.10)):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -15389,6 +15511,8 @@ snapshots:
|
|||||||
|
|
||||||
resolve-pathname@3.0.0: {}
|
resolve-pathname@3.0.0: {}
|
||||||
|
|
||||||
|
resolve-pkg-maps@1.0.0: {}
|
||||||
|
|
||||||
resolve@1.22.10:
|
resolve@1.22.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-core-module: 2.16.1
|
is-core-module: 2.16.1
|
||||||
@@ -15851,7 +15975,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tailwindcss: 4.1.18
|
tailwindcss: 4.1.18
|
||||||
|
|
||||||
tailwindcss@3.4.19:
|
tailwindcss@3.4.19(tsx@4.21.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@alloc/quick-lru': 5.2.0
|
'@alloc/quick-lru': 5.2.0
|
||||||
arg: 5.0.2
|
arg: 5.0.2
|
||||||
@@ -15870,7 +15994,7 @@ snapshots:
|
|||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
postcss-import: 15.1.0(postcss@8.5.6)
|
postcss-import: 15.1.0(postcss@8.5.6)
|
||||||
postcss-js: 4.1.0(postcss@8.5.6)
|
postcss-js: 4.1.0(postcss@8.5.6)
|
||||||
postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)
|
postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.21.0)
|
||||||
postcss-nested: 6.2.0(postcss@8.5.6)
|
postcss-nested: 6.2.0(postcss@8.5.6)
|
||||||
postcss-selector-parser: 6.1.2
|
postcss-selector-parser: 6.1.2
|
||||||
resolve: 1.22.10
|
resolve: 1.22.10
|
||||||
@@ -15999,6 +16123,13 @@ snapshots:
|
|||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
|
tsx@4.21.0:
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.27.2
|
||||||
|
get-tsconfig: 4.13.0
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.3
|
||||||
|
|
||||||
tw-animate-css@1.4.0: {}
|
tw-animate-css@1.4.0: {}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
@@ -16189,13 +16320,13 @@ snapshots:
|
|||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
vfile-message: 4.0.3
|
vfile-message: 4.0.3
|
||||||
|
|
||||||
vite-plugin-singlefile@2.3.0(rollup@4.54.0)(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)):
|
vite-plugin-singlefile@2.3.0(rollup@4.54.0)(vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
micromatch: 4.0.8
|
micromatch: 4.0.8
|
||||||
rollup: 4.54.0
|
rollup: 4.54.0
|
||||||
vite: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)
|
vite: 7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)
|
||||||
|
|
||||||
vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1):
|
vite@7.3.0(@types/node@24.7.0)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.27.2
|
esbuild: 0.27.2
|
||||||
fdir: 6.5.0(picomatch@4.0.3)
|
fdir: 6.5.0(picomatch@4.0.3)
|
||||||
@@ -16209,6 +16340,7 @@ snapshots:
|
|||||||
jiti: 2.6.1
|
jiti: 2.6.1
|
||||||
lightningcss: 1.30.2
|
lightningcss: 1.30.2
|
||||||
terser: 5.44.1
|
terser: 5.44.1
|
||||||
|
tsx: 4.21.0
|
||||||
|
|
||||||
void-elements@3.1.0: {}
|
void-elements@3.1.0: {}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ const { execSync } = require('child_process');
|
|||||||
console.log('Building Claude Code Router (Monorepo)...');
|
console.log('Building Claude Code Router (Monorepo)...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Build shared package first
|
// Build core package first (@musistudio/llms)
|
||||||
|
console.log('Building core package (@musistudio/llms)...');
|
||||||
|
execSync('node scripts/build-core.js', { stdio: 'inherit' });
|
||||||
|
|
||||||
|
// Build shared package
|
||||||
console.log('Building shared package...');
|
console.log('Building shared package...');
|
||||||
execSync('node scripts/build-shared.js', { stdio: 'inherit' });
|
execSync('node scripts/build-shared.js', { stdio: 'inherit' });
|
||||||
|
|
||||||
@@ -15,10 +19,11 @@ try {
|
|||||||
|
|
||||||
console.log('\n✅ Build completed successfully!');
|
console.log('\n✅ Build completed successfully!');
|
||||||
console.log('\nArtifacts are available in packages/*/dist:');
|
console.log('\nArtifacts are available in packages/*/dist:');
|
||||||
console.log(' - packages/shared/dist/ (Shared package)');
|
console.log(' - packages/core/dist/ (Core package: @musistudio/llms)');
|
||||||
console.log(' - packages/cli/dist/ (CLI + UI + tiktoken)');
|
console.log(' - packages/shared/dist/ (Shared package)');
|
||||||
console.log(' - packages/server/dist/ (Server standalone)');
|
console.log(' - packages/cli/dist/ (CLI + UI + tiktoken)');
|
||||||
console.log(' - packages/ui/dist/ (UI standalone)');
|
console.log(' - packages/server/dist/ (Server standalone)');
|
||||||
|
console.log(' - packages/ui/dist/ (UI standalone)');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Build failed:', error.message);
|
console.error('Build failed:', error.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
# 发布脚本
|
# 发布脚本
|
||||||
|
# - Core 包作为 @musistudio/llms npm 包发布
|
||||||
# - CLI 包作为 @CCR/cli npm 包发布
|
# - CLI 包作为 @CCR/cli npm 包发布
|
||||||
# - Server 包发布为 Docker 镜像
|
# - Server 包发布为 Docker 镜像
|
||||||
|
|
||||||
@@ -37,7 +38,39 @@ case "$PUBLISH_TYPE" in
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
# ===========================
|
# ===========================
|
||||||
# 发布 npm 包
|
# 发布 Core npm 包 (@musistudio/llms)
|
||||||
|
# ===========================
|
||||||
|
publish_core_npm() {
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo "发布 npm 包 @musistudio/llms"
|
||||||
|
echo "========================================="
|
||||||
|
|
||||||
|
# 检查是否已登录 npm
|
||||||
|
if ! npm whoami &>/dev/null; then
|
||||||
|
echo "错误: 未登录 npm,请先运行: npm login"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
CORE_DIR="../packages/core"
|
||||||
|
CORE_VERSION=$(node -p "require('../packages/core/package.json').version")
|
||||||
|
|
||||||
|
# 复制 README 到 core 包
|
||||||
|
cp ../README.md "$CORE_DIR/" 2>/dev/null || echo "README.md 不存在,跳过..."
|
||||||
|
cp ../LICENSE "$CORE_DIR/" 2>/dev/null || echo "LICENSE 文件不存在,跳过..."
|
||||||
|
|
||||||
|
# 发布到 npm
|
||||||
|
cd "$CORE_DIR"
|
||||||
|
echo "执行 npm publish..."
|
||||||
|
npm publish --access public
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Core npm 包发布成功!"
|
||||||
|
echo " 包名: @musistudio/llms@${CORE_VERSION}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===========================
|
||||||
|
# 发布 CLI npm 包
|
||||||
# ===========================
|
# ===========================
|
||||||
publish_npm() {
|
publish_npm() {
|
||||||
echo ""
|
echo ""
|
||||||
@@ -138,6 +171,7 @@ publish_docker() {
|
|||||||
# 执行发布
|
# 执行发布
|
||||||
# ===========================
|
# ===========================
|
||||||
if [ "$PUBLISH_TYPE" = "npm" ] || [ "$PUBLISH_TYPE" = "all" ]; then
|
if [ "$PUBLISH_TYPE" = "npm" ] || [ "$PUBLISH_TYPE" = "all" ]; then
|
||||||
|
publish_core_npm
|
||||||
publish_npm
|
publish_npm
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user