import { useState, useEffect, useMemo } from 'react' import { Spin } from 'antd' import { useNavigate } from 'react-router-dom' import { generateDiagnosisReport } from '../../services/analyticsService' /** * AI 诊断报告摘要块 — 收视分析页内嵌 * - 根据 avgShare 判定三档 tier * - 调用 DeepSeek 生成报告 * - 从 markdown 提取核心发现和行动建议 */ /** 从 markdown 中提取指定章节内容(正则模糊匹配) */ function extractSection(markdown, keyword, endKeywords) { if (!markdown) return '' // 用正则匹配:任意数量的 # 开头,后面包含关键词的行 const startRegex = new RegExp(`^#{1,6}\\s*.*${keyword}.*$`, 'm') const match = markdown.match(startRegex) if (!match) return '' const afterStart = match.index + match[0].length // 找最近的结束标记(下一个同级或更高级标题) let endIdx = markdown.length if (endKeywords && endKeywords.length > 0) { for (const kw of endKeywords) { const endRegex = new RegExp(`^#{1,6}\\s*.*${kw}.*$`, 'm') const endMatch = markdown.substring(afterStart).match(endRegex) if (endMatch && (afterStart + endMatch.index) < endIdx) { endIdx = afterStart + endMatch.index } } } else { // 没有 endKeywords 时,找下一个同级标题作为结束 const nextHeading = markdown.substring(afterStart).match(/^#{1,6}\s+/m) if (nextHeading) { endIdx = afterStart + nextHeading.index } } return markdown.substring(afterStart, endIdx).trim() } /** 将 markdown 文本按行拆分,保留 **bold** 为 */ function parseMarkdownLines(text) { if (!text) return [] return text .split('\n') .map((line) => line.replace(/^#+\s*/, '').trim()) .filter((line) => line.length > 0) } /** 将行内 **text** 转为 React 元素 */ function renderInlineMarkdown(text) { const parts = text.split(/(\*\*[^*]+\*\*)/) return parts.map((part, i) => { const boldMatch = part.match(/^\*\*(.+)\*\*$/) if (boldMatch) { return {boldMatch[1]} } return part }) } const LEFT_STYLE = { color: '#c0584f', bg: 'rgba(192,88,79,0.06)', } const TIER_CONFIG = { danger: { leftTitle: '问题聚焦', rightTitle: '病因与预警', rightColor: '#7aa874', rightBg: 'rgba(122,168,116,0.06)', }, on_target: { leftTitle: '核心发现', rightTitle: '病因与提振建议', rightColor: '#5b8db8', rightBg: 'rgba(91,141,184,0.06)', }, excellent: { leftTitle: '高光复盘', rightTitle: '经验总结', rightColor: '#c0584f', rightBg: 'rgba(192,88,79,0.06)', }, } function DiagnosisSummary({ episodes, yearlyTarget, selectedYear }) { const navigate = useNavigate() const [report, setReport] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) // 计算平均份额和 tier const { avgShare, tier, epStart, epEnd } = useMemo(() => { if (!episodes || episodes.length === 0 || !yearlyTarget) { return { avgShare: 0, tier: null, epStart: 0, epEnd: 0 } } const withShare = episodes.filter((ep) => ep.audience_share != null) if (withShare.length === 0) { return { avgShare: 0, tier: null, epStart: 0, epEnd: 0 } } const sum = withShare.reduce((acc, ep) => acc + Number(ep.audience_share), 0) const avg = sum / withShare.length const base = Number(yearlyTarget.base_target) const stretch = Number(yearlyTarget.stretch_target) let t = 'danger' if (avg > stretch) t = 'excellent' else if (avg >= base) t = 'on_target' const nums = withShare.map((ep) => ep.episode_number) return { avgShare: avg, tier: t, epStart: Math.min(...nums), epEnd: Math.max(...nums), } }, [episodes, yearlyTarget]) const config = tier ? TIER_CONFIG[tier] : null // 调用 API useEffect(() => { if (!tier || !episodes || episodes.length === 0) return let cancelled = false setLoading(true) setError(null) setReport(null) generateDiagnosisReport({ year: selectedYear, ep_start: epStart, ep_end: epEnd, }) .then((data) => { if (cancelled) return if (data.error) { setError(data.error) } else { setReport(data) } }) .catch((err) => { if (cancelled) return setError(err?.response?.data?.detail || '报告生成失败') }) .finally(() => { if (!cancelled) setLoading(false) }) return () => { cancelled = true } }, [selectedYear, epStart, epEnd, tier, episodes]) // 空态 if (!episodes || episodes.length === 0 || !tier) { return (

AI 诊断报告

请先选择包含收视数据的期次范围

) } // 提取核心发现和行动建议 const markdown = report?.report_markdown || '' // 核心发现:关键词匹配"核心发现",到"深度分析"或"三、" let coreFindings = extractSection(markdown, '核心发现', ['深度分析', '三、']) // 行动建议:关键词匹配"行动建议",到末尾 let actions = extractSection(markdown, '行动建议', []) const coreLines = parseMarkdownLines(coreFindings) const actionLines = parseMarkdownLines(actions) return (
{/* 标题行 */}

AI 诊断报告

{report && !loading && ( navigate(`/analytics/report?year=${selectedYear}&start=${epStart}&end=${epEnd}`) } > 查看完整报告 → )}
{/* 内容 */} {loading ? (
正在生成 AI 诊断报告…
) : error ? (
{error}
) : report ? (
{/* 左块(固定粉红色系) */}
{config.leftTitle}
{coreLines.length > 0 ? ( coreLines.map((line, i) => (

{renderInlineMarkdown(line)}

)) ) : (

报告已生成,请查看完整报告

)}
{/* 右块(跟随 tier 变色) */}
{config.rightTitle}
{actionLines.length > 0 ? ( actionLines.map((line, i) => (

{renderInlineMarkdown(line)}

)) ) : (

报告已生成,请查看完整报告

)}
) : null}
) } export default DiagnosisSummary