mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 优化前端构建分发和模型价格更新机制
1. 前端构建分发优化: - 修改 GitHub Actions workflow,构建前端后推送到 web-dist 分支 - 更新 manage.sh 安装脚本,从 web-dist 分支获取预构建文件 - 解决部分机器无法本地编译前端的问题 - 添加测试脚本 test-web-dist.sh 验证流程 2. 模型价格更新功能: - 添加手动更新模型价格脚本 update-model-pricing.js - 新增 npm run update:pricing 命令 - 在 manage.sh 添加 update-pricing 命令和菜单选项 - 支持备份、进度显示和统计信息 3. 其他改进: - 优化安装流程,减少对 Node.js 环境的依赖 - 提供多种更新模型价格的方式 - 改进错误处理和回退机制 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
73
.github/workflows/auto-release-pipeline.yml
vendored
73
.github/workflows/auto-release-pipeline.yml
vendored
@@ -123,8 +123,6 @@ jobs:
|
||||
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "new_tag=v$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
# 前端构建已移至 Docker 构建流程中
|
||||
|
||||
- name: Update VERSION file
|
||||
if: steps.check.outputs.needs_bump == 'true'
|
||||
run: |
|
||||
@@ -138,6 +136,77 @@ jobs:
|
||||
git add VERSION
|
||||
git commit -m "chore: sync VERSION file with release ${{ steps.next_version.outputs.new_tag }} [skip ci]"
|
||||
|
||||
# 构建前端并推送到 web-dist 分支
|
||||
- name: Setup Node.js
|
||||
if: steps.check.outputs.needs_bump == 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: web/admin-spa/package-lock.json
|
||||
|
||||
- name: Build Frontend
|
||||
if: steps.check.outputs.needs_bump == 'true'
|
||||
run: |
|
||||
echo "Building frontend for version ${{ steps.next_version.outputs.new_version }}..."
|
||||
cd web/admin-spa
|
||||
npm ci
|
||||
npm run build
|
||||
echo "Frontend build completed"
|
||||
|
||||
- name: Push Frontend Build to web-dist Branch
|
||||
if: steps.check.outputs.needs_bump == 'true'
|
||||
run: |
|
||||
# 创建临时目录
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
echo "Using temp directory: $TEMP_DIR"
|
||||
|
||||
# 复制构建产物到临时目录
|
||||
cp -r web/admin-spa/dist/* "$TEMP_DIR/"
|
||||
|
||||
# 检查 web-dist 分支是否存在
|
||||
if git ls-remote --heads origin web-dist | grep -q web-dist; then
|
||||
echo "Checking out existing web-dist branch"
|
||||
git fetch origin web-dist:web-dist
|
||||
git checkout web-dist
|
||||
else
|
||||
echo "Creating new web-dist branch"
|
||||
git checkout --orphan web-dist
|
||||
fi
|
||||
|
||||
# 清空当前目录(保留 .git)
|
||||
git rm -rf . 2>/dev/null || true
|
||||
|
||||
# 复制构建产物
|
||||
cp -r "$TEMP_DIR"/* .
|
||||
|
||||
# 添加 README
|
||||
cat > README.md << 'EOF'
|
||||
# Claude Relay Service - Web Frontend Build
|
||||
|
||||
This branch contains the pre-built frontend assets for Claude Relay Service.
|
||||
|
||||
**DO NOT EDIT FILES IN THIS BRANCH DIRECTLY**
|
||||
|
||||
These files are automatically generated by the CI/CD pipeline.
|
||||
|
||||
Version: ${{ steps.next_version.outputs.new_version }}
|
||||
Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||
EOF
|
||||
|
||||
# 提交并推送
|
||||
git add -A
|
||||
git commit -m "chore: update frontend build for v${{ steps.next_version.outputs.new_version }} [skip ci]"
|
||||
git push origin web-dist --force
|
||||
|
||||
# 切换回主分支
|
||||
git checkout main
|
||||
|
||||
# 清理临时目录
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
echo "Frontend build pushed to web-dist branch successfully"
|
||||
|
||||
- name: Install git-cliff
|
||||
if: steps.check.outputs.needs_bump == 'true'
|
||||
run: |
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"dev": "nodemon src/app.js",
|
||||
"build:web": "cd web/admin-spa && npm run build",
|
||||
"install:web": "cd web/admin-spa && npm install",
|
||||
"update:pricing": "node scripts/update-model-pricing.js",
|
||||
"setup": "node scripts/setup.js",
|
||||
"cli": "node cli/index.js",
|
||||
"init:costs": "node src/cli/initCosts.js",
|
||||
|
||||
@@ -34,4 +34,4 @@ The file contains JSON data with model pricing information including:
|
||||
- Context window sizes
|
||||
- Model capabilities
|
||||
|
||||
Last updated: 2025-07-29
|
||||
Last updated: 2025-08-06
|
||||
@@ -607,9 +607,9 @@
|
||||
"supports_system_messages": true,
|
||||
"supports_tool_choice": true,
|
||||
"search_context_cost_per_query": {
|
||||
"search_context_size_low": 30.0,
|
||||
"search_context_size_medium": 35.0,
|
||||
"search_context_size_high": 50.0
|
||||
"search_context_size_low": 0.025,
|
||||
"search_context_size_medium": 0.0275,
|
||||
"search_context_size_high": 0.03
|
||||
}
|
||||
},
|
||||
"codex-mini-latest": {
|
||||
@@ -3750,7 +3750,7 @@
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 3.3e-06,
|
||||
"output_cost_per_token": 16.5e-06,
|
||||
"output_cost_per_token": 1.65e-05,
|
||||
"litellm_provider": "azure_ai",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
@@ -3764,7 +3764,7 @@
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 3e-06,
|
||||
"output_cost_per_token": 15e-06,
|
||||
"output_cost_per_token": 1.5e-05,
|
||||
"litellm_provider": "azure_ai",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
@@ -3777,7 +3777,7 @@
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 0.25e-06,
|
||||
"input_cost_per_token": 2.5e-07,
|
||||
"output_cost_per_token": 1.27e-06,
|
||||
"litellm_provider": "azure_ai",
|
||||
"mode": "chat",
|
||||
@@ -3792,7 +3792,7 @@
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 0.275e-06,
|
||||
"input_cost_per_token": 2.75e-07,
|
||||
"output_cost_per_token": 1.38e-06,
|
||||
"litellm_provider": "azure_ai",
|
||||
"mode": "chat",
|
||||
@@ -5741,6 +5741,32 @@
|
||||
"supports_reasoning": true,
|
||||
"supports_computer_use": true
|
||||
},
|
||||
"claude-opus-4-1-20250805": {
|
||||
"max_tokens": 32000,
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 32000,
|
||||
"input_cost_per_token": 1.5e-05,
|
||||
"output_cost_per_token": 7.5e-05,
|
||||
"search_context_cost_per_query": {
|
||||
"search_context_size_low": 0.01,
|
||||
"search_context_size_medium": 0.01,
|
||||
"search_context_size_high": 0.01
|
||||
},
|
||||
"cache_creation_input_token_cost": 1.875e-05,
|
||||
"cache_read_input_token_cost": 1.5e-06,
|
||||
"litellm_provider": "anthropic",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 159,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_computer_use": true
|
||||
},
|
||||
"claude-sonnet-4-20250514": {
|
||||
"max_tokens": 64000,
|
||||
"max_input_tokens": 200000,
|
||||
@@ -9039,9 +9065,9 @@
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"vertex_ai/meta/llama-4-scout-17b-16e-instruct-maas": {
|
||||
"max_tokens": 10000000.0,
|
||||
"max_input_tokens": 10000000.0,
|
||||
"max_output_tokens": 10000000.0,
|
||||
"max_tokens": 10000000,
|
||||
"max_input_tokens": 10000000,
|
||||
"max_output_tokens": 10000000,
|
||||
"input_cost_per_token": 2.5e-07,
|
||||
"output_cost_per_token": 7e-07,
|
||||
"litellm_provider": "vertex_ai-llama_models",
|
||||
@@ -9059,9 +9085,9 @@
|
||||
]
|
||||
},
|
||||
"vertex_ai/meta/llama-4-scout-17b-128e-instruct-maas": {
|
||||
"max_tokens": 10000000.0,
|
||||
"max_input_tokens": 10000000.0,
|
||||
"max_output_tokens": 10000000.0,
|
||||
"max_tokens": 10000000,
|
||||
"max_input_tokens": 10000000,
|
||||
"max_output_tokens": 10000000,
|
||||
"input_cost_per_token": 2.5e-07,
|
||||
"output_cost_per_token": 7e-07,
|
||||
"litellm_provider": "vertex_ai-llama_models",
|
||||
@@ -9079,9 +9105,9 @@
|
||||
]
|
||||
},
|
||||
"vertex_ai/meta/llama-4-maverick-17b-128e-instruct-maas": {
|
||||
"max_tokens": 1000000.0,
|
||||
"max_input_tokens": 1000000.0,
|
||||
"max_output_tokens": 1000000.0,
|
||||
"max_tokens": 1000000,
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 1000000,
|
||||
"input_cost_per_token": 3.5e-07,
|
||||
"output_cost_per_token": 1.15e-06,
|
||||
"litellm_provider": "vertex_ai-llama_models",
|
||||
@@ -9099,9 +9125,9 @@
|
||||
]
|
||||
},
|
||||
"vertex_ai/meta/llama-4-maverick-17b-16e-instruct-maas": {
|
||||
"max_tokens": 1000000.0,
|
||||
"max_input_tokens": 1000000.0,
|
||||
"max_output_tokens": 1000000.0,
|
||||
"max_tokens": 1000000,
|
||||
"max_input_tokens": 1000000,
|
||||
"max_output_tokens": 1000000,
|
||||
"input_cost_per_token": 3.5e-07,
|
||||
"output_cost_per_token": 1.15e-06,
|
||||
"litellm_provider": "vertex_ai-llama_models",
|
||||
@@ -9174,7 +9200,7 @@
|
||||
"max_input_tokens": 128000,
|
||||
"max_output_tokens": 2048,
|
||||
"input_cost_per_token": 5e-06,
|
||||
"output_cost_per_token": 16e-06,
|
||||
"output_cost_per_token": 1.6e-05,
|
||||
"litellm_provider": "vertex_ai-llama_models",
|
||||
"mode": "chat",
|
||||
"supports_system_messages": true,
|
||||
@@ -10480,12 +10506,26 @@
|
||||
"supports_tool_choice": true,
|
||||
"supports_prompt_caching": true
|
||||
},
|
||||
"openrouter/bytedance/ui-tars-1.5-7b":{
|
||||
"openrouter/x-ai/grok-4": {
|
||||
"max_tokens": 256000,
|
||||
"max_input_tokens": 256000,
|
||||
"max_output_tokens": 256000,
|
||||
"input_cost_per_token": 3e-06,
|
||||
"output_cost_per_token": 1.5e-05,
|
||||
"litellm_provider": "openrouter",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true,
|
||||
"source": "https://openrouter.ai/x-ai/grok-4",
|
||||
"supports_web_search": true
|
||||
},
|
||||
"openrouter/bytedance/ui-tars-1.5-7b": {
|
||||
"max_tokens": 2048,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 2048,
|
||||
"input_cost_per_token": 0.1e-06,
|
||||
"output_cost_per_token": 0.2e-06,
|
||||
"input_cost_per_token": 1e-07,
|
||||
"output_cost_per_token": 2e-07,
|
||||
"litellm_provider": "openrouter",
|
||||
"mode": "chat",
|
||||
"source": "https://openrouter.ai/api/v1/models/bytedance/ui-tars-1.5-7b",
|
||||
@@ -11164,8 +11204,8 @@
|
||||
"max_tokens": 8192,
|
||||
"max_input_tokens": 8192,
|
||||
"max_output_tokens": 2048,
|
||||
"input_cost_per_token": 0.21e-06,
|
||||
"output_cost_per_token": 0.63e-06,
|
||||
"input_cost_per_token": 2.1e-07,
|
||||
"output_cost_per_token": 6.3e-07,
|
||||
"litellm_provider": "openrouter",
|
||||
"mode": "chat",
|
||||
"supports_tool_choice": true
|
||||
@@ -11877,6 +11917,32 @@
|
||||
"supports_pdf_input": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"anthropic.claude-opus-4-1-20250805-v1:0": {
|
||||
"max_tokens": 32000,
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 32000,
|
||||
"input_cost_per_token": 1.5e-05,
|
||||
"output_cost_per_token": 7.5e-05,
|
||||
"search_context_cost_per_query": {
|
||||
"search_context_size_low": 0.01,
|
||||
"search_context_size_medium": 0.01,
|
||||
"search_context_size_high": 0.01
|
||||
},
|
||||
"cache_creation_input_token_cost": 1.875e-05,
|
||||
"cache_read_input_token_cost": 1.5e-06,
|
||||
"litellm_provider": "bedrock_converse",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 159,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_computer_use": true
|
||||
},
|
||||
"anthropic.claude-opus-4-20250514-v1:0": {
|
||||
"max_tokens": 32000,
|
||||
"max_input_tokens": 200000,
|
||||
@@ -12079,6 +12145,32 @@
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true
|
||||
},
|
||||
"us.anthropic.claude-opus-4-1-20250805-v1:0": {
|
||||
"max_tokens": 32000,
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 32000,
|
||||
"input_cost_per_token": 1.5e-05,
|
||||
"output_cost_per_token": 7.5e-05,
|
||||
"search_context_cost_per_query": {
|
||||
"search_context_size_low": 0.01,
|
||||
"search_context_size_medium": 0.01,
|
||||
"search_context_size_high": 0.01
|
||||
},
|
||||
"cache_creation_input_token_cost": 1.875e-05,
|
||||
"cache_read_input_token_cost": 1.5e-06,
|
||||
"litellm_provider": "bedrock_converse",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 159,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_computer_use": true
|
||||
},
|
||||
"us.anthropic.claude-opus-4-20250514-v1:0": {
|
||||
"max_tokens": 32000,
|
||||
"max_input_tokens": 200000,
|
||||
@@ -12252,6 +12344,32 @@
|
||||
"supports_pdf_input": true,
|
||||
"supports_tool_choice": true
|
||||
},
|
||||
"eu.anthropic.claude-opus-4-1-20250805-v1:0": {
|
||||
"max_tokens": 32000,
|
||||
"max_input_tokens": 200000,
|
||||
"max_output_tokens": 32000,
|
||||
"input_cost_per_token": 1.5e-05,
|
||||
"output_cost_per_token": 7.5e-05,
|
||||
"search_context_cost_per_query": {
|
||||
"search_context_size_low": 0.01,
|
||||
"search_context_size_medium": 0.01,
|
||||
"search_context_size_high": 0.01
|
||||
},
|
||||
"cache_creation_input_token_cost": 1.875e-05,
|
||||
"cache_read_input_token_cost": 1.5e-06,
|
||||
"litellm_provider": "bedrock_converse",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 159,
|
||||
"supports_assistant_prefill": true,
|
||||
"supports_pdf_input": true,
|
||||
"supports_prompt_caching": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_reasoning": true,
|
||||
"supports_computer_use": true
|
||||
},
|
||||
"eu.anthropic.claude-opus-4-20250514-v1:0": {
|
||||
"max_tokens": 32000,
|
||||
"max_input_tokens": 200000,
|
||||
@@ -14749,7 +14867,7 @@
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 16384,
|
||||
"input_cost_per_token": 0.6e-06,
|
||||
"input_cost_per_token": 6e-07,
|
||||
"output_cost_per_token": 2.5e-06,
|
||||
"litellm_provider": "fireworks_ai",
|
||||
"mode": "chat",
|
||||
@@ -14795,6 +14913,58 @@
|
||||
"source": "https://fireworks.ai/pricing",
|
||||
"supports_tool_choice": false
|
||||
},
|
||||
"fireworks_ai/accounts/fireworks/models/glm-4p5": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 128000,
|
||||
"max_output_tokens": 96000,
|
||||
"input_cost_per_token": 5.5e-07,
|
||||
"output_cost_per_token": 2.19e-06,
|
||||
"litellm_provider": "fireworks_ai",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"source": "https://fireworks.ai/models/fireworks/glm-4p5"
|
||||
},
|
||||
"fireworks_ai/accounts/fireworks/models/glm-4p5-air": {
|
||||
"max_tokens": 128000,
|
||||
"max_input_tokens": 128000,
|
||||
"max_output_tokens": 96000,
|
||||
"input_cost_per_token": 2.2e-07,
|
||||
"output_cost_per_token": 8.8e-07,
|
||||
"litellm_provider": "fireworks_ai",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"source": "https://artificialanalysis.ai/models/glm-4-5-air"
|
||||
},
|
||||
"fireworks_ai/accounts/fireworks/models/gpt-oss-120b": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 1.5e-07,
|
||||
"output_cost_per_token": 6e-07,
|
||||
"litellm_provider": "fireworks_ai",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"source": "https://fireworks.ai/pricing"
|
||||
},
|
||||
"fireworks_ai/accounts/fireworks/models/gpt-oss-20b": {
|
||||
"max_tokens": 131072,
|
||||
"max_input_tokens": 131072,
|
||||
"max_output_tokens": 131072,
|
||||
"input_cost_per_token": 5e-08,
|
||||
"output_cost_per_token": 2e-07,
|
||||
"litellm_provider": "fireworks_ai",
|
||||
"mode": "chat",
|
||||
"supports_function_calling": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"source": "https://fireworks.ai/pricing"
|
||||
},
|
||||
"fireworks_ai/nomic-ai/nomic-embed-text-v1.5": {
|
||||
"max_tokens": 8192,
|
||||
"max_input_tokens": 8192,
|
||||
|
||||
@@ -454,13 +454,61 @@ EOF
|
||||
print_info "运行初始化设置..."
|
||||
npm run setup
|
||||
|
||||
# 安装Web界面依赖
|
||||
print_info "安装Web界面依赖..."
|
||||
npm run install:web
|
||||
# 获取预构建的前端文件
|
||||
print_info "获取预构建的前端文件..."
|
||||
|
||||
# 构建前端
|
||||
print_info "构建前端界面..."
|
||||
npm run build:web
|
||||
# 创建目标目录
|
||||
mkdir -p web/admin-spa/dist
|
||||
|
||||
# 从 web-dist 分支获取构建好的文件
|
||||
if git ls-remote --heads origin web-dist | grep -q web-dist; then
|
||||
print_info "从 web-dist 分支下载前端文件..."
|
||||
|
||||
# 创建临时目录用于 clone
|
||||
TEMP_CLONE_DIR=$(mktemp -d)
|
||||
|
||||
# 使用 sparse-checkout 来只获取需要的文件
|
||||
git clone --depth 1 --branch web-dist --single-branch \
|
||||
https://github.com/Wei-Shaw/claude-relay-service.git \
|
||||
"$TEMP_CLONE_DIR" 2>/dev/null || {
|
||||
# 如果 HTTPS 失败,尝试使用当前仓库的 remote URL
|
||||
REPO_URL=$(git config --get remote.origin.url)
|
||||
git clone --depth 1 --branch web-dist --single-branch "$REPO_URL" "$TEMP_CLONE_DIR"
|
||||
}
|
||||
|
||||
# 复制文件到目标目录(排除 .git 和 README.md)
|
||||
rsync -av --exclude='.git' --exclude='README.md' "$TEMP_CLONE_DIR/" web/admin-spa/dist/ 2>/dev/null || {
|
||||
# 如果没有 rsync,使用 cp
|
||||
cp -r "$TEMP_CLONE_DIR"/* web/admin-spa/dist/ 2>/dev/null
|
||||
rm -rf web/admin-spa/dist/.git 2>/dev/null
|
||||
rm -f web/admin-spa/dist/README.md 2>/dev/null
|
||||
}
|
||||
|
||||
# 清理临时目录
|
||||
rm -rf "$TEMP_CLONE_DIR"
|
||||
|
||||
print_success "前端文件下载完成"
|
||||
else
|
||||
print_warning "web-dist 分支不存在,尝试本地构建..."
|
||||
|
||||
# 检查是否有 Node.js 和 npm
|
||||
if command_exists npm; then
|
||||
# 回退到原始构建方式
|
||||
if [ -f "web/admin-spa/package.json" ]; then
|
||||
print_info "开始本地构建前端..."
|
||||
cd web/admin-spa
|
||||
npm install
|
||||
npm run build
|
||||
cd ../..
|
||||
print_success "前端本地构建完成"
|
||||
else
|
||||
print_error "无法找到前端项目文件"
|
||||
fi
|
||||
else
|
||||
print_error "无法获取前端文件,且本地环境不支持构建"
|
||||
print_info "请确保仓库已正确配置 web-dist 分支"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 创建systemd服务文件(Linux)
|
||||
if [[ "$OS" == "debian" || "$OS" == "redhat" || "$OS" == "arch" ]]; then
|
||||
@@ -549,11 +597,65 @@ update_service() {
|
||||
# 更新依赖
|
||||
print_info "更新依赖..."
|
||||
npm install
|
||||
npm run install:web
|
||||
|
||||
# 构建前端
|
||||
print_info "构建前端界面..."
|
||||
npm run build:web
|
||||
# 获取最新的预构建前端文件
|
||||
print_info "更新前端文件..."
|
||||
|
||||
# 创建目标目录
|
||||
mkdir -p web/admin-spa/dist
|
||||
|
||||
# 清理旧的前端文件
|
||||
rm -rf web/admin-spa/dist/*
|
||||
|
||||
# 从 web-dist 分支获取构建好的文件
|
||||
if git ls-remote --heads origin web-dist | grep -q web-dist; then
|
||||
print_info "从 web-dist 分支下载最新前端文件..."
|
||||
|
||||
# 创建临时目录用于 clone
|
||||
TEMP_CLONE_DIR=$(mktemp -d)
|
||||
|
||||
# 使用 sparse-checkout 来只获取需要的文件
|
||||
git clone --depth 1 --branch web-dist --single-branch \
|
||||
https://github.com/Wei-Shaw/claude-relay-service.git \
|
||||
"$TEMP_CLONE_DIR" 2>/dev/null || {
|
||||
# 如果 HTTPS 失败,尝试使用当前仓库的 remote URL
|
||||
REPO_URL=$(git config --get remote.origin.url)
|
||||
git clone --depth 1 --branch web-dist --single-branch "$REPO_URL" "$TEMP_CLONE_DIR"
|
||||
}
|
||||
|
||||
# 复制文件到目标目录(排除 .git 和 README.md)
|
||||
rsync -av --exclude='.git' --exclude='README.md' "$TEMP_CLONE_DIR/" web/admin-spa/dist/ 2>/dev/null || {
|
||||
# 如果没有 rsync,使用 cp
|
||||
cp -r "$TEMP_CLONE_DIR"/* web/admin-spa/dist/ 2>/dev/null
|
||||
rm -rf web/admin-spa/dist/.git 2>/dev/null
|
||||
rm -f web/admin-spa/dist/README.md 2>/dev/null
|
||||
}
|
||||
|
||||
# 清理临时目录
|
||||
rm -rf "$TEMP_CLONE_DIR"
|
||||
|
||||
print_success "前端文件更新完成"
|
||||
else
|
||||
print_warning "web-dist 分支不存在,尝试本地构建..."
|
||||
|
||||
# 检查是否有 Node.js 和 npm
|
||||
if command_exists npm; then
|
||||
# 回退到原始构建方式
|
||||
if [ -f "web/admin-spa/package.json" ]; then
|
||||
print_info "开始本地构建前端..."
|
||||
cd web/admin-spa
|
||||
npm install
|
||||
npm run build
|
||||
cd ../..
|
||||
print_success "前端本地构建完成"
|
||||
else
|
||||
print_error "无法找到前端项目文件"
|
||||
fi
|
||||
else
|
||||
print_error "无法获取前端文件,且本地环境不支持构建"
|
||||
print_info "请确保仓库已正确配置 web-dist 分支"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 启动服务
|
||||
start_service
|
||||
@@ -678,6 +780,36 @@ restart_service() {
|
||||
start_service
|
||||
}
|
||||
|
||||
# 更新模型价格
|
||||
update_model_pricing() {
|
||||
if ! check_installation; then
|
||||
print_error "服务未安装,请先运行: $0 install"
|
||||
return 1
|
||||
fi
|
||||
|
||||
print_info "更新模型价格数据..."
|
||||
|
||||
cd "$APP_DIR"
|
||||
|
||||
# 运行更新脚本
|
||||
if npm run update:pricing; then
|
||||
print_success "模型价格数据更新完成"
|
||||
|
||||
# 显示更新后的信息
|
||||
if [ -f "data/model_pricing.json" ]; then
|
||||
local model_count=$(grep -o '"[^"]*"\s*:' data/model_pricing.json | wc -l)
|
||||
local file_size=$(du -h data/model_pricing.json | cut -f1)
|
||||
echo -e "\n更新信息:"
|
||||
echo -e " 模型数量: ${GREEN}$model_count${NC}"
|
||||
echo -e " 文件大小: ${GREEN}$file_size${NC}"
|
||||
echo -e " 文件位置: $APP_DIR/data/model_pricing.json"
|
||||
fi
|
||||
else
|
||||
print_error "模型价格数据更新失败"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示状态
|
||||
show_status() {
|
||||
echo -e "\n${BLUE}=== Claude Relay Service 状态 ===${NC}"
|
||||
@@ -758,6 +890,7 @@ show_help() {
|
||||
echo " stop - 停止服务"
|
||||
echo " restart - 重启服务"
|
||||
echo " status - 查看状态"
|
||||
echo " update-pricing - 更新模型价格数据"
|
||||
echo " symlink - 创建 crs 快捷命令"
|
||||
echo " help - 显示帮助"
|
||||
echo ""
|
||||
@@ -834,10 +967,11 @@ show_menu() {
|
||||
echo " 3) 停止服务"
|
||||
echo " 4) 重启服务"
|
||||
echo " 5) 更新服务"
|
||||
echo " 6) 卸载服务"
|
||||
echo " 7) 退出"
|
||||
echo " 6) 更新模型价格"
|
||||
echo " 7) 卸载服务"
|
||||
echo " 8) 退出"
|
||||
echo ""
|
||||
echo -n "请输入选项 [1-7]: "
|
||||
echo -n "请输入选项 [1-8]: "
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -924,13 +1058,19 @@ handle_menu_choice() {
|
||||
read
|
||||
;;
|
||||
6)
|
||||
echo ""
|
||||
update_model_pricing
|
||||
echo -n "按回车键继续..."
|
||||
read
|
||||
;;
|
||||
7)
|
||||
echo ""
|
||||
uninstall_service
|
||||
if [ $? -eq 0 ]; then
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
7)
|
||||
8)
|
||||
echo "退出管理工具"
|
||||
exit 0
|
||||
;;
|
||||
@@ -1109,6 +1249,9 @@ main() {
|
||||
status)
|
||||
show_status
|
||||
;;
|
||||
update-pricing)
|
||||
update_model_pricing
|
||||
;;
|
||||
symlink)
|
||||
# 单独创建软链接
|
||||
# 确保 APP_DIR 已设置
|
||||
|
||||
227
scripts/test-web-dist.sh
Normal file
227
scripts/test-web-dist.sh
Normal file
@@ -0,0 +1,227 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 测试 web-dist 分支构建和获取流程
|
||||
# 用于验证 CI/CD 流程和 manage.sh 的修改
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
# 测试构建并推送到 web-dist 分支
|
||||
test_build_and_push() {
|
||||
print_info "开始测试构建和推送流程..."
|
||||
|
||||
# 检查是否在项目根目录
|
||||
if [ ! -f "package.json" ] || [ ! -d "web/admin-spa" ]; then
|
||||
print_error "请在项目根目录运行此脚本"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 构建前端
|
||||
print_info "构建前端..."
|
||||
cd web/admin-spa
|
||||
|
||||
# 检查 node_modules
|
||||
if [ ! -d "node_modules" ]; then
|
||||
print_info "安装前端依赖..."
|
||||
npm install
|
||||
fi
|
||||
|
||||
# 执行构建
|
||||
npm run build
|
||||
|
||||
if [ ! -d "dist" ]; then
|
||||
print_error "构建失败,dist 目录不存在"
|
||||
cd ../..
|
||||
return 1
|
||||
fi
|
||||
|
||||
print_success "前端构建成功"
|
||||
cd ../..
|
||||
|
||||
# 创建临时目录保存构建产物
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
print_info "复制构建产物到临时目录: $TEMP_DIR"
|
||||
cp -r web/admin-spa/dist/* "$TEMP_DIR/"
|
||||
|
||||
# 配置 git
|
||||
git config user.name "Test User"
|
||||
git config user.email "test@example.com"
|
||||
|
||||
# 检查 web-dist 分支是否存在
|
||||
print_info "检查 web-dist 分支..."
|
||||
if git ls-remote --heads origin web-dist | grep -q web-dist; then
|
||||
print_info "web-dist 分支已存在,获取最新版本"
|
||||
git fetch origin web-dist:web-dist
|
||||
git checkout web-dist
|
||||
else
|
||||
print_info "创建新的 web-dist 分支"
|
||||
git checkout --orphan web-dist
|
||||
fi
|
||||
|
||||
# 清空当前目录(保留 .git)
|
||||
git rm -rf . 2>/dev/null || true
|
||||
|
||||
# 复制构建产物
|
||||
cp -r "$TEMP_DIR"/* .
|
||||
|
||||
# 添加 README
|
||||
cat > README.md << 'EOF'
|
||||
# Claude Relay Service - Web Frontend Build
|
||||
|
||||
This branch contains the pre-built frontend assets for Claude Relay Service.
|
||||
|
||||
**DO NOT EDIT FILES IN THIS BRANCH DIRECTLY**
|
||||
|
||||
These files are automatically generated by the CI/CD pipeline.
|
||||
|
||||
Test Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||
EOF
|
||||
|
||||
# 提交
|
||||
git add -A
|
||||
git commit -m "test: frontend build test $(date +%Y%m%d%H%M%S)"
|
||||
|
||||
print_success "本地 web-dist 分支创建成功"
|
||||
print_warning "注意:这只是本地测试,没有推送到远程仓库"
|
||||
print_info "如需推送,请运行: git push origin web-dist --force"
|
||||
|
||||
# 切换回主分支
|
||||
git checkout main
|
||||
|
||||
# 清理临时目录
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
print_success "测试完成"
|
||||
}
|
||||
|
||||
# 测试从 web-dist 分支获取文件
|
||||
test_fetch_from_web_dist() {
|
||||
print_info "测试从 web-dist 分支获取文件..."
|
||||
|
||||
# 创建测试目录
|
||||
TEST_DIR="test-web-dist-fetch"
|
||||
rm -rf "$TEST_DIR"
|
||||
mkdir -p "$TEST_DIR"
|
||||
|
||||
# 检查远程 web-dist 分支
|
||||
if ! git ls-remote --heads origin web-dist | grep -q web-dist; then
|
||||
print_warning "远程 web-dist 分支不存在"
|
||||
print_info "尝试使用本地 web-dist 分支..."
|
||||
|
||||
# 检查本地分支
|
||||
if ! git branch | grep -q web-dist; then
|
||||
print_error "本地和远程都没有 web-dist 分支"
|
||||
rm -rf "$TEST_DIR"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
print_info "克隆 web-dist 分支到测试目录..."
|
||||
|
||||
# 创建临时目录用于 clone
|
||||
TEMP_CLONE_DIR=$(mktemp -d)
|
||||
|
||||
# 获取仓库 URL
|
||||
REPO_URL=$(git config --get remote.origin.url)
|
||||
|
||||
# 克隆 web-dist 分支
|
||||
if git clone --depth 1 --branch web-dist --single-branch "$REPO_URL" "$TEMP_CLONE_DIR" 2>/dev/null; then
|
||||
print_success "成功克隆 web-dist 分支"
|
||||
|
||||
# 复制文件(排除 .git 和 README.md)
|
||||
if command -v rsync >/dev/null 2>&1; then
|
||||
rsync -av --exclude='.git' --exclude='README.md' "$TEMP_CLONE_DIR/" "$TEST_DIR/"
|
||||
else
|
||||
cp -r "$TEMP_CLONE_DIR"/* "$TEST_DIR/" 2>/dev/null
|
||||
rm -rf "$TEST_DIR/.git" 2>/dev/null
|
||||
rm -f "$TEST_DIR/README.md" 2>/dev/null
|
||||
fi
|
||||
|
||||
print_success "文件复制成功"
|
||||
print_info "测试目录内容:"
|
||||
ls -la "$TEST_DIR" | head -10
|
||||
|
||||
# 验证关键文件
|
||||
if [ -f "$TEST_DIR/index.html" ]; then
|
||||
print_success "✓ index.html 文件存在"
|
||||
else
|
||||
print_error "✗ index.html 文件不存在"
|
||||
fi
|
||||
|
||||
if [ -d "$TEST_DIR/assets" ]; then
|
||||
print_success "✓ assets 目录存在"
|
||||
else
|
||||
print_error "✗ assets 目录不存在"
|
||||
fi
|
||||
|
||||
else
|
||||
print_error "克隆 web-dist 分支失败"
|
||||
print_info "可能需要先运行: test_build_and_push"
|
||||
fi
|
||||
|
||||
# 清理
|
||||
rm -rf "$TEMP_CLONE_DIR"
|
||||
rm -rf "$TEST_DIR"
|
||||
|
||||
print_success "获取测试完成"
|
||||
}
|
||||
|
||||
# 显示帮助
|
||||
show_help() {
|
||||
echo "用法: $0 [命令]"
|
||||
echo ""
|
||||
echo "命令:"
|
||||
echo " build - 测试构建并创建本地 web-dist 分支"
|
||||
echo " fetch - 测试从 web-dist 分支获取文件"
|
||||
echo " all - 运行所有测试"
|
||||
echo " help - 显示帮助"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
case "$1" in
|
||||
build)
|
||||
test_build_and_push
|
||||
;;
|
||||
fetch)
|
||||
test_fetch_from_web_dist
|
||||
;;
|
||||
all)
|
||||
test_build_and_push
|
||||
echo ""
|
||||
test_fetch_from_web_dist
|
||||
;;
|
||||
help)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
print_error "未知命令: $1"
|
||||
echo ""
|
||||
show_help
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 运行主函数
|
||||
main "$@"
|
||||
262
scripts/update-model-pricing.js
Normal file
262
scripts/update-model-pricing.js
Normal file
@@ -0,0 +1,262 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 手动更新模型价格数据脚本
|
||||
* 从 LiteLLM 仓库下载最新的模型价格和上下文窗口信息
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
|
||||
// 颜色输出
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[36m',
|
||||
magenta: '\x1b[35m'
|
||||
};
|
||||
|
||||
// 日志函数
|
||||
const log = {
|
||||
info: (msg) => console.log(`${colors.blue}[INFO]${colors.reset} ${msg}`),
|
||||
success: (msg) => console.log(`${colors.green}[SUCCESS]${colors.reset} ${msg}`),
|
||||
error: (msg) => console.error(`${colors.red}[ERROR]${colors.reset} ${msg}`),
|
||||
warn: (msg) => console.warn(`${colors.yellow}[WARNING]${colors.reset} ${msg}`)
|
||||
};
|
||||
|
||||
// 配置
|
||||
const config = {
|
||||
dataDir: path.join(process.cwd(), 'data'),
|
||||
pricingFile: path.join(process.cwd(), 'data', 'model_pricing.json'),
|
||||
pricingUrl: 'https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json',
|
||||
fallbackFile: path.join(process.cwd(), 'resources', 'model-pricing', 'model_prices_and_context_window.json'),
|
||||
backupFile: path.join(process.cwd(), 'data', 'model_pricing.backup.json'),
|
||||
timeout: 30000 // 30秒超时
|
||||
};
|
||||
|
||||
// 创建数据目录
|
||||
function ensureDataDir() {
|
||||
if (!fs.existsSync(config.dataDir)) {
|
||||
fs.mkdirSync(config.dataDir, { recursive: true });
|
||||
log.info('Created data directory');
|
||||
}
|
||||
}
|
||||
|
||||
// 备份现有文件
|
||||
function backupExistingFile() {
|
||||
if (fs.existsSync(config.pricingFile)) {
|
||||
try {
|
||||
fs.copyFileSync(config.pricingFile, config.backupFile);
|
||||
log.info('Backed up existing pricing file');
|
||||
return true;
|
||||
} catch (error) {
|
||||
log.warn(`Failed to backup existing file: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 恢复备份
|
||||
function restoreBackup() {
|
||||
if (fs.existsSync(config.backupFile)) {
|
||||
try {
|
||||
fs.copyFileSync(config.backupFile, config.pricingFile);
|
||||
log.info('Restored from backup');
|
||||
return true;
|
||||
} catch (error) {
|
||||
log.error(`Failed to restore backup: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 下载价格数据
|
||||
function downloadPricingData() {
|
||||
return new Promise((resolve, reject) => {
|
||||
log.info(`Downloading model pricing data from LiteLLM...`);
|
||||
log.info(`URL: ${config.pricingUrl}`);
|
||||
|
||||
const request = https.get(config.pricingUrl, (response) => {
|
||||
if (response.statusCode !== 200) {
|
||||
reject(new Error(`HTTP ${response.statusCode}: ${response.statusMessage}`));
|
||||
return;
|
||||
}
|
||||
|
||||
let data = '';
|
||||
let downloadedBytes = 0;
|
||||
|
||||
response.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
downloadedBytes += chunk.length;
|
||||
// 显示下载进度
|
||||
process.stdout.write(`\rDownloading... ${Math.round(downloadedBytes / 1024)}KB`);
|
||||
});
|
||||
|
||||
response.on('end', () => {
|
||||
process.stdout.write('\n'); // 换行
|
||||
try {
|
||||
const jsonData = JSON.parse(data);
|
||||
|
||||
// 验证数据结构
|
||||
if (typeof jsonData !== 'object' || Object.keys(jsonData).length === 0) {
|
||||
throw new Error('Invalid pricing data structure');
|
||||
}
|
||||
|
||||
// 保存到文件
|
||||
fs.writeFileSync(config.pricingFile, JSON.stringify(jsonData, null, 2));
|
||||
|
||||
const modelCount = Object.keys(jsonData).length;
|
||||
const fileSize = Math.round(fs.statSync(config.pricingFile).size / 1024);
|
||||
|
||||
log.success(`Downloaded pricing data for ${modelCount} models (${fileSize}KB)`);
|
||||
|
||||
// 显示一些统计信息
|
||||
const claudeModels = Object.keys(jsonData).filter(k => k.includes('claude')).length;
|
||||
const gptModels = Object.keys(jsonData).filter(k => k.includes('gpt')).length;
|
||||
const geminiModels = Object.keys(jsonData).filter(k => k.includes('gemini')).length;
|
||||
|
||||
log.info(`Model breakdown:`);
|
||||
log.info(` - Claude models: ${claudeModels}`);
|
||||
log.info(` - GPT models: ${gptModels}`);
|
||||
log.info(` - Gemini models: ${geminiModels}`);
|
||||
log.info(` - Other models: ${modelCount - claudeModels - gptModels - geminiModels}`);
|
||||
|
||||
resolve(jsonData);
|
||||
} catch (error) {
|
||||
reject(new Error(`Failed to parse pricing data: ${error.message}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
request.on('error', (error) => {
|
||||
reject(new Error(`Network error: ${error.message}`));
|
||||
});
|
||||
|
||||
request.setTimeout(config.timeout, () => {
|
||||
request.destroy();
|
||||
reject(new Error(`Download timeout after ${config.timeout / 1000} seconds`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 使用 fallback 文件
|
||||
function useFallback() {
|
||||
log.warn('Attempting to use fallback pricing data...');
|
||||
|
||||
if (!fs.existsSync(config.fallbackFile)) {
|
||||
log.error(`Fallback file not found: ${config.fallbackFile}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const fallbackData = fs.readFileSync(config.fallbackFile, 'utf8');
|
||||
const jsonData = JSON.parse(fallbackData);
|
||||
|
||||
// 保存到data目录
|
||||
fs.writeFileSync(config.pricingFile, JSON.stringify(jsonData, null, 2));
|
||||
|
||||
const modelCount = Object.keys(jsonData).length;
|
||||
log.warn(`Using fallback pricing data for ${modelCount} models`);
|
||||
log.info('Note: Fallback data may be outdated. Try updating again later.');
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
log.error(`Failed to use fallback: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示当前状态
|
||||
function showCurrentStatus() {
|
||||
if (fs.existsSync(config.pricingFile)) {
|
||||
const stats = fs.statSync(config.pricingFile);
|
||||
const fileAge = Date.now() - stats.mtime.getTime();
|
||||
const ageInHours = Math.round(fileAge / (60 * 60 * 1000));
|
||||
const ageInDays = Math.floor(ageInHours / 24);
|
||||
|
||||
let ageString = '';
|
||||
if (ageInDays > 0) {
|
||||
ageString = `${ageInDays} day${ageInDays > 1 ? 's' : ''} and ${ageInHours % 24} hour${(ageInHours % 24) !== 1 ? 's' : ''}`;
|
||||
} else {
|
||||
ageString = `${ageInHours} hour${ageInHours !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
log.info(`Current pricing file age: ${ageString}`);
|
||||
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(config.pricingFile, 'utf8'));
|
||||
log.info(`Current file contains ${Object.keys(data).length} models`);
|
||||
} catch (error) {
|
||||
log.warn('Current file exists but could not be parsed');
|
||||
}
|
||||
} else {
|
||||
log.info('No existing pricing file found');
|
||||
}
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
console.log(`${colors.bright}${colors.blue}======================================${colors.reset}`);
|
||||
console.log(`${colors.bright} Model Pricing Update Tool${colors.reset}`);
|
||||
console.log(`${colors.bright}${colors.blue}======================================${colors.reset}\n`);
|
||||
|
||||
// 显示当前状态
|
||||
showCurrentStatus();
|
||||
console.log('');
|
||||
|
||||
// 确保数据目录存在
|
||||
ensureDataDir();
|
||||
|
||||
// 备份现有文件
|
||||
const hasBackup = backupExistingFile();
|
||||
|
||||
try {
|
||||
// 尝试下载最新数据
|
||||
await downloadPricingData();
|
||||
|
||||
// 清理备份文件(成功下载后)
|
||||
if (hasBackup && fs.existsSync(config.backupFile)) {
|
||||
fs.unlinkSync(config.backupFile);
|
||||
log.info('Cleaned up backup file');
|
||||
}
|
||||
|
||||
console.log(`\n${colors.green}✅ Model pricing updated successfully!${colors.reset}`);
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
log.error(`Download failed: ${error.message}`);
|
||||
|
||||
// 尝试恢复备份
|
||||
if (hasBackup) {
|
||||
if (restoreBackup()) {
|
||||
log.info('Original file restored');
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试使用 fallback
|
||||
if (useFallback()) {
|
||||
console.log(`\n${colors.yellow}⚠️ Using fallback data (update completed with warnings)${colors.reset}`);
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log(`\n${colors.red}❌ Failed to update model pricing${colors.reset}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理未捕获的错误
|
||||
process.on('unhandledRejection', (error) => {
|
||||
log.error(`Unhandled error: ${error.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// 运行主函数
|
||||
main().catch((error) => {
|
||||
log.error(`Fatal error: ${error.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user