# 互动组件配置JSON生成器 — 需求描述

## 项目概述
从飞书wiki剧本文档出发，经过sheet数据解析、组件类型匹配、LLM驱动的jsonData生成，
最终输出组件配置JSON并持久化到本地SQLite数据库。

## 数据来源
**剧本文档表格的"组件配置"列**。多维表格（bitable）是旧的数据流程，
我们基于剧本文档进行自动化生产是新流程，目标是替换多维表格流程。
bitable数据可用于验证和参考。

## 已实现的互动类型

### 中互动（27种，v1-v3.1完成）
覆盖对话类(6)、信息类(6)、图片类(4)、其他(11)共27种中互动组件。
详见 `scripts/match_component.py` 的 `MID_INTERACTION_TYPES`。

- 每种类型使用独立的精确Prompt模板（`prompts/prompt_registry.py`）
- 角色配置通过section-character映射解析
- 教研配置文本拆解为结构化字段
- 语音识别热词自动生成（表达类组件）

### 核心互动（5种，v4-v6完成）

#### 1. 合作阅读 core_reading_order（v4）
- 7步LLM调用流水线
- 输出: taskInfo, materialInfo, flowInfo, studyInfo
- bitable wiki: H6DJweNkpigCbak2Y5LcTZ8Vnfb

#### 2. 看图选词 core_reading_imageDrag（v5）
- 5步LLM调用流水线
- 输入: 教研-导览配置 + 教研-互动配置
- 中间步骤: 学习流程配置, 互动描述
- 最终JSON: taskInfo, questionGroup, studyInfo
- bitable wiki: MVo7wugWfimJPIkxhJCcqppFnyf, table: tblKl9CGmARjpw1O
- prompt模板: `prompts/core_imagedrag_prompts.py`

#### 3. 口语快答 core_speaking_reply（v5）
- 7步LLM调用流水线
- 输入: 教研-用户视角任务信息 + 教研-任务规则与NPC设定
- 中间步骤: 学习流程配置, promptInfo配置, 语音识别热词
- 最终JSON: taskInfo, dialogSetting, dialogConfig, studyInfo
- bitable wiki: TSwcw0nFmi21khkpUndchNMsn6f, table: tbl4Qg8d45O58Cqf
- prompt模板: `prompts/core_speaking_prompts.py`

#### 4. 口语妙问 core_speaking_inquiry（v6）
- 7步LLM调用流水线
- 输入: 教研-用户视角任务信息 + 教研-任务规则与NPC设定
- 中间步骤: 学习流程配置, promptInfo配置(NPC角色扮演被动问答型), 语音识别热词
- 最终JSON: taskInfo, dialogSetting, dialogConfig, studyInfo
- 与口语快答的关键差异: Step2生成被动型NPC角色提示词(含知识库)，而非主动提问型
- bitable wiki: GJUVwNSEkis3EXkrVj0ccbqdn8c, table: tbl1Q68oopST9Mel
- prompt模板: `prompts/core_speaking_inquiry_prompts.py`

#### 5. 看图说话 core_speaking_image（v6）
- 8步LLM调用流水线
- 输入: 教研-导览配置 + 教研-对话配置
- 中间步骤: 学习流程配置, 语音识别热词
- 最终JSON: taskInfo, dialogConfig, imageInfo, optionList, questionList, studyInfo
- Step7(dialogConfig)依赖Step6(questionList), Step8(studyInfo)依赖Step1(学习流程配置)
- bitable wiki: KBOXwzVHfin6ORkKbA3c3eWEnoh, table: tblvi5HF0uSU2GNo
- prompt模板: `prompts/core_speaking_image_prompts.py`

## 通用机制：resourceMapping

**所有核心互动都使用统一的 resourceMapping 处理逻辑**，不是某个类型的特有字段：

1. **数据来源**: `parse_script.py` 提取两级角色映射:
   - `section_char_map`: 从文档"角色-section对应"表解析，按section分组
   - `character_map`: 从sheet数据全局提取，作为fallback
2. **解析流程**: `generate_json.py` → `resolve_resource_mapping()` 解析section对应 → 格式化为 `"Name：ID"` 字符串 → 注入prompt的 `{角色配置}` 占位符
3. **LLM提取**: prompt模板统一指示LLM从 `{角色配置}` 提取 `{"name": int_id}` JSON对象
4. **npcName#id规则**: 部分类型(合作阅读、合作听力)在 textData/sequenceData 步骤中，npcName字段追加 `#id`（仅限npcName字段，不适用于content等字段）

## 已实现的核心互动类型（续）

### v7 — 听力类（3种，已完成）
详见 `prd/v7.md`，bitable 数据已验证

| 类型 | cType | LLM步数 | 输出字段 | bitable wiki |
|------|-------|---------|---------|-------------|
| 合作听力 | core_listening_order | 6 | taskInfo, materialInfo, flowInfo, studyInfo | FrxtwNRQDizqiikPkATcBzTCnYe |
| 听力拖拽 | core_listening_drag | 7 | taskInfo, preDialog, dialogList, questionList, studyInfo | K3QrwQnWqiPBm1krhnNcWDTqnhe |
| 听力选择 | core_listening_choose | 5 | taskInfo, questionGroup, studyInfo | Kwrcw6A4jip2sxkdLn4czd1knvf |

- 输入: 合作听力用 `教研配置-任务` + `教研配置-材料`；听力拖拽/选择用 `教研配置-导览配置` + `教研配置-互动配置`
- 中间步骤: 学习过程配置(LLM生成) + 互动描述(LLM生成)
- 合作听力的听音选意用2个中文含义选项；听力拖拽/选择改为"对/错"判断题
- 所有taskInfo均包含 resourceMapping（通用机制）
- 专有名词对照暂不实现，后续单独处理
- prompt模板: `prompts/core_listening_order_prompts.py`, `prompts/core_listening_drag_prompts.py`, `prompts/core_listening_choose_prompts.py`

## 待实现的核心互动类型

### v8 — 写作类（4种，已完成）
详见 `prd/v8.md`，bitable 数据已验证

| 类型 | cType | LLM步数 | 输出字段 | bitable wiki |
|------|-------|---------|---------|-------------|
| 看图组句 | core_writing_imgMakeSentence | 6 | taskInfo, textInfo, studyInfo, evalInfo | BkmtwUBwMiHd5Ak7VS6ccE9SnHd |
| 看图撰写 | core_writing_imgWrite | 6 | taskInfo, textInfo, studyInfo, evalInfo | KwPHwnaqdiWlvNkDm5fcFreDnQh |
| 邮件组句 | core_writing_questionMakeSentence | 6 | taskInfo, textInfo, studyInfo, evalInfo | M5oTwUP6wiImC4kVJU8cRYnfnyf |
| 邮件撰写 | core_writing_questionWrite | 6 | taskInfo, textInfo, studyInfo, evalInfo | Brn0wldKYizsLZkBqK6clp1tnKd |

- 输入: `教研配置-任务信息` + `教研配置-全文信息`
- 中间步骤: 学习流程配置(LLM生成) + 互动描述(LLM生成)
- 4种类型共享通用6步流水线 `_generate_core_writing()`
- 按输入源特征分为2个prompt文件: `core_writing_img_prompts.py`(看图类) + `core_writing_question_prompts.py`(邮件类)
- 关键差异: 看图类stemDesc=图片列表+leadIn用image；邮件类stemDesc=题干描述+leadIn用desc
- 组句类inputType=sentence(optionList+answer)；撰写类inputType=write(answerText+hint)
- prompt模板: `prompts/core_writing_img_prompts.py`, `prompts/core_writing_question_prompts.py`

### v8.1 — 看图拼词（1种，已完成）

| 类型 | cType | LLM步数 | 输出字段 | bitable wiki |
|------|-------|---------|---------|-------------|
| 看图拼词 | core_writing_imgMakeWord | 7 (Step5规则) | taskInfo, preDialog, imageInfo, questionList, studyInfo | QDyhwiY7PiSER8kHGZncDsrinde |

- 输入: 单一教研配置块（【任务标题】【任务背景】【任务描述】【通关知识】【前置对话】【互动问题】）
- 中间步骤: 学习流程配置(LLM生成) + 互动描述(LLM生成)
- Step 5 imageInfo 为规则生成: `{"questionImg": "{cId}_question.png"}`
- 独立7步流水线 `generate_core_writing_imgmakeword()`
- prompt模板: `prompts/core_writing_imgmakeword_prompts.py`

### 其他未实现类型
- 口语独白、口语探讨
- 写作互动

## v9 — 中互动 cDesc 互动描述预生成（已完成）
详见 `prd/v9.md`

为 10 种中互动类型增加 cDesc（互动描述）LLM 预生成步骤：
- 信息类(6种): mid_message_trace/spell/combine/fillin/word/sentence
- 图片类(4种): mid_image_choose/multiple/sequence/drag

实现方式:
- 在 `_generate_with_template()` 中，jsonData 生成前调用 LLM 预生成互动描述
- 信息类使用 `CDESC_PROMPT_MESSAGE` 模板（输入: 互动名称 + 互动内容）
- 图片类使用 `CDESC_PROMPT_IMAGE` 模板（输入: 互动名称 + 情境引入 + 互动内容 + 互动反馈 + 后置对话）
- 生成结果注入 `parsed_fields["互动描述"]`，由主 prompt 模板映射到 jsonData 的 `cDesc` 字段
- 补全了 `mid_image_sequence` 模板中遗漏的 cDesc 字段定义
- 始终使用 LLM 生成，覆盖教研配置中可能存在的值

## v10 — 知识点匹配 + 触发命令 + HTML增强（已完成）
详见 `prd/v10.md`

### 知识点匹配功能
- 新增 `scripts/kp_matcher.py`：连接 MySQL `vala_test.vala_kp` 表（2613条有效记录）
- 两阶段匹配：title 精确/模糊粗召回 → LLM 多候选精筛（用于同名不同释义消歧）
- kpInfo 中 `kpId` 从数据库 `kp_id` 字段获取，`kpType` 从 `type` 字段获取
- kpSkill 使用默认值（vocab→vocab_meaning, sentence→sentence_meaning）
- MySQL 不可达时 fallback 到 kpId=null

### 集成改动
- `generate_json.py`：所有 `_try_rule_based_kp` 调用替换为 `generate_kp_info()` → `kp_matcher.match_knowledge_points()`
- **kpInfo 数据来源统一为 sheet 知识点列**（`knowledge_text`），不再从教研配置的【通关知识】/【用户任务知识】section 获取
- 核心互动生成器内部不再生成 kpInfo，统一由 `generate_component` 从 sheet 知识点列调用 `generate_kp_info()`
- 新增 `level` 参数贯穿 pipeline → generate_component → 各生成器 → kp_matcher

### 触发命令
- SKILL.md 添加触发场景：`*组件配置-json* {飞书剧本文档链接}`
- 触发后执行完整 pipeline，返回 HTML 报告

### HTML 增强
- 中互动组件在 HTML 报告中也展示 kpInfo section（之前仅核心互动展示）

## v11 — 字段重命名 + 交互式HTML + 推送功能（已完成）
详见 `prd/v11.md`

### 字段重命名（方案B：全量重命名为推送接口字段名）
- 所有核心互动输出字段统一改名为推送接口字段名
- 映射: taskData→taskInfo, textData→materialInfo, sequenceData→flowInfo, learningData→studyInfo,
  settingData→dialogSetting, configData→dialogConfig, questionGroupData→questionGroup,
  studyData→studyInfo, preDialogData→preDialog, dialogListData→dialogList, questionListData→questionList
- 涉及文件: generate_json.py, pipeline.py, db_manager.py, html_report.py, validate_core_v5.py

### 交互式HTML报告（v2）
- 每个JSON section使用 textarea 可直接编辑
- kpId 使用下拉选择（显示粗筛 top10 候选）
- 每个组件 header 内嵌两个推送按钮：[配置↑] [知识↑]
- 顶部固定操作栏：一键推送全部 + 进度条 + 成功/失败统计
- 推送状态实时指示（loading/ok/fail）

### 推送接口
- POST https://api-test.valavala.com/content/feishu/notify
- 中互动: `{"componentData": jsonData}`
- 核心互动: `{"pushType":"core", ...各字段}`（按cType不同组装）
- 知识点: `{"pushRelationKp": kpInfoObj}`

### kp_matcher增强
- 粗召回加 LIMIT 10
- 每条知识点匹配结果附带 `candidates` 字段（top10候选）供HTML下拉展示

## 参考文档
- v11需求详细文档: `prd/v11.md`
- v10需求详细文档: `prd/v10.md`
- v9需求详细文档: `prd/v9.md`
- v8需求详细文档: `prd/v8.md`
- v7需求详细文档: `prd/v7.md`
- v6需求详细文档: `prd/v6.md`
- v5需求详细文档: `prd/v5.md`
- v4需求详细文档: `prd/v4.md`
- 组件类型注册表: `references/component_registry.md`

## v12 — 解析增强 + pipeline 自动 HTML + 进度输出（已完成）

### 组件行识别增强（parse_script.py）
- 支持单行空格分隔格式: `"合作阅读  0000800"` → type_name + cId
- 支持多行带前缀格式: `"核心互动- 囗语\n听力选择\n0000810"` → 跳过前缀取实际类型
- LLM 兜底: 正则无法提取时，如果 cell 含中文+5位以上数字，调 LLM 解析（带缓存）
- 测试文档识别率: 15 → 42 组件

### pipeline 自动生成交互式 HTML
- 无需 `--dry-run --html` 参数，执行完自动生成 v2 交互式 HTML 报告
- 非 dry-run 模式也保留完整 jsonData/kpInfo 到 report results
- report 返回 `html_path` 字段供外部调用者使用

### 进度输出
- 每个组件完成后打印 ✓/✗ 状态行
- 每 5 个组件输出进度摘要（成功/失败/跳过计数）
- 末尾输出最终统计

### cType=None 优雅跳过
- 未实现类型（口语独白、口语探讨、写作互动）标记为 skipped，不触发异常

## v13 — CORS 代理服务（已完成）

### 问题背景
用户直接打开本地HTML文件（file://协议）点击推送按钮时，浏览器发送 `Origin: null`，
目标API服务端校验 Origin 返回 403。curl 直接测试正常但浏览器无法绕过。

### 解决方案：轻量代理中转服务
- 新增 `scripts/proxy_server.py`：基于 http.server + requests 的转发代理
- 新增 `config.json`：代理配置（外网IP、端口、速率限制、目标URL）
- 代理只转发到固定目标地址，加上正确的 Origin 头
- HTML 报告中 API 地址改为代理地址

### 安全措施
- 硬编码单一目标URL（不可被滥用为开放代理）
- body 结构校验（必须含 componentData/pushRelationKp/pushType）
- 可配置速率限制（默认 60 req/min/IP）
- 专用端口 18080，用户在安全组中精确控制

### 集成方式
- pipeline 执行时自动检测代理运行状态（GET /health）
- 未运行则 subprocess 自动后台启动
- 代理 URL 传入 html_report 生成到 HTML 的默认 API 地址中

### 配置文件 config.json
```json
{
  "proxy": {
    "external_ip": "115.190.225.235",
    "port": 18080,
    "rate_limit_per_minute": 60,
    "target_url": "https://api-test.valavala.com/content/feishu/notify",
    "target_origin": "https://flow-test.valavala.com"
  }
}
```

## v14 — 重新生成拆分 + 知识点推送失败判定（已完成）

### 重新生成拆分
将原来的单一"重新生成"按钮拆分为两个独立操作：
- **重新生成组件**: 只重新生成 jsonData/core 字段，保留现有 kpInfo 不变
- **重新生成知识点**: 只重新生成 kpInfo，保留现有 jsonData/core 字段不变

实现方式：
- 前端拆分为 `regenerateComponentJson(idx)` 和 `regenerateComponentKp(idx)` 两个函数
- 后端 `/api/regenerate` 通过 `mode` 参数区分：`mode='component'` 或 `mode='kp'`
- `mode='component'`: 调用 `generate_component()` 但跳过 kpInfo
- `mode='kp'`: 只调用 `generate_kp_info()`
- `knowledge_text` 嵌入到 HTML 报告的 COMPONENTS 前端数据中，供重新生成知识点使用

### 知识点推送失败判定
- 单个组件"知识推送"按钮: 无知识点或所有 kpId 为空时，标记为推送**失败**（原来算成功/info）
- 批量"推送全部知识点": 无知识点或 kpId 为空时，计入 failCount 并显示失败详情（原来静默跳过或算成功）

### 涉及文件
- `scripts/pipeline.py`: report entry 添加 `knowledge_text` 字段
- `scripts/html_report.py`: 前端按钮/面板/JS 逻辑拆分 + 推送失败判定
- `scripts/proxy_server.py`: `_do_regenerate` 按 mode 分流
