diff --git a/apps/web/src/components/ai/AiAnalysisCard.tsx b/apps/web/src/components/ai/AiAnalysisCard.tsx new file mode 100644 index 0000000..ccfb8d6 --- /dev/null +++ b/apps/web/src/components/ai/AiAnalysisCard.tsx @@ -0,0 +1,139 @@ +import { useState, useCallback } from 'react'; +import { Button, Card, Spin, Empty, Alert } from 'antd'; +import { ThunderboltOutlined, ReloadOutlined } from '@ant-design/icons'; +import { startAnalysis, type AnalysisType } from '../../api/ai/analysisSse'; +import { AuthButton } from '../AuthButton'; + +export interface AiAnalysisCardProps { + analysisType: AnalysisType; + sourceRef: string; + patientId?: string; + triggerLabel?: string; + permission?: string; + metrics?: string[]; +} + +type AnalysisState = 'idle' | 'loading' | 'success' | 'error'; + +export function AiAnalysisCard({ + analysisType, + sourceRef, + triggerLabel = 'AI 分析', + permission = 'ai.analysis.manage', + metrics, +}: AiAnalysisCardProps) { + const [state, setState] = useState('idle'); + const [content, setContent] = useState(''); + const [errorMsg, setErrorMsg] = useState(''); + + const handleStart = useCallback(async () => { + setState('loading'); + setContent(''); + setErrorMsg(''); + + const body: Record = {}; + if (analysisType === 'lab-report' || analysisType === 'report-summary') { + body.report_id = sourceRef; + } + if (analysisType === 'trends' || analysisType === 'checkup-plan') { + body.patient_id = sourceRef; + } + if (metrics) { + body.metrics = metrics; + } + + try { + await startAnalysis(analysisType, body, { + onChunk: (chunk) => setContent(prev => prev + chunk), + onError: (msg) => { + setErrorMsg(msg); + setState('error'); + }, + onDone: () => setState('success'), + }); + } catch { + setErrorMsg('分析请求失败'); + setState('error'); + } + }, [analysisType, sourceRef, metrics]); + + const handleReset = useCallback(() => { + setState('idle'); + setContent(''); + setErrorMsg(''); + }, []); + + const TriggerButton = permission ? ( + } + loading={state === 'loading'} + onClick={handleStart} + size="small" + > + {triggerLabel} + + ) : ( + + ); + + if (state === 'idle') { + return TriggerButton; + } + + if (state === 'loading') { + return ( + +
+ } /> +
+ AI 正在分析... +
+
+
+ ); + } + + if (state === 'error') { + return ( + + } onClick={handleStart}> + 重试 + + } + /> + + ); + } + + if (!content) { + return ; + } + + return ( + {triggerLabel}结果} + size="small" + style={{ marginTop: 12 }} + extra={ + + } + > +
+ {content} +
+
+ ); +}