diff --git a/apps/admin-web/src/App.tsx b/apps/admin-web/src/App.tsx index f2195db..3a256bf 100644 --- a/apps/admin-web/src/App.tsx +++ b/apps/admin-web/src/App.tsx @@ -32,6 +32,8 @@ import { useAuthStore } from "./store/authStore"; import { useBusinessDayStore } from "./store/businessDayStore"; import { fetchQueue } from "./api/execution"; import type { QueuedTask } from "./types"; +// F1-5b UI-4: 全局 sandbox 徽章 +import { fetchRuntimeContext, type RuntimeContext } from "./api/runtimeContext"; import Login from "./pages/Login"; import EnvConfig from "./pages/EnvConfig"; import DBViewer from "./pages/DBViewer"; @@ -150,8 +152,11 @@ const AppLayout: React.FC = () => { const navigate = useNavigate(); const location = useLocation(); const logout = useAuthStore((s) => s.logout); + const userSiteId = useAuthStore((s) => s.user?.site_id); const [runningTask, setRunningTask] = useState(null); + // F1-5b UI-4: 当前 site 的 runtime context(sandbox 徽章渲染依据) + const [runtimeCtx, setRuntimeCtx] = useState(null); const pollQueue = useCallback(async () => { try { @@ -163,12 +168,29 @@ const AppLayout: React.FC = () => { } }, []); + // F1-5b UI-4: 拉 runtime context(30 秒轮询,sandbox 切换不需即时但 5 分钟内必须更新) + const pollRuntimeCtx = useCallback(async () => { + if (!userSiteId) return; + try { + const ctx = await fetchRuntimeContext(userSiteId); + setRuntimeCtx(ctx); + } catch { + // 失败不阻塞顶栏渲染 + } + }, [userSiteId]); + useEffect(() => { pollQueue(); const timer = setInterval(pollQueue, 5_000); return () => clearInterval(timer); }, [pollQueue]); + useEffect(() => { + pollRuntimeCtx(); + const timer = setInterval(pollRuntimeCtx, 30_000); + return () => clearInterval(timer); + }, [pollRuntimeCtx]); + const onMenuClick: MenuProps["onClick"] = ({ key }) => { navigate(key); }; const handleLogout = () => { @@ -266,12 +288,37 @@ const AppLayout: React.FC = () => {
+ {/* F1-5b UI-4: 左侧 sandbox 徽章 - sandbox 模式始终可见,提醒离开 AIOperations 后仍知道在虚拟时间 */} +
+ {runtimeCtx?.is_sandbox ? ( + + 沙箱 + + 业务日 {runtimeCtx.business_date} + + + {runtimeCtx.sandbox_instance_id?.slice(0, 12)}… + + + ) : runtimeCtx ? ( + + 实时 + + {runtimeCtx.business_date} + + + ) : null} +
+ +
{runningTask ? ( @@ -285,6 +332,9 @@ const AppLayout: React.FC = () => { ) : ( 无任务执行中 )} +
+ {/* 右侧占位,保持 Footer 三段式平衡 */} +
diff --git a/apps/admin-web/src/api/adminAI.ts b/apps/admin-web/src/api/adminAI.ts index 22714a6..f1cf56f 100644 --- a/apps/admin-web/src/api/adminAI.ts +++ b/apps/admin-web/src/api/adminAI.ts @@ -139,6 +139,9 @@ export interface RunLogItem { status: string; site_id: number; created_at: string; + // F1-5b UI-1: sandbox 透出 + runtime_mode?: string | null; + sandbox_instance_id?: string | null; } export interface RunLogDetailResponse extends RunLogItem { diff --git a/apps/admin-web/src/pages/AIRunLogs.tsx b/apps/admin-web/src/pages/AIRunLogs.tsx index a1c63d3..ef95849 100644 --- a/apps/admin-web/src/pages/AIRunLogs.tsx +++ b/apps/admin-web/src/pages/AIRunLogs.tsx @@ -101,6 +101,21 @@ const AIRunLogs: React.FC = () => { title: "状态", dataIndex: "status", key: "status", width: 110, render: (v: string) => {v}, }, + { + title: "运行模式", dataIndex: "runtime_mode", key: "runtime_mode", width: 100, + render: (v: string | null) => { + if (!v) return "—"; + return {v}; + }, + }, + { + title: "沙箱实例", dataIndex: "sandbox_instance_id", key: "sandbox_instance_id", width: 130, + render: (v: string | null) => { + if (!v || v === "live") return "—"; + const short = v.length > 12 ? v.slice(0, 8) + "…" : v; + return {short}; + }, + }, { title: "时间", dataIndex: "created_at", key: "created_at", width: 170, render: fmtTime }, ]; @@ -192,6 +207,20 @@ const AIRunLogs: React.FC = () => { {detail.status} {detail.session_id ?? "—"} + + {detail.runtime_mode ? ( + + {detail.runtime_mode} + + ) : "—"} + + + {detail.sandbox_instance_id && detail.sandbox_instance_id !== "live" ? ( + + {detail.sandbox_instance_id} + + ) : "—"} + {fmtTime(detail.created_at)} {fmtTime(detail.finished_at)} diff --git a/apps/backend/app/schemas/admin_ai.py b/apps/backend/app/schemas/admin_ai.py index 5adf5d9..e1c6a13 100644 --- a/apps/backend/app/schemas/admin_ai.py +++ b/apps/backend/app/schemas/admin_ai.py @@ -112,7 +112,7 @@ class RetryResponse(BaseModel): class RunLogItem(BaseModel): - """调用记录列表项。""" + """调用记录列表项。F1-5b UI-1: 加 runtime_mode + sandbox_instance_id 透出 sandbox 状态。""" id: int app_type: str trigger_type: str @@ -122,6 +122,8 @@ class RunLogItem(BaseModel): status: str site_id: int created_at: str + runtime_mode: str | None = None + sandbox_instance_id: str | None = None class RunLogListResponse(BaseModel): diff --git a/apps/backend/app/services/ai/admin_service.py b/apps/backend/app/services/ai/admin_service.py index 85c8120..9338545 100644 --- a/apps/backend/app/services/ai/admin_service.py +++ b/apps/backend/app/services/ai/admin_service.py @@ -496,7 +496,8 @@ class AdminAIService: cur.execute( f""" SELECT id, app_type, trigger_type, member_id, - tokens_used, latency_ms, status, site_id, created_at + tokens_used, latency_ms, status, site_id, created_at, + runtime_mode, sandbox_instance_id FROM biz.ai_run_logs {where_sql} ORDER BY created_at DESC @@ -527,7 +528,8 @@ class AdminAIService: SELECT id, app_type, trigger_type, member_id, tokens_used, latency_ms, status, site_id, created_at, request_prompt, response_text, - error_message, session_id, finished_at + error_message, session_id, finished_at, + runtime_mode, sandbox_instance_id FROM biz.ai_run_logs WHERE id = %s """, diff --git a/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui12_4a_live.png b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui12_4a_live.png new file mode 100644 index 0000000..457a4c3 Binary files /dev/null and b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui12_4a_live.png differ diff --git a/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui12_4b_sandbox_tag.png b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui12_4b_sandbox_tag.png new file mode 100644 index 0000000..cb72a49 Binary files /dev/null and b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui12_4b_sandbox_tag.png differ diff --git a/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui12_4b_v2.png b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui12_4b_v2.png new file mode 100644 index 0000000..82856b2 Binary files /dev/null and b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui12_4b_v2.png differ diff --git a/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui2_drawer.png b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui2_drawer.png new file mode 100644 index 0000000..14c1f90 Binary files /dev/null and b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui2_drawer.png differ diff --git a/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui4_4a_full.png b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui4_4a_full.png new file mode 100644 index 0000000..12204cf Binary files /dev/null and b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui4_4a_full.png differ diff --git a/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui4_4a_live_badge.png b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui4_4a_live_badge.png new file mode 100644 index 0000000..0997265 Binary files /dev/null and b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui4_4a_live_badge.png differ diff --git a/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui4_4b_sandbox_full.png b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui4_4b_sandbox_full.png new file mode 100644 index 0000000..a055205 Binary files /dev/null and b/docs/audit/changes/screenshots/2026-05-05_f1_5b_wave_a/f1_5b_ui4_4b_sandbox_full.png differ