From 8be51ebbdef90ae13bd70b7ce2067f2b3650913b Mon Sep 17 00:00:00 2001 From: simonkoson <28867558@qq.com> Date: Fri, 10 Jul 2026 18:06:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=9F=A5=E8=AF=86=E5=BA=93=E7=AC=94?= =?UTF-8?q?=E8=AE=B0=E5=B7=A5=E5=9D=8A=E7=AB=8B=E9=A1=B9=20+=20contributor?= =?UTF-8?q?=20=E5=8A=9F=E8=83=BD=20+=20Fable5=20=E8=A7=86=E8=A7=92?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 知识库笔记工坊子项目立项:PRD v1.0 + 寄存条 + CLAUDE.md 登记 - 后端:知识库上传记录 contributor、新增 /contributors 接口 - 前端:知识库列表支持按贡献人筛选 - 新增 Fable5 视角模式库/案例集/建议文档 Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 12 +- Fable5suggestion.md | 182 +++++++++++++ backend/app/api/knowledge.py | 12 +- backend/app/services/knowledge_service.py | 21 +- .../src/pages/KnowledgeBase/KnowledgeBase.jsx | 112 ++++---- frontend/src/services/knowledgeService.js | 5 + new_notemd/CLAUDE.md | 72 +++++ new_notemd/PRD_知识库笔记工坊_v1.md | 246 ++++++++++++++++++ note/寄存条期次一条龙录入子项目已外迁.md | 1 + note/寄存条知识库笔记工坊子项目已外迁.md | 71 +++++ 视角模式库冷启动_Fable5.md | 140 ++++++++++ 金牌视角案例集.md | 19 ++ 12 files changed, 836 insertions(+), 57 deletions(-) create mode 100644 Fable5suggestion.md create mode 100644 new_notemd/CLAUDE.md create mode 100644 new_notemd/PRD_知识库笔记工坊_v1.md create mode 100644 note/寄存条知识库笔记工坊子项目已外迁.md create mode 100644 视角模式库冷启动_Fable5.md create mode 100644 金牌视角案例集.md diff --git a/CLAUDE.md b/CLAUDE.md index b19852d..c3dada7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,8 +13,8 @@ ## 🔖 状态栏(每次结束 session 前必须更新这三行) - - **最后更新**:Claude Opus(顾问)| 2026-07-08 - - **当前状态一句话**:**知识库语义搜索基本可用**——Obsidian 知识库 164 篇批量导入完成(总计 186 条含 doco 22 篇),搜索体验三项改进已落地(智能摘要/相关度阈值/展开全文),中文分词 snippet bug 已修。下一步:搜索体验微调 + 进入 Phase 4a 对话式 TPS。 + - **最后更新**:Claude Opus(顾问)| 2026-07-09 + - **当前状态一句话**:**知识库笔记工坊子项目立项完成**——PRD v1.0 就绪(查看/编辑 + AI 加工新建 + 审核制,四刀分期),知识库升级为全组共建模式。下一步:排期开发 or 进入 Phase 4a 对话式 TPS。 - **下一个动手的人从这里开始**:见下方「⏩ 交接备注」 --- @@ -39,6 +39,7 @@ - **Doco 文稿整理**(`寄存条_doco子项目已外迁.md`):关键词——doco / 文稿整理 / 三方融合 / 视频双路拆分 / A·B 稿 / ASR / 讯飞 / DeepSeek Vision / 终版文稿 / 差异报告 / 段落对齐。**⚡ 2026-06-26 已交付:22 期融合A稿成品在 `doco/deliverables/`(按播出顺序命名),缺第01期(无A稿)。下一步:批量导入知识库(走 Phase 3 上传/embedding 链路)。** - **CCA 唱词助手**(`寄存条CCA唱词助手子项目已外迁.md`):关键词——cca / 唱词助手 / changci / SRT 字幕 / 拍词规则 / 编导审稿台 / 唱词校对 / ASR 转字幕 / 大洋字幕格式 / 折行规则。**⚡ 2026-07-04 立项:编导 A 稿+人声音频 → ASR+AI 校对+编导审稿 → 大洋格式 SRT。先部署 lanhao 配音 2.0 测试,成熟后并入 TPS。** - **期次一条龙录入**(`寄存条期次一条龙录入子项目已外迁.md`,目录 `episode-intake/`):关键词——一条龙 / 期次录入流水线 / 流水线状态点 / 文稿回联期次 / transcript_item_id / 005 迁移 / AI 处理按钮 / 标签审核台 / draft-reviewed 流转 / 责编录入抽屉。**⚡ 2026-07-07 立项:责编录入页升级为每期任务清单(收视→文稿入库→AI打标+摘要卡→制片人审核→进看板),让收视分析持续生长。PRD v1.0 就绪,暂不开发;实施时落主干、走主干纪律。已拍板:文稿口径=doco 融合A稿(doco 转常态运行)、22 期导入时回联期次、责编可触发 AI 处理。** + - **知识库笔记工坊**(`寄存条知识库笔记工坊子项目已外迁.md`,目录 `new_notemd/`):关键词——笔记工坊 / 新建笔记 / AI 加工笔记 / 编导上传知识库 / 知识库审核流程 / pending/approved/rejected 状态 / 006 迁移 / 笔记编辑抽屉 / 文本提取 / PDF OCR / 网页抓取入库 / 编导共建知识库。**⚡ 2026-07-09 立项:知识库升级为全组共建——编导可查看/编辑笔记、自助提交多格式素材(文本/PDF/Word/链接/md)经 AI 加工成规范笔记、制片人/责编审核后入搜索池。PRD v1.0 就绪,暂不开发;实施时落主干、走主干纪律。已拍板:不做独立软件(网页端解决)、五种输入类型、审核制防污染、AI 加工复用 MST 规则。** **主干仍管**:Phase 0–3 主干代码、全部 backend schema/API/迁移、全部前端 React 实施、主干 bug/性能/新需求、Cline 的全部 Plan+Act。 @@ -77,14 +78,15 @@ ## 3. 当前进度(动态,核心交接区 — 以最新快照为准) - **已完成至**:收视分析看板 L1-L4 + 知识库语义搜索(Obsidian 164 篇 + doco 22 篇批量导入,搜索体验三项改进)。 - - **正在做**:语义搜索体验微调(中文 snippet 已修,待制片人复测),准备进入 Phase 4a 对话式 TPS。 + - **正在做**:知识库笔记工坊子项目已立项(PRD v1.0 就绪),待排期开发。准备进入 Phase 4a 对话式 TPS。 - **卡点/待解**:无硬卡点。 - - **Schema 状态**:episodes 表已通过 003(+7 AI 标签列)和 004(+content_digest JSONB)迁移。知识库两表不再改。 + - **Schema 状态**:episodes 表已通过 003(+7 AI 标签列)和 004(+content_digest JSONB)迁移。知识库两表暂不改(006 迁移属笔记工坊子项目第二刀)。 --- ## 4. 已完成(只追加,最新在上) + - [2026-07-09] **知识库笔记工坊子项目立项(只立项不开发)**:① PRD v1.0 写入 `new_notemd/PRD_知识库笔记工坊_v1.md`(查看/编辑抽屉 + AI 加工新建笔记 + 审核制、五种输入类型、四刀分期+七条验收);② 制片人四项拍板:不做独立软件(网页端解决)、五种输入类型(文本/PDF/Word/链接/md)、审核制防污染、AI 加工复用 MST 规则;③ 子项目 CLAUDE.md + 寄存条建立。 - [2026-07-08] **Obsidian 知识库批量导入 + 语义搜索体验升级**:① `parse_md_file` 补强 6 项("来源"→source_detail fallback、"相关装备/应用领域"实体提取、Obsidian 双链 `[[]]` 去括号、原始类别存 metadata、源文件路径预埋、SOURCE_TYPE_MAP 扩充含"军报文章/装备/技术/动态/术语/厂商/索引");② 脚本 `backend/scripts/import_obsidian_kb.py`(递归遍历、跳过 .obsidian+原始素材+空文件+无 frontmatter、按相对路径查重、支持 --limit/--dry-run/--dir),成功导入 164 篇(7+157),知识库从 22 → 186 条;③ 搜索体验三项改进:`search_similar` 返回完整 content_md + 智能摘要 `_extract_smart_snippet`(中文 2-gram 拆词+加权段落匹配+关键词加粗)+ min_similarity=0.3 过滤 + top_k 5→10;④ 前端搜索卡片升级(展开全文 Set 状态管理、`renderSnippet` 加粗渲染、相关度分档样式:≥70%蓝左边框/<50%半透明灰底)。 - [2026-07-07] **知识库冷启动:doco 22 期融合A稿批量导入**:脚本 `backend/scripts/import_doco_transcripts.py`,docx → 提取纯文本 → 包 YAML frontmatter → 调 `store_md_file()` 入库(自动算 MiniMax embo-01 embedding)。22 篇全部成功(第02期~第23期,缺第01期无A稿),source_type=manuscript,知识库从 0 → 22 条节目文稿。 - [2026-07-07] **期次一条龙录入子项目立项(只立项不开发)**:① PRD v1.0 写入 `episode-intake/PRD_期次一条龙录入_v1.md`(每期任务清单模式、4 状态点、抽屉四区块、6 个新 API、005 迁移只加 transcript_item_id 一列、看板只用 reviewed 期次、四刀分期+七条验收);② 制片人三项拍板:文稿口径=doco 融合A稿(CCA 是播出前工具不算终稿,doco 转常态运行)、22 期批量导入时回联期次、责编可触发 AI 处理;③ 子项目 CLAUDE.md + 寄存条建立,CCA 寄存条清单表同步更新。 @@ -108,6 +110,7 @@ - [x] ~~200+ Obsidian md 批量录入~~ → 已完成(2026-07-08),164 篇导入成功,知识库 186 条 - [ ] **下一刀**:Phase 4a 对话式 TPS 选题策划助手(左对话右报告、脚注式引用、知识库检索底座已就绪)。 - [ ] **期次一条龙录入开发**(子项目已立项 2026-07-07,PRD 就绪在 `episode-intake/`,等制片人排期;四刀分期,第二刀含 doco 22 期导入回联)。 + - [ ] **知识库笔记工坊开发**(子项目已立项 2026-07-09,PRD 就绪在 `new_notemd/`,等制片人排期;四刀分期——查看编辑/AI加工新建/审核流程/打磨,006 迁移在第二刀)。 - [ ] PDF 大文件存储:**大文件不入库,单独文件仓库,DB 只存地址指针**(md 正文+向量留库参与检索;pdf 仅按需调阅)。地基一次定对,避免上云返工。Obsidian 导入时已把源文件路径存入 `metadata["source_files"]` 预埋。 - [ ] 「按编导看稿」独立筛选视图(路线 A 重构时一并处理,从来源树里迁出来)。 - [ ] 操作留痕 schema(episodes 等加 created_by/updated_by/updated_at;涉 schema 须 Opus 审)。 @@ -144,6 +147,7 @@ ## 7. ⏩ 交接备注(换人/换账号 0 摩擦续上) + - **知识库笔记工坊已立项**(2026-07-09):子项目目录 `new_notemd/`,PRD v1.0 含查看/编辑抽屉 + AI 加工新建笔记(五种输入:文本/PDF/Word/链接/md)+ 审核制(编导提交→制片人/责编审批→入搜索池)。四刀分期,006 迁移在第二刀(knowledge_items +status/submitted_by/reviewed_by 等)。寄存条 `note/寄存条知识库笔记工坊子项目已外迁.md`。 - **知识库语义搜索已可用**(2026-07-08):186 条知识库条目(doco 22 篇 + Obsidian 164 篇),搜索链路完整(MiniMax embo-01 query embedding → pgvector 余弦检索 → 智能摘要 + 关键词加粗 + 展开全文)。新增文件:`backend/scripts/import_obsidian_kb.py`(批量导入脚本)。`knowledge_service.py` 的 `parse_md_file` 已补强支持全部 Obsidian frontmatter 字段。搜索结果返回 `content_md` 完整正文供展开查看。Obsidian 源文件路径已存入 `metadata["source_files"]` 预埋 PDF 关联。 - **收视分析看板 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`。 diff --git a/Fable5suggestion.md b/Fable5suggestion.md new file mode 100644 index 0000000..a7b1f1d --- /dev/null +++ b/Fable5suggestion.md @@ -0,0 +1,182 @@ +# Fable 5 对 Phase 4a(对话式 TPS)的评审建议 + +> 来源:2026-07-09 制片人刘通 × Claude Fable 5 评审对话。 +> 用途:交接给 Opus 4.6 继续细化 Phase 4a 时的输入材料。 +> 状态:九条全部经制片人逐条拍板。第 9 条经 2026-07-09 两轮讨论收敛,已从讨论稿升级为拍板方向。 + +--- + +## 1. 历史查重的数据地基与措辞红线(制片人已拍板修正方案) + +**问题**:episodes 表目前只有 25 期数据,编导问"做过没有"时系统极易给出错误的"没做过"(假阴性),一旦编导采信,比没有此功能更糟。 + +**已否决的方案**:回填"节目名+播出日期+编导"最小目录。原因(制片人指出):《军事科技》节目名笼统宽泛(如《重走战争老路的武器装备》看不出讲了日向级护卫舰),光有标题对查重无帮助,**查重必须靠文稿全文语义检索**。 + +**拍板方案**: +- 制片人承诺补齐 **2025 年全年 52 期完整内容**:收视成绩 + doco 融合A稿全文。 +- 查重回答的措辞红线:**永远说"自 2025 年以来未出现相关选题",绝不说"没做过"**。此措辞写进 Prompt 约束 + 前端固定文案双保险。 +- 联动提醒:52 期 vs 现有 22 期,多出 30 期的 doco 加工量与"期次一条龙"子项目的回联规则需在排期时统筹。 + +--- + +## 2. 报告区块与数据表启用节奏对齐 + +模块 A 右栏报告承诺五类依据,其中审片意见(review_comments)、外拍资源(shooting_resources)两表 Phase 4b 才灌数据。 + +- 制片人决定:系统全部做完才给编导看,无提前内测,故不构成第一印象风险。 +- 实施仍建议:4a 开发时报告先只做三区块(历史查重+收视 / 可参考节目 / 知识库脚注),4b 数据就位后再加区块,**代码里不留空壳区块**。 + +--- + +## 3. 脚注防幻觉的三道工程防线(制片人已同意,实施时写死) + +**风险**:LLM 生成报告时可能编造不存在的脚注,或把 A 期节目的结论挂到 B 篇文稿上(张冠李戴)。脚注制的公信力一次穿帮全组就不信了。 + +**三道防线(给 Cline 的实施要求,缺一不可)**: + +1. **编号约束进 Prompt**:每次检索到的知识库条目/期次,带编号 [1][2][3]… 连同元信息(标题、期次、来源)一起喂给模型;Prompt 明确规定"只允许引用给定编号,禁止引用编号之外的任何来源,无法支撑的论断不挂脚注"。 +2. **后端校验引用编号**:模型返回报告后,后端解析所有脚注标记,逐个核对编号是否在本次检索结果集内;不在的**直接剥掉该脚注**(保留正文句子),并记一条日志供排查。 +3. **宁裸奔不假装**:挂不上依据的句子就不挂脚注。UI 上有脚注的句子和没脚注的句子视觉上可区分,编导一眼知道哪些话"有出处"、哪些是 AI 的综合分析。 + +**配套**:检索结果沿用语义搜索已有的 min_similarity 阈值过滤(当前 0.3),低相关条目不进候选池,避免"硬凑脚注"。 + +--- + +## 4. 报题单生成:结构化 JSON 直填模板,禁止文本解析(制片人已同意) + +**前车之鉴**:2026-07-06 修过的 bug——AI 诊断摘要因 DeepSeek 返回标题格式不一致导致正则提取失败、内容不显示。 + +**要求**:模块 G 生成报题单时,让模型输出**结构化 JSON**(示意): + +```json +{ + "title": "选题名", + "intro": "引子段,200-300字", + "points": [ + {"keyword": "加粗关键词1", "content": "该条内容"}, + {"keyword": "加粗关键词2", "content": "该条内容"} + ] +} +``` + +- JSON 字段直接对应 docxtpl 模板变量,**中间不做任何自由文本解析/正则提取**。 +- 后端校验 JSON 结构(字段齐全、points 条数 3-5),不合格自动重试一次,再失败报错给用户而不是塞坏数据进 docx。 +- 双段结构(引子+列举)红线不变,纯叙述式仅编导明确指定时切换另一套 JSON 结构。 + +--- + +## 5. 右栏报告改"手动更新"按钮(制片人已同意) + +编导聊,报告不动;编导点 **「更新策划报告」** 按钮,才根据到目前为止的全部对话重新生成报告。一个设计同时解决三件事: + +1. 符合红线 1"求助式不投喂"——展开与深入的主动权在编导手里; +2. 省成本——不是每轮对话都烧一次完整报告生成; +3. 报告稳定不跳动——编导阅读中途内容不会突变,"沉淀感"更强。 + +细节:按钮旁可显示"自上次更新后已新增 N 轮对话"作温和提示,但**绝不自动刷新**。 + +--- + +## 6. 对话会话从第一天落库 + 轮数硬上限(制片人已同意) + +- **落库**:对话记录随 topics 表(或独立会话表)入库,编导关页面后随时回来接着聊。这同时是第十一章成本机制"按人+选题归因"的技术前提——每次调用天然隶属于一个选题会话。 +- **轮数上限(制片人拍板)**:**每个选题会话最多保留 20 轮对话**,且此限制要在界面显眼位置标注(如对话框顶部常驻提示"本选题对话保留最近 20 轮")。理由:一个选题实际用不到更多轮讨论,同时防止 token 滚雪球。 +- **上下文策略**:调模型时只带最近 N 轮 + 一段前情摘要(朴素做法即可,严禁上重型记忆框架),既省钱又防对话跑偏。 + +--- + +## 7. 对话主力模型:先测再定 + 候选名单 + +**测试方法**(照搬 L4 诊断报告选型的成功经验):3-5 个真实选题灵感同题对比,其中至少含一个"帮我找角度"任务(见第 9 条)。 + +**四个考点**:① 多轮对话稳定性;② 脚注纪律(会不会引用给定编号之外的来源);③ JSON 格式服从性;④ 创意角度质量。外加成本对比。 + +**候选名单(均可从腾讯云访问,开测时以各家当时最新旗舰为准)**: + +| 候选 | 入选理由 | 重点考察 | +|---|---|---| +| DeepSeek V4 Pro | L4 诊断卫冕者,分析深度已验证 | 多轮稳定、JSON 纪律 | +| Kimi(月之暗面)最新 K 系列 | 中文创意写作口碑最好,"开智"潜在黑马 | 角度卡质量 | +| GLM(智谱)最新旗舰 | 结构化输出/工具调用纪律好,将来联网生态最顺 | 脚注编号服从性 | +| Qwen-Max 最新版 | 综合稳,L4 时测过有基础分 | 综合 | +| MiniMax M2.7(基线) | 账号现成(与 embedding 同家)、便宜 | 成本性价比垫底对照 | + +腾讯混元不建议占测试名额(综合能力弱于上列)。日欧无遗珠:欧洲仅法国 Mistral 算准一线但中文弱、国内访问不稳;日本无自研一线模型。第一梯队就在中美,国产池子里选即可。 + +--- + +## 8. Phase 4a 切四刀(+可选第五刀) + +> 原则:风险最高的部分(脚注防幻觉、报告质量)放第一刀最先暴露;每刀有独立验收,验收不过不开下一刀。 + +**第一刀:单轮版"灵感→报告"(无对话)** +- 输入一段灵感文字 → 语义检索(复用 Phase 3 底座)→ 生成一份带脚注的策划参考报告(三区块:历史查重+收视 / 可参考节目 / 知识库脚注)。 +- 实现第 3 条的三道脚注防线 + 第 1 条的措辞红线。 +- 验收:制片人拿 5 个真实灵感实测,逐个点开脚注核对真伪,**零张冠李戴**才算过。 + +**第二刀:多轮对话 + 会话落库 + 手动更新按钮** +- 左右双栏成型;对话入库(20 轮上限 + 显眼标注);「更新策划报告」按钮(第 5 条);topics 表启用(建表含会话字段,schema 走 Cline Plan + Opus 审 + 制片人批准流程)。 +- 验收:关浏览器重开能接着聊;连续 10 轮对话后更新报告,内容仍贴合且脚注全真。 + +**第三刀:报题单生成(模块 G)** +- 聊定后一键生成:结构化 JSON(第 4 条)→ docxtpl → docx 下载。双段结构、只规范化不代写立意。 +- 验收:连续生成 5 份报题单,docx 格式全部正确、无解析失败;内容全部来自编导聊定的东西,AI 无添油加醋。 + +**第四刀:成本记账 + 额度** +- 一张用量表(谁、何时、烧多少 token、为哪个选题),管理员页面看汇总;每人月度保守上限 + 超额提醒制片人。严禁 AI 网关/可观测性框架。 +- 验收:制片人页面能看到按人、按选题的用量汇总;模拟超额能触发提醒。 + +**第五刀(可选,或并入第二刀):视角开智"帮我找角度"**——见第 9 条。 + +--- + +## 9. 视角开智:"帮我找角度"(制片人最关注,已拍板) + +### 需求本质 + +制片人指出:TPS 最核心的价值不是"聚焦某一型装备"的资料辅助,而是**提供视角**。栏目公认的金牌选题——《武器装备的颜值洼地》(美丑切入)、《失败的"王者"》(昙花一现但影响深远)、《接过雷锋的枪》(节日钩子勾连 50 年代轻武器)、《空天之上的分层绝杀》《海平面之下的分层较量》(仅以高度/深度为主线)——共性是:**一个大众零门槛的"组织原则",把一堆本不相干的装备串成一条线**。参照系还有纪录片《地球之劫后重生》(一集一场灭绝级灾难 + 霸主物种命运的故事化讲述)。诉求:AI 不只继承往期经验,还要有"开智"能力,帮编导扩展思路。 + +### 可行性判断(Fable 5 结论:能做到,且是 LLM 强项) + +分三层: + +1. **模式复用——确定能做**。把金牌选题提炼成有名字的"视角模式"(审美反差 / 量纲主线 / 失败遗产 / 节日勾连……),AI 拿模式套新题材("量纲主线套速度线、温度线、重量线行不行?")。类比迁移是 LLM 最擅长的事。 +2. **跨域借鉴——能做,且 4a 不需要联网**。关键判断:模型迁移的是**叙事手法**,不是某部具体片子——"一集一场灭绝级灾难 + 霸主物种命运"这类组织原则是纪录片行业的成熟手法(如 Netflix《我们星球上的生命》、BBC《与恐龙同行》),全世界纪录片/科普/历史叙事的手法都在训练数据里。制片人实测验证过:模型**不知道**近期新发布的具体作品内容(这正常,知识有截止日期),但新作品的"资讯"由看过片子的编导一句话带进对话即可,"迁移"由 AI 完成——人带资讯、AI 做迁移,分工恰好符合创作主权哲学。联网搜索搜回来的是"资讯"(装备新动态,那是 Phase 4c 热点雷达的活),不是"视角"。**结论:4a 开智不联网,实时互联网借鉴留 2.0**,省一套工程和一笔运行成本。 + - **配套防线(制片人实测后新增)**:开智 Prompt 必须写明"不知道的作品明说不知道,禁止编造具体作品/纪录片的分集内容"——防幻觉纪律与报告脚注区一致,不留裸奔角落。 +3. **命中率——要管理期望**。AI 发散 10 个角度,约 1-2 个眼前一亮、5-6 个平庸、2-3 个不着调,头脑风暴的正常成绩。产品定位:**"打火石"不是"军师"**——火花 AI 打,火编导点。 + +### 产品形态(四项拍板,2026-07-09 两轮讨论收敛) + +**拍板 ①——输入模式:1.0 只做"带题材来"**。编导带着题材("我想做潜艇")点「帮我找角度」;"空手来"(这周想不出报什么题)的全开放发散模式发散空间太大、命中率更低,留 2.0。 + +**拍板 ②——角度卡自带"查重角标"**。每张卡生成后自动做一次语义查重,卡上带一行小字:"自 2025 年以来未出现相似选题" 或 "与第X期《XX》视角相近"。开智与查重咬合,编导拿到的是过了第一道筛的角度。成本为每卡一次 embedding 检索(几厘钱量级,可忽略)。 + +**拍板 ③——角度卡三层结构 + 红线位置(制片人修正后定稿)**。制片人指出:团队编导多为广电编导/导演/编剧出身,**不是军迷**,知识库本就是为补齐军事知识短板——只给视角不给素材方向,他们想不到有什么内容可以支撑。故角度卡定为三层: + +1. **组织原则**(视角模式名 + 来源标注:往期模式复用 / 跨域类比 / AI 原创); +2. **一句话方向**; +3. **3-5 条候选装备/素材**——**必须来自知识库语义检索命中**,每条带脚注(哪篇文稿/哪篇军报),走第 3 条同一套编号校验防线;库内检索不到支撑素材的,卡上如实标"库内暂无支撑资料",**严禁 AI 空口编装备**。 + +红线退守到真正该守的位置:**有出处的素材候选可以给(这是"摆牌");故事编排、分集结构、立意结论不写(这是"出牌")**。角度卡原文不自动进报题单——从角度到立意的发展,永远由编导在对话中完成。附带好处:能被知识库支撑的角度天然比空想的靠谱,角度卡质量自我筛选。 + +**拍板 ④——收藏/忽略动作从第一天记录**。一张朴素动作表:谁、哪张卡(含卡内容快照)、收藏还是忽略、时间、**topic_id(必须带,预埋"卡→选题→期次→收视成绩"四表链路,与期次一条龙子项目衔接)**。数据量无忧:一行不足 1KB,按 6 编导正常使用估算一年一万多行、几个 MB。1.0 **只存不分析、不做任何人员维度展示**;半年后可回答"哪类视角模式收藏率高、收藏的卡最终成片后收视如何",是迭代 Prompt 策略库的唯一依据(制片人确认:纯业务数据,不存在监控顾虑)。 + +- 其余形态不变:一次输出 5-8 张角度卡;编导可"收藏 / 忽略",收藏的角度带入后续对话深聊;AI 不排序不推荐"最佳",编导自选(摆牌不出牌)。 + +### 落地三件事 + +1. **【制片人任务,只有您能做】整理"金牌视角案例集"**:10-20 条历史公认好选题,每条格式"选题名 + 视角一句话 + 好在哪(为什么零门槛/反差在哪)"。这是 AI few-shot 学习的弹药,作用等同看板项目的 ground-truth 25 期,比任何算法都值钱。 +2. **跨域叙事策略库写进 Prompt**:十来条从纪录片/科普/历史叙事归纳的组织原则,作为 AI 发散的脚手架,防止发散退化成泛泛而谈。**冷启动底稿已备好:`视角模式库冷启动_Fable5.md`(21 条模式、甲至戊五类,含跨域参照与套用示意),制片人增删后精选 10-15 条入 Prompt。** +3. **工程量修正**:主体仍是 Prompt 工程 + 复用语义检索底座,不联网;因拍板 ④ 需加**一张动作记录表**(朴素单表,schema 走 Cline Plan + Opus 审 + 制片人批准流程)。可作为 4a 第五刀,或并入第二刀(对话)一起做。 + +### 模型选型联动 + +"角度卡质量"列入第 7 条模型对比测试的四考点之一;创意发散能力各家差异大,Kimi 是潜在黑马,以实测为准。测试题中的"找角度"任务应按三层结构出题(含候选素材是否只来自给定检索结果的纪律考察)。 + +--- + +## 附:本次评审中被制片人否决/修正的建议(留痕防重提) + +- ❌ "回填历史期次最小目录(节目名+日期+编导)"——节目名笼统,对查重无用。改为补 2025 全年 52 期完整文稿(第 1 条)。 +- ℹ️ 空壳区块风险降级——系统整体完工才交付编导,无提前内测(第 2 条)。 +- 🔄 "角度卡只给方向不给素材"(Fable 原方案)——制片人修正:编导非军迷出身,只给视角想不到内容支撑。改为三层结构(组织原则+方向+知识库有据素材候选),红线移至"不写故事编排/分集结构/立意结论"(第 9 条拍板 ③)。 diff --git a/backend/app/api/knowledge.py b/backend/app/api/knowledge.py index 6e3e14d..fe0fe40 100644 --- a/backend/app/api/knowledge.py +++ b/backend/app/api/knowledge.py @@ -37,7 +37,7 @@ async def upload_md_files( continue try: content = await f.read() - item = svc.store_md_file(content, f.filename) + item = svc.store_md_file(content, f.filename, contributor=current_user.display_name or current_user.username) results.append({ "id": item.id, "title": item.title, @@ -93,6 +93,16 @@ def list_distinct_sources( return [{"source": s} for s in sources] +@router.get("/contributors") +def list_distinct_contributors( + session: Session = Depends(get_session), + current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian, UserRole.biandao)), +): + svc = KnowledgeService() + contributors = svc.get_distinct_contributors() + return [{"contributor": c} for c in contributors] + + @router.get("/grouped") def get_grouped_knowledge_items( session: Session = Depends(get_session), diff --git a/backend/app/services/knowledge_service.py b/backend/app/services/knowledge_service.py index 8dfce92..d625dbd 100644 --- a/backend/app/services/knowledge_service.py +++ b/backend/app/services/knowledge_service.py @@ -187,12 +187,16 @@ class KnowledgeService: import re return re.findall(r"\[\[([^\]]+)\]\]", text) - def store_md_file(self, file_content: bytes, file_name: str) -> KnowledgeItem: + def store_md_file(self, file_content: bytes, file_name: str, contributor: Optional[str] = None) -> KnowledgeItem: """ 读取一篇 md 内容,调用 embo-01 拿到向量,写入 knowledge_items + knowledge_embeddings """ parsed = self.parse_md_file(file_content, file_name) + metadata = parsed["metadata"] or {} + if contributor: + metadata["contributor"] = contributor + # 调用 embedding(type="db" 表示存入知识库) embedding_list = self.embedder.embed_single(parsed["content_md"], embed_type="db") @@ -204,7 +208,7 @@ class KnowledgeService: source_file_name=parsed["source_file_name"], author=parsed["author"], publish_date=parsed["publish_date"], - tags=parsed["metadata"], + tags=metadata if metadata else None, related_entities=parsed["related_entities"], ) session.add(item) @@ -244,6 +248,7 @@ class KnowledgeService: # 从 tags(JSONB) 取 source_detail tags = item.tags or {} source_detail = tags.get("source_detail") if isinstance(tags, dict) else None + contributor = tags.get("contributor") if isinstance(tags, dict) else None results.append({ "id": item.id, "title": item.title, @@ -252,6 +257,7 @@ class KnowledgeService: "source_type": item.source_type, "source_file_name": item.source_file_name, "source_detail": source_detail, + "contributor": contributor or "栏目冷启动", "created_at": item.created_at, }) return results @@ -267,6 +273,17 @@ class KnowledgeService: sources.add(tags["source_detail"]) return sorted(list(sources)) + def get_distinct_contributors(self) -> list[str]: + """返回库里所有不重复的贡献人(从 JSONB 提取),供筛选下拉用。""" + with Session(engine) as session: + items = session.exec(select(KnowledgeItem)).all() + contributors = set() + for item in items: + tags = item.tags or {} + c = tags.get("contributor") if isinstance(tags, dict) else None + contributors.add(c or "栏目冷启动") + return sorted(list(contributors)) + def search_similar(self, query_text: str, top_k: int = 10, min_similarity: float = 0.3) -> list[dict]: """ 语义检索:查询句转为向量,用 SQL 余弦距离(<=>)在数据库层检索 diff --git a/frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx b/frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx index 3aa9202..17c0696 100644 --- a/frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx +++ b/frontend/src/pages/KnowledgeBase/KnowledgeBase.jsx @@ -5,12 +5,10 @@ import useAuthStore from '../../stores/authStore' import knowledgeService from '../../services/knowledgeService' import KnowledgeTree from '../../components/KnowledgeTree/KnowledgeTree' -// 渲染 snippet 中的 **keyword** 加粗标记(不使用 dangerouslySetInnerHTML) function renderSnippet(text) { if (!text) return null const parts = text.split(/\*\*(.+?)\*\*/g) return parts.map((part, i) => { - // 奇数索引是被 ** 包裹的关键词 if (i % 2 === 1) { return {part} } @@ -20,7 +18,6 @@ function renderSnippet(text) { const { Dragger } = Upload -// source_type 枚举值(固定五类,不写死但供 Select 用) const SOURCE_TYPE_OPTIONS = [ { label: '全部类型', value: '' }, { label: '杂志文章', value: 'military_report' }, @@ -28,7 +25,6 @@ const SOURCE_TYPE_OPTIONS = [ { label: '报题单', value: 'baoti' }, ] -// source_type 中文标签 const SOURCE_TYPE_LABEL = { military_report: '杂志文章', manuscript: '节目文稿', @@ -42,18 +38,18 @@ export default function KnowledgeBase() { const [treeData, setTreeData] = useState([]) const [loading, setLoading] = useState(false) const [uploading, setUploading] = useState(false) - const [sources, setSources] = useState([]) // 出处下拉选项 + const [sources, setSources] = useState([]) + const [contributors, setContributors] = useState([]) const [sourceTypeFilter, setSourceTypeFilter] = useState('') const [sourceDetailFilter, setSourceDetailFilter] = useState('') - const [selectedTreeNode, setSelectedTreeNode] = useState(null) // { type, detail } + const [contributorFilter, setContributorFilter] = useState('') + const [selectedTreeNode, setSelectedTreeNode] = useState(null) - // 搜索状态 const [searchQuery, setSearchQuery] = useState('') const [searchResults, setSearchResults] = useState([]) const [searchLoading, setSearchLoading] = useState(false) - const [expandedIds, setExpandedIds] = useState(new Set()) // 展开全文的卡片 id 集合 + const [expandedIds, setExpandedIds] = useState(new Set()) - // 切换卡片展开/收起 const toggleExpand = (id) => { setExpandedIds(prev => { const next = new Set(prev) @@ -90,20 +86,28 @@ export default function KnowledgeBase() { const fetchSources = async () => { try { const data = await knowledgeService.listSources() - // 接口返回 [{source: "航空知识 2026年第1期"}, ...],提取 source 字段 setSources(data.map(s => s.source).filter(Boolean)) } catch { // ignore } } + const fetchContributors = async () => { + try { + const data = await knowledgeService.listContributors() + setContributors(data.map(c => c.contributor).filter(Boolean)) + } catch { + // ignore + } + } + useEffect(() => { fetchItems() fetchTree() fetchSources() + fetchContributors() }, [sourceTypeFilter]) - // 上传 const handleUpload = async (file) => { if (!file.name.endsWith('.md')) { message.warning('仅支持 .md 文件') @@ -117,6 +121,7 @@ export default function KnowledgeBase() { message.success(`成功入库 ${uploaded.length} 篇`) fetchItems() fetchTree() + fetchContributors() } if (errors.length > 0) { errors.forEach(e => message.error(`${e.file}: ${e.error}`)) @@ -126,10 +131,9 @@ export default function KnowledgeBase() { } finally { setUploading(false) } - return false // 阻止默认上传行为 + return false } - // 删除 const handleDelete = async (id) => { try { await knowledgeService.deleteItem(id) @@ -141,7 +145,6 @@ export default function KnowledgeBase() { } } - // 搜索处理 const handleSearch = async () => { if (!searchQuery.trim()) { setSearchResults([]) @@ -159,42 +162,39 @@ export default function KnowledgeBase() { } } - // 清空搜索 → 恢复树形浏览(不丢树状态) const handleClearSearch = () => { setSearchQuery('') setSearchResults([]) } - // 树节点选中 → 联动过滤 const handleNodeSelect = (node) => { setSelectedTreeNode(node) } - // 根据树节点过滤列表 const displayedItems = (() => { let result = items - // 树节点过滤优先(覆盖原有 Select 筛选) if (selectedTreeNode) { const { type, detail } = selectedTreeNode result = result.filter(i => { if (i.source_type !== type) return false if (detail !== null) { - // 按大类决定用哪个字段比对(节目文稿=author,杂志文章=source_detail) if (type === 'manuscript') { if (i.author !== detail) return false } else { - // 杂志文章等用 source_detail if (i.source_detail !== detail) return false } } return true }) } else if (sourceDetailFilter) { - // 保留原有的 source_detail 筛选逻辑 result = result.filter(i => i.source_detail === sourceDetailFilter) } + if (contributorFilter) { + result = result.filter(i => i.contributor === contributorFilter) + } + return result })() @@ -203,47 +203,52 @@ export default function KnowledgeBase() { title: '标题', dataIndex: 'title', key: 'title', - width: 280, + ellipsis: true, render: (text) => ( - {text} + {text} ), }, { title: '作者', dataIndex: 'author', key: 'author', - width: 100, - render: (val) => val || '-', - }, - { - title: '播出/发表时间', - dataIndex: 'publish_date', - key: 'publish_date', - width: 160, + width: 80, render: (val) => val || '-', }, { title: '出处', dataIndex: 'source_detail', key: 'source_detail', - width: 200, + width: 160, + ellipsis: true, render: (val) => val || '-', }, { title: '类型', dataIndex: 'source_type', key: 'source_type', - width: 96, + width: 90, render: (val) => ( {SOURCE_TYPE_LABEL[val] || val} ), }, + { + title: '贡献人', + dataIndex: 'contributor', + key: 'contributor', + width: 100, + render: (val) => ( + + {val || '栏目冷启动'} + + ), + }, { title: '操作', key: 'action', - width: 90, + width: 80, render: (_, record) => ( +
{/* 标题栏 */}

知识库

上传 md 笔记 · 语义检索 · 报题参考

- {/* 上传区(收小为一行按钮) */} + {/* 上传区 */}
支持批量上传,系统自动解析 yaml frontmatter 并生成语义向量
- {/* 搜索栏(搜索模式/浏览模式互斥,清空恢复树形浏览不丢状态) */} + {/* 搜索栏 */}
- {/* 搜索结果列表(搜索模式显示,浏览模式隐藏) */} + {/* 搜索结果 */} {searchQuery && searchResults.length > 0 && (
@@ -376,7 +381,6 @@ export default function KnowledgeBase() { > {renderSnippet(item.snippet)}
- {/* 展开全文区域 */} {isExpanded && item.content_md && (
)} - {/* 展开/收起按钮 */} {item.content_md && (
)} - {/* 主体:左侧树 + 右侧列表(flex 左右并列,宽度稳定) */} + {/* 主体:左侧树 + 右侧列表 */}
{/* 左侧树 */} {/* 筛选栏 */} - + 筛选: ({ label: s, value: s }))]} value={sourceDetailFilter} onChange={val => { setSourceDetailFilter(val); setSelectedTreeNode(null) }} - style={{ width: 200 }} + style={{ width: 180 }} + allowClear + /> +