From f99892ee16919396edfcd289130efbf63e9e23e4 Mon Sep 17 00:00:00 2001 From: iven Date: Tue, 28 Apr 2026 20:12:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(web+mp):=20AI=20=E5=88=86=E6=9E=90?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E5=A2=9E=E5=BC=BA=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Web 端 AiAnalysisList: - 分析结果 Markdown 风格渲染(标题/列表/粗体/代码) - 趋势分析类型显示统计方法提示 - 自动分析结果显示「系统自动分析」标签 小程序 ai-report/detail: - 新增 result_metadata 字段 - 自动分析标记(紫色标签) - 趋势分析统计方法说明卡片 --- .../src/pages/ai-report/detail/index.scss | 29 ++++ .../src/pages/ai-report/detail/index.tsx | 16 +++ apps/miniprogram/src/services/ai-analysis.ts | 1 + apps/web/src/pages/health/AiAnalysisList.tsx | 133 ++++++++++++++++-- 4 files changed, 170 insertions(+), 9 deletions(-) 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 && ( - 暂无结果内容 + 暂无结果内容 )}
);