Files
nj/apps/web/src/components/ai/RichMessage.tsx
iven 8111471e93
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled
feat: 添加管理端前端 (HMS 基座 React 管理面板)
- 从 HMS 基座复制 apps/web/ (React + Ant Design + Vite + TypeScript)
- 管理端自动代理 API 到 localhost:3000 (vite.config.ts)
- 更新 scripts/dev.sh 支持三端启动: backend/admin/app
- 登录验证通过, 用户管理/角色权限/审计日志等页面正常
- 添加 .gitignore 排除 node_modules/dist
2026-06-02 10:03:13 +08:00

181 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Card, Tag, Typography, theme } from 'antd';
import {
WarningOutlined,
HeartOutlined,
LineChartOutlined,
ExperimentOutlined,
UserOutlined,
} from '@ant-design/icons';
import type { DisplayHint } from '../../api/ai/chat';
const { Text } = Typography;
const SEVERITY_COLOR: Record<string, string> = {
high: 'red',
medium: 'orange',
low: 'green',
};
const RISK_LEVEL_COLOR: Record<string, string> = {
critical: '#cf1322',
high: 'red',
medium: 'orange',
low: 'green',
};
export default function RichMessage({ hints }: { hints: DisplayHint[] }) {
const { token } = theme.useToken();
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 8 }}>
{hints.map((hint, i) => (
<RichHint key={i} hint={hint} token={token} />
))}
</div>
);
}
function RichHint({ hint, token }: { hint: DisplayHint; token: { colorBorderSecondary: string; colorTextSecondary: string; colorPrimary: string } }) {
switch (hint.type) {
case 'insight_card':
return (
<Card
size="small"
title={
<span style={{ fontSize: 12 }}>
<HeartOutlined style={{ marginRight: 4, color: token.colorPrimary }} />
{hint.title}
</span>
}
styles={{ body: { padding: '6px 12px' } }}
>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
{hint.items.map((item, j) => (
<Tag key={j} color={SEVERITY_COLOR[hint.severity] ?? 'blue'} style={{ fontSize: 11, margin: 0 }}>
{item}
</Tag>
))}
</div>
</Card>
);
case 'risk_alert':
return (
<div
style={{
padding: '8px 12px',
borderRadius: 8,
border: `1px solid ${RISK_LEVEL_COLOR[hint.level] ?? token.colorBorderSecondary}`,
background: `${RISK_LEVEL_COLOR[hint.level] ?? token.colorBorderSecondary}10`,
fontSize: 13,
}}
>
<WarningOutlined style={{ color: RISK_LEVEL_COLOR[hint.level] ?? token.colorPrimary, marginRight: 6 }} />
{hint.message}
</div>
);
case 'lab_report_card':
return (
<Card
size="small"
title={
<span style={{ fontSize: 12 }}>
<ExperimentOutlined style={{ marginRight: 4 }} />
{hint.report_date}
</span>
}
styles={{ body: { padding: '6px 12px', fontSize: 12 } }}
>
{hint.abnormal_count > 0 ? (
<Tag color="red" style={{ fontSize: 11 }}>
{hint.abnormal_count}
</Tag>
) : (
<Tag color="green" style={{ fontSize: 11 }}></Tag>
)}
</Card>
);
case 'trend_chart':
return (
<Card
size="small"
title={
<span style={{ fontSize: 12 }}>
<LineChartOutlined style={{ marginRight: 4 }} />
{hint.period}
</span>
}
styles={{ body: { padding: '6px 12px', fontSize: 12 } }}
>
<div style={{ marginBottom: 4 }}>
{hint.metrics.map((m, j) => (
<Tag key={j} style={{ fontSize: 11, margin: '0 4px 2px 0' }}>{m}</Tag>
))}
</div>
<Text type="secondary" style={{ fontSize: 11 }}>{hint.summary}</Text>
</Card>
);
case 'patient_profile':
return (
<Card
size="small"
title={
<span style={{ fontSize: 12 }}>
<UserOutlined style={{ marginRight: 4 }} />
</span>
}
styles={{ body: { padding: '6px 12px', fontSize: 12 } }}
>
{hint.chronic_conditions.length > 0 && (
<div style={{ marginBottom: 4 }}>
{hint.chronic_conditions.map((c, j) => (
<Tag key={j} color="orange" style={{ fontSize: 11, margin: '0 4px 2px 0' }}>{c}</Tag>
))}
</div>
)}
{hint.medication_count > 0 && (
<Text type="secondary" style={{ fontSize: 11 }}> {hint.medication_count} </Text>
)}
</Card>
);
case 'vital_card':
return (
<Card
size="small"
title={
<span style={{ fontSize: 12 }}>
<HeartOutlined style={{ marginRight: 4, color: token.colorPrimary }} />
</span>
}
styles={{ body: { padding: '6px 12px', fontSize: 12 } }}
>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
{hint.values.slice(-5).map(([date, val], j) => (
<span key={j}>
<Text type="secondary" style={{ fontSize: 11 }}>{date.slice(5)}:</Text>{' '}
{val} {hint.unit}
</span>
))}
</div>
</Card>
);
case 'action_confirm':
return (
<Card size="small" styles={{ body: { padding: '8px 12px', fontSize: 13 } }}>
<Text>{hint.summary}</Text>
</Card>
);
case 'text':
default:
return null;
}
}