diff --git a/.gitignore b/.gitignore index 8405ef2..76c8fa7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # Frontend generated/dependency artifacts frontend/node_modules/ frontend/dist/ +frontend/src/data/latest_models.json # Generated binaries /fetch_multi_source diff --git a/frontend/src/data/latest_models.json b/frontend/src/data/latest_models.json deleted file mode 100644 index 4d8da52..0000000 --- a/frontend/src/data/latest_models.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "generated_at": "2026-05-09T21:30:54+08:00", - "total": 2, - "free": 1, - "paid": 1, - "models": [ - { - "id": "openai/gpt-4o", - "context_length": 128000, - "pricing": { - "input": 2.5, - "output": 10 - } - }, - { - "id": "anthropic/claude-3.5-sonnet:free", - "context_length": 200000, - "pricing": { - "input": 0, - "output": 0 - } - } - ] -} diff --git a/frontend/src/lib/models.ts b/frontend/src/lib/models.ts index 3c4c659..455d948 100644 --- a/frontend/src/lib/models.ts +++ b/frontend/src/lib/models.ts @@ -111,6 +111,8 @@ export function normalizeModel(raw: any): Model | null { } export async function loadFallbackModels() { + // latest_models.json is a local runtime snapshot when present. + // models.json is the committed fixture fallback kept in the repo. const sources = [ () => import('../data/latest_models.json'), () => import('../data/models.json'), diff --git a/scripts/verify_phase4.sh b/scripts/verify_phase4.sh index c6aae4e..c031ea4 100755 --- a/scripts/verify_phase4.sh +++ b/scripts/verify_phase4.sh @@ -13,7 +13,7 @@ check_file "frontend/tsconfig.json" "前端 tsconfig.json 存在" check_shell "前端生产构建通过" "cd frontend && npm run build >/tmp/llm_phase4_build.log 2>&1" check_shell "App 已接入 Dashboard 和 Explorer 入口" "grep -q 'Dashboard' frontend/src/App.tsx && grep -q 'Explorer' frontend/src/App.tsx" check_shell "Explorer 已实现分页/排序/筛选" "grep -q 'PAGE_SIZE' frontend/src/pages/Explorer.tsx && grep -q 'toggleSort' frontend/src/pages/Explorer.tsx && grep -q 'providerFilter' frontend/src/pages/Explorer.tsx && grep -q 'modalityFilter' frontend/src/pages/Explorer.tsx" -check_shell "Explorer 具备 API 失败回退到本地 JSON" "grep -q \"latest_models.json\" frontend/src/lib/models.ts && grep -q \"models.json\" frontend/src/lib/models.ts" +check_shell "前端回退层具备 latest 本地快照 + models fixture 双层 JSON 回退" "grep -q \"latest_models.json\" frontend/src/lib/models.ts && grep -q \"models.json\" frontend/src/lib/models.ts" check_shell "Dashboard 已集成 ECharts" "grep -q \"from 'echarts'\" frontend/src/pages/Dashboard.tsx" check_shell "Explorer 已实现 stale 状态显示" "grep -qi 'stale' frontend/src/pages/Explorer.tsx" check_shell "Explorer 已实现 pricing unavailable 显示" "grep -qi 'pricing unavailable' frontend/src/lib/models.ts" diff --git a/scripts/verify_t34.sh b/scripts/verify_t34.sh index e2116a9..b8dd5d7 100755 --- a/scripts/verify_t34.sh +++ b/scripts/verify_t34.sh @@ -1,9 +1,9 @@ #!/bin/bash -# verify_t34.sh — 验收 T-3.4:Explorer 接入真实 Schema JSON +# verify_t34.sh — 验收 T-3.4:前端 fixture JSON 接入与回退层 set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" -FILE="$PROJECT_ROOT/frontend/src/pages/Explorer.tsx" +FILE="$PROJECT_ROOT/frontend/src/lib/models.ts" JSON="$PROJECT_ROOT/frontend/src/data/models.json" echo "=== T-3.4 验收检查 ===" @@ -18,18 +18,18 @@ print('json-schema OK') " && echo "json-schema PASS — JSON 含 generated_at/total/free/paid/models,且 models 含 pricing.input/output" \ || { echo "json-schema FAIL"; exit 1; } -# T-3.4.2: mapAPIResponseToModels 映射函数存在 -if grep -q 'mapAPIResponseToModels' "$FILE"; then - echo "mapping PASS — mapAPIResponseToModels 函数存在" +# T-3.4.2: 前端具备 normalizeModel 映射函数 +if grep -q 'normalizeModel' "$FILE"; then + echo "mapping PASS — normalizeModel 映射函数存在" else echo "mapping FAIL" exit 1 fi -# T-3.4.3: getMockModels 改为从 JSON 加载 +# T-3.4.3: fixture fallback 从 models.json 加载,而不是页面内硬编码 mock 数据 if grep -q "models.json" "$FILE" && \ ! grep -q "provider.*OpenAI\|provider.*Anthropic\|provider.*DeepSeek" "$FILE"; then - echo "import PASS — getMockModels 引用 models.json,无硬编码 provider" + echo "import PASS — 回退层引用 models.json,无页面内硬编码 mock 数据" else echo "import FAIL — 仍有硬编码 mock 数据" exit 1 diff --git a/scripts/verify_t35.sh b/scripts/verify_t35.sh index 12d2731..702c84a 100755 --- a/scripts/verify_t35.sh +++ b/scripts/verify_t35.sh @@ -1,36 +1,62 @@ #!/bin/bash -# verify_t35.sh — 验收 T-3.5:日报生成器同步产出 latest_models.json + Explorer fallback +# verify_t35.sh — 验收 T-3.5:前端本地 latest 快照语义 + fixture fallback 约定 set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" -REPORT="$PROJECT_ROOT/scripts/generate_daily_report.go" -EXPLORER="$PROJECT_ROOT/frontend/src/pages/Explorer.tsx" +MODELS_LIB="$PROJECT_ROOT/frontend/src/lib/models.ts" +GITIGNORE="$PROJECT_ROOT/.gitignore" LATEST="$PROJECT_ROOT/frontend/src/data/latest_models.json" +FIXTURE="$PROJECT_ROOT/frontend/src/data/models.json" echo "=== T-3.5 验收检查 ===" -# T-3.5.1: generate_daily_report.go 含 latest_models.json 写入,且路径从 outDir 推导而非硬编码相对 cwd -if grep -q 'latest_models.json' "$REPORT" && \ - grep -q 'outDir.*frontend.*latest_models.json\|filepath.Join.*outDir.*latest' "$REPORT"; then - echo "report-json-write PASS — latest_models.json 写入且路径从 outDir 推导" +# T-3.5.1: latest_models.json 视为本地运行快照,不纳入 git 跟踪 +if grep -q '^frontend/src/data/latest_models.json$' "$GITIGNORE"; then + echo "latest-ignore PASS — latest_models.json 已被 .gitignore 标记为本地运行快照" else - echo "report-json-write FAIL" + echo "latest-ignore FAIL" exit 1 fi -# T-3.5.2: Explorer.tsx 含 latest_models.json 优先加载和 models.json fallback -if grep -q 'latest_models.json' "$EXPLORER" && \ - grep -q 'models.json' "$EXPLORER"; then - echo "explorer-fallback PASS — latest 优先 + models fallback 同时存在" +# T-3.5.2: 回退层在 models.ts 中定义 latest 优先 + models fixture fallback +if grep -q 'latest_models.json' "$MODELS_LIB" && \ + grep -q 'models.json' "$MODELS_LIB"; then + echo "fallback-order PASS — latest 优先 + models fixture fallback 同时存在" else - echo "explorer-fallback FAIL" + echo "fallback-order FAIL" exit 1 fi -# T-3.5.1 补丁验证: latest_models.json 免费模型 pricing 字段完整性 +# T-3.5.3: committed fixture 必须长期存在,保证无 API 时仍能展示基础数据 +if [ ! -f "$FIXTURE" ]; then + echo "fixture-present FAIL — models.json fixture 不存在" + exit 1 +fi + +if python3 - "$FIXTURE" <<'PY' +import json +import sys + +path = sys.argv[1] +with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + +assert isinstance(data.get("models"), list) and len(data["models"]) > 0 +assert all("pricing" in model for model in data["models"]) +PY +then + echo "fixture-present PASS — models.json fixture 存在且结构可用" +else + echo "fixture-present FAIL — models.json fixture 结构异常" + exit 1 +fi + +# T-3.5.4: latest_models.json 如果存在,必须满足快照 schema 和免费模型归一化 if [ ! -f "$LATEST" ]; then - echo "pricing-normalized FAIL — latest_models.json 不存在" - exit 1 + echo "latest-optional PASS — latest_models.json 当前不存在,前端将回退到 committed fixture" + echo "" + echo "all PASS" + exit 0 fi if python3 - "$LATEST" <<'PY' @@ -58,9 +84,9 @@ for model in free_models: raise SystemExit(1) PY then - echo "pricing-normalized PASS — 免费模型 pricing.input/output 均显式为 0" + echo "latest-schema PASS — latest_models.json 存在且免费模型 pricing.input/output 均显式为 0" else - echo "pricing-normalized FAIL — 免费模型 pricing 字段缺失或未显式归一为 0" + echo "latest-schema FAIL — latest_models.json schema 异常或免费模型 pricing 未归一" exit 1 fi