diff --git a/apps/miniprogram/src/pages/ai-report/detail/index.scss b/apps/miniprogram/src/pages/ai-report/detail/index.scss index e3c0acf..b39fb22 100644 --- a/apps/miniprogram/src/pages/ai-report/detail/index.scss +++ b/apps/miniprogram/src/pages/ai-report/detail/index.scss @@ -99,3 +99,32 @@ color: $tx3; font-size: 28px; } + +.auto-badge { + margin-top: 16px; + display: inline-block; +} + +.auto-badge-text { + display: inline-block; + padding: 4px 16px; + border-radius: 8px; + font-size: 20px; + font-weight: 500; + background: #f0e6ff; + color: #7c3aed; +} + +.trend-tip-card { + background: #fffbeb; + border: 1px solid #fde68a; + border-radius: $r; + padding: 20px 24px; + margin-bottom: 20px; +} + +.trend-tip-text { + font-size: 22px; + color: #92400e; + line-height: 1.6; +} diff --git a/apps/miniprogram/src/pages/ai-report/detail/index.tsx b/apps/miniprogram/src/pages/ai-report/detail/index.tsx index 42a2e53..1e86e27 100644 --- a/apps/miniprogram/src/pages/ai-report/detail/index.tsx +++ b/apps/miniprogram/src/pages/ai-report/detail/index.tsx @@ -55,6 +55,9 @@ export default function AiReportDetail() { ? markdownToHtml(analysis.result_content) : '

暂无分析结果

'; + const isTrendAnalysis = analysis.analysis_type === 'trend'; + const isAutoAnalysis = (analysis.result_metadata as Record)?.auto_analysis === true; + return ( @@ -63,8 +66,21 @@ export default function AiReportDetail() { 模型: {analysis.model_used} {new Date(analysis.created_at).toLocaleString('zh-CN')} + {isAutoAnalysis && ( + + 系统自动分析 + + )} + {isTrendAnalysis && ( + + + 趋势分析基于最小二乘法线性回归和 2 倍标准差异常检测。R² 越接近 1 表示趋势拟合越好。斜率为正表示上升趋势,斜率为负表示下降趋势。 + + + )} + diff --git a/apps/miniprogram/src/services/ai-analysis.ts b/apps/miniprogram/src/services/ai-analysis.ts index a6f2627..73f3c81 100644 --- a/apps/miniprogram/src/services/ai-analysis.ts +++ b/apps/miniprogram/src/services/ai-analysis.ts @@ -7,6 +7,7 @@ export interface AiAnalysisItem { model_used: string; status: string; result_content: string | null; + result_metadata: Record | null; error_message: string | null; created_at: string; } diff --git a/apps/web/src/pages/health/AiAnalysisList.tsx b/apps/web/src/pages/health/AiAnalysisList.tsx index ce4a9bd..f5ecc99 100644 --- a/apps/web/src/pages/health/AiAnalysisList.tsx +++ b/apps/web/src/pages/health/AiAnalysisList.tsx @@ -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 = { 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 ( + + {line.slice(4)} + + ); + } + if (line.startsWith('## ')) { + return ( + + {line.slice(3)} + + ); + } + // 列表项 + if (line.startsWith('- ') || line.startsWith('* ')) { + return ( +
+ + {renderInlineStyles(line.slice(2))} +
+ ); + } + // 有序列表 + const orderedMatch = line.match(/^(\d+)\.\s/); + if (orderedMatch) { + return ( +
+ {orderedMatch[1]}. + {renderInlineStyles(line.slice(orderedMatch[0].length))} +
+ ); + } + // 空行 + if (line.trim() === '') { + return
; + } + // 普通段落 + return
{renderInlineStyles(line)}
; + }); + + return
{rendered}
; +} + +/** 简单行内样式渲染(粗体、代码) */ +function renderInlineStyles(text: string) { + // 拆分 **bold** 和 `code` 模式 + const parts = text.split(/(\*\*[^*]+\*\*|`[^`]+`)/g); + return parts.map((part, i) => { + if (part.startsWith('**') && part.endsWith('**')) { + return {part.slice(2, -2)}; + } + if (part.startsWith('`') && part.endsWith('`')) { + return ( + + {part.slice(1, -1)} + + ); + } + return {part}; + }); +} + +// --------------------------------------------------------------------------- +// 主组件 +// --------------------------------------------------------------------------- + export default function AiAnalysisList() { const [data, setData] = useState([]); 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; + // 自动分析结果中可能包含 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 (
+ {/* 自动分析标记 */} + {trendMetrics && (trendMetrics as Record).auto_analysis === true && ( +
+ + + 系统自动分析 + +
+ )} + {detail.error_message && (
- 错误: {detail.error_message} + 错误: {detail.error_message}
)} {detail.result_content && (
- + 分析结果 - +
- {detail.result_content} +
+ + {/* 趋势分析类型显示统计摘要提示 */} + {isTrendAnalysis && ( +
+ + 提示:趋势分析基于最小二乘法线性回归和 2 倍标准差异常检测。R² 越接近 1 表示趋势拟合越好。 + 斜率为正表示上升趋势,斜率为负表示下降趋势。 + +
+ )}
)} {!detail.result_content && !detail.error_message && ( - 暂无结果内容 + 暂无结果内容 )}
);