import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { RefreshCw, TriangleAlert } from "lucide-react"; import { Button } from "~/components/ui/button"; import { Card, CardContent } from "~/components/ui/card"; import { Skeleton } from "~/components/ui/skeleton"; import { useAuth } from "~/context/AuthContext"; import { getMonthlyMonitoringResult, getOperationsModel, getOperationsWorkbench, listOperationsModels, OperationsApiError, type OperationsWorkbenchDto, operationsApiMode, type OperationsApiMode, } from "~/services/operationsApi"; import type { ModelRecord, MonitoringRow } from "./modelData"; type OperationsDataContextValue = { models: ModelRecord[]; workbench: OperationsWorkbenchDto; loading: boolean; error: string | null; source: OperationsApiMode; reload: () => Promise; }; const OperationsDataContext = createContext(null); const EMPTY_WORKBENCH: OperationsWorkbenchDto = { role: "model_team", alerts: [], kpis: [], todos: [], watches: [], activities: [], }; export function OperationsDataProvider({ children }: { children: ReactNode }) { const { currentWorkspace } = useAuth(); const workspaceId = currentWorkspace?.workspace_id; const [models, setModels] = useState([]); const [workbench, setWorkbench] = useState(EMPTY_WORKBENCH); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const load = useCallback(async (signal?: AbortSignal) => { setLoading(true); setError(null); try { const [modelsResult, workbenchResult] = await Promise.allSettled([ listOperationsModels({ workspaceId, signal }), getOperationsWorkbench(workspaceId, signal), ]); if (modelsResult.status === "rejected") throw modelsResult.reason; setModels(modelsResult.value); setWorkbench(workbenchResult.status === "fulfilled" ? workbenchResult.value : EMPTY_WORKBENCH); } catch (cause) { if (cause instanceof DOMException && cause.name === "AbortError") return; setModels([]); setError(cause instanceof Error ? cause.message : "模型数据加载失败"); } finally { if (!signal?.aborted) setLoading(false); } }, [workspaceId]); useEffect(() => { const controller = new AbortController(); void load(controller.signal); return () => controller.abort(); }, [load]); const value = useMemo(() => ({ models, workbench, loading, error, source: operationsApiMode, reload: () => load(), }), [error, load, loading, models, workbench]); return {children}; } export function useOperationsData(): OperationsDataContextValue { const context = useContext(OperationsDataContext); if (!context) throw new Error("useOperationsData 必须在 OperationsDataProvider 内使用"); return context; } export function useOperationsModelDetail(modelId: string, month: string) { const { currentWorkspace } = useAuth(); const workspaceId = currentWorkspace?.workspace_id; const [model, setModel] = useState(null); const [monitoringResult, setMonitoringResult] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [reloadKey, setReloadKey] = useState(0); useEffect(() => { const controller = new AbortController(); setLoading(true); setError(null); void Promise.all([ getOperationsModel(modelId, workspaceId, controller.signal), getMonthlyMonitoringResult(modelId, month, workspaceId, controller.signal).catch((cause) => { if (cause instanceof OperationsApiError && cause.code === "MONITOR_RESULT_NOT_FOUND") { return null; } throw cause; }), ]).then(([modelValue, resultValue]) => { setModel(modelValue); setMonitoringResult(resultValue); }).catch((cause) => { if (cause instanceof DOMException && cause.name === "AbortError") return; setError(cause instanceof Error ? cause.message : "模型监控详情加载失败"); }).finally(() => { if (!controller.signal.aborted) setLoading(false); }); return () => controller.abort(); }, [modelId, month, reloadKey, workspaceId]); return { model, monitoringResult, loading, error, reload: () => setReloadKey((value) => value + 1), }; } export function OperationsDataBoundary({ children }: { children: ReactNode }) { const { loading, error, reload } = useOperationsData(); if (loading) { return (
{Array.from({ length: 4 }, (_, index) => )}
); } if (error) { return (

运维数据加载失败

{error}

当前数据源:真实接口

); } return children; }