From 9df48681919da6cb0ec2ab879467c7da59220f36 Mon Sep 17 00:00:00 2001 From: simonkoson <28867558@qq.com> Date: Fri, 3 Jul 2026 21:25:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20L4=20AI=20=E8=AF=8A=E6=96=AD=E6=8A=A5?= =?UTF-8?q?=E5=91=8A=E4=BC=98=E5=8C=96=20+=20Prompt/=E6=91=98=E8=A6=81?= =?UTF-8?q?=E5=8D=A1/=E8=BF=81=E7=A7=BB/=E6=96=87=E6=A1=A3=E5=AE=8C?= =?UTF-8?q?=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正 DeepSeek 模型名 deepseek-chat → deepseek-v4-pro - 摘要块:修复 **粗体** markdown 渲染、左右块可滚动、左块固定粉红色系 - 新增 prompt4(内容摘要卡)+ prompt5(诊断报告)+ 三处 prompt5 优化 - 新增 004 迁移(episodes +content_digest JSONB)、导入脚本、摘要卡生成脚本 - 更新 CLAUDE.md 状态栏/进度/交接备注/关键决策 Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 30 +- ai-labeling/prompts/prompt4_content_digest.md | 108 ++ .../prompts/prompt5_diagnosis_report.md | 153 ++ ai-labeling/scripts/gen_content_digest.py | 260 ++++ backend/app/api/analytics.py | 4 +- backend/app/models/episode.py | 3 + backend/sql/004_add_content_digest.sql | 9 + doco/CLAUDE.md | 7 +- docs/api_credentials_inventory.md | 8 +- .../components/Analytics/DiagnosisSummary.jsx | 49 +- frontend/src/pages/Analytics/Analytics.css | 24 +- note/寄存条Doco文稿整理子项目已外迁.md | 4 +- package-lock.json | 1291 ++++++++++++++++- package.json | 7 + scripts/import_content_digests.py | 78 + 15 files changed, 1993 insertions(+), 42 deletions(-) create mode 100644 ai-labeling/prompts/prompt4_content_digest.md create mode 100644 ai-labeling/prompts/prompt5_diagnosis_report.md create mode 100644 ai-labeling/scripts/gen_content_digest.py create mode 100644 backend/sql/004_add_content_digest.sql create mode 100644 package.json create mode 100644 scripts/import_content_digests.py diff --git a/CLAUDE.md b/CLAUDE.md index 9029e77..721427c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,7 @@ ## 🔖 状态栏(每次结束 session 前必须更新这三行) - **最后更新**:Claude(顾问+动手)| 2026-07-03 - - **当前状态一句话**:收视分析看板 React 前端 L1–L4 已实现并验收(指标卡+走势图+季度/编导/题材对比+双引擎象限图)。25 期真实收视+AI 标签已导入。下一步:L4 AI 诊断报告(新 session 讨论)。 + - **当前状态一句话**:L4 AI 诊断报告已实现(DeepSeek V4 Pro 生成、摘要块+详情页、三档自适应、22期内容摘要卡已入库)。收视分析看板 L1-L4 全部完成。 - **下一个动手的人从这里开始**:见下方「⏩ 交接备注」 --- @@ -74,16 +74,17 @@ ## 3. 当前进度(动态,核心交接区 — 以最新快照为准) - - **已完成至**:收视分析看板 React 前端 L1–L4(指标卡、走势图、季度/编导/题材对比、双引擎象限图),25 期真实数据已导入。 - - **正在做**:无。下一步为 L4 AI 诊断报告,制片人将开新 session 讨论。 - - **卡点/待解**:AI 诊断报告方案待细化(双步骤生成+制片人人工验证,方案草稿见 `memory/project_ai_diagnosis_plan.md`)。 - - **Schema 状态**:知识库两表 schema 已就绪,**Phase 3 不再改表**。episodes 表已通过 003 迁移加 7 列 AI 标签字段(看板升级子项目 2026-06-26 执行),25 期数据含 AI 标签已就位。 + - **已完成至**:收视分析看板 L1-L4 全部完成,含 L4 AI 诊断报告(DeepSeek V4 Pro 生成、摘要块三档自适应+详情页 Markdown 渲染、22期内容摘要卡入库)。 + - **正在做**:无。 + - **卡点/待解**:无硬卡点。下一刀候选见待办。 + - **Schema 状态**:episodes 表已通过 003(+7 AI 标签列)和 004(+content_digest JSONB)迁移。知识库两表不再改。 --- ## 4. 已完成(只追加,最新在上) - - [2026-07-03] **收视分析看板 React 前端实现(L1–L4)**:① 指标卡 5 列(均值/基础达标率/摸高完成率含四档动画/期次数/年度目标);② 走势折线图(dataZoom 滑块+确认按钮范围过滤,下游模块联动);③ 双列对比区(左列季度+编导、右列题材饼图+条形图);④ 双引擎象限图(题材热度×叙事结构散点,气泡编码份额/三色/钩子强度)。附带:25 期真实收视+AI 标签数据导入、侧边栏 fixed 定位、滑块滚轮冲突修复。后端 analytics API 增返 4 个 AI 标签字段。 + - [2026-07-03] **L4 AI 诊断报告**:① Prompt 4(内容摘要卡,MiMo 生成)+ Prompt 5(诊断报告,DeepSeek V4 Pro)撰写完成;② 22 期文稿摘要卡批量生成并导入 episodes.content_digest(004 迁移);③ 后端 POST /api/analytics/diagnosis-report 端点(组装数据+调 DeepSeek+内存缓存);④ 前端摘要块 DiagnosisSummary(左右两块、三档自适应色调、可滚动、粗体渲染)+ 详情页 DiagnosisReport(react-markdown 渲染+重新生成+免责声明);⑤ 话题性(社交货币)三维框架设计(大众认知度/降维切口/惊奇密度)。模型选型:DeepSeek V4 Pro 胜出(vs Qwen-Max 对比测试)。 + - [2026-07-03] **收视分析看板 React 前端实现(L1–L3)**:① 指标卡 5 列(均值/基础达标率/摸高完成率含四档动画/期次数/年度目标);② 走势折线图(dataZoom 滑块+确认按钮范围过滤,下游模块联动);③ 双列对比区(左列季度+编导、右列题材饼图+条形图);④ 双引擎象限图(题材热度×叙事结构散点,气泡编码份额/三色/钩子强度)。附带:25 期真实收视+AI 标签数据导入、侧边栏 fixed 定位、滑块滚轮冲突修复。后端 analytics API 增返 4 个 AI 标签字段。 - [2026-07-03] 看板升级子项目原型阶段收工:L2 打标流水线就绪(GT v0.6.0 / 25 期 / 三个 Prompt 可用)+ L3 HTML 原型验证通过(双引擎象限图+5 模块+视觉定稿)+ 003 迁移已执行(episodes +7 AI 标签列)。交付清单见寄存条。 - [2026-05-27] Phase 3 Task 3/3.1/3.2:知识库树形视图(按来源大类→二级维度分组、左右布局、节点联动、展开收起;修出处筛选/宽度跳动/排版)。commit `3409d48`。 - [2026-05-27] Phase 3 Task 2:知识库管理后台最小版(上传/列表/删除)。 @@ -96,7 +97,7 @@ ## 5. 待办(下一刀候选,开局前定先做哪件) - - [ ] **L4 AI 诊断报告**:收视分析看板最后一个模块,双步骤生成(先 AI 出草稿→制片人审核发布),方案草稿见 `memory/project_ai_diagnosis_plan.md`,制片人将开新 session 讨论。 + - [x] ~~L4 AI 诊断报告~~ → 已完成(2026-07-03) - [ ] **下一刀三选一**:① 语义搜索界面(不依赖任何材料,随时能开,是 Phase 4a 硬门槛);② PDF 原文关联 + 大文件存储架构(需 Opus 审方案,优先级较高);③ 界面像素级打磨+视觉规范统一。 - [ ] PDF 大文件存储:**大文件不入库,单独文件仓库,DB 只存地址指针**(md 正文+向量留库参与检索;pdf 仅按需调阅)。地基一次定对,避免上云返工。 - [ ] 200+ Obsidian md 批量录入(**建议在 PDF 存储方案定后做**;先试 10 篇验证解析/落位再全量,每篇都真调 MiniMax 耗额度)。 @@ -124,14 +125,21 @@ - embedding 用 **MiniMax embo-01**:请求用 `texts` 数组 + `type`(db/query),返回在 `vectors` 字段,需 API Key + GroupId;花 MiniMax 账号额度(与 Claude token 两笔账,一篇几厘钱)。 - **PDF 等大文件不入数据库**,单独文件仓库 + DB 存指针(详见待办,待立项)。 - **收视分析看板 dataZoom 模式**:只用 slider(禁 `type:'inside'` 避免滚轮冲突),拖滑块更新 `zoomRange` 但不立即过滤,用户点「应用此范围」按钮才写入 `appliedRange` → `filteredEpisodes`;走势图始终用全量 `episodes`,其余模块(指标卡/对比/象限图)用 `filteredEpisodes`。 - - **收视分析看板页面顺序**:指标卡 → 走势图 → AI 诊断报告(占位) → 双引擎象限图 → 双列对比(左列季度+编导堆叠、右列题材跨两行)。 + - **收视分析看板页面顺序**:指标卡 → 走势图 → AI 诊断报告(摘要块) → 双引擎象限图 → 双列对比(左列季度+编导堆叠、右列题材跨两行)。 + - **L4 AI 诊断报告模型选型**:DeepSeek V4 Pro(胜出,分析深度+论证结构优于 Qwen-Max)。不搞多模型共识——诊断报告是完整分析文章,逻辑连贯性比多家一致更重要。 + - **L4 摘要块视觉**:左块固定粉红色系(`#c0584f`),右块跟 tier 三档变色(绿/蓝/红)。两块均可滚动查看完整内容。 + - **L4 话题性框架**:社交货币理论三维(大众认知度/降维切口/惊奇密度),写入摘要卡不作为独立 AI 标签字段。 + - **L4 prompt5 约束**:① 提及期次必须用「第X期《节目名》」格式;② 话题性必须独立分析段;③ 军事装备分类须准确(枪械≠冷兵器)。 --- ## 7. ⏩ 交接备注(换人/换账号 0 摩擦续上) - - **收视分析看板前端 L1–L4 已验收**,下一步是 L4 AI 诊断报告(新 session)。页面布局顺序已定稿:指标卡 → 走势图 → AI 诊断报告(占位) → 双引擎象限图 → 双列对比(左:季度+编导, 右:题材)。 - - **看板前端新增文件**:`frontend/src/components/Analytics/`(QuarterCompare / EditorCompare / TopicCompare / QuadrantChart);`scripts/`(import_real_episodes.py / fix_label_mapping.py,一次性导入脚本,已用完)。 + - **收视分析看板 L1-L4 全部完成**。页面布局:指标卡 → 走势图 → AI 诊断报告(摘要块) → 双引擎象限图 → 双列对比(左:季度+编导, 右:题材)。 + - **L4 AI 诊断报告新增文件**:后端 `backend/app/api/analytics.py`(POST 端点);前端 `DiagnosisSummary.jsx`(摘要块)+ `DiagnosisReport.jsx/.css`(详情页);Prompt 文件 `ai-labeling/prompts/prompt4_content_digest.md` + `prompt5_diagnosis_report.md`;批量脚本 `ai-labeling/scripts/gen_content_digest.py` + `scripts/import_content_digests.py`。 + - **L4 依赖**:`backend/.env` 需配 `DEEPSEEK_API_KEY`;前端需 `react-markdown` 包(已装);后端需 `openai` 包(已装)。 + - **004 迁移已执行**:episodes 表 +content_digest JSONB 列,22 期摘要卡已导入。 + - **看板前端其他文件**:`frontend/src/components/Analytics/`(QuarterCompare / EditorCompare / TopicCompare / QuadrantChart);`scripts/`(import_real_episodes.py / fix_label_mapping.py / import_content_digests.py,一次性导入脚本)。 - **schema 红线**:Phase 3 不改知识库两表;`topics` / `topic_embeddings` 属 Phase 4a,**勿碰**;五类 `source_type` 枚举固定不增减,细分杂志/军报=改表=须 Opus 审。 - **`logs/` 是受保护资产**:Act 模式严禁 Cline 改;phase 日志由顾问协助生成。Task 3 三轮内容待并入 `phase3_log`(Task 3 段)。 - **避雷(Phase 3 实战教训)**: @@ -151,6 +159,6 @@ ## 9. 待确认 / 开放问题(需制片人拍板,AI 别自行假设) - - [ ] AI 诊断报告(L4)方案细化(新 session 讨论)。 + - [x] ~~AI 诊断报告(L4)方案细化~~ → 已完成(2026-07-03) - [ ] PDF 文件存哪、笔记如何关联中台内文件位置(Backlog #2 的前提)。 - [ ] 视觉风格参考图、6 名编导初始画像(各 200 字)、甘特图样本 —— 后续 Phase 需要。 diff --git a/ai-labeling/prompts/prompt4_content_digest.md b/ai-labeling/prompts/prompt4_content_digest.md new file mode 100644 index 0000000..5bf6741 --- /dev/null +++ b/ai-labeling/prompts/prompt4_content_digest.md @@ -0,0 +1,108 @@ +# Prompt 4:节目内容摘要卡生成 + +## 角色 + +你是一位资深电视节目分析师,擅长从军事科技类节目文稿中提炼核心信息。你的任务是将一篇 5000-7000 字的节目文稿压缩为一份 150-200 字的结构化摘要卡,供后续的收视诊断分析系统使用。 + +## 背景知识 + +你分析的是央视《军事科技》栏目的节目文稿。该栏目每周一期,每期约 27 分钟,面向对军事装备和国防科技感兴趣的大众观众(非专业军事人员)。 + +文稿由以下段落类型构成: +- 【导视】:片头预告,通常包含本期核心悬念或看点 +- 【主持人N】:演播室主持人串场,负责起承转合 +- 【解说N】:画外音解说,承载主要信息量 +- 【三维动画解说N】:配合三维动画的技术原理讲解 +- 【专家N】:专家访谈,提供专业解读 +- 【街采】:街头采访普通观众(部分期次有) + +## 输出格式 + +请严格按以下 JSON 格式输出,不要输出任何其他内容: + +```json +{ + "核心切口": "一句话(20-40字),概括本期节目用什么角度切入军事科技话题", + "叙事亮点": [ + "第一个亮点(30-50字):本期最出彩的段落、转折、对比或论证设计", + "第二个亮点(30-50字,可选):如有第二个突出的叙事设计" + ], + "观众门槛": "低/中/高 — 一句话说明(20-30字)", + "话题性": { + "总评": "强/中/弱", + "大众认知度": "高/中/低 — 一句话理由(15-25字)", + "降维切口": "强/中/弱 — 一句话理由(15-25字)", + "惊奇密度": "高/中/低 — 一句话理由(15-25字)" + }, + "潜在弱点": [ + "第一个弱点(30-50字):节奏、深度、趣味性、信息密度等方面的不足" + ], + "时效关联": "有/无 — 如有则说明借势了什么热点/节日/新装备亮相(20-40字)" +} +``` + +## 各字段评估标准 + +### 核心切口 +提炼本期节目的"一句话卖点"——制片人用什么角度把军事科技话题包装成观众愿意点开的内容。重点是"角度"而非"题材"。 + +### 叙事亮点 +关注文稿中最能抓住观众的叙事设计,例如: +- 出人意料的对比(如丑装备 vs 美装备、竞标失败者 vs 成功者) +- 引人入胜的历史故事或真实事件 +- 巧妙的类比或降维解释 +- 层层递进的悬念设置 +- 街头采访带来的反差或趣味 + +### 观众门槛 +评估一个对军事完全不了解的普通观众,能否顺畅理解本期内容: +- **低**:日常概念切入(如颜值、仿生、玩具),无需军事知识储备 +- **中**:需要一定常识(如知道航母是什么、战斗机分代),但节目有充分铺垫 +- **高**:涉及专业概念(如气动布局、相控阵雷达原理),且节目未充分降维 + +### 话题性(社交货币评估) +话题性的本质是:观众看完这期节目后,在多大程度上愿意主动跟别人聊起它——在微信群、饭桌上、茶歇时说"你看了昨天那期军事科技吗"。 + +三个判据: + +**大众认知度**:节目涉及的核心装备或概念,普通人听没听说过? +- 高:航母、坦克、AK47、隐身战机、核潜艇——街上随便拉个人都知道 +- 中:预警机、驱逐舰、无人机——看过新闻的人大概知道 +- 低:鸭翼气动布局、舰载相控阵雷达、弹道导弹防御层级——需要军事爱好者才懂 + +**降维切口**:节目的切入角度,不懂军事的人能不能秒懂、想聊? +- 强:用日常概念(颜值、仿生、玩具、进化论)包装军事话题,任何人都能参与讨论 +- 中:切口有一定趣味(竞标失败者的命运、谁更强),但仍需基本军事兴趣 +- 弱:直接讲技术原理或装备参数,非爱好者难以产生讨论欲 + +**惊奇密度**:全片中有几个让观众"哇!"的瞬间? +- 高:3个以上反直觉事实、震撼对比、意外反转(如"丑的反而打赢了""这个国家竟然拦截过黑鸟") +- 中:1-2个有趣的知识点或对比,但整体平稳 +- 低:全片信息性为主,缺少让人惊讶或想转述的瞬间 + +三项中占 2 项以上为"强",占 1 项为"中",0 项为"弱"。 + +### 潜在弱点 +从"观众会不会中途换台"的角度审视,常见弱点包括: +- 信息密度过高,观众消化不了 +- 中段节奏拖沓,缺乏新的刺激点 +- 技术讲解过于抽象,缺少具象化呈现 +- 案例之间缺乏递进关系,像在念清单 +- 结论过于笼统,缺少有记忆点的金句 +- 同类题材短期内重复出现(如连续多期讲枪械) + +### 时效关联 +判断本期播出时是否借势了外部事件: +- 新装备亮相/首飞/下水/交付 +- 军事冲突或国际安全事件 +- 国庆/建军节等军事相关节日 +- 航展/军事展会 +- 如文稿中未提及任何时事背景,标"无"即可 + +## 注意事项 + +1. 摘要卡的读者不是观众,而是后续的 AI 诊断系统——用分析性语言,不要用宣传性语言。 +2. 每个字段的评估必须基于文稿内容本身,不要凭题目猜测。 +3. 话题性评估关注的是"传播潜力"而非"内容质量"——一期制作精良但话题封闭的节目可能话题性弱,一期制作普通但切口有趣的节目可能话题性强。 +4. 潜在弱点要具体到本期内容,不要给泛泛的评价。 +5. 总字数控制在 150-200 字(JSON 内纯文本,不含 key 和格式符号)。 diff --git a/ai-labeling/prompts/prompt5_diagnosis_report.md b/ai-labeling/prompts/prompt5_diagnosis_report.md new file mode 100644 index 0000000..5bc8289 --- /dev/null +++ b/ai-labeling/prompts/prompt5_diagnosis_report.md @@ -0,0 +1,153 @@ +# Prompt 5:收视诊断报告生成 + +## 角色 + +你是央视《军事科技》栏目的资深收视分析顾问。你的任务是基于一组节目的收视数据、AI 标签和内容摘要,撰写一份有深度、有细节的收视诊断分析报告。 + +你的分析对象是"节目",不是"人"。报告中不对任何编导进行评价、排名或点名。 + +## 背景知识 + +### 栏目基本情况 +《军事科技》是央视国防军事频道的周播节目,每期约 27 分钟,面向对军事装备和国防科技感兴趣的大众观众。栏目 8 人团队(制片人、责编、6 名编导)。 + +### 收视颜色判定(与通常直觉相反) +- 🔴 红色 = 优秀:收视份额 **高于** 摸高目标 +- 🔵 蓝色 = 达标:收视份额介于基础目标与摸高目标之间 +- 🟢 绿色 = 待提升:收视份额 **低于** 基础目标 + +### 核心分析框架:双引擎模型 +收视表现由两个"引擎"驱动,加上一个独立维度: + +**引擎 1:题材热度(地基)** +热门装备(航母、隐身战机)、热点事件自带观众基础。即使叙事一般,热门题材也能撑住收视。 + +**引擎 2:叙事结构(放大器)** +有贯穿全片的主线悬念(主线演进)能放大收视,板块式并列结构则依赖每个板块自身吸引力。 + +**独立维度:开篇钩子** +前 1-2 分钟决定观众是否换台。强钩子能挽留犹豫的观众,弱钩子让潜在观众流失。 + +**核心规律:两条腿至少占一条。** 冷门题材 + 并列结构 = 高风险组合。 + +### 话题性(社交货币) +话题性 = 观众看完后愿不愿意主动跟别人聊这期节目。由三个因素决定: +- 大众认知度:核心装备/概念普通人知不知道 +- 降维切口:节目角度非军迷能不能秒懂、想聊 +- 惊奇密度:全片有多少个让人"哇!"的瞬间 + +## 输入数据格式 + +你会收到以下结构化数据: + +``` +分析范围:第X期 至 第Y期(共N期) +年度目标:基础目标 0.XXXX,摸高目标 0.XXXX +整体统计:平均份额、达标率、摸高完成率等 + +逐期数据: +| 期号 | 节目名 | 份额 | 判定 | 题材类型 | 叙事结构 | 钩子强度 | 话题性 | +| ... | + +各期内容摘要卡: +第X期《节目名》: + 核心切口:... + 叙事亮点:... + 观众门槛:... + 话题性:总评/大众认知度/降维切口/惊奇密度 + 潜在弱点:... + 时效关联:... +``` + +## 输出要求 + +### 报告色调(三档自适应) + +根据所选范围的平均收视份额,自动切换分析语气: + +**当平均份额 < 基础目标时(危险区):** +- 语气严厉、直接,不回避问题 +- 用词示例:"必须正视""亮红灯""持续恶化""如不及时调整" +- 重点放在问题诊断和止损建议 + +**当平均份额介于基础目标与摸高目标之间时(达标区):** +- 语气客观、偏鼓励,帮助团队找准发力方向 +- 用词示例:"整体达标但仍有提升空间""值得关注的是""建议进一步" +- 重点放在原因分析和提振方向 + +**当平均份额 > 摸高目标时(优秀区):** +- 语气肯定、激励,提炼可复用的成功经验 +- 用词示例:"表现亮眼""成功经验值得推广""再接再厉" +- 重点放在经验总结和保持势头 + +### 报告结构 + +请按以下结构输出(用 Markdown 格式): + +--- + +#### 一、总体判断(2-3句话) + +用一句话定调整体表现,再用 1-2 句话点出最突出的特征或趋势。要求:精准、有冲击力、能让读者立刻把握全局。 + +#### 二、核心发现(3-5条) + +每条发现必须: +- 以加粗的结论性判断开头 +- 紧跟具体数据佐证(期号、份额、百分比) +- 点到具体节目名称(**必须用"第X期《节目名》"格式,禁止只写期号不带节目名**——读者记不住期号对应哪期节目) +- 50-80 字/条 + +核心发现应覆盖以下维度(不必全覆盖,选最有价值的): +- 收视走势的关键转折点或趋势 +- 题材类型与收视的关联规律 +- 叙事结构(主线演进 vs 并列结构)的效果差异 +- 钩子强度与观众留存的关系 +- 话题性与收视的对应关系 +- 异常值(特别高或特别低)的原因 + +#### 三、深度分析(3-4段) + +这是报告的核心价值区。每段 80-120 字,要求: +- 不是简单地重复数据,而是**解释为什么** +- 结合内容摘要卡中的具体信息(节目的切口、叙事亮点、观众门槛、潜在弱点) +- 运用双引擎模型解释收视高低的因果关系 +- 指出**可迁移的规律**,而不是就事论事 +- **必须进行跨期归纳**:这份报告分析的是一段时间内的多期节目,你要找出这些节目的共性规律。如果整体表现好,要总结"这批节目做对了什么、共同特征是什么";如果整体表现差,要总结"这批节目共同踩了什么坑";如果好坏参半,要对比分析"好的那几期和差的那几期,差异到底在哪"。逐期点评是数据罗列,跨期归纳才是分析价值。 + +分析角度示例: +- "第X期《节目名》虽然是冷门题材(XX),但凭借'XX'的降维切口和强钩子逆袭到XX份额,证明切口设计能弥补题材劣势" +- "第X期《节目名》至第Y期《节目名》连续N期低于基础线,共同特征是XX类题材+并列结构+XX门槛,说明……" +- "对比同为XX题材的第X期《节目名》(份额XX)和第Y期《节目名》(份额XX),差异主要在于……" + +**话题性/社交货币维度必须独立分析:** 深度分析中必须有一段专门分析话题性(社交货币)维度。不要笼统地说"话题性偏弱",而要拆到大众认知度、降维切口、惊奇密度三个子维度,指出这批节目在哪个子维度上集体失分、哪个子维度是个别节目的亮点。话题性分析的价值在于:告诉制片人"观众为什么看完不想跟人聊",而非重复收视数字。 + +#### 四、行动建议(2-3条) + +每条建议必须: +- 具体可执行,不说正确的废话 +- 基于前面的分析逻辑推导出来 +- 面向选题策划和节目制作环节 +- 40-60 字/条 + +好的建议示例: +- "控制XX类题材的连续出现频次,建议间隔至少N期,中间插入XX类题材缓冲" +- "XX类节目建议优先采用主线演进结构,第X期已验证该组合的收视拉动效果" + +差的建议示例(避免): +- "提高节目质量"(太空) +- "加强选题策划"(无具体方向) +- "某编导应该……"(不评价人) + +--- + +## 注意事项 + +1. **只分析节目,不评价编导。** 报告中不出现任何编导姓名,不对编导进行排名、评价或建议。 +1.5. **全文使用"第X期《节目名》"格式。** 每次提及具体期次都必须同时带期号和节目名,不允许只写"第X期"而不带节目名。读者不会记住期号与节目的对应关系。 +1.6. **军事装备分类须准确。** 枪械(机枪、步枪、冲锋枪等)属于轻武器/热兵器,不是冷兵器;舰炮属于海军武器系统,不是"冷兵器"。请使用准确的军事分类术语。 +2. **数据驱动,不凭感觉。** 每个判断都要有数据或具体案例支撑。 +3. **写给制片人看。** 语言简洁有力,不堆术语,不写学术论文。制片人熟悉每一期节目,不需要你复述剧情,只需要你指出规律和原因。 +4. **实事求是。** 数据好就说好,数据差就说差,不要为了平衡而硬找优点或硬找缺点。 +5. **报告总字数 600-900 字。** 这是一份内部工作文档,不是公开发表的研究报告。 +6. **外部因素诚实标注。** 如果某期收视异常可能受外部因素影响(如同时段重大事件、特殊节假日),但你无法确认,可以标注"可能受外部因素影响,建议结合当期播出环境分析",不要编造外部因素。 diff --git a/ai-labeling/scripts/gen_content_digest.py b/ai-labeling/scripts/gen_content_digest.py new file mode 100644 index 0000000..d2f6b1c --- /dev/null +++ b/ai-labeling/scripts/gen_content_digest.py @@ -0,0 +1,260 @@ +""" +gen_content_digest.py - 批量生成 22 期节目内容摘要卡 +用法: + cd E:\tps-dashboard\ai-labeling + python scripts/gen_content_digest.py + +功能: + - 读取 prompt4_content_digest.md 作为 system prompt + - 遍历 doco/deliverables/ 下所有融合A稿 .docx 文件 + - 用 python-docx 提取文稿文本,调用 MiMo API 生成结构化摘要卡 + - 支持断点续跑(已存在的 digest 文件自动跳过) +""" + +import sys +sys.stdout.reconfigure(encoding='utf-8') +sys.stderr.reconfigure(encoding='utf-8') + +import os +import re +import json +import time +from pathlib import Path +from datetime import datetime +from openai import OpenAI +from dotenv import load_dotenv +from docx import Document + +# 加载 .env(优先加载 ai-labeling 目录下的 .env) +SCRIPT_DIR = Path(__file__).parent +BASE_DIR = SCRIPT_DIR.parent # ai-labeling/ +load_dotenv(BASE_DIR / ".env") +load_dotenv() # 也尝试加载项目根目录的 .env + +# 目录配置 +DELIVERABLES_DIR = BASE_DIR.parent / "doco" / "deliverables" +PROMPTS_DIR = BASE_DIR / "prompts" +EXPERIMENTS_DIR = BASE_DIR / "experiments" / "content_digests" + +# MiMo API 配置(与 run_labeling.py 一致) +MIMO_CONFIG = { + "base_url": "https://api.xiaomimimo.com/v1", + "model_name": "mimo-v2.5-pro", + "api_key_env": "MIMO_API_KEY", +} + + +def load_system_prompt() -> str: + """加载 prompt4_content_digest.md 作为 system prompt。""" + prompt_file = PROMPTS_DIR / "prompt4_content_digest.md" + if not prompt_file.exists(): + raise FileNotFoundError(f"找不到 prompt 文件: {prompt_file}") + return prompt_file.read_text(encoding="utf-8") + + +def parse_filename(filename: str) -> dict: + """ + 从文件名解析元信息。 + 文件名格式: 第02期_20260113_武器进化论:海战颠覆者_付天雨_融合A稿.docx + 返回: {"ep": 2, "date": "2026-01-13", "title": "...", "editor": "..."} + """ + name = filename.replace(".docx", "") + parts = name.split("_") + if len(parts) < 4: + return None + # 解析期号 + ep_match = re.search(r'第(\d+)期', parts[0]) + if not ep_match: + return None + ep = int(ep_match.group(1)) + # 解析日期(YYYYMMDD -> YYYY-MM-DD) + raw_date = parts[1] + if len(raw_date) == 8 and raw_date.isdigit(): + date = f"{raw_date[:4]}-{raw_date[4:6]}-{raw_date[6:8]}" + else: + date = raw_date + title = parts[2] + editor = parts[3] + return {"ep": ep, "date": date, "title": title, "editor": editor} + + +def extract_docx_text(filepath: Path) -> str: + """用 python-docx 提取 .docx 文件的全部文本,逐段拼接。""" + doc = Document(str(filepath)) + paragraphs = [] + for para in doc.paragraphs: + text = para.text.strip() + if text: + paragraphs.append(text) + return "\n".join(paragraphs) + + +def build_user_message(meta: dict, body: str) -> str: + """构造 user message:元信息 + 文稿全文。""" + return ( + f"期号:第{meta['ep']:02d}期\n" + f"播出日期:{meta['date']}\n" + f"节目名:{meta['title']}\n" + f"编导:{meta['editor']}\n" + f"\n以下是节目文稿全文:\n\n{body}" + ) + + +def extract_json_from_response(raw: str) -> dict: + """从模型响应中提取 JSON,兼容推理模型的...输出。""" + # 先去掉...标签及其内容 + text = re.sub(r'.*?', '', raw, flags=re.DOTALL) + text = text.strip() + # 去掉 markdown 代码块 + text = re.sub(r'^```(?:json)?\s*', '', text) + text = re.sub(r'\s*```$', '', text) + text = text.strip() + # 从第一个 { 开始,到最后一个 } 结束 + first_brace = text.find('{') + last_brace = text.rfind('}') + if first_brace != -1 and last_brace != -1 and last_brace >= first_brace: + json_str = text[first_brace:last_brace + 1] + return json.loads(json_str) + # 兜底:直接尝试解析 + return json.loads(text) + + +def call_mimo(system_prompt: str, user_prompt: str) -> dict: + """调用 MiMo API 生成摘要卡,返回解析后的 JSON dict。""" + api_key = os.environ.get(MIMO_CONFIG["api_key_env"]) + if not api_key: + raise EnvironmentError( + f"环境变量 {MIMO_CONFIG['api_key_env']} 未设置,请检查 ai-labeling/.env 或根目录 .env 文件" + ) + client = OpenAI( + api_key=api_key, + base_url=MIMO_CONFIG["base_url"], + ) + response = client.chat.completions.create( + model=MIMO_CONFIG["model_name"], + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_prompt}, + ], + temperature=0.0, + # 关闭 thinking(与 run_labeling.py 一致) + extra_body={"thinking": {"type": "disabled"}}, + ) + raw = response.choices[0].message.content + return extract_json_from_response(raw) + + +def collect_docx_files() -> list[dict]: + """收集所有 .docx 文件并按期号排序。""" + files = [] + for f in sorted(DELIVERABLES_DIR.iterdir()): + if not f.name.endswith(".docx"): + continue + meta = parse_filename(f.name) + if meta is None: + print(f"⚠️ 跳过无法解析的文件: {f.name}") + continue + meta["filepath"] = f + files.append(meta) + # 按期号排序 + files.sort(key=lambda x: x["ep"]) + return files + + +def main(): + # 确保输出目录存在 + EXPERIMENTS_DIR.mkdir(parents=True, exist_ok=True) + + # 加载 system prompt + system_prompt = load_system_prompt() + print(f"✅ 已加载 system prompt: prompt4_content_digest.md") + + # 收集 docx 文件 + docx_files = collect_docx_files() + print(f"✅ 找到 {len(docx_files)} 个融合A稿 docx 文件\n") + + all_digests = [] + skipped = 0 + success = 0 + failed = 0 + + for i, meta in enumerate(docx_files): + ep = meta["ep"] + out_file = EXPERIMENTS_DIR / f"ep{ep:02d}_digest.json" + + # 断点续跳:已存在则跳过 + if out_file.exists(): + print(f"[{i+1}/{len(docx_files)}] ep{ep:02d} 已存在,跳过") + try: + existing = json.loads(out_file.read_text(encoding="utf-8")) + all_digests.append(existing) + except Exception: + pass + skipped += 1 + continue + + print(f"[{i+1}/{len(docx_files)}] 正在处理 ep{ep:02d} - {meta['title']} ...") + + try: + # 提取 docx 文本 + body = extract_docx_text(meta["filepath"]) + if not body.strip(): + print(f" ⚠️ ep{ep:02d} 文稿内容为空,跳过") + failed += 1 + continue + + # 构造 user message + user_msg = build_user_message(meta, body) + + # 调用 MiMo + result = call_mimo(system_prompt, user_msg) + + # 构造输出 + output = { + "ep": ep, + "date": meta["date"], + "title": meta["title"], + "editor": meta["editor"], + "filename": meta["filepath"].name, + "digest": result, + "generated_at": datetime.now().isoformat(), + } + + # 写入单期文件 + out_file.write_text( + json.dumps(output, ensure_ascii=False, indent=2), + encoding="utf-8", + ) + all_digests.append(output) + print(f" ✅ ep{ep:02d} 摘要卡已生成 -> {out_file.name}") + success += 1 + + except json.JSONDecodeError as e: + print(f" ⚠️ ep{ep:02d} LLM 返回的不是合法 JSON: {e}") + failed += 1 + except Exception as e: + print(f" ❌ ep{ep:02d} 处理失败: {e}") + failed += 1 + + # 限流保护:每期之间 sleep 1 秒 + if i < len(docx_files) - 1: + time.sleep(1) + + # 写入汇总文件 + summary_file = EXPERIMENTS_DIR / "_all_digests.json" + summary_file.write_text( + json.dumps(all_digests, ensure_ascii=False, indent=2), + encoding="utf-8", + ) + + print("\n" + "=" * 60) + print(f"📊 处理完成:") + print(f" 成功: {success}") + print(f" 跳过(已存在): {skipped}") + print(f" 失败: {failed}") + print(f" 汇总文件: {summary_file}") + print("=" * 60) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/backend/app/api/analytics.py b/backend/app/api/analytics.py index 3a91a20..386df51 100644 --- a/backend/app/api/analytics.py +++ b/backend/app/api/analytics.py @@ -295,7 +295,7 @@ def generate_diagnosis_report( base_url="https://api.deepseek.com", ) response = client.chat.completions.create( - model="deepseek-chat", + model="deepseek-v4-pro", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_message}, @@ -322,7 +322,7 @@ def generate_diagnosis_report( }, "report_markdown": report_markdown, "generated_at": datetime.now().isoformat(), - "model": "deepseek-chat", + "model": "deepseek-v4-pro", "disclaimer": "本报告基于已入库的收视数据、节目标签及内容摘要生成,未纳入同时段竞品、社会热点等外部因素。分析结论难免挂一漏万,仅供栏目内部讨论参考,不构成节目决策依据。", } diff --git a/backend/app/models/episode.py b/backend/app/models/episode.py index f4196c0..4369811 100644 --- a/backend/app/models/episode.py +++ b/backend/app/models/episode.py @@ -69,6 +69,9 @@ class Episode(SQLModel, table=True): opening_hook: str | None = Field(default=None, max_length=10) # 开篇钩子(单选) ai_label_confidence: str | None = Field(default=None, max_length=10) # draft/reviewed/inferred + # ── 内容摘要卡(AI 生成,供诊断报告输入)── + content_digest: Any | None = Field(default=None, sa_column=Column(JSONB)) + created_at: datetime | None = Field( default=None, sa_column=Column(SADateTime(timezone=True), nullable=False, server_default=sa_func.now()), diff --git a/backend/sql/004_add_content_digest.sql b/backend/sql/004_add_content_digest.sql new file mode 100644 index 0000000..e539665 --- /dev/null +++ b/backend/sql/004_add_content_digest.sql @@ -0,0 +1,9 @@ +-- 004_add_content_digest.sql +-- 给 episodes 表添加内容摘要卡字段(AI 生成的节目内容结构化摘要,供诊断报告使用) +-- 前置:执行前先 pg_dump 备份 + +ALTER TABLE episodes + ADD COLUMN IF NOT EXISTS content_digest JSONB; + +COMMENT ON COLUMN episodes.content_digest + IS '节目内容摘要卡(AI生成JSONB:核心切口/叙事亮点/观众门槛/话题性/潜在弱点/时效关联)'; \ No newline at end of file diff --git a/doco/CLAUDE.md b/doco/CLAUDE.md index d02c88a..97551d5 100644 --- a/doco/CLAUDE.md +++ b/doco/CLAUDE.md @@ -16,9 +16,9 @@ ## 🔖 状态栏 (STATUS — 每次结束 session 前必须更新这三行) -- **最后更新**:Claude Code | 2026-06-26 -- **当前状态一句话**:**doco 子项目开发收尾。** 22 期融合A稿全部产出(含说话人区分重跑),已归拢至 `doco/deliverables/`。第01期(武器装备里的形状规律)缺A稿暂跳过。下一步:带 22 期成品回 TPS 主项目知识库批量导入。 -- **下一个动手的人从这里开始**:见下方「⏩ 交接备注」 +- **最后更新**:Claude Code | 2026-07-03 +- **当前状态一句话**:**doco 子项目阶段性收工。** 22 期融合A稿全部产出,成品在 `doco/deliverables/`。主项目寄存条交接核查已完成(2026-07-03):寄存条状态表已更新、api_credentials_inventory 已更新(MiMo/讯飞)。下一步由主项目侧执行批量导入知识库。 +- **下一个动手的人从这里开始**:doco 开发完成,无待办。主项目侧批量导入走 Phase 3 上传/embedding 链路。 --- @@ -89,6 +89,7 @@ ## 4. 已完成(只追加,最新在最上) +- [2026-07-03 |Claude Code] **主项目寄存条交接核查完成。** 更新内容:① 主项目 `note/寄存条Doco文稿整理子项目已外迁.md` 底部状态表从"等子项目出 PRD v3 启动 P1"更新为"开发收尾(2026-07-03)";② `docs/api_credentials_inventory.md` doco 节更新——讯飞改已激活、DeepSeek Vision 改为 Ollama 本地 DeepSeek-OCR、新增小米 MiMo 2.5 Pro(替代原 Anthropic Claude API 条目);③ doco CLAUDE.md 状态栏更新。交付物核查:22 份 docx + 20 份 md 在 `deliverables/`、术语词典 `term_dict.json` 10600 行、测试 `test_video_split.py` 存在。 - [2026-06-26 |Claude Code] **doco 子项目收尾:ep021/022 补稿 + 说话人重跑 + 成品归拢。** 本 session 完成:① **ep021(海战颠覆者)、ep022(硬核脑洞)两期新补 A 稿全流程 P1→C4 跑通**——ep021 骨架 LLM 自动生成失败(`extract_a_paragraphs()` 过滤空段后重编号,LLM 用了 raw docx 下标),手写骨架 JSON(32 段=26 normal+6 ignore 粗体章节标题)通过校验;ep022 骨架 LLM 自动通过(38 段全 normal,inline header);ep021 C3 review 13 条(均 OCR 修正,专名零替换)、C4 punct_ok 25/26;ep022 C3 review 5 条、C4 punct_ok 全过;② **cli.py 路径 bug 修复**:`episode_dir = Path("programs")/episode_id` 相对路径在子进程中二次解析导致路径翻倍(`programs/ep021/programs/ep021/...`),加 `.resolve()` 修复——此 bug 从未触发因 ep004 用 `--skip-p1` 且 `_batch_run.py` 用绝对路径;③ **ep007、ep010 C4 说话人区分重跑**(06-24 批量重跑时这两期被遗漏,融合A稿时间戳仍为 06-23),重跑完成、产物已更新;④ **排播对照表** `doco/data/2026episode_list.xlsx`(25 期播出日期/期次/节目名/编导/收视)导入,建立 ep→播出顺序完整映射;⑤ **成品归拢** `doco/deliverables/` 文件夹:22 份融合A稿按播出顺序统一命名(`第XX期_YYYYMMDD_节目名_编导_融合A稿.docx`),缺第01期(武器装备里的形状规律,无A稿);⑥ 通哥人工核验 ep021/022 质量高,仅两处小毛刺(ep021 一段话归错段落、ep022 一个标签应为三维动画),确认技术路径有效。 - [2026-06-23 |Claude Code] **批量化基础设施完工 + ep004 全流程通过 + 16 期批量跑启动**。本 session 完成:① ep004(枪王对决,最难·小剧场密·58段)全流程 P1→C4 一次通过,C4 punct_ok 58/58 全过、confidence 全 ≥0.80(batch_size=25 效果显著,无批次回退);② 新增 `doco run` 一键全流程命令(Cline 实现,Opus 审核),参数 `--episode-id / --a-script / --input-video / --batch-size / --skip-p1`,ep004 `--skip-p1` 验证通过;③ C3 SYSTEM_PROMPT 收紧专名铁律(防 ASR 同音字替换权威 B稿专名);④ Ollama 并发提至 16 路(`OLLAMA_NUM_PARALLEL=16`,8 路实测 GPU 96%、显存充裕);⑤ 16 期新目录建立 + 文件拷贝 + 骨架批量生成并集中核验(真名零泄露、隔断/ignore 正确);⑥ `_batch_run.py` 批量脚本启动跑 ep005-ep020。**doco PRD 完成标准已与通哥确定**:20 期全出稿 → 带成品回 TPS 主项目知识库批量导入。 - [2026-06-22 晚|Claude Code] **ep002 C4 审核完毕,全流程收工**。Cline 跑完 C4 compose 出稿 `20260127潜艇的仿生之路_穆佩弦_融合A稿.docx`。Opus 审核核验:硬校验(汉字零改)全过、733 行全覆盖、标点回退 0 段。**但发现两个问题**:① Cline 自报空段名称错了两个(报"解说3/解说6",实际是"三维动画解说3/解说8")、隔断数也报错(报 3 个,实际 4 个)——再次印证不信 Cline 自检;② **9/19 批次(47%)LLM 对齐失败走回退**(全 confidence=0.30),本次因失败批次恰在大段中间未翻车,但属黄色预警。**分段偏差根因**:A 稿是拍摄前剧本,专家采访段与实际播出内容差异巨大(A 稿一句话提纲 vs 专家自由发挥两分钟),LLM 无法正确匹配——这是信息不对应,非算法问题。通哥手动批改分段(~10 处高亮标签),内容本身正确。**结论(通哥拍板)**:治本靠编导给贴近播出版的稿子,现阶段接受"程序保证文字零改 + 编导手调分段"的模式。 diff --git a/docs/api_credentials_inventory.md b/docs/api_credentials_inventory.md index b9e6f0f..509c0f0 100644 --- a/docs/api_credentials_inventory.md +++ b/docs/api_credentials_inventory.md @@ -25,9 +25,9 @@ | 子模块 | API 服务 | Key 类型 | 开通日 | 激活状态 | 到期日 | 责任人 | 备注 | |---|---|---|---|---|---|---|---| -| doco | 讯飞开放平台 - 录音文件转写(标准版) | APP_ID + SECRET_KEY | 2026-06-12 | 待激活(需走 0 元购买) | 2027-06-12 | 制片人 | demo 凭证已过期,需新申请 | -| doco | DeepSeek Vision | API_KEY | 2026-06-12 | 已激活 | — | 制片人 | doco OCR 用 | -| doco | Anthropic Claude API | API_KEY | 2026-06-12 | 已激活 | — | 制片人 | AI 融合层(P3) | +| doco | 讯飞开放平台 - 录音文件转写(标准版) | APP_ID + SECRET_KEY | 2026-06-12 | 已激活 | 2027-06-12 | 制片人 | C2 ASR 转写,22 期已跑完 | +| doco | Ollama 本地 DeepSeek-OCR | 本地部署 | 2026-06-12 | 已激活 | — | 制片人 | P2 本地 OCR,16 路并发,4090D 24GB | +| doco | 小米 MiMo 2.5 Pro | API_KEY | 2026-06-22 | 已激活 | — | 制片人 | AI 融合层(C1/C3/C4),OpenAI 兼容端,替代原 DeepSeek v4-pro | --- @@ -41,4 +41,4 @@ --- -*最后更新: 2026-06-12* +*最后更新: 2026-07-03* diff --git a/frontend/src/components/Analytics/DiagnosisSummary.jsx b/frontend/src/components/Analytics/DiagnosisSummary.jsx index ed341ab..268b0f7 100644 --- a/frontend/src/components/Analytics/DiagnosisSummary.jsx +++ b/frontend/src/components/Analytics/DiagnosisSummary.jsx @@ -27,32 +27,49 @@ function extractSection(markdown, startMarker, endMarkers) { return markdown.substring(afterStart, endIdx).trim() } -/** 清理 markdown 为纯文本(去掉标题标记、多余符号等) */ -function cleanMarkdown(text) { +/** 将 markdown 文本按行拆分,保留 **bold** 为 */ +function parseMarkdownLines(text) { if (!text) return [] return text .split('\n') - .map((line) => line.replace(/^#+\s*/, '').replace(/^\*\*(.+?)\*\*/, '$1').trim()) + .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: '病因与预警', - color: '#7aa874', + rightColor: '#7aa874', rightBg: 'rgba(122,168,116,0.06)', }, on_target: { leftTitle: '核心发现', rightTitle: '病因与提振建议', - color: '#5b8db8', + rightColor: '#5b8db8', rightBg: 'rgba(91,141,184,0.06)', }, excellent: { leftTitle: '高光复盘', rightTitle: '经验总结', - color: '#c0584f', + rightColor: '#c0584f', rightBg: 'rgba(192,88,79,0.06)', }, } @@ -144,8 +161,8 @@ function DiagnosisSummary({ episodes, yearlyTarget, selectedYear }) { // 行动建议:从 "#### 四、行动建议" 到末尾 let actions = extractSection(markdown, '#### 四、行动建议', []) - const coreLines = cleanMarkdown(coreFindings) - const actionLines = cleanMarkdown(actions) + const coreLines = parseMarkdownLines(coreFindings) + const actionLines = parseMarkdownLines(actions) return (
@@ -176,30 +193,30 @@ function DiagnosisSummary({ episodes, yearlyTarget, selectedYear }) {
) : report ? (
- {/* 左块 */} + {/* 左块(固定粉红色系) */}
-
{config.leftTitle}
+
{config.leftTitle}
{coreLines.length > 0 ? ( coreLines.map((line, i) => ( -

{line}

+

{renderInlineMarkdown(line)}

)) ) : (

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

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

{line}

+

{renderInlineMarkdown(line)}

)) ) : (

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

diff --git a/frontend/src/pages/Analytics/Analytics.css b/frontend/src/pages/Analytics/Analytics.css index 871631e..abd8b01 100644 --- a/frontend/src/pages/Analytics/Analytics.css +++ b/frontend/src/pages/Analytics/Analytics.css @@ -276,15 +276,33 @@ font-size: 13px; line-height: 1.8; color: #444; + max-height: 220px; + overflow-y: auto; + position: relative; +} + +.diagnosis-summary-left::-webkit-scrollbar, +.diagnosis-summary-right::-webkit-scrollbar { + width: 4px; +} + +.diagnosis-summary-left::-webkit-scrollbar-thumb, +.diagnosis-summary-right::-webkit-scrollbar-thumb { + background: rgba(0,0,0,0.12); + border-radius: 2px; +} + +.diagnosis-summary-left::-webkit-scrollbar-track, +.diagnosis-summary-right::-webkit-scrollbar-track { + background: transparent; } .diagnosis-summary-left { - border-left: 4px solid currentColor; - background: rgba(255, 255, 255, 0.3); + border-left: 3px solid currentColor; } .diagnosis-summary-right { - background: rgba(255, 255, 255, 0.3); + border-left: 3px solid currentColor; } .diagnosis-block-title { diff --git a/note/寄存条Doco文稿整理子项目已外迁.md b/note/寄存条Doco文稿整理子项目已外迁.md index c73a8eb..bcd2660 100644 --- a/note/寄存条Doco文稿整理子项目已外迁.md +++ b/note/寄存条Doco文稿整理子项目已外迁.md @@ -102,5 +102,5 @@ Doco 子项目交付物的接口约定,见**子项目主 Brief §六**(交付什 | 子项目 | 外迁日期 | 寄存条文件 | 状态 | | ---------------- | ---------- | -------------------------- | ------------------------- | -| TPS 看板分析升级 | 2026-06-12 | `寄存条_看板升级已外迁.md` | 设计阶段 | -| Doco 文稿整理 | 2026-06-12 | 本文件 | 等子项目出 PRD v3 启动 P1 | \ No newline at end of file +| TPS 看板分析升级 | 2026-06-12 | `寄存条_看板升级已外迁.md` | 原型阶段收工(2026-07-03) | +| Doco 文稿整理 | 2026-06-12 | 本文件 | **开发收尾(2026-07-03)**:22 期融合A稿已产出,成品在 `doco/deliverables/`,待批量导入主项目知识库 | \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4e7e27c..36e720c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2,5 +2,1294 @@ "name": "tps-dashboard", "lockfileVersion": 3, "requires": true, - "packages": {} + "packages": { + "": { + "dependencies": { + "echarts": "^6.1.0", + "echarts-for-react": "^3.0.6", + "react-markdown": "^10.1.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", + "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", + "license": "ISC" + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT", + "peer": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/echarts": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz", + "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.1.0" + } + }, + "node_modules/echarts-for-react": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-3.0.6.tgz", + "integrity": "sha512-4zqLgTGWS3JvkQDXjzkR1k1CHRdpd6by0988TWMJgnvDytegWLbeP/VNZmMa+0VJx2eD7Y632bi2JquXDgiGJg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "size-sensor": "^1.0.1" + }, + "peerDependencies": { + "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "react": "^15.0.0 || >=16.0.0" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/size-sensor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/size-sensor/-/size-sensor-1.0.3.tgz", + "integrity": "sha512-+k9mJ2/rQMiRmQUcjn+qznch260leIXY8r4FyYKKyRBO/s5UoeMAHGkCJyE1R/4wrIhTJONfyloY55SkE7ve3A==", + "license": "ISC" + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/zrender": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz", + "integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } } diff --git a/package.json b/package.json new file mode 100644 index 0000000..80db86b --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "echarts": "^6.1.0", + "echarts-for-react": "^3.0.6", + "react-markdown": "^10.1.0" + } +} diff --git a/scripts/import_content_digests.py b/scripts/import_content_digests.py new file mode 100644 index 0000000..7f679c4 --- /dev/null +++ b/scripts/import_content_digests.py @@ -0,0 +1,78 @@ +""" +一次性脚本:将 AI 生成的内容摘要卡写入 episodes 表的 content_digest 字段 +数据来源:ai-labeling/experiments/content_digests/_all_digests.json +前置:先手动执行 backend/sql/004_add_content_digest.sql 添加字段 +""" + +import sys +import os +import json +from datetime import date + +# Windows 终端中文输出 +sys.stdout.reconfigure(encoding='utf-8') +sys.stderr.reconfigure(encoding='utf-8') + +# 加 backend 到 path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'backend')) + +from app.db.session import get_session +from app.models.episode import Episode +from app.models.user import User # FK 解析需要 +from sqlmodel import select + +# ── 1. 读 digest 数据 ── +project_root = os.path.join(os.path.dirname(__file__), '..') +digest_file = os.path.join(project_root, 'ai-labeling', 'experiments', 'content_digests', '_all_digests.json') + +with open(digest_file, 'r', encoding='utf-8') as f: + all_digests = json.load(f) + +print(f"读入 {len(all_digests)} 条摘要记录") + +# ── 2. 逐条匹配并写入 ── +session = next(get_session()) +success = 0 +skipped = 0 +mismatch = 0 + +for d in all_digests: + ep_num = d['ep'] + digest_date = d['date'] # "2026-04-21" 格式 + + # 查 episode + ep = session.exec(select(Episode).where(Episode.episode_number == ep_num)).first() + if not ep: + print(f"⚠️ ep{ep_num:02d} 在数据库中不存在,跳过") + skipped += 1 + continue + + # 校验日期 + if str(ep.air_date) != digest_date: + print(f"⚠️ ep{ep_num:02d} 日期不匹配: DB={ep.air_date}, digest={digest_date},跳过") + mismatch += 1 + continue + + # 写入(只存 digest 字段,不存外壳的 ep/date/title/editor/filename/generated_at) + ep.content_digest = d['digest'] + session.add(ep) + success += 1 + print(f"✅ ep{ep_num:02d} {ep.program_name} <- digest 已写入") + +session.commit() + +# ── 3. 打印统计 ── +print() +print(f"=== 统计 ===") +print(f"成功写入: {success}") +print(f"跳过(不存在): {skipped}") +print(f"跳过(日期不匹配): {mismatch}") +print(f"总记录: {len(all_digests)}") + +# ── 4. 验证:所有 episodes 的 content_digest 状态 ── +print() +print(f"=== 验证: episodes content_digest 状态 ===") +all_eps = session.exec(select(Episode).order_by(Episode.episode_number)).all() +for e in all_eps: + has_digest = "✅" if e.content_digest is not None else "❌" + print(f"ep{e.episode_number:02d} | {e.program_name} | {e.air_date} | digest: {has_digest}") \ No newline at end of file