diff --git a/api/app/services/requirement_service.py b/api/app/services/requirement_service.py index 61ab1b2..dabc7c4 100644 --- a/api/app/services/requirement_service.py +++ b/api/app/services/requirement_service.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +from datetime import datetime from fastapi import HTTPException, status from sqlalchemy import or_, select diff --git a/memory/2026-04-12.md b/memory/2026-04-12.md index f63a19f..23a26cf 100644 --- a/memory/2026-04-12.md +++ b/memory/2026-04-12.md @@ -231,3 +231,16 @@ - 验证: - `cd web && npx eslint src/lib/api.ts src/components/auth-provider.tsx src/components/ws-provider.tsx src/app/page.tsx` 通过。 - `cd web && npx tsc --noEmit` 通过。 + +## 追加修复(需求创建 500) + +- 触发问题: + - 需求管理页面创建需求失败,接口 `POST /api/v1/requirements` 返回 `500 Internal Server Error`。 +- 根因: + - `api/app/services/requirement_service.py` 的 `_next_requirement_code()` 使用 `datetime.utcnow()` 但文件缺少 `datetime` 导入,运行时报 `NameError: name 'datetime' is not defined`。 +- 处理: + - 在 `api/app/services/requirement_service.py` 增加 `from datetime import datetime`。 +- 验证: + - `python3 -m compileall api/app/services/requirement_service.py` 通过。 + - `docker compose up -d --build api` 重建 API 容器成功。 + - 复测 `POST /api/v1/requirements` 返回 `200`,并成功创建需求(示例 `code=REQ-2026-0001`)。 diff --git a/web/src/app/admin/models/page.tsx b/web/src/app/admin/models/page.tsx index 87c01c1..77b2076 100644 --- a/web/src/app/admin/models/page.tsx +++ b/web/src/app/admin/models/page.tsx @@ -8,6 +8,7 @@ import { useAuth } from "@/components/auth-provider"; import { useTopicSubscription } from "@/hooks/use-topic-subscription"; import { readApiError } from "@/lib/api"; import type { + ModelHealthStatus, ModelListResponse, ModelRegistryItem, ModelRouteRuleListResponse, @@ -17,14 +18,35 @@ import type { } from "@/types/auth"; const MODEL_STATUS_OPTIONS: ModelStatus[] = ["DRAFT", "ENABLED", "DISABLED", "DEPRECATED"]; +const MODEL_STATUS_LABELS: Record = { + DRAFT: "草稿", + ENABLED: "已启用", + DISABLED: "已停用", + DEPRECATED: "已废弃", +}; const MODEL_STATUS_TRANSITIONS: Record = { DRAFT: ["ENABLED", "DISABLED", "DEPRECATED"], ENABLED: ["DISABLED", "DEPRECATED"], DISABLED: ["ENABLED", "DEPRECATED"], DEPRECATED: ["DISABLED"], }; +const HEALTH_STATUS_LABELS: Record = { + HEALTHY: "健康", + DEGRADED: "退化", + UNHEALTHY: "不健康", +}; const ROUTE_TYPE_OPTIONS: ModelRouteType[] = ["GLOBAL", "CAPABILITY", "BUSINESS", "AGENT"]; const GLOBAL_ROUTE_KEY = "__global__"; +const PROVIDER_OPTIONS: Array<{ value: string; label: string }> = [ + { value: "openai", label: "OpenAI" }, + { value: "anthropic", label: "Anthropic" }, + { value: "google", label: "Google" }, + { value: "deepseek", label: "DeepSeek" }, + { value: "qwen", label: "Qwen" }, + { value: "grok", label: "Grok" }, + { value: "azure-openai", label: "Azure OpenAI" }, + { value: "other", label: "其他" }, +]; const EMPTY_MODEL_FORM = { code: "", @@ -61,6 +83,15 @@ function formatPercent(value: number | null): string { return `${(value * 100).toFixed(1)}%`; } +function formatModelStatus(status: ModelStatus): string { + return `${MODEL_STATUS_LABELS[status]}(${status})`; +} + +function formatHealthStatus(status: ModelHealthStatus | null): string { + if (!status) return "-"; + return `${HEALTH_STATUS_LABELS[status]}(${status})`; +} + async function invalidateModelQueries( queryClient: QueryClient, modelsPath: string, @@ -425,6 +456,17 @@ export default function AdminModelsPage() { return (modelsQuery.data?.items ?? []).map((item) => item.code); }, [modelsQuery.data?.items]); + const providerOptions = useMemo(() => { + const currentProvider = modelForm.provider.trim().toLowerCase(); + if (!currentProvider) { + return PROVIDER_OPTIONS; + } + if (PROVIDER_OPTIONS.some((item) => item.value === currentProvider)) { + return PROVIDER_OPTIONS; + } + return [{ value: currentProvider, label: `${currentProvider}(当前)` }, ...PROVIDER_OPTIONS]; + }, [modelForm.provider]); + const summary = summaryQuery.data; const models = modelsQuery.data?.items ?? []; const routes = routesQuery.data?.items ?? []; @@ -473,7 +515,7 @@ export default function AdminModelsPage() {

模型总数

{summary?.total_models ?? 0}

-

ENABLED: {summary?.status_counts.ENABLED ?? 0}

+

已启用: {summary?.status_counts.ENABLED ?? 0}

路由规则

@@ -512,7 +554,7 @@ export default function AdminModelsPage() { > {MODEL_STATUS_OPTIONS.map((item) => ( - + ))}
@@ -545,7 +587,7 @@ export default function AdminModelsPage() {

{model.provider_model}

-

{model.status}

+

{formatModelStatus(model.status)}

{model.capabilities.join(", ") || "-"}

@@ -553,7 +595,7 @@ export default function AdminModelsPage() {

v{model.active_key_version ?? "-"}

-

{model.latest_health_status ?? "-"}

+

{formatHealthStatus(model.latest_health_status)}

{model.latest_health_at ? new Date(model.latest_health_at).toLocaleString() : "-"}

@@ -613,7 +655,7 @@ export default function AdminModelsPage() { disabled={transitionMutation.isPending} > {"-> "} - {nextStatus} + {formatModelStatus(nextStatus)} ))} {model.status !== "ENABLED" && ( @@ -683,11 +725,15 @@ export default function AdminModelsPage() { @@ -790,7 +836,7 @@ export default function AdminModelsPage() { {route.route_key} {route.target_model_code} {route.priority} - {route.enabled ? "enabled" : "disabled"} + {route.enabled ? "启用" : "停用"} {canManage && (