feat(web+mp): AI 分析结果增强展示
Web 端 AiAnalysisList: - 分析结果 Markdown 风格渲染(标题/列表/粗体/代码) - 趋势分析类型显示统计方法提示 - 自动分析结果显示「系统自动分析」标签 小程序 ai-report/detail: - 新增 result_metadata 字段 - 自动分析标记(紫色标签) - 趋势分析统计方法说明卡片
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
import { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { Table, Select, Tag, Space, message, Typography } from 'antd';
|
||||
import {
|
||||
RobotOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useThemeMode } from '../../hooks/useThemeMode';
|
||||
import { analysisApi, type AnalysisItem } from '../../api/ai/analysis';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const ANALYSIS_TYPE_MAP: Record<string, string> = {
|
||||
lab_report_interpretation: '化验单解读',
|
||||
health_trend_analysis: '趋势分析',
|
||||
@@ -22,6 +27,88 @@ const TYPE_OPTIONS = Object.entries(ANALYSIS_TYPE_MAP).map(([value, label]) => (
|
||||
label,
|
||||
}));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 分析结果渲染(Markdown 风格)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function AnalysisContent({ content, isDark }: { content: string; isDark: boolean }) {
|
||||
// 简单的 Markdown 风格渲染
|
||||
const lines = content.split('\n');
|
||||
const rendered = lines.map((line, i) => {
|
||||
// 标题行
|
||||
if (line.startsWith('### ')) {
|
||||
return (
|
||||
<Text key={i} strong style={{ display: 'block', fontSize: 14, marginTop: 12, marginBottom: 4 }}>
|
||||
{line.slice(4)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
if (line.startsWith('## ')) {
|
||||
return (
|
||||
<Text key={i} strong style={{ display: 'block', fontSize: 15, marginTop: 16, marginBottom: 6, borderBottom: '1px solid ' + (isDark ? '#1e293b' : '#f0f0f0'), paddingBottom: 4 }}>
|
||||
{line.slice(3)}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
// 列表项
|
||||
if (line.startsWith('- ') || line.startsWith('* ')) {
|
||||
return (
|
||||
<div key={i} style={{ paddingLeft: 16, position: 'relative', lineHeight: 1.8 }}>
|
||||
<span style={{ position: 'absolute', left: 4, color: '#3b82f6' }}>•</span>
|
||||
{renderInlineStyles(line.slice(2))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// 有序列表
|
||||
const orderedMatch = line.match(/^(\d+)\.\s/);
|
||||
if (orderedMatch) {
|
||||
return (
|
||||
<div key={i} style={{ paddingLeft: 16, lineHeight: 1.8 }}>
|
||||
<Text type="secondary" style={{ marginRight: 4 }}>{orderedMatch[1]}.</Text>
|
||||
{renderInlineStyles(line.slice(orderedMatch[0].length))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// 空行
|
||||
if (line.trim() === '') {
|
||||
return <div key={i} style={{ height: 8 }} />;
|
||||
}
|
||||
// 普通段落
|
||||
return <div key={i} style={{ lineHeight: 1.8 }}>{renderInlineStyles(line)}</div>;
|
||||
});
|
||||
|
||||
return <div style={{ fontSize: 13 }}>{rendered}</div>;
|
||||
}
|
||||
|
||||
/** 简单行内样式渲染(粗体、代码) */
|
||||
function renderInlineStyles(text: string) {
|
||||
// 拆分 **bold** 和 `code` 模式
|
||||
const parts = text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g);
|
||||
return parts.map((part, i) => {
|
||||
if (part.startsWith('**') && part.endsWith('**')) {
|
||||
return <Text key={i} strong>{part.slice(2, -2)}</Text>;
|
||||
}
|
||||
if (part.startsWith('`') && part.endsWith('`')) {
|
||||
return (
|
||||
<code key={i} style={{
|
||||
background: 'rgba(59, 130, 246, 0.1)',
|
||||
padding: '1px 4px',
|
||||
borderRadius: 3,
|
||||
fontSize: 12,
|
||||
fontFamily: 'monospace',
|
||||
}}>
|
||||
{part.slice(1, -1)}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
return <span key={i}>{part}</span>;
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 主组件
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export default function AiAnalysisList() {
|
||||
const [data, setData] = useState<AnalysisItem[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
@@ -69,6 +156,15 @@ export default function AiAnalysisList() {
|
||||
}
|
||||
};
|
||||
|
||||
// 解析 metadata 中的趋势统计信息
|
||||
const trendMetrics = useMemo(() => {
|
||||
if (!detail?.result_metadata) return null;
|
||||
const meta = detail.result_metadata as Record<string, unknown>;
|
||||
// 自动分析结果中可能包含 metrics 统计
|
||||
// 也从 sanitized_input 中解析(如果有的话)
|
||||
return meta;
|
||||
}, [detail]);
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
title: '分析类型',
|
||||
@@ -151,36 +247,55 @@ export default function AiAnalysisList() {
|
||||
onExpand: handleExpand,
|
||||
expandedRowRender: () => {
|
||||
if (!detail) return null;
|
||||
const isTrendAnalysis = detail.analysis_type === 'trend';
|
||||
|
||||
return (
|
||||
<div style={{ padding: '8px 0' }}>
|
||||
{/* 自动分析标记 */}
|
||||
{trendMetrics && (trendMetrics as Record<string, unknown>).auto_analysis === true && (
|
||||
<div style={{ marginBottom: 8 }}>
|
||||
<Tag color="purple" style={{ fontSize: 11 }}>
|
||||
<RobotOutlined style={{ marginRight: 4 }} />
|
||||
系统自动分析
|
||||
</Tag>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{detail.error_message && (
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Typography.Text type="danger">错误: {detail.error_message}</Typography.Text>
|
||||
<Text type="danger">错误: {detail.error_message}</Text>
|
||||
</div>
|
||||
)}
|
||||
{detail.result_content && (
|
||||
<div>
|
||||
<Typography.Text strong style={{ display: 'block', marginBottom: 8 }}>
|
||||
<Text strong style={{ display: 'block', marginBottom: 8 }}>
|
||||
分析结果
|
||||
</Typography.Text>
|
||||
</Text>
|
||||
<div
|
||||
style={{
|
||||
background: isDark ? '#0f172a' : '#f8fafc',
|
||||
padding: 16,
|
||||
borderRadius: 8,
|
||||
whiteSpace: 'pre-wrap',
|
||||
fontSize: 13,
|
||||
lineHeight: 1.8,
|
||||
maxHeight: 400,
|
||||
maxHeight: 600,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
{detail.result_content}
|
||||
<AnalysisContent content={detail.result_content} isDark={isDark} />
|
||||
</div>
|
||||
|
||||
{/* 趋势分析类型显示统计摘要提示 */}
|
||||
{isTrendAnalysis && (
|
||||
<div style={{ marginTop: 12, padding: '8px 12px', background: isDark ? '#0f172a' : '#fffbeb', borderRadius: 6, border: `1px solid ${isDark ? '#1e293b' : '#fde68a'}` }}>
|
||||
<Text type="secondary" style={{ fontSize: 11 }}>
|
||||
提示:趋势分析基于最小二乘法线性回归和 2 倍标准差异常检测。R² 越接近 1 表示趋势拟合越好。
|
||||
斜率为正表示上升趋势,斜率为负表示下降趋势。
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!detail.result_content && !detail.error_message && (
|
||||
<Typography.Text type="secondary">暂无结果内容</Typography.Text>
|
||||
<Text type="secondary">暂无结果内容</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user