- erp-auth/config/workflow/message/plugin/health: 44 处 DTO 校验缺失修复 - erp-plugin/data_dto: utoipa derive 宏 import 修复 - erp-server/main: tracing 宏类型推断修复 - web AuthButton: AiAnalysisCard/VitalSignsTab Button 包裹在 children 内 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
146 lines
3.6 KiB
TypeScript
146 lines
3.6 KiB
TypeScript
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[];
|
|
taskId?: string;
|
|
}
|
|
|
|
type AnalysisState = 'idle' | 'loading' | 'success' | 'error';
|
|
|
|
export function AiAnalysisCard({
|
|
analysisType,
|
|
sourceRef,
|
|
triggerLabel = 'AI 分析',
|
|
permission = 'ai.analysis.manage',
|
|
metrics,
|
|
taskId,
|
|
}: AiAnalysisCardProps) {
|
|
const [state, setState] = useState<AnalysisState>('idle');
|
|
const [content, setContent] = useState('');
|
|
const [errorMsg, setErrorMsg] = useState('');
|
|
|
|
const handleStart = useCallback(async () => {
|
|
setState('loading');
|
|
setContent('');
|
|
setErrorMsg('');
|
|
|
|
const body: Record<string, unknown> = {};
|
|
if (analysisType === 'lab-report' || analysisType === 'report-summary') {
|
|
body.report_id = sourceRef;
|
|
}
|
|
if (analysisType === 'trends' || analysisType === 'checkup-plan') {
|
|
body.patient_id = sourceRef;
|
|
}
|
|
if (analysisType === 'follow-up-summary') {
|
|
body.source_id = taskId || 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, taskId]);
|
|
|
|
const handleReset = useCallback(() => {
|
|
setState('idle');
|
|
setContent('');
|
|
setErrorMsg('');
|
|
}, []);
|
|
|
|
const TriggerButton = permission ? (
|
|
<AuthButton code={permission}>
|
|
<Button
|
|
icon={<ThunderboltOutlined />}
|
|
loading={state === 'loading'}
|
|
onClick={handleStart}
|
|
size="small"
|
|
>
|
|
{triggerLabel}
|
|
</Button>
|
|
</AuthButton>
|
|
) : (
|
|
<Button
|
|
icon={<ThunderboltOutlined />}
|
|
loading={state === 'loading'}
|
|
onClick={handleStart}
|
|
size="small"
|
|
>
|
|
{triggerLabel}
|
|
</Button>
|
|
);
|
|
|
|
if (state === 'idle') {
|
|
return TriggerButton;
|
|
}
|
|
|
|
if (state === 'loading') {
|
|
return (
|
|
<Card size="small" style={{ marginTop: 12 }}>
|
|
<div style={{ textAlign: 'center', padding: '24px 0' }}>
|
|
<Spin indicator={<ThunderboltOutlined spin style={{ fontSize: 24 }} />} />
|
|
<div style={{ marginTop: 8, color: 'var(--ant-color-text-secondary)' }}>
|
|
AI 正在分析...
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (state === 'error') {
|
|
return (
|
|
<Card size="small" style={{ marginTop: 12 }}>
|
|
<Alert
|
|
type="error"
|
|
message={errorMsg}
|
|
showIcon
|
|
action={
|
|
<Button size="small" icon={<ReloadOutlined />} onClick={handleStart}>
|
|
重试
|
|
</Button>
|
|
}
|
|
/>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (!content) {
|
|
return <Empty description="分析结果为空" />;
|
|
}
|
|
|
|
return (
|
|
<Card
|
|
title={<><ThunderboltOutlined /> {triggerLabel}结果</>}
|
|
size="small"
|
|
style={{ marginTop: 12 }}
|
|
extra={
|
|
<Button size="small" onClick={handleReset}>关闭</Button>
|
|
}
|
|
>
|
|
<div style={{ whiteSpace: 'pre-wrap', lineHeight: 1.8, fontSize: 14 }}>
|
|
{content}
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|