diff --git a/.vala_skill_hashes b/.vala_skill_hashes
index 3accc2e..b24e1dc 100644
--- a/.vala_skill_hashes
+++ b/.vala_skill_hashes
@@ -34,3 +34,12 @@ script_writing 08b378e090c12083762462e9d310fc43e8bb6f0904eebc5dee1828b785abbbb5
knowledge-mastery-calculator 61a098cebf90ccca12e918f5bb82fe08d8c2baf576416385b8415e7fbaf77516
feishu-embedded-sheet c680d2fd1e086c7b9f931f4809d06b2797bd9f7d9f75761165573b58221f0338
dialogue-interaction-config f4e14e10149fe0ee796e483834ad93a22f912b245b6b4e4d74492c1edcccf93d
+feishu-bitable-translate-fill e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+feishu-table-translate-fill 0fc1b1887af65413b2a37efd3ce71ec90d384e63907a9445273291dd5a30e98d
+dialogue-reading-config e810cc1f7f6f00d4348cb6cf3c71ba17e188230cd6cb7535758d4550d241abc6
+dialogue-fill-in-blanks-config d1a72b826a9f9e1a0d57f373aaff416dab0a4a13fd12cd501bd8b9b487da3f4c
+dialogue-single-choice-image-config a3793a75783cb08ee7059fb3366bd0eb3ee8b4ad0401d3059ebaa217df26420c
+dialogue-sentence-building-config cbe11c073068cf36139112a663b8fd0c10abe5ad5e73163bfbd90f0abb6d46f0
+dialogue-core-navigation-config 2791b8214f62a36ecc80481ab16cd74843b2a475251b29f92fbdd7e6dedffc69
+dialogue-image-description-config 4ea4c6e81d01e6c1f94a5adafab7553874db77eee64ce10f7e1cdf089bcc407f
+interactive-component-json 02054b521a862bb67245ed2a1fa85ff6cbdf200a901ec24a8e8ca849387d7319
diff --git a/AGENTS.md b/AGENTS.md
index 1ab53a5..f9e97e5 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -35,24 +35,24 @@
记忆分为两层,这是你的连续性保障:
-### 短期记忆:`memory/YYYY-MM-DD.md`
+### 记忆分层(分区沉淀,避免交叉污染)
+#### 1. 全局公共记忆(根目录下)
+- **短期公共记忆:`memory/YYYY-MM-DD.md`**
+ - 记录跨模块的通用工作经验、全团队共识、跨业务需求要点
+ - 仅存放不专属某个业务模块的内容
+- **长期公共记忆:`MEMORY.md`**
+ - 记录经过验证的通用核心业务规则、关键决策、跨模块经验教训
-- 在 `memory/` 目录下**按天建立文档**,文件名格式为 `YYYY-MM-DD.md`
-- 记录当天工作中的**临时经验、对话要点、待跟进事项、中间结论**
-- 每天首次需要记录时自动创建当天的文件
-- 这些是原始工作日志,允许内容较零散
-
-### 长期记忆:`MEMORY.md`
-
-- 只记录**经过验证的重要内容**:核心业务规则、关键决策、通用经验教训、团队共识
-- 从日记忆中提炼,去除临时性、个人化的内容后写入
-- 保持精简,定期清理过时条目
+#### 2. 业务模块专属记忆(各模块独立目录)
+- **路径:`business_production/[模块名]/memory/YYYY-MM-DD.md`**
+ - 仅记录对应业务模块的专属内容:生产要点、审校规则、模块专属需求、中间结论
+ - 各模块记忆完全隔离,不会和其他模块的内容混淆
+- 无需单独的长期记忆文件,模块专属的沉淀内容仅在模块内使用,通用后再提炼到全局`MEMORY.md`
### 写入原则
-
-- **日常工作 → 先写 `memory/YYYY-MM-DD.md`**,不要急于写入 `MEMORY.md`
-- **确认为重要且通用 → 提炼到 `MEMORY.md`**,附带简要来源说明
-- 拿不准是否重要时,先放在日记忆里,后续心跳维护时再决定是否提炼
+- **模块内工作 → 优先写入对应模块的`memory/YYYY-MM-DD.md`**,仅跨模块通用内容写入全局记忆
+- **确认为重要且通用 → 提炼到全局`MEMORY.md`**,附带简要来源说明
+- 拿不准归属时,先放在对应模块的记忆里,后续确认通用后再同步到全局
### 记忆写入规范(多人场景)
@@ -148,6 +148,7 @@ System: [...] Feishu[xiaobian] group oc_xxx | 发送者 (ou_xxx) [msg:om_xxx]
|------|------|------|
| `memory/` | 短期记忆 | 按天记录工作日志,格式 `YYYY-MM-DD.md` |
| `business_knowledge/` | 业务知识库 | **所有业务知识统一存放于此**,包括业务术语、数据表说明、SQL 模板、数据抽取脚本等 |
+| `business_production/` | 核心业务生产区 | 按业务模块独立隔离,包含「剧本翻译/课程组件/巩固题/单元挑战」4个独立子目录,每个子目录下分production(生产中)/review(审校中)/final(已定稿)三层,各模块生产审校流程互不干扰 |
| `scripts/` | 脚本文件 | 所有 `.py`、`.sh`、`.sql` 等脚本文件必须放在此目录 |
| `output/` | 输出文件 | 所有生成的报表(`.xlsx`、`.csv`)、日志(`.log`)、导出文件等必须放在此目录 |
| `skills/` | 技能定义 | 个人技能目录 |
diff --git a/11-5_script.xlsx b/business_production/剧本翻译/final/11-5_script.xlsx
similarity index 100%
rename from 11-5_script.xlsx
rename to business_production/剧本翻译/final/11-5_script.xlsx
diff --git a/11-5_剧本定稿.xlsx b/business_production/剧本翻译/final/11-5_剧本定稿.xlsx
similarity index 100%
rename from 11-5_剧本定稿.xlsx
rename to business_production/剧本翻译/final/11-5_剧本定稿.xlsx
diff --git a/script_meixu.xlsx b/business_production/剧本翻译/final/script_meixu.xlsx
similarity index 100%
rename from script_meixu.xlsx
rename to business_production/剧本翻译/final/script_meixu.xlsx
diff --git a/unit11_content.md b/business_production/剧本翻译/final/unit11_content.md
similarity index 100%
rename from unit11_content.md
rename to business_production/剧本翻译/final/unit11_content.md
diff --git a/11-5_jiaoyantu.txt b/business_production/剧本翻译/production/11-5_jiaoyantu.txt
similarity index 100%
rename from 11-5_jiaoyantu.txt
rename to business_production/剧本翻译/production/11-5_jiaoyantu.txt
diff --git a/11-5_美需已填写.xlsx b/business_production/剧本翻译/production/11-5_美需已填写.xlsx
similarity index 100%
rename from 11-5_美需已填写.xlsx
rename to business_production/剧本翻译/production/11-5_美需已填写.xlsx
diff --git a/jiaoyantu.txt b/business_production/剧本翻译/production/jiaoyantu.txt
similarity index 100%
rename from jiaoyantu.txt
rename to business_production/剧本翻译/production/jiaoyantu.txt
diff --git a/business_production/剧本翻译/skills/feishu-table-translate-fill/SKILL.md b/business_production/剧本翻译/skills/feishu-table-translate-fill/SKILL.md
new file mode 100644
index 0000000..78914d9
--- /dev/null
+++ b/business_production/剧本翻译/skills/feishu-table-translate-fill/SKILL.md
@@ -0,0 +1,109 @@
+---
+name: feishu-doc-io
+description: 飞书文档通用读写能力。通过飞书Block API精准读写文档表格/多维表格单元格,100%行列对齐零错位。作为通用I/O层,供各专项技能(台词生产、组件生产等)调用。触发场景:(1) 读取飞书文档表格指定列数据;(2) 向飞书表格指定列写入处理结果;(3) 查看飞书文档表格结构信息;(4) 任何需要从飞书读取数据或写回结果的场景
+---
+
+# 飞书文档通用读写技能
+
+## 定位
+**通用 I/O 能力层**,不包含任何业务逻辑(翻译、校验等)。各专项技能通过 import 调用本技能完成飞书数据的读取和回写。
+
+```
+┌──────────────────────────────────────┐
+│ feishu-doc-io(本技能) │ ← 通用能力层
+│ • read_table_column() │
+│ • write_table_column() │
+│ • read_bitable_column() │
+│ • write_bitable_column() │
+└───────┬──────────┬───────────┬───────┘
+ │ │ │
+ 台词生产 组件生产 其他技能 ← 专项技能层
+```
+
+## 核心 API
+
+### 作为模块调用(推荐)
+```python
+from feishu_io import FeishuDocIO
+
+io = FeishuDocIO() # 从环境变量 FEISHU_APP_ID/FEISHU_APP_SECRET 读取凭证
+
+# 读取飞书表格某一列
+rows = io.read_table_column(url="飞书文档链接", column_name="中文台词")
+# 返回: [{"row": 1, "content": "台词内容"}, {"row": 2, "content": "..."}, ...]
+
+# 读取多列
+rows = io.read_table_columns(url, column_names=["中文台词", "角色"])
+# 返回: [{"row": 1, "中文台词": "...", "角色": "..."}, ...]
+
+# 写回处理结果
+results = [{"row": 1, "content": "English line"}, {"row": 2, "content": "..."}]
+stats = io.write_table_column(url, column_name="英文翻译", data=results)
+# 返回: {"success": 10, "skipped": 2, "failed": 0}
+```
+
+### 命令行独立使用
+```bash
+# 查看文档表格结构
+python scripts/feishu_io.py info --url "飞书链接"
+
+# 读取指定列,输出为JSON
+python scripts/feishu_io.py read --url "飞书链接" --column "中文台词" --output data.json
+
+# 从JSON写入指定列
+python scripts/feishu_io.py write --url "飞书链接" --column "英文翻译" --input results.json
+```
+
+## 支持的飞书链接类型
+| 类型 | 链接格式 | 读取 | 写入 |
+|------|---------|:---:|:---:|
+| 文档表格 | `https://xxx.feishu.cn/docx/xxx` | ✅ | ✅ |
+| Wiki文档表格 | `https://xxx.feishu.cn/wiki/xxx` | ✅ | ✅ |
+| 多维表格 | `https://xxx.feishu.cn/bitable/xxx` | ✅ | ✅ |
+
+## 技术原理
+飞书文档中的表格由 Block 树结构组成,每个单元格是独立的 Block 节点:
+1. **定位表格**:遍历文档 blocks,`block_type=31` 为表格
+2. **解析单元格**:单元格按行优先排列,公式 `cell_index = row × total_cols + col`
+3. **按列名定位**:读取表头行匹配列名 → 确定 `col_index`
+4. **精准读写**:通过 Block API 直接操作目标单元格,不依赖文本匹配
+
+## 环境变量
+| 变量 | 说明 |
+|------|------|
+| `FEISHU_APP_ID` | 飞书应用 App ID |
+| `FEISHU_APP_SECRET` | 飞书应用 App Secret |
+
+## 权限要求
+Bot 应用需要以下权限,并被添加为目标文档的可编辑成员:
+- 文档:`docx:document:readonly`, `docx:document:write`
+- 多维表格:`bitable:app:readonly`, `bitable:app:write`
+- Wiki:`wiki:node:retrieve`
+
+## 飞书单元格换行规范
+- 多行内容必须使用真实换行符 `\n`,不要使用 HTML 的 `
`、`
`、`
` 等标签
+- 飞书表格单元格内的换行不支持 HTML 换行标签;如果误用 ` ` 等标签
+- 如果把多句台词写成 `
`,读取后可能变成普通文本,导致多句内容粘连在一起
+- 标准写法示例:
+ ```text
+ Tom: Oh my!
+ Dan: We are fine.
+ Grace: Thank you for asking, Tom.
+ ```
+ - 非标准写法(不要这样写):
+ ```text
+ Tom: Oh my!
Dan: We are fine.
Grace: Thank you for asking, Tom.
+ ```
+ - 本技能在读取时会自动兼容清洗历史 `
` / `
` 数据,但这只是兜底策略,写入规范始终应为 `\n`
+
+## 脚本说明
+| 脚本 | 用途 |
+|------|------|
+| `scripts/feishu_io.py` | 核心模块,提供 FeishuDocIO 类(import 调用 + CLI 独立使用) |
+| `scripts/write_translations.py` | 旧版写入脚本(保留兼容,推荐使用 feishu_io.py) |
+| `scripts/translate_fill.py` | 旧版翻译编排脚本(保留兼容,翻译逻辑已移至专项技能) |
+
+## 常见问题
+- **Q: 文档有多个表格怎么办?** A: 用 `table_index` 参数指定(从0开始),或先用 `info` 命令查看表格结构
+- **Q: 写入失败怎么办?** A: 检查 Bot 是否有文档编辑权限,确认环境变量已正确设置
+- **Q: 如何避免覆盖已有内容?** A: `write_table_column` 默认 `skip_existing=True`,已有内容的单元格自动跳过
+- **Q: API 频率限制?** A: 内置 0.3s 写入间隔,可通过 `batch_delay` 参数调整
diff --git a/business_production/剧本翻译/skills/feishu-table-translate-fill/references/usage.md b/business_production/剧本翻译/skills/feishu-table-translate-fill/references/usage.md
new file mode 100644
index 0000000..c665043
--- /dev/null
+++ b/business_production/剧本翻译/skills/feishu-table-translate-fill/references/usage.md
@@ -0,0 +1,20 @@
+# 使用说明
+## 权限要求
+1. Bot应用(App ID: cli_a931175d41799cc7)需要被添加为目标文档/多维表格的可编辑成员
+2. 所需API权限:
+ - 文档权限:docx:document:readonly, docx:document:write
+ - 多维表格权限:bitable:app:readonly, bitable:app:write
+ - Wiki权限:wiki:node:retrieve
+
+## 常见问题
+### Q: 翻译时知识点丢失怎么办?
+A: 脚本内置了知识点自动检测功能,会自动保留`
', '\n', result, flags=re.IGNORECASE)
+ # 清理连续空行
+ result = re.sub(r'\n{3,}', '\n\n', result)
+ return result.strip()
+
+ def read_table_column(self, url: str, column_name: str,
+ table_index: int = 0, start_row: int = 1,
+ end_row: int = None) -> List[Dict]:
+ """
+ 从飞书文档表格中读取指定列的数据。
+
+ Args:
+ url: 飞书文档链接
+ column_name: 目标列的表头名称
+ table_index: 表格序号(文档中第几个表格,从0开始)
+ start_row: 起始数据行(0为表头行,1为第一行数据)
+ end_row: 结束行(None=读到最后)
+
+ Returns:
+ [{"row": 行号, "content": "单元格文本"}, ...]
+ """
+ url_info = self.parse_url(url)
+ if url_info["type"] == "wiki":
+ doc_id = self._resolve_wiki_to_doc_id(url_info["wiki_token"])
+ elif url_info["type"] == "docx":
+ doc_id = url_info["doc_id"]
+ else:
+ raise ValueError(f"read_table_column 仅支持文档表格(docx/wiki),当前类型: {url_info['type']}")
+
+ # 找到目标表格
+ tables = self._find_tables(doc_id)
+ if table_index >= len(tables):
+ raise ValueError(f"文档中只有 {len(tables)} 个表格,无法访问第 {table_index} 个")
+ table = tables[table_index]
+ total_cols = table["cols"]
+
+ # 获取所有单元格
+ cells = self._get_table_cells(doc_id, table["block_id"])
+
+ # 读取表头行,找到目标列的索引
+ header_cells = cells[:total_cols]
+ col_index = None
+ for i, cell in enumerate(header_cells):
+ header_text = self._read_cell_text(doc_id, cell["block_id"])
+ if column_name in header_text:
+ col_index = i
+ break
+ if col_index is None:
+ available = [self._read_cell_text(doc_id, c["block_id"]) for c in header_cells]
+ raise ValueError(f"未找到列 '{column_name}',可用列: {available}")
+
+ # 读取数据行
+ total_rows = len(cells) // total_cols
+ if end_row is None:
+ end_row = total_rows - 1
+ end_row = min(end_row, total_rows - 1)
+
+ results = []
+ for row in range(start_row, end_row + 1):
+ cell_idx = row * total_cols + col_index
+ if cell_idx < len(cells):
+ content = self._read_cell_text(doc_id, cells[cell_idx]["block_id"])
+ results.append({"row": row, "content": content})
+ time.sleep(0.1) # 避免API频率限制
+ return results
+
+ def read_table_columns(self, url: str, column_names: List[str],
+ table_index: int = 0, start_row: int = 1,
+ end_row: int = None) -> List[Dict]:
+ """
+ 从飞书表格中读取多列数据。
+
+ Returns:
+ [{"row": 行号, "col1_name": "内容", "col2_name": "内容"}, ...]
+ """
+ url_info = self.parse_url(url)
+ if url_info["type"] == "wiki":
+ doc_id = self._resolve_wiki_to_doc_id(url_info["wiki_token"])
+ elif url_info["type"] == "docx":
+ doc_id = url_info["doc_id"]
+ else:
+ raise ValueError(f"仅支持文档表格(docx/wiki)")
+
+ tables = self._find_tables(doc_id)
+ table = tables[table_index]
+ total_cols = table["cols"]
+ cells = self._get_table_cells(doc_id, table["block_id"])
+
+ # 解析表头
+ header_cells = cells[:total_cols]
+ col_map = {} # column_name → col_index
+ for i, cell in enumerate(header_cells):
+ header_text = self._read_cell_text(doc_id, cell["block_id"])
+ for name in column_names:
+ if name in header_text:
+ col_map[name] = i
+
+ missing = [n for n in column_names if n not in col_map]
+ if missing:
+ raise ValueError(f"未找到列: {missing}")
+
+ total_rows = len(cells) // total_cols
+ if end_row is None:
+ end_row = total_rows - 1
+ end_row = min(end_row, total_rows - 1)
+
+ results = []
+ for row in range(start_row, end_row + 1):
+ row_data = {"row": row}
+ for name, col_idx in col_map.items():
+ cell_idx = row * total_cols + col_idx
+ if cell_idx < len(cells):
+ row_data[name] = self._read_cell_text(doc_id, cells[cell_idx]["block_id"])
+ results.append(row_data)
+ time.sleep(0.1)
+ return results
+
+ # ============================================================
+ # 文档表格:写入
+ # ============================================================
+ def _cell_has_content(self, doc_id: str, cell_id: str) -> Tuple[bool, List]:
+ """检查单元格是否已有文本内容"""
+ resp = requests.get(
+ f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/blocks/{cell_id}/children?page_size=50",
+ headers=self._headers())
+ data = resp.json()
+ if data.get("code") != 0:
+ return False, []
+ children = data["data"].get("items", [])
+ for child in children:
+ if child.get("block_type") == 2:
+ for el in child.get("text", {}).get("elements", []):
+ if el.get("text_run", {}).get("content", "").strip():
+ return True, children
+ return False, children
+
+ def _write_cell(self, doc_id: str, cell_id: str, text: str) -> bool:
+ """写入文本到单个单元格"""
+ # 清理现有子blocks
+ _, existing = self._cell_has_content(doc_id, cell_id)
+ for child in existing:
+ requests.delete(
+ f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/blocks/{child['block_id']}",
+ headers=self._headers())
+
+ # 写入新内容
+ text_elements = [{"text_run": {"content": line + "\n"}} for line in text.split("\n")]
+ payload = {
+ "children": [{"block_type": 2, "text": {"elements": text_elements, "style": {}}}],
+ "index": 0
+ }
+ resp = requests.post(
+ f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/blocks/{cell_id}/children",
+ headers=self._headers(), json=payload)
+ return resp.json().get("code") == 0
+
+ def write_table_column(self, url: str, column_name: str, data: List[Dict],
+ table_index: int = 0, skip_existing: bool = True,
+ batch_size: int = 10, batch_delay: float = 0.3) -> Dict:
+ """
+ 向飞书文档表格指定列写入数据。
+
+ Args:
+ url: 飞书文档链接
+ column_name: 目标列的表头名称
+ data: [{"row": 行号, "content": "要写入的文本"}, ...]
+ table_index: 表格序号
+ skip_existing: 是否跳过已有内容的单元格
+ batch_size: 每批处理行数
+ batch_delay: 每次写入间隔(秒)
+
+ Returns:
+ {"success": 成功数, "skipped": 跳过数, "failed": 失败数}
+ """
+ url_info = self.parse_url(url)
+ if url_info["type"] == "wiki":
+ doc_id = self._resolve_wiki_to_doc_id(url_info["wiki_token"])
+ elif url_info["type"] == "docx":
+ doc_id = url_info["doc_id"]
+ else:
+ raise ValueError(f"write_table_column 仅支持文档表格(docx/wiki)")
+
+ tables = self._find_tables(doc_id)
+ table = tables[table_index]
+ total_cols = table["cols"]
+ cells = self._get_table_cells(doc_id, table["block_id"])
+
+ # 找到目标列
+ header_cells = cells[:total_cols]
+ col_index = None
+ for i, cell in enumerate(header_cells):
+ header_text = self._read_cell_text(doc_id, cell["block_id"])
+ if column_name in header_text:
+ col_index = i
+ break
+ if col_index is None:
+ raise ValueError(f"未找到列 '{column_name}'")
+
+ stats = {"success": 0, "skipped": 0, "failed": 0}
+
+ for item in data:
+ row = item["row"]
+ text = item["content"]
+ cell_idx = row * total_cols + col_index
+ if cell_idx >= len(cells):
+ stats["failed"] += 1
+ continue
+
+ cell_id = cells[cell_idx]["block_id"]
+
+ # 跳过已有内容
+ if skip_existing:
+ has_content, _ = self._cell_has_content(doc_id, cell_id)
+ if has_content:
+ stats["skipped"] += 1
+ print(f" 行{row}: 跳过(已有内容)")
+ continue
+
+ if self._write_cell(doc_id, cell_id, text):
+ stats["success"] += 1
+ print(f" 行{row}: ✅ 写入成功")
+ else:
+ stats["failed"] += 1
+ print(f" 行{row}: ❌ 写入失败")
+
+ time.sleep(batch_delay)
+
+ return stats
+
+ # ============================================================
+ # 多维表格:读写
+ # ============================================================
+ def read_bitable_column(self, url: str, column_name: str) -> List[Dict]:
+ """从多维表格读取指定列"""
+ url_info = self.parse_url(url)
+ if url_info["type"] != "bitable":
+ raise ValueError("仅支持多维表格链接")
+
+ app_token = url_info["app_token"]
+ table_id = url_info.get("table_id", "")
+ if not table_id:
+ raise ValueError("链接中未包含 table_id")
+
+ # 获取所有记录
+ resp = requests.get(
+ f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records?page_size=500",
+ headers=self._headers())
+ data = resp.json()
+ if data.get("code") != 0:
+ raise RuntimeError(f"获取多维表格记录失败: {data}")
+
+ results = []
+ for i, record in enumerate(data["data"].get("items", [])):
+ fields = record.get("fields", {})
+ content = ""
+ if column_name in fields:
+ val = fields[column_name]
+ if isinstance(val, list):
+ content = "".join([v.get("text", "") for v in val if isinstance(v, dict)])
+ elif isinstance(val, str):
+ content = val
+ results.append({
+ "row": i,
+ "record_id": record["record_id"],
+ "content": content
+ })
+ return results
+
+ def write_bitable_column(self, url: str, column_name: str, data: List[Dict],
+ batch_delay: float = 0.3) -> Dict:
+ """向多维表格指定列写入数据"""
+ url_info = self.parse_url(url)
+ if url_info["type"] != "bitable":
+ raise ValueError("仅支持多维表格链接")
+
+ app_token = url_info["app_token"]
+ table_id = url_info.get("table_id", "")
+
+ stats = {"success": 0, "failed": 0}
+ for item in data:
+ record_id = item.get("record_id")
+ text = item["content"]
+ if not record_id:
+ stats["failed"] += 1
+ continue
+
+ payload = {"fields": {column_name: text}}
+ resp = requests.put(
+ f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}",
+ headers=self._headers(), json=payload)
+ if resp.json().get("code") == 0:
+ stats["success"] += 1
+ else:
+ stats["failed"] += 1
+ time.sleep(batch_delay)
+
+ return stats
+
+
+# ============================================================
+# CLI 入口(独立使用时)
+# ============================================================
+def main():
+ import argparse
+ parser = argparse.ArgumentParser(description="飞书文档通用读写工具")
+ sub = parser.add_subparsers(dest="command")
+
+ # read 子命令
+ read_p = sub.add_parser("read", help="读取飞书表格列")
+ read_p.add_argument("--url", required=True, help="飞书文档链接")
+ read_p.add_argument("--column", required=True, help="目标列名称")
+ read_p.add_argument("--table-index", type=int, default=0)
+ read_p.add_argument("--start-row", type=int, default=1)
+ read_p.add_argument("--end-row", type=int, default=None)
+ read_p.add_argument("--output", help="输出为JSON文件")
+
+ # write 子命令
+ write_p = sub.add_parser("write", help="写入飞书表格列")
+ write_p.add_argument("--url", required=True, help="飞书文档链接")
+ write_p.add_argument("--column", required=True, help="目标列名称")
+ write_p.add_argument("--input", required=True, help="输入JSON文件")
+ write_p.add_argument("--table-index", type=int, default=0)
+ write_p.add_argument("--skip-existing", action="store_true", default=True)
+
+ # info 子命令
+ info_p = sub.add_parser("info", help="查看飞书文档表格结构")
+ info_p.add_argument("--url", required=True, help="飞书文档链接")
+
+ args = parser.parse_args()
+ io = FeishuDocIO()
+
+ if args.command == "read":
+ url_info = io.parse_url(args.url)
+ if url_info["type"] == "bitable":
+ rows = io.read_bitable_column(args.url, args.column)
+ else:
+ rows = io.read_table_column(args.url, args.column,
+ args.table_index, args.start_row, args.end_row)
+ print(f"读取到 {len(rows)} 行数据")
+ for r in rows:
+ print(f" 行{r['row']}: {r['content'][:60]}{'...' if len(r['content'])>60 else ''}")
+ if args.output:
+ with open(args.output, "w", encoding="utf-8") as f:
+ json.dump(rows, f, ensure_ascii=False, indent=2)
+ print(f"已保存到 {args.output}")
+
+ elif args.command == "write":
+ with open(args.input, "r", encoding="utf-8") as f:
+ data = json.load(f)
+ url_info = io.parse_url(args.url)
+ if url_info["type"] == "bitable":
+ stats = io.write_bitable_column(args.url, args.column, data)
+ else:
+ stats = io.write_table_column(args.url, args.column, data,
+ args.table_index, args.skip_existing)
+ print(f"\n写入完成: 成功{stats['success']} 跳过{stats.get('skipped',0)} 失败{stats['failed']}")
+
+ elif args.command == "info":
+ url_info = io.parse_url(args.url)
+ print(f"链接类型: {url_info['type']}")
+ if url_info["type"] in ["docx", "wiki"]:
+ if url_info["type"] == "wiki":
+ doc_id = io._resolve_wiki_to_doc_id(url_info["wiki_token"])
+ print(f"Wiki token: {url_info['wiki_token']} → doc_id: {doc_id}")
+ else:
+ doc_id = url_info["doc_id"]
+ tables = io._find_tables(doc_id)
+ print(f"找到 {len(tables)} 个表格:")
+ for i, t in enumerate(tables):
+ print(f" 表格{i}: {t['rows']}行 x {t['cols']}列 (block_id={t['block_id']})")
+ else:
+ parser.print_help()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/business_production/剧本翻译/skills/feishu-table-translate-fill/scripts/translate_fill.py b/business_production/剧本翻译/skills/feishu-table-translate-fill/scripts/translate_fill.py
new file mode 100644
index 0000000..603e776
--- /dev/null
+++ b/business_production/剧本翻译/skills/feishu-table-translate-fill/scripts/translate_fill.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python3
+import argparse
+import requests
+import json
+import re
+from typing import List, Dict
+
+# 飞书Bot应用配置
+APP_ID = "cli_a931175d41799cc7"
+APP_SECRET = "Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14"
+TENANT_TOKEN_URL = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
+
+# 加载教研词库/句型库(路径根据实际情况调整)
+L1_WORD_LIST = "/root/.openclaw/workspace-xiaoyan/business_knowledge/L1_word_list.json"
+L1_PATTERN_LIST = "/root/.openclaw/workspace-xiaoyan/business_knowledge/L1_pattern_list.json"
+L2_WORD_LIST = "/root/.openclaw/workspace-xiaoyan/business_knowledge/L2_word_list.json"
+L2_PATTERN_LIST = "/root/.openclaw/workspace-xiaoyan/business_knowledge/L2_pattern_list.json"
+
+def get_tenant_token() -> str:
+ """获取Bot身份的租户访问凭证"""
+ payload = {"app_id": APP_ID, "app_secret": APP_SECRET}
+ resp = requests.post(TENANT_TOKEN_URL, json=payload)
+ return resp.json()["tenant_access_token"]
+
+def parse_url(url: str) -> Dict:
+ """解析飞书链接,判断类型并提取关键参数"""
+ result = {"type": "unknown", "token": ""}
+ if "/wiki/" in url:
+ result["type"] = "docx"
+ # 先获取wiki节点对应的obj_token
+ token = re.findall(r"/wiki/([a-zA-Z0-9]+)", url)[0]
+ result["wiki_token"] = token
+ elif "/docx/" in url:
+ result["type"] = "docx"
+ result["token"] = re.findall(r"/docx/([a-zA-Z0-9]+)", url)[0]
+ elif "/bitable/" in url:
+ result["type"] = "bitable"
+ result["app_token"] = re.findall(r"/bitable/([a-zA-Z0-9]+)", url)[0]
+ if "table" in url:
+ result["table_id"] = re.findall(r"table=([a-zA-Z0-9]+)", url)[0]
+ return result
+
+def translate_text(text: str, rule: str = "general") -> tuple:
+ """翻译文本并应用校验规则,返回(翻译结果, 校验结果字典)"""
+ # 这里调用翻译API,示例使用模拟实现,实际替换为对应翻译服务调用
+ # 模拟翻译:简单的中译英演示,实际接入豆包/DeepL等翻译服务
+ translate_map = {
+ "你好": "Hello",
+ "我是": "I am",
+ "让我帮帮你": "Let me help you",
+ "我确信我们能帮忙": "I'm sure we can help",
+ "家": "home",
+ "房子": "house",
+ "沙发": "sofa",
+ "橱柜": "cupboard"
+ }
+ translated = text
+ for cn, en in translate_map.items():
+ translated = translated.replace(cn, en)
+
+ # 校验规则
+ check_result = {
+ "pass": True,
+ "errors": [],
+ "warnings": []
+ }
+
+ if rule in ["L1", "L2"]:
+ # 长度校验
+ word_count = len(translated.split())
+ max_len = 10 if rule == "L1" else 15
+ if word_count > max_len:
+ check_result["warnings"].append(f"句子长度超过{max_len}词:当前{word_count}词")
+
+ # 超纲词校验(实际加载词库校验)
+ # 知识点保留校验:检查标色内容是否保留
+ if '
S1=4-5岁零基础
S2=5-6岁入门
S3=6-7岁进阶
S4=7-8岁提升 |
+| --output | 否 | 目录路径 | 结果输出目录,指定后自动保存所有结果到该目录,不指定则直接打印到控制台 |
+| --quiet | 否 | 无 | 静默模式,关闭节点过程日志,只输出最终结果 |
+| --trace | 否 | 无 | 保存每个节点的中间产物到output目录(需配合--output使用) |
+| --source-col | 否 | 字符串 | 飞书模式:源列名称(默认"中文台词") |
+| --target-col | 否 | 字符串 | 飞书模式:目标列名称(默认"英文台词") |
+| --table-index | 否 | 整数 | 飞书模式:文档中第几个表格,从0开始(默认0) |
+
+## 错误处理规则
+- 输入路径不存在/无txt文件:直接报错退出,给出明确提示
+- 配置文件加载失败:报错退出,提示检查配置文件格式
+- LLM调用失败:单个剧本生成失败不影响其他批量任务,给出错误提示
+- 参数不合法:直接输出参数说明,提示正确用法
+- 飞书写回失败:自动备份结果到本地 JSON 文件,不丢数据
+
+## 飞书模式输入规范
+- 飞书表格中的多行台词必须使用真实换行符 `\n`,不要使用 HTML 的 `
`、`
`、`
` 连接,飞书读取后可能会变成单行粘连文本,破坏 `角色: 台词` 结构,影响后续意群拆解和英语思维重构
+- 推荐单元格内容格式:
+ ```text
+ Tom: Oh my!
+ Dan: We are fine.
+ Grace: Thank you for asking, Tom.
+ ```
+- 飞书模式内置两层兜底:
+ - 读取层会自动把历史 `
` / `
` 清洗为 `\n`
+ - 进入 pipeline 前会再做一次粘连台词智能拆分
+- 但兜底不等于标准输入,最佳实践始终是:写入飞书时直接使用 `\n`
+
+## 使用示例
+```bash
+# 1. 中英混合剧本(最常见)→ 意群拆解+英语思维重构
+openclaw skill run kids-english-script-production --input "Ben: 好的,我去拿毛巾,顺便把water bucket也拿过来。" --stage S2
+
+# 2. 纯英文剧本 → 难度适配(不经过中文,直接降级)
+openclaw skill run kids-english-script-production --input "Ben: Let me go fetch the towel and bring the water bucket as well." --stage S1
+
+# 3. 处理单个文件,结果保存到output目录
+openclaw skill run kids-english-script-production --path ./script.txt --stage S3 --output ./result
+
+# 4. 批量处理目录下所有txt剧本
+openclaw skill run kids-english-script-production --path ./scripts_dir --stage S1 --output ./batch_result
+
+# 5. 飞书模式:从飞书文档表格读取 → 处理 → 写回(最完整的工作流)
+openclaw skill run kids-english-script-production --feishu-url "https://xxx.feishu.cn/wiki/xxx" --stage S2
+
+# 6. 飞书模式:自定义源列和目标列名称
+openclaw skill run kids-english-script-production --feishu-url "https://xxx.feishu.cn/wiki/xxx" --stage S1 --source-col "原始台词" --target-col "英文翻译" --output ./local_backup
+```
+
+## 输入输出对照样例
+
+### 输入台词
+```
+Peter: Hey, do you want to see what I found?
+Peter: Do you? Do you?
+Vicky: Alex, look after your little brother!
+Alex: He wants to be a train. He is not our little brother.
+Peter: But... 万一火车很快,停不下来怎么办?
+Vicky: Peter...
+```
+
+### 输出台词(S1)
+```
+Peter: Hey, guess what I found?
+Peter: Wanna see? Wanna see?
+Vicky: Alex, look at your little brother!
+Alex: He wants to be a train.
+Alex: Just let him be.
+Peter: But...
+Peter: I am the train! I can't stop!
+Vicky: Peter...
+```
+
+### 样例解读
+| 变化 | 说明 |
+| ---- | ---- |
+| `do you want to see` → `guess what` / `Wanna see` | 口语化替换,更符合儿童表达习惯 |
+| `look after` → `look at` | 降低词汇难度(look after 对 S1 偏难) |
+| `He is not our little brother` → `Just let him be` | 英语思维重构:不是逐词翻译,而是用孩子会说的方式表达同样的意思 |
+| 一句拆两句:`He wants to be a train. He is not...` | 一句一意,符合 S1 句长要求 |
+| 中文意群重构:`万一火车很快停不下来` → `I am the train! I can't stop!` | 不是翻译中文,而是用英语孩子的方式重新表达这个情境 |
+| `Vicky: Peter...` 保持不变 | 忠实度:不新增不删减,语气词原样保留 |
diff --git a/skills/kids-english-script-production/kids-english-script-production/assets/expression_map.yaml b/business_production/剧本翻译/skills/kids-english-script-production/assets/expression_map.yaml
similarity index 95%
rename from skills/kids-english-script-production/kids-english-script-production/assets/expression_map.yaml
rename to business_production/剧本翻译/skills/kids-english-script-production/assets/expression_map.yaml
index 45c3e89..a6dff16 100644
--- a/skills/kids-english-script-production/kids-english-script-production/assets/expression_map.yaml
+++ b/business_production/剧本翻译/skills/kids-english-script-production/assets/expression_map.yaml
@@ -13,7 +13,7 @@ emotion_map:
# 同义替换表:标准表达 → 更口语化的儿童表达(同义替换,不改变原意)
synonym_replace:
it is so nice: it is beautiful
- Do you?: Wanna see?
+ Do you want to see?: Want to see?
Let's get in: Here we go
Let's start: Let's go
very good: Great
diff --git a/skills/kids-english-script-production/kids-english-script-production/assets/prompt_config.yaml b/business_production/剧本翻译/skills/kids-english-script-production/assets/prompt_config.yaml
similarity index 100%
rename from skills/kids-english-script-production/kids-english-script-production/assets/prompt_config.yaml
rename to business_production/剧本翻译/skills/kids-english-script-production/assets/prompt_config.yaml
diff --git a/skills/kids-english-script-production/kids-english-script-production/assets/sci_fi_map.yaml b/business_production/剧本翻译/skills/kids-english-script-production/assets/sci_fi_map.yaml
similarity index 100%
rename from skills/kids-english-script-production/kids-english-script-production/assets/sci_fi_map.yaml
rename to business_production/剧本翻译/skills/kids-english-script-production/assets/sci_fi_map.yaml
diff --git a/business_production/剧本翻译/skills/kids-english-script-production/assets/skill.yml b/business_production/剧本翻译/skills/kids-english-script-production/assets/skill.yml
new file mode 100644
index 0000000..034793d
--- /dev/null
+++ b/business_production/剧本翻译/skills/kids-english-script-production/assets/skill.yml
@@ -0,0 +1,54 @@
+name: kids-english-script-production
+description: 4-8岁儿童英文台词生产工具,支持中英混合/纯英文/纯中文输入,通过意群拆解+英语思维重构生成自然地道的分级英文台词
+version: 2.1.0
+author: shark
+entry: python3 scripts/gen_script.py
+dependencies:
+ - feishu-doc-io # 飞书模式需要此通用I/O技能
+parameters:
+ - name: input
+ type: string
+ description: 直接输入待处理的剧本文本
+ required: false
+ - name: path
+ type: string
+ description: 待处理的单个剧本文件路径或包含多个剧本的目录路径
+ required: false
+ - name: feishu-url
+ type: string
+ description: 飞书文档链接,从表格读取台词列→pipeline处理→写回英文列
+ required: false
+ - name: stage
+ type: string
+ description: 目标难度等级 S1/S2/S3/S4
+ required: true
+ - name: output
+ type: string
+ description: 结果输出目录,指定后自动保存结果
+ required: false
+ - name: quiet
+ type: boolean
+ description: 静默模式,关闭节点过程日志,只输出最终结果
+ required: false
+ - name: trace
+ type: boolean
+ description: 保存每个节点的中间产物到output目录
+ required: false
+ - name: source-col
+ type: string
+ description: 飞书模式:源列名称(默认"中文台词")
+ required: false
+ - name: target-col
+ type: string
+ description: 飞书模式:目标列名称(默认"英文台词")
+ required: false
+ - name: table-index
+ type: integer
+ description: 飞书模式:文档中第几个表格,从0开始
+ required: false
+tags:
+ - 内容生产
+ - 英语课程
+ - 台词生成
+ - 批量处理
+ - 飞书集成
diff --git a/skills/kids-english-script-production/kids-english-script-production/assets/stage_config.yaml b/business_production/剧本翻译/skills/kids-english-script-production/assets/stage_config.yaml
similarity index 100%
rename from skills/kids-english-script-production/kids-english-script-production/assets/stage_config.yaml
rename to business_production/剧本翻译/skills/kids-english-script-production/assets/stage_config.yaml
diff --git a/skills/kids-english-script-production/kids-english-script-production/assets/validation_config.yaml b/business_production/剧本翻译/skills/kids-english-script-production/assets/validation_config.yaml
similarity index 87%
rename from skills/kids-english-script-production/kids-english-script-production/assets/validation_config.yaml
rename to business_production/剧本翻译/skills/kids-english-script-production/assets/validation_config.yaml
index ec49607..2e966f1 100644
--- a/skills/kids-english-script-production/kids-english-script-production/assets/validation_config.yaml
+++ b/business_production/剧本翻译/skills/kids-english-script-production/assets/validation_config.yaml
@@ -1,7 +1,7 @@
# 英文台词完整校验规则配置
# 版本:2026-04-01
---
-## 一、基础通用校验规则(强制执行)
+# 一、基础通用校验规则(强制执行)
basic_rules:
sentence_spec:
- 默认保留be going to标准表达,无特殊标注(如指定学龄前场景)禁止使用gonna等过度口语化表达
@@ -10,9 +10,9 @@ basic_rules:
redundancy_check:
- 禁止同一句台词连续重复出现2次及以上,此类排版错误直接标注
confirmed_optimization:
- - "Today, we must train!" 统一优化为 "Let's start training!"
+ - 'Today, we must train! 统一优化为 Let''s start training!'
-## 二、核心精校5大法则(A1级别内容强制遵循)
+# 二、核心精校5大法则(A1级别内容强制遵循)
core_principles:
simplification:
name: 极简降维法则
@@ -40,7 +40,7 @@ core_principles:
- 允许使用低认知负荷语气词传递情绪(Phew!/Ouch!/Oops!/Aha!/Waaaaah!等)
- 情绪表达直接使用A1级形容词(sad/happy/angry等),禁止复杂心理描写
-## 三、AR等级/词汇/难度校验规则
+# 三、AR等级/词汇/难度校验规则
ar_validation:
enable: true
S1_allow_AR2_ratio: 0.1 # S1允许AR2占比最大10%
@@ -51,6 +51,8 @@ vocab_validation:
enable_OOV_remind: true # 是否开启超纲词提醒
S1_allow_OOV_ratio: 0.05 # S1允许超纲词占比最大5%
S2_allow_OOV_ratio: 0.1 # S2允许超纲词占比最大10%
+ S3_allow_OOV_ratio: 0.15 # S3允许超纲词占比最大15%
+ S4_allow_OOV_ratio: 0.2 # S4允许超纲词占比最大20%
stop_words: # 超纲词校验时忽略的词
- hey
- look
@@ -83,6 +85,6 @@ difficulty_validation:
enable: true
allow_lexile_deviation: 50 # 允许蓝思值偏差±50L
-## 四、校验输出标准
+# 四、校验输出标准
output_standard:
- 所有问题标注需包含:上下文引用+问题类型说明+具体优化方案,确保可直接落地修改
diff --git a/skills/kids-english-script-production/kids-english-script-production/assets/调优指南.md b/business_production/剧本翻译/skills/kids-english-script-production/assets/调优指南.md
similarity index 92%
rename from skills/kids-english-script-production/kids-english-script-production/assets/调优指南.md
rename to business_production/剧本翻译/skills/kids-english-script-production/assets/调优指南.md
index 9c8ea3c..7dc19ae 100644
--- a/skills/kids-english-script-production/kids-english-script-production/assets/调优指南.md
+++ b/business_production/剧本翻译/skills/kids-english-script-production/assets/调优指南.md
@@ -1,6 +1,6 @@
# 英文台词生产技能调优指南
## 📌 核心原则
-**所有调优无需修改代码/核心Prompt,仅需修改`assets/`目录下的yaml配置文件,改完立即生效**,核心生成策略完全不变,避免人为改动导致规则混乱。
+**所有调优无需修改代码/核心Prompt,仅需修改`assets/`目录下的yaml配置文件,改完立即生效**。核心流程(意群拆解→英语思维重构→代码校验→自动修正)完全不变,避免人为改动导致规则混乱。
---
## 📁 可修改配置文件说明
diff --git a/business_production/剧本翻译/skills/kids-english-script-production/examples/sample_output.txt b/business_production/剧本翻译/skills/kids-english-script-production/examples/sample_output.txt
new file mode 100644
index 0000000..7215e76
--- /dev/null
+++ b/business_production/剧本翻译/skills/kids-english-script-production/examples/sample_output.txt
@@ -0,0 +1,8 @@
+Peter: Hey, guess what I found?
+Peter: Wanna see? Wanna see?
+Vicky: Alex, look at your little brother!
+Alex: He wants to be a train.
+Alex: Just let him be.
+Peter: But...
+Peter: I am the train! I can't stop!
+Vicky: Peter...
diff --git a/business_production/剧本翻译/skills/kids-english-script-production/examples/sample_script.txt b/business_production/剧本翻译/skills/kids-english-script-production/examples/sample_script.txt
new file mode 100644
index 0000000..8ebacae
--- /dev/null
+++ b/business_production/剧本翻译/skills/kids-english-script-production/examples/sample_script.txt
@@ -0,0 +1,6 @@
+Peter: Hey, do you want to see what I found?
+Peter: Do you? Do you?
+Vicky: Alex, look after your little brother!
+Alex: He wants to be a train. He is not our little brother.
+Peter: But... 万一火车很快,停不下来怎么办?
+Vicky: Peter...
diff --git a/business_production/剧本翻译/skills/kids-english-script-production/references/l1_word_list.json b/business_production/剧本翻译/skills/kids-english-script-production/references/l1_word_list.json
new file mode 100644
index 0000000..aa867f3
--- /dev/null
+++ b/business_production/剧本翻译/skills/kids-english-script-production/references/l1_word_list.json
@@ -0,0 +1 @@
+{"update_time": "2026-03-26", "total_count": 980, "words": [{"word": "red", "pos": "adj", "meaning": "红色的", "level": "LV1(上)", "unit": "S0-U0-L1", "difficulty": 1, "enabled": true}, {"word": "hat", "pos": "n", "meaning": "帽子", "level": "LV1(上)", "unit": "S0-U0-L1", "difficulty": 1, "enabled": true}, {"word": "blue", "pos": "adj", "meaning": "蓝的", "level": "LV1(上)", "unit": "S0-U0-L1", "difficulty": 1, "enabled": true}, {"word": "bag", "pos": "n", "meaning": "袋,包", "level": "LV1(上)", "unit": "S0-U0-L1", "difficulty": 1, "enabled": true}, {"word": "orange", "pos": "adj", "meaning": "橙色的", "level": "LV1(上)", "unit": "S0-U0-L3", "difficulty": 1, "enabled": true}, {"word": "green", "pos": "adj", "meaning": "绿色的", "level": "LV1(上)", "unit": "S0-U0-L2", "difficulty": 1, "enabled": true}, {"word": "dress", "pos": "n", "meaning": "连衣裙", "level": "LV1(上)", "unit": "S0-U0-L2", "difficulty": 1, "enabled": true}, {"word": "there", "pos": "adv", "meaning": "那里", "level": "LV1(上)", "unit": "S0-U0-L3", "difficulty": 1, "enabled": true}, {"word": "put", "pos": "v", "meaning": "放", "level": "LV1(上)", "unit": "S0-U0-L3", "difficulty": 1, "enabled": true}, {"word": "purple", "pos": "adj", "meaning": "紫色的", "level": "LV1(上)", "unit": "S0-U0-L5", "difficulty": 1, "enabled": true}, {"word": "give", "pos": "v", "meaning": "给", "level": "LV1(上)", "unit": "S0-U0-L3", "difficulty": 1, "enabled": true}, {"word": "water", "pos": "n", "meaning": "水", "level": "LV1(上)", "unit": "S0-U0-L4", "difficulty": 1, "enabled": true}, {"word": "here", "pos": "adv", "meaning": "在这里", "level": "LV1(上)", "unit": "S0-U0-L4", "difficulty": 1, "enabled": true}, {"word": "dirty", "pos": "adj", "meaning": "肮脏的", "level": "LV1(上)", "unit": "S0-U0-L4", "difficulty": 1, "enabled": true}, {"word": "clean", "pos": "v", "meaning": "清洁", "level": "LV1(上)", "unit": "S0-U0-L4", "difficulty": 1, "enabled": true}, {"word": "pink", "pos": "adj", "meaning": "粉红色的", "level": "LV1(上)", "unit": "S0-U0-L2", "difficulty": 1, "enabled": true}, {"word": "get", "pos": "v", "meaning": "收到", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "wall", "pos": "n", "meaning": "墙", "level": "LV1(上)", "unit": "S1-U1-L1", "difficulty": 1, "enabled": true}, {"word": "table", "pos": "n", "meaning": "桌子", "level": "LV1(上)", "unit": "S1-U1-L1", "difficulty": 1, "enabled": true}, {"word": "floor", "pos": "n", "meaning": "地板", "level": "LV1(上)", "unit": "S1-U1-L1", "difficulty": 1, "enabled": true}, {"word": "hand", "pos": "n", "meaning": "手", "level": "LV1(上)", "unit": "S1-U1-L2", "difficulty": 1, "enabled": true}, {"word": "foot", "pos": "n", "meaning": "脚", "level": "LV1(上)", "unit": "S1-U1-L2", "difficulty": 1, "enabled": true}, {"word": "black", "pos": "adj", "meaning": "黑的", "level": "LV1(上)", "unit": "S1-U1-L2", "difficulty": 1, "enabled": true}, {"word": "tomato", "pos": "n", "meaning": "西红柿", "level": "LV1(上)", "unit": "S1-U1-L3", "difficulty": 1, "enabled": true}, {"word": "fish", "pos": "n", "meaning": "鱼", "level": "LV1(上)", "unit": "S1-U1-L3", "difficulty": 1, "enabled": true}, {"word": "chicken", "pos": "n", "meaning": "鸡肉", "level": "LV1(上)", "unit": "S1-U1-L3", "difficulty": 1, "enabled": true}, {"word": "nose", "pos": "n", "meaning": "鼻子", "level": "LV1(上)", "unit": "S1-U1-L4", "difficulty": 1, "enabled": true}, {"word": "eye", "pos": "n", "meaning": "眼睛", "level": "LV1(上)", "unit": "S1-U1-L4", "difficulty": 1, "enabled": true}, {"word": "brown", "pos": "adj", "meaning": "棕色的", "level": "LV1(上)", "unit": "S1-U1-L4", "difficulty": 1, "enabled": true}, {"word": "yellow", "pos": "adj", "meaning": "黄色的", "level": "LV1(上)", "unit": "S1-U1-L5", "difficulty": 1, "enabled": true}, {"word": "white", "pos": "adj", "meaning": "白色的", "level": "LV1(上)", "unit": "S1-U1-L5", "difficulty": 1, "enabled": true}, {"word": "go", "pos": "v", "meaning": "去,走", "level": "LV1(上)", "unit": "S1-U2-L1", "difficulty": 1, "enabled": true}, {"word": "dog", "pos": "n", "meaning": "狗", "level": "LV1(上)", "unit": "S1-U2-L1", "difficulty": 1, "enabled": true}, {"word": "come", "pos": "v", "meaning": "来", "level": "LV1(上)", "unit": "S1-U2-L1", "difficulty": 1, "enabled": true}, {"word": "cat", "pos": "n", "meaning": "猫", "level": "LV1(上)", "unit": "S1-U2-L1", "difficulty": 1, "enabled": true}, {"word": "pie", "pos": "n", "meaning": "馅饼", "level": "LV1(上)", "unit": "S1-U2-L2", "difficulty": 1, "enabled": true}, {"word": "can", "pos": "v", "meaning": "能,会", "level": "LV1(上)", "unit": "S1-U2-L4", "difficulty": 1, "enabled": true}, {"word": "open", "pos": "v", "meaning": "打开", "level": "LV1(上)", "unit": "S1-U2-L5", "difficulty": 1, "enabled": true}, {"word": "happy", "pos": "adj", "meaning": "快乐的", "level": "LV1(上)", "unit": "S1-U2-L5", "difficulty": 1, "enabled": true}, {"word": "see", "pos": "v", "meaning": "看见", "level": "LV1(上)", "unit": "S1-U3-L1", "difficulty": 1, "enabled": true}, {"word": "1", "pos": "num", "meaning": "1", "level": "LV1(上)", "unit": "S1-U3-L1", "difficulty": 1, "enabled": true}, {"word": "paper", "pos": "n", "meaning": "纸", "level": "LV1(上)", "unit": "S1-U3-L2", "difficulty": 1, "enabled": true}, {"word": "book", "pos": "n", "meaning": "书", "level": "LV1(上)", "unit": "S1-U3-L2", "difficulty": 1, "enabled": true}, {"word": "3", "pos": "num", "meaning": "3", "level": "LV1(上)", "unit": "S1-U3-L2", "difficulty": 1, "enabled": true}, {"word": "2", "pos": "num", "meaning": "2", "level": "LV1(上)", "unit": "S1-U3-L2", "difficulty": 1, "enabled": true}, {"word": "pen", "pos": "n", "meaning": "钢笔", "level": "LV1(上)", "unit": "S1-U3-L3", "difficulty": 1, "enabled": true}, {"word": "box", "pos": "n", "meaning": "盒子", "level": "LV1(上)", "unit": "S1-U3-L3", "difficulty": 1, "enabled": true}, {"word": "4", "pos": "num", "meaning": "4", "level": "LV1(上)", "unit": "S1-U3-L3", "difficulty": 1, "enabled": true}, {"word": "6", "pos": "num", "meaning": "6", "level": "LV1(上)", "unit": "S1-U3-L4", "difficulty": 1, "enabled": true}, {"word": "5", "pos": "num", "meaning": "5", "level": "LV1(上)", "unit": "S1-U3-L4", "difficulty": 1, "enabled": true}, {"word": "sing", "pos": "v", "meaning": "唱", "level": "LV1(上)", "unit": "S1-U4-L1", "difficulty": 1, "enabled": true}, {"word": "eat", "pos": "v", "meaning": "吃", "level": "LV1(上)", "unit": "S1-U4-L2", "difficulty": 1, "enabled": true}, {"word": "7", "pos": "num", "meaning": "7", "level": "LV1(上)", "unit": "S1-U4-L2", "difficulty": 1, "enabled": true}, {"word": "rug", "pos": "n", "meaning": "小地毯", "level": "LV1(上)", "unit": "S1-U4-L3", "difficulty": 1, "enabled": true}, {"word": "door", "pos": "n", "meaning": " 门", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "bed", "pos": "n", "meaning": "床", "level": "LV1(上)", "unit": "S1-U4-L3", "difficulty": 1, "enabled": true}, {"word": "9", "pos": "num", "meaning": "9", "level": "LV1(上)", "unit": "S1-U4-L4", "difficulty": 1, "enabled": true}, {"word": "8", "pos": "num", "meaning": "8", "level": "LV1(上)", "unit": "S1-U4-L4", "difficulty": 1, "enabled": true}, {"word": "10", "pos": "num", "meaning": "10", "level": "LV1(上)", "unit": "S1-U4-L5", "difficulty": 1, "enabled": true}, {"word": "zoo", "pos": "n", "meaning": "动物园", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "zebra", "pos": "n", "meaning": "斑马", "level": "LV1(上)", "unit": "S1-U7-L2", "difficulty": 1, "enabled": true}, {"word": "young", "pos": "adj", "meaning": "年轻的", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "year", "pos": "n", "meaning": "年", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "write", "pos": "v", "meaning": "写", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "woman", "pos": "n", "meaning": "女人", "level": "LV1(上)", "unit": "S1-U5-L1", "difficulty": 1, "enabled": true}, {"word": "window", "pos": "n", "meaning": "窗户", "level": "LV1(上)", "unit": "S1-U12-L4", "difficulty": 1, "enabled": true}, {"word": "warm", "pos": "adj", "meaning": "温暖的", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "want", "pos": "v", "meaning": "想要", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "walk", "pos": "v", "meaning": "走", "level": "LV1(上)", "unit": "S1-U7-L2", "difficulty": 1, "enabled": true}, {"word": "very", "pos": "adv", "meaning": "非常", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "under", "pos": "prep", "meaning": "在...下面", "level": "LV1(上)", "unit": "S2-U14-L5", "difficulty": 1, "enabled": true}, {"word": "try", "pos": "n", "meaning": "尝试", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "try", "pos": "v", "meaning": "尝试", "level": "LV1(上)", "unit": "S1-U12-L5", "difficulty": 1, "enabled": true}, {"word": "tree", "pos": "n", "meaning": "树", "level": "LV1(上)", "unit": "S1-U7-L1", "difficulty": 1, "enabled": true}, {"word": "train", "pos": "n", "meaning": "火车", "level": "LV1(上)", "unit": "S1-U8-L1", "difficulty": 1, "enabled": true}, {"word": "too", "pos": "adv", "meaning": "也", "level": "LV1(上)", "unit": "S1-U12-L2", "difficulty": 1, "enabled": true}, {"word": "tomorrow", "pos": "adv", "meaning": "明天", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "tomorrow", "pos": "n", "meaning": "明天", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "today", "pos": "adv", "meaning": "今天", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "today", "pos": "n", "meaning": "今天", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "tiger", "pos": "n", "meaning": "老虎", "level": "LV1(上)", "unit": "S1-U5-L3", "difficulty": 1, "enabled": true}, {"word": "those", "pos": "det", "meaning": "那些", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "those", "pos": "pron", "meaning": "那些", "level": "LV1(上)", "unit": "S1-U12-L1", "difficulty": 1, "enabled": true}, {"word": "this", "pos": "det", "meaning": "这个", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "this", "pos": "pron", "meaning": "这个", "level": "LV1(上)", "unit": "S1-U12-L1", "difficulty": 1, "enabled": true}, {"word": "these", "pos": "det", "meaning": "这些", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "these", "pos": "pron", "meaning": "这些", "level": "LV1(上)", "unit": "S1-U12-L1", "difficulty": 1, "enabled": true}, {"word": "that", "pos": "det", "meaning": "那个", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "that", "pos": "pron", "meaning": "那个", "level": "LV1(上)", "unit": "S1-U12-L1", "difficulty": 1, "enabled": true}, {"word": "tell", "pos": "v", "meaning": "告诉", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "teacher", "pos": "n", "meaning": "老师", "level": "LV1(上)", "unit": "S2-U14-L2", "difficulty": 1, "enabled": true}, {"word": "talk", "pos": "v", "meaning": "说话", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "swim", "pos": "v", "meaning": "游泳", "level": "LV1(上)", "unit": "S1-U11-L5", "difficulty": 1, "enabled": true}, {"word": "sun", "pos": "n", "meaning": "太阳", "level": "LV1(上)", "unit": "S1-U8-L2", "difficulty": 1, "enabled": true}, {"word": "study", "pos": "v", "meaning": "学习", "level": "LV1(上)", "unit": "S2-U14-L1", "difficulty": 1, "enabled": true}, {"word": "student", "pos": "n", "meaning": "学生", "level": "LV1(上)", "unit": "S2-U14-L1", "difficulty": 1, "enabled": true}, {"word": "speak", "pos": "v", "meaning": "说", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "some", "pos": "det", "meaning": "一些", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "sofa", "pos": "n", "meaning": "沙发", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "sock", "pos": "n", "meaning": "袜子", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "small", "pos": "adj", "meaning": "小的", "level": "LV1(上)", "unit": "S1-U9-L1", "difficulty": 1, "enabled": true}, {"word": "sleep", "pos": "v", "meaning": "睡觉", "level": "LV1(上)", "unit": "S1-U7-L4", "difficulty": 1, "enabled": true}, {"word": "skirt", "pos": "n", "meaning": "裙子", "level": "LV1(上)", "unit": "S1-U9-L4", "difficulty": 1, "enabled": true}, {"word": "sit", "pos": "v", "meaning": "坐下", "level": "LV1(上)", "unit": "S1-U8-L1", "difficulty": 1, "enabled": true}, {"word": "sister", "pos": "n", "meaning": "姐姐、妹妹", "level": "LV1(上)", "unit": "S1-U8-L1", "difficulty": 1, "enabled": true}, {"word": "short", "pos": "adj", "meaning": "短的", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "shoe", "pos": "n", "meaning": "鞋", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "shirt", "pos": "n", "meaning": "衬衫", "level": "LV1(上)", "unit": "S1-U9-L1", "difficulty": 1, "enabled": true}, {"word": "ship", "pos": "n", "meaning": "船", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "sheep", "pos": "n", "meaning": "绵羊", "level": "LV1(上)", "unit": "S1-U5-L3", "difficulty": 1, "enabled": true}, {"word": "school", "pos": "n", "meaning": "学校", "level": "LV1(上)", "unit": "S2-U14-L1", "difficulty": 1, "enabled": true}, {"word": "say", "pos": "v", "meaning": "说", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "sad", "pos": "adj", "meaning": "悲伤的", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "run", "pos": "v", "meaning": "跑", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "ruler", "pos": "n", "meaning": "尺子", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "room", "pos": "n", "meaning": "房间", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "rice", "pos": "n", "meaning": "米饭", "level": "LV1(上)", "unit": "S1-U6-L4", "difficulty": 1, "enabled": true}, {"word": "read", "pos": "v", "meaning": "阅读", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "radio", "pos": "n", "meaning": "收音机", "level": "LV1(上)", "unit": "S1-U9-L2", "difficulty": 1, "enabled": true}, {"word": "potato", "pos": "n", "meaning": "土豆", "level": "LV1(上)", "unit": "S1-U7-L3", "difficulty": 1, "enabled": true}, {"word": "play", "pos": "v", "meaning": "玩", "level": "LV1(上)", "unit": "S2-U14-L1", "difficulty": 1, "enabled": true}, {"word": "plane", "pos": "n", "meaning": "飞机", "level": "LV1(上)", "unit": "S1-U8-L3", "difficulty": 1, "enabled": true}, {"word": "pig", "pos": "n", "meaning": "猪", "level": "LV1(上)", "unit": "S1-U7-L4", "difficulty": 1, "enabled": true}, {"word": "phone", "pos": "n", "meaning": "电话", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "phone", "pos": "v", "meaning": "给…...打电话", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "people", "pos": "n", "meaning": "人", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "pencil", "pos": "n", "meaning": "铅笔", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "pea", "pos": "n", "meaning": "豌豆", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "PE", "pos": "n", "meaning": "体育", "level": "LV1(上)", "unit": "S1-U10-L4", "difficulty": 1, "enabled": true}, {"word": "paper", "pos": "adj", "meaning": "纸质的", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "paint", "pos": "n", "meaning": "绘画颜料", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "paint", "pos": "v", "meaning": "(用颜料)绘画", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "orange", "pos": "n", "meaning": "橙子", "level": "LV1(上)", "unit": "S1-U10-L3", "difficulty": 1, "enabled": true}, {"word": "open", "pos": "adj", "meaning": "开着的", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "one", "pos": "det", "meaning": "一个", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "one", "pos": "pron", "meaning": "一个人(或物)", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "old", "pos": "adj", "meaning": "年老的", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "now", "pos": "adv", "meaning": "现在", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "night", "pos": "n", "meaning": "夜晚", "level": "LV1(上)", "unit": "S1-U12-L5", "difficulty": 1, "enabled": true}, {"word": "nice", "pos": "adj", "meaning": "好的", "level": "LV1(上)", "unit": "S1-U6-L3", "difficulty": 1, "enabled": true}, {"word": "new", "pos": "adj", "meaning": "新的", "level": "LV1(上)", "unit": "S1-U9-L2", "difficulty": 1, "enabled": true}, {"word": "mum", "pos": "n", "meaning": "妈妈", "level": "LV1(上)", "unit": "S1-U8-L4", "difficulty": 1, "enabled": true}, {"word": "mouth", "pos": "n", "meaning": "嘴巴", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "mother", "pos": "n", "meaning": "母亲", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "morning", "pos": "n", "meaning": "早上", "level": "LV1(上)", "unit": "S1-U12-L5", "difficulty": 1, "enabled": true}, {"word": "month", "pos": "n", "meaning": "月", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "monkey", "pos": "n", "meaning": "猴子", "level": "LV1(上)", "unit": "S1-U7-L1", "difficulty": 1, "enabled": true}, {"word": "minute", "pos": "n", "meaning": "分钟", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "mine", "pos": "pron", "meaning": "我的", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "milk", "pos": "n", "meaning": "牛奶", "level": "LV1(上)", "unit": "S1-U5-L2", "difficulty": 1, "enabled": true}, {"word": "men", "pos": "n", "meaning": "男人(man 的复数)", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "meat", "pos": "n", "meaning": "肉类", "level": "LV1(上)", "unit": "S1-U10-L3", "difficulty": 1, "enabled": true}, {"word": "me", "pos": "pron", "meaning": "我", "level": "LV1(上)", "unit": "S1-U12-L2", "difficulty": 1, "enabled": true}, {"word": "mat", "pos": "n", "meaning": "地垫", "level": "LV1(上)", "unit": "S2-U14-L4", "difficulty": 1, "enabled": true}, {"word": "many", "pos": "det", "meaning": "许多", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "man", "pos": "n", "meaning": "男人", "level": "LV1(上)", "unit": "S1-U5-L1", "difficulty": 1, "enabled": true}, {"word": "make", "pos": "v", "meaning": "制造", "level": "LV1(上)", "unit": "S1-U6-L3", "difficulty": 1, "enabled": true}, {"word": "love", "pos": "v", "meaning": "爱", "level": "LV1(上)", "unit": "S1-U12-L2", "difficulty": 1, "enabled": true}, {"word": "lots of", "pos": "det", "meaning": "大量的", "level": "LV1(上)", "unit": "", "difficulty": 1, "enabled": true}, {"word": "long", "pos": "adj", "meaning": "长的", "level": "LV1(上)", "unit": "S1-U7-L5 ", "difficulty": 1, "enabled": true}]}
\ No newline at end of file
diff --git a/business_production/剧本翻译/skills/kids-english-script-production/scripts/gen_script.py b/business_production/剧本翻译/skills/kids-english-script-production/scripts/gen_script.py
new file mode 100644
index 0000000..23ae433
--- /dev/null
+++ b/business_production/剧本翻译/skills/kids-english-script-production/scripts/gen_script.py
@@ -0,0 +1,1002 @@
+#!/usr/bin/env python3
+import argparse
+import sys
+import os
+import re
+import json
+import yaml
+import time
+from openai import OpenAI
+from pathlib import Path
+
+# 加载配置
+BASE_DIR = Path(__file__).parent.parent
+ASSETS_DIR = BASE_DIR / "assets"
+
+# 加载外部配置文件(所有可调优参数全部在assets目录下的yaml文件,无需改代码)
+try:
+ # 基础配置
+ with open(ASSETS_DIR / "sci_fi_map.yaml", "r", encoding="utf-8") as f:
+ SCI_FI_WORD_MAP = yaml.safe_load(f)
+ with open(ASSETS_DIR / "stage_config.yaml", "r", encoding="utf-8") as f:
+ STAGE_CONFIG = yaml.safe_load(f)
+ # 调优配置
+ with open(ASSETS_DIR / "expression_map.yaml", "r", encoding="utf-8") as f:
+ EXPRESSION_MAP = yaml.safe_load(f)
+ with open(ASSETS_DIR / "prompt_config.yaml", "r", encoding="utf-8") as f:
+ PROMPT_CONFIG = yaml.safe_load(f)
+ with open(ASSETS_DIR / "validation_config.yaml", "r", encoding="utf-8") as f:
+ VALIDATION_CONFIG = yaml.safe_load(f)
+ # 词表配置
+ with open(BASE_DIR / "references" / "l1_word_list.json", "r", encoding="utf-8") as f:
+ word_data = json.load(f)
+ L1_WORD_LIST = set([w['word'].lower() for w in word_data.get('words', []) if w.get('enabled', True)])
+except Exception as e:
+ print(f"❌ 配置文件加载失败,请检查yaml格式是否正确: {str(e)}")
+ sys.exit(1)
+
+# 初始化LLM客户端,配置从环境变量读取
+try:
+ client = OpenAI(
+ api_key=os.getenv("OPENAI_API_KEY", "your-api-key"),
+ base_url=os.getenv("OPENAI_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3")
+ )
+ MODEL = os.getenv("OPENAI_MODEL", "volcengine/doubao-seed-2-0-pro-260215")
+except Exception as e:
+ print(f"❌ LLM客户端初始化失败: {str(e)}")
+ sys.exit(1)
+
+def load_input(input_path):
+ """加载输入内容,支持单个文件或目录批量加载"""
+ input_path = Path(input_path)
+ if not input_path.exists():
+ print(f"❌ 输入路径不存在: {input_path}")
+ sys.exit(1)
+
+ if input_path.is_file():
+ with open(input_path, "r", encoding="utf-8") as f:
+ return [(input_path.name, f.read())]
+ elif input_path.is_dir():
+ # 批量加载目录下所有txt文件
+ script_files = list(input_path.glob("*.txt"))
+ if not script_files:
+ print(f"❌ 目录下没有找到txt格式的剧本文件: {input_path}")
+ sys.exit(1)
+ results = []
+ for f in script_files:
+ with open(f, "r", encoding="utf-8") as fp:
+ results.append((f.name, fp.read()))
+ return results
+ else:
+ print(f"❌ 不支持的输入类型: {input_path}")
+ sys.exit(1)
+
+# ============================================================
+# Pipeline 节点日志工具
+# ============================================================
+class PipelineLogger:
+ """流程节点日志记录器,跟踪每个节点的输入/输出/耗时/状态"""
+ def __init__(self, verbose=True):
+ self.verbose = verbose
+ self.nodes = [] # 全部节点记录
+ self._current = None
+
+ def start_node(self, name, description=""):
+ self._current = {
+ 'name': name,
+ 'description': description,
+ 'start_time': time.time(),
+ 'input_summary': '',
+ 'output_summary': '',
+ 'output_full': '',
+ 'status': 'running',
+ 'warnings': [],
+ 'duration': 0,
+ }
+ if self.verbose:
+ print(f"\n{'='*60}")
+ print(f"▶ 节点 {len(self.nodes)+1}: {name}")
+ if description:
+ print(f" {description}")
+ print(f"{'='*60}")
+
+ def set_input(self, summary):
+ if self._current:
+ self._current['input_summary'] = summary
+ if self.verbose:
+ print(f"📥 输入: {summary[:200]}{'...' if len(summary)>200 else ''}")
+
+ def set_output(self, summary, full_content=""):
+ if self._current:
+ self._current['output_summary'] = summary
+ self._current['output_full'] = full_content or summary
+ if self.verbose:
+ print(f"📤 输出: {summary[:300]}{'...' if len(summary)>300 else ''}")
+
+ def add_warning(self, msg):
+ if self._current:
+ self._current['warnings'].append(msg)
+ if self.verbose:
+ print(f"⚠️ {msg}")
+
+ def end_node(self, status='pass'):
+ if self._current:
+ self._current['duration'] = time.time() - self._current['start_time']
+ self._current['status'] = status
+ icon = {'pass': '✅', 'warn': '⚠️', 'fail': '❌', 'skip': '⏭️'}.get(status, '❓')
+ if self.verbose:
+ print(f"{icon} 状态: {status.upper()} | 耗时: {self._current['duration']:.1f}s")
+ if self._current['warnings']:
+ print(f" 共 {len(self._current['warnings'])} 条警告")
+ self.nodes.append(self._current)
+ self._current = None
+
+ def get_trace_report(self):
+ """生成完整流程追踪报告"""
+ lines = []
+ lines.append("\n" + "="*60)
+ lines.append("📋 全流程追踪报告")
+ lines.append("="*60)
+ total_time = sum(n['duration'] for n in self.nodes)
+ for i, n in enumerate(self.nodes, 1):
+ icon = {'pass': '✅', 'warn': '⚠️', 'fail': '❌', 'skip': '⏭️'}.get(n['status'], '❓')
+ lines.append(f"\n{icon} 节点{i}: {n['name']} [{n['status'].upper()}] ({n['duration']:.1f}s)")
+ if n['input_summary']:
+ lines.append(f" 输入: {n['input_summary'][:80]}{'...' if len(n['input_summary'])>80 else ''}")
+ if n['output_summary']:
+ lines.append(f" 输出: {n['output_summary'][:80]}{'...' if len(n['output_summary'])>80 else ''}")
+ for w in n['warnings']:
+ lines.append(f" ⚠️ {w}")
+ lines.append(f"\n⏱️ 总耗时: {total_time:.1f}s | 节点数: {len(self.nodes)}")
+ pass_count = sum(1 for n in self.nodes if n['status'] == 'pass')
+ warn_count = sum(1 for n in self.nodes if n['status'] == 'warn')
+ fail_count = sum(1 for n in self.nodes if n['status'] == 'fail')
+ lines.append(f"📊 结果: {pass_count}通过 / {warn_count}警告 / {fail_count}失败")
+ lines.append("="*60)
+ return "\n".join(lines)
+
+ def get_intermediate_outputs(self):
+ """获取所有中间产物,用于保存"""
+ outputs = {}
+ for n in self.nodes:
+ outputs[n['name']] = {
+ 'status': n['status'],
+ 'duration': n['duration'],
+ 'content': n['output_full'],
+ 'warnings': n['warnings'],
+ }
+ return outputs
+
+
+# ============================================================
+# LLM 调用封装(带重试)
+# ============================================================
+SYSTEM_MSG = (
+ "你是专为4-8岁儿童打造的英文台词生产专家。"
+ "你必须严格遵守所有生产规则,100%忠于原剧本内容,"
+ "禁止新增或删除原剧本没有的信息。"
+ "输出必须完全匹配目标Stage的词汇、句法、句长要求。"
+)
+
+def call_llm(prompt, stage, max_retries=3):
+ """通用LLM调用,带重试和错误处理"""
+ temp = PROMPT_CONFIG['temperature'].get(stage, 0.3)
+ for attempt in range(1, max_retries + 1):
+ try:
+ response = client.chat.completions.create(
+ model=MODEL,
+ messages=[
+ {"role": "system", "content": SYSTEM_MSG},
+ {"role": "user", "content": prompt}
+ ],
+ temperature=temp,
+ max_tokens=2000,
+ timeout=60
+ )
+ return response.choices[0].message.content, None
+ except Exception as e:
+ if attempt < max_retries:
+ wait = 2 ** attempt
+ print(f" ⚠️ 第{attempt}次调用失败,{wait}秒后重试: {str(e)}")
+ time.sleep(wait)
+ else:
+ return None, f"LLM调用失败(已重试{max_retries}次): {str(e)}"
+
+
+# ============================================================
+# Node 1: 输入解析(纯代码)
+# ============================================================
+def node_input_parse(raw_text, logger):
+ """解析输入文本,检测类型/角色/行数"""
+ logger.start_node("输入解析", "检测输入类型、角色、行数,纯代码节点")
+ logger.set_input(raw_text[:100])
+
+ lines = [l.strip() for l in raw_text.strip().split('\n') if l.strip()]
+ # 检测输入语言类型
+ cn_chars = len(re.findall(r'[\u4e00-\u9fff]', raw_text))
+ en_chars = len(re.findall(r'[a-zA-Z]', raw_text))
+ if cn_chars > 0 and en_chars > 0:
+ lang_type = "中英混合"
+ elif cn_chars > 0:
+ lang_type = "纯中文"
+ elif en_chars > 0:
+ lang_type = "纯英文"
+ else:
+ lang_type = "未知"
+
+ # 提取角色名
+ roles = set()
+ for line in lines:
+ for sep in [':', ':']:
+ if sep in line:
+ role = line.split(sep, 1)[0].strip()
+ if role and len(role) < 20:
+ roles.add(role)
+ break
+
+ result = {
+ 'raw_text': raw_text,
+ 'line_count': len(lines),
+ 'lang_type': lang_type,
+ 'cn_chars': cn_chars,
+ 'en_chars': en_chars,
+ 'roles': list(roles),
+ 'lines': lines,
+ }
+ summary = f"{lang_type} | {len(lines)}行 | 角色: {', '.join(roles) if roles else '未检测到'} | 中文{cn_chars}字/英文{en_chars}字"
+ logger.set_output(summary)
+ logger.end_node('pass')
+ return result
+
+
+# ============================================================
+# Node 2: 输入归一(LLM #1,按语言类型分流)
+# ============================================================
+def node_normalize(parsed_input, logger):
+ """根据输入语言类型分流处理:
+ - 纯中文:直接保留
+ - 中英混合:归一为标准中文
+ - 纯英文:只做格式标准化,保留英文(避免英→中→英的语义折损)
+ """
+ raw_text = parsed_input['raw_text']
+ lang_type = parsed_input['lang_type']
+
+ # ---- 纯中文:已是标准格式,直接返回 ----
+ if lang_type == '纯中文' and parsed_input['roles']:
+ logger.start_node("输入归一", "纯中文输入,已是标准格式,跳过")
+ logger.set_input(f"纯中文 | {parsed_input['line_count']}行")
+ logger.set_output(raw_text, raw_text)
+ logger.end_node('pass')
+ return raw_text
+
+ # ---- 纯英文:只做格式标准化,不转中文 ----
+ if lang_type == '纯英文':
+ logger.start_node("输入归一", "纯英文输入 → 格式标准化(保留英文,不转中文)")
+ logger.set_input(f"纯英文 | {parsed_input['line_count']}行")
+
+ if parsed_input['roles']:
+ # 已有角色格式,直接保留
+ logger.set_output(raw_text, raw_text)
+ logger.end_node('pass')
+ return raw_text
+
+ # 没有角色格式,用LLM标准化格式(保持英文)
+ prompt = f"""将以下英文输入标准化为「Role: dialogue」格式。
+要求:
+1. 保持英文原文,不要翻译成中文
+2. 完整保留所有剧情、动作、角色关系、道具信息
+3. 每行格式:RoleName: dialogue content
+4. 只输出标准化结果,不要其他说明
+
+输入:
+{raw_text}"""
+ result, error = call_llm(prompt, 'S2')
+ if error:
+ logger.add_warning(error)
+ logger.end_node('fail')
+ return None
+ logger.set_output(result, result)
+ logger.end_node('pass')
+ return result
+
+ # ---- 中英混合:归一为标准中文 ----
+ logger.start_node("输入归一", "LLM调用 → 中英混合输入统一转成标准中文")
+ logger.set_input(f"中英混合 | {parsed_input['line_count']}行")
+
+ prompt = f"""将以下中英混合输入统一转成标准中文「角色: 台词」格式。
+要求:
+1. 完整保留所有剧情、动作、角色关系、道具、事件触发点信息
+2. 不能丢失任何核心内容,不能新增内容
+3. 每行格式:角色名: 台词内容
+4. 保留原始英文角色名(如Ben、Amy等),不要翻译角色名
+5. 只输出转换结果,不要其他说明
+
+输入:
+{raw_text}"""
+
+ result, error = call_llm(prompt, 'S2')
+ if error:
+ logger.add_warning(error)
+ logger.end_node('fail')
+ return None
+
+ logger.set_output(result, result)
+
+ # 简单校验:行数不应该差太多
+ result_lines = [l for l in result.strip().split('\n') if l.strip()]
+ if len(result_lines) < len(parsed_input['lines']) * 0.5:
+ logger.add_warning(f"归一后行数({len(result_lines)})远少于原始行数({parsed_input['line_count']}),可能丢失内容")
+ logger.end_node('warn')
+ else:
+ logger.end_node('pass')
+ return result
+
+
+# ============================================================
+# Node 3: AR预处理(LLM #2,按语言类型分流)
+# ============================================================
+def node_ar_preprocess(normalized_text, stage, lang_type, logger):
+ """拆句 + 打AR等级,根据语言类型走不同路径"""
+ is_english = (lang_type == '纯英文')
+ desc = "LLM调用 → 英文句子难度标注+拆分" if is_english else "LLM调用 → 中文意群拆解(为英语思维重构做准备)"
+ logger.start_node("AR预处理", desc)
+ logger.set_input(f"{len(normalized_text)}字 | 语言={'EN' if is_english else 'CN'}")
+
+ fidelity_rule = "100%忠于原剧本内容:禁止新增任何原剧本没有的信息、禁止删除任何原剧本已有的信息" if PROMPT_CONFIG['script_fidelity']['strictly_no_add'] and PROMPT_CONFIG['script_fidelity']['strictly_no_delete'] else "允许适当调整细节"
+
+ if is_english:
+ # 纯英文路径:直接在英文上标注难度和拆分
+ prompt = f"""你是英文台词AR预处理专家。将下面的英文台词拆分为简单短句并标注AR等级。
+
+### 核心规则
+{fidelity_rule}
+保持英文原文,不要翻译成中文。
+
+### 拆分规则
+1. 复合句拆成简单句
+2. 多步动作拆成单动作句
+3. 因果/条件句拆分,保留全部信息
+4. 情绪与事实拆分
+5. 保留完整事件链、角色关系、道具信息
+6. 上下文连贯:前文已提到的事物,后文用代词(them/it/these)指代,不重复同一名词
+
+### 输出格式
+每行格式:Role: dialogue [AR等级]
+AR1=最简单(4-7词) AR2=简单(7-10词) AR3=中等(10-15词) AR4=复杂(15+词)
+
+### 待处理台词
+{normalized_text}"""
+ else:
+ # 中文路径:意群拆解(不是简单拆句,而是为英语思维重构做准备)
+ prompt = f"""你是儿童台词意群拆解专家。你的任务不是简单拆句,而是将中文台词拆解为**最小语义意群**,为后续用英语思维重构做准备。
+
+### 核心理念
+目标不是翻译,而是让每个意群都能被英语母语者用最自然的方式独立表达。
+拆解时要想:"一个4-8岁英语母语孩子会怎么一句句说出这些意思?"
+
+### 忠实度(最高优先级)
+{fidelity_rule}
+
+### 4个保留机制(绝对不能改)
+1. 保留完整事件动词链
+2. 保留所有事件触发点
+3. 保留完整道具逻辑链
+4. 保留原有角色关系
+
+### 意群拆解规则
+1. 一个意群 = 一个完整的最小语义单元(一个动作/一个状态/一个情绪)
+2. 因果关系拆成独立事实意群,去掉"因为/所以"等中文逻辑连接词(英语孩子不这样说话)
+3. 多步动作拆成单动作意群
+4. 条件+结果拆成独立意群,去掉"如果/假设"等假设框架
+5. 情绪和事实分开成独立意群("我好开心,终于找到了" → "我好开心!" + "终于找到了!")
+6. 去掉中文特有的修饰堆叠,只留核心可感知信息
+7. 保留语气和情绪强度,用标点标注(!表强烈 ?表疑问)
+
+### 上下文连贯性(重要!)
+台词是连续的对话,拆解时必须考虑上下文语境:
+- 前文已经明确提到的事物,后文可以用代词/简称指代,不需要重复完整名词
+- 例如:"收集了很多衣物准备义卖" → 下一句"小朋友一定会喜欢这些东西的" → 意群保留"这些"而不是重复"衣物"
+- 英语孩子说话时自然会用 them/these/it/this stuff 等代词,而不会反复重复同一个名词
+
+### 输出格式
+每行格式:角色: 意群内容 [AR等级]
+AR等级说明:AR1=最简单(对应4-7英文词) AR2=简单(7-10词) AR3=中等(10-15词) AR4=复杂(15+词)
+
+### 待拆解台词
+{normalized_text}"""
+
+ result, error = call_llm(prompt, stage)
+ if error:
+ logger.add_warning(error)
+ logger.end_node('fail')
+ return None
+
+ logger.set_output(result, result)
+
+ # 校验AR等级分布
+ ar_counts = {f'AR{i}': len(re.findall(f'\\[AR{i}\\]', result)) for i in range(1, 5)}
+ total_ar = sum(ar_counts.values())
+ if total_ar > 0:
+ ar_summary = " | ".join([f"{k}:{v}({v/total_ar:.0%})" for k, v in ar_counts.items() if v > 0])
+ logger.set_output(f"{ar_summary}\n{result}", result)
+
+ if stage == 'S1' and (ar_counts.get('AR3', 0) + ar_counts.get('AR4', 0)) > 0:
+ ratio = (ar_counts['AR3'] + ar_counts['AR4']) / total_ar
+ logger.add_warning(f"S1阶段存在AR3/AR4句子({ratio:.0%}),生成时需降级")
+ if stage == 'S2' and ar_counts.get('AR4', 0) > 0:
+ logger.add_warning(f"S2阶段存在AR4句子({ar_counts['AR4']}条),生成时需降级")
+
+ status = 'warn' if (logger._current and logger._current['warnings']) else 'pass'
+ logger.end_node(status)
+ return result
+
+
+# ============================================================
+# Node 4: 分级英文生成 / 英文难度适配(LLM #3,按语言类型分流)
+# ============================================================
+def _build_naturalization_rules():
+ """构建自然化规则文本(中英文路径共用)"""
+ sci_fi_map_str = "\n".join([f"{k} → {v}" for k, v in SCI_FI_WORD_MAP.items()])
+ emotion_map_rule = "优先使用以下映射匹配情绪词:" + "、".join([f"{k}→{v}" for k,v in EXPRESSION_MAP['emotion_map'].items()]) if PROMPT_CONFIG['naturalization']['enable_emotion_word'] else "不使用自定义情绪词映射"
+ synonym_replace_rule = "可使用以下同义口语替换(不改变原意):" + "、".join([f"{k}→{v}" for k,v in EXPRESSION_MAP['synonym_replace'].items()]) if PROMPT_CONFIG['naturalization']['enable_synonym_replace'] else "不使用同义替换"
+ split_rule = "包含2个及以上信息的句子拆成单信息短句" if PROMPT_CONFIG['naturalization']['enable_long_sentence_split'] else "不拆分长句"
+ repeat_rule = "允许自然重复(比如It is dirty. Very dirty.)" if PROMPT_CONFIG['naturalization']['allow_repeat_expression'] else "不允许重复表达"
+ exclamation_rule = "情绪强烈的句子可用感叹号" if PROMPT_CONFIG['naturalization']['enable_exclamation_mark'] else "统一使用句号"
+ return sci_fi_map_str, emotion_map_rule, synonym_replace_rule, split_rule, repeat_rule, exclamation_rule
+
+def node_generate_english(ar_processed_text, stage, lang_type, logger):
+ """按Stage要求生成/适配英文台词,纯英文走难度适配,中文走翻译"""
+ is_english = (lang_type == '纯英文')
+ task_desc = f"英文难度适配 → {stage}" if is_english else f"英语思维重构意群 → {stage}"
+ logger.start_node("分级英文生成", f"LLM调用 → {task_desc}({STAGE_CONFIG[stage]['age']})")
+ logger.set_input(f"Stage={stage} | 蓝思值={STAGE_CONFIG[stage]['lexile']} | 模式={'EN适配' if is_english else '意群重构'}")
+
+ sci_fi_map_str, emotion_map_rule, synonym_replace_rule, split_rule, repeat_rule, exclamation_rule = _build_naturalization_rules()
+
+ if is_english:
+ # 纯英文路径:直接在英文上做难度适配,不经过中文
+ prompt = f"""将下面的英文台词调整为{stage}级别的儿童英文。
+注意:这是英文难度适配,不是翻译!保持原文语义和语气,只调整词汇/句法/句长难度。
+
+### 目标Stage:{stage}
+年龄段:{STAGE_CONFIG[stage]['age']}
+要求:{STAGE_CONFIG[stage]['rules']}
+蓝思值:{STAGE_CONFIG[stage]['lexile']}
+
+### 适配规则
+1. 用更简单的同义词替换难词(保持原意)
+2. 拆分复合句为简单句
+3. 调整句长匹配目标Stage要求
+4. 同义替换:{synonym_replace_rule}
+5. 情绪词:{emotion_map_rule}
+6. 标点:{exclamation_rule}
+7. 绝对禁止成人化连接词(actually/in fact/however等)
+8. 完全符合母语小朋友说话习惯
+9. 科幻词汇替换:
+{sci_fi_map_str}
+
+### 上下文连贯性
+台词是连续对话,前文已提到的事物,后文用代词(them/it/these)指代,不重复同一名词。
+
+### 忠实度要求
+100%保留原文语义,禁止新增/删除任何原剧本没有的信息。
+
+### 输出格式(只输出台词,不要其他内容)
+Role: adapted dialogue
+
+### 待适配台词
+{ar_processed_text}"""
+ else:
+ # 中文路径:英语思维重构意群(不是翻译!)
+ prompt = f"""你的任务是用英语母语儿童的思维方式,将下面的中文意群重构为地道的{stage}级别英文台词。
+
+### 核心理念(最重要!)
+**这不是翻译!** 你要做的是:
+1. 理解每个中文意群要表达的核心意思和情绪
+2. 忘掉中文的表达方式和句式结构
+3. 想象一个{STAGE_CONFIG[stage]['age']}的英语母语孩子在同样情境下会怎么说
+4. 用那个孩子的方式说出来
+
+判断标准:如果一句话听起来像是"从中文翻译过来的",那就是失败的。
+
+### 上下文连贯性(重要!)
+台词是连续对话,重构时必须通读全文语境:
+- 前文已提到的事物,后文用代词指代(them/it/these/this stuff),不要反复重复同一个名词
+- 英语母语孩子的对话习惯:第一次提到用具体词,后续自然切换为代词/简称
+- 例如:"我们收集了很多衣物" → "We got lots of clothes!" → 下一句"小朋友会喜欢这些" → "Kids will love them!" 而不是 "Kids will love these clothes!"
+- 同理,上一句刚说完某个动作,下一句不需要重复主语/对象,直接连接即可
+
+### 目标Stage:{stage}
+年龄段:{STAGE_CONFIG[stage]['age']}
+要求:{STAGE_CONFIG[stage]['rules']}
+蓝思值:{STAGE_CONFIG[stage]['lexile']}
+
+### 自然表达规则
+1. 情绪词:{emotion_map_rule}
+2. 口语替换:{synonym_replace_rule}
+3. 句子拆分:{split_rule}
+4. 自然重复:{repeat_rule}
+5. 标点:{exclamation_rule}
+6. 绝对禁止成人化连接词(actually/in fact/however等)
+7. 优先用祈使句、感叹句、短问句等儿童高频句式
+8. 科幻词汇替换:
+{sci_fi_map_str}
+
+### 忠实度要求
+100%保留原始意群的语义,禁止新增/删除任何原剧本没有的信息。
+
+### 输出格式(只输出台词,不要其他内容)
+角色: 英文台词
+
+### 待重构意群
+{ar_processed_text}"""
+
+ result, error = call_llm(prompt, stage)
+ if error:
+ logger.add_warning(error)
+ logger.end_node('fail')
+ return None
+
+ logger.set_output(result, result)
+ logger.end_node('pass')
+ return result
+
+
+# ============================================================
+# Node 5: 代码侧校验(纯代码)
+# ============================================================
+def node_code_validation(english_text, stage, logger):
+ """代码侧校验:句长、连接词、从句、时态、OOV、重复"""
+ logger.start_node("代码侧校验", "纯代码 → 句长/连接词/从句/时态/超纲词/重复")
+ logger.set_input(f"Stage={stage}")
+
+ validation = validate_output(english_text, stage)
+ oov_words = check_out_of_vocab(english_text, stage)
+
+ all_warnings = validation['warnings'][:]
+ if oov_words:
+ all_warnings.append(f"超纲词: {', '.join(oov_words)}")
+
+ for w in all_warnings:
+ logger.add_warning(w)
+
+ status = 'fail' if len(all_warnings) > 3 else ('warn' if all_warnings else 'pass')
+ summary = f"{len(all_warnings)}条问题" if all_warnings else "全部通过"
+ logger.set_output(summary)
+ logger.end_node(status)
+ return {'warnings': all_warnings, 'oov_words': oov_words, 'status': status}
+
+
+# ============================================================
+# Node 6: 自动修正(可选,LLM #4)
+# ============================================================
+def node_auto_fix(english_text, validation_result, stage, logger):
+ """仅在校验不通过时触发,根据校验报告自动修正"""
+ if validation_result['status'] == 'pass':
+ logger.start_node("自动修正", "校验通过,跳过修正")
+ logger.set_output("无需修正")
+ logger.end_node('skip')
+ return english_text
+
+ logger.start_node("自动修正", "LLM调用 → 根据校验报告修正问题")
+ warnings_text = "\n".join([f"- {w}" for w in validation_result['warnings']])
+ logger.set_input(f"{len(validation_result['warnings'])}条待修正问题")
+
+ prompt = f"""以下英文台词存在问题,请按要求修正。
+
+### 当前台词
+{english_text}
+
+### 需要修正的问题
+{warnings_text}
+
+### 修正要求
+目标Stage:{stage}({STAGE_CONFIG[stage]['age']})
+句长要求:{STAGE_CONFIG[stage]['rules']}
+1. 只修正上面列出的具体问题
+2. 保持原有台词内容和角色不变
+3. 不要新增/删除台词行
+
+### 输出格式(只输出修正后的台词,不要其他说明)
+角色: 修正后的英文台词"""
+
+ result, error = call_llm(prompt, stage)
+ if error:
+ logger.add_warning(f"修正失败: {error}")
+ logger.set_output("修正失败,保留原始结果", english_text)
+ logger.end_node('fail')
+ return english_text
+
+ logger.set_output(result, result)
+ logger.end_node('pass')
+ return result
+
+
+# ============================================================
+# Pipeline 主流程
+# ============================================================
+def generate_single_script(input_text, stage, verbose=True):
+ """完整Pipeline:6个可观测节点,生成单个剧本的台词"""
+ logger = PipelineLogger(verbose=verbose)
+
+ # Node 1: 输入解析
+ parsed = node_input_parse(input_text, logger)
+ lang_type = parsed['lang_type']
+
+ # Node 2: 输入归一(纯英文保留英文,中文/混合归一为中文)
+ normalized = node_normalize(parsed, logger)
+ if normalized is None:
+ return logger.get_trace_report()
+
+ # Node 3: AR预处理(纯英文在英文上拆分,中文在中文上拆分)
+ ar_processed = node_ar_preprocess(normalized, stage, lang_type, logger)
+ if ar_processed is None:
+ return logger.get_trace_report()
+
+ # Node 4: 分级英文生成(纯英文做难度适配,中文做意群重构)
+ english = node_generate_english(ar_processed, stage, lang_type, logger)
+ if english is None:
+ return logger.get_trace_report()
+
+ # Node 5: 代码侧校验
+ validation = node_code_validation(english, stage, logger)
+
+ # Node 6: 自动修正(仅校验不通过时触发)
+ final = node_auto_fix(english, validation, stage, logger)
+
+ # 拼接最终输出
+ output_lines = []
+ output_lines.append(f"【Stage {stage} 英文台词(适配{STAGE_CONFIG[stage]['age']})】")
+ output_lines.append(final)
+
+ # 附加追踪报告
+ output_lines.append(logger.get_trace_report())
+
+ result = "\n".join(output_lines)
+ # 附加 logger 引用供外部保存中间产物
+ result = type('PipelineResult', (), {'text': result, 'logger': logger, '__str__': lambda self: self.text})()
+ return result
+
+def extract_script_lines(content):
+ """从 LLM 输出中提取纯台词行,排除元数据标签"""
+ lines = content.strip().split('\n')
+ script_lines = []
+ for line in lines:
+ line = line.strip()
+ # 跳过元数据行
+ if not line or line.startswith('【') or line.startswith('---') or line.startswith('#'):
+ continue
+ # 台词行格式: "角色: 台词"
+ if ':' in line or ':' in line:
+ # 提取冒号后的台词部分
+ sep = ':' if ':' in line else ':'
+ parts = line.split(sep, 1)
+ if len(parts) == 2:
+ script_lines.append(parts[1].strip())
+ return script_lines
+
+def simple_lemmatize(word):
+ """简单词形还原,覆盖常见词尾变化"""
+ w = word.lower()
+ # 不规则过去式常见补充
+ irregulars = {
+ 'went': 'go', 'got': 'get', 'said': 'say', 'made': 'make',
+ 'came': 'come', 'took': 'take', 'ran': 'run', 'saw': 'see',
+ 'had': 'have', 'did': 'do', 'was': 'be', 'were': 'be',
+ 'ate': 'eat', 'gave': 'give', 'told': 'tell', 'found': 'find',
+ 'thought': 'think', 'knew': 'know', 'felt': 'feel', 'left': 'leave',
+ 'heard': 'hear', 'sat': 'sit', 'stood': 'stand', 'lost': 'lose',
+ 'brought': 'bring', 'kept': 'keep', 'began': 'begin', 'fell': 'fall',
+ 'children': 'child', 'mice': 'mouse', 'feet': 'foot', 'teeth': 'tooth',
+ }
+ if w in irregulars:
+ return irregulars[w]
+ # 规则词尾: -ing, -ed, -s, -er, -est, -ly
+ if w.endswith('ing'):
+ base = w[:-3]
+ if base + 'e' in L1_WORD_LIST: return base + 'e' # making -> make
+ if len(base) > 2 and base[-1] == base[-2]: return base[:-1] # running -> run
+ if base in L1_WORD_LIST: return base
+ if w.endswith('ed'):
+ base = w[:-2]
+ if base in L1_WORD_LIST: return base
+ if base + 'e' in L1_WORD_LIST: return base + 'e' # liked -> like
+ if w[:-1] in L1_WORD_LIST: return w[:-1] # cleaned -> clean (clean+ed)
+ if len(base) > 2 and base[-1] == base[-2]: return base[:-1] # stopped -> stop
+ if w.endswith('ies'):
+ base = w[:-3] + 'y'
+ if base in L1_WORD_LIST: return base # carries -> carry
+ if w.endswith('es'):
+ base = w[:-2]
+ if base in L1_WORD_LIST: return base
+ if w.endswith('s') and not w.endswith('ss'):
+ base = w[:-1]
+ if base in L1_WORD_LIST: return base
+ if w.endswith('er'):
+ base = w[:-2]
+ if base in L1_WORD_LIST: return base
+ if base + 'e' in L1_WORD_LIST: return base + 'e'
+ if w.endswith('est'):
+ base = w[:-3]
+ if base in L1_WORD_LIST: return base
+ if base + 'e' in L1_WORD_LIST: return base + 'e'
+ if w.endswith('ly'):
+ base = w[:-2]
+ if base in L1_WORD_LIST: return base
+ return w
+
+def check_out_of_vocab(script_content, stage):
+ """检查超纲词汇,规则从配置文件读取"""
+ if not VALIDATION_CONFIG['vocab_validation']['enable_OOV_remind']:
+ return []
+ # 只提取纯台词行,排除元数据
+ script_lines = extract_script_lines(script_content)
+ script_text = ' '.join(script_lines)
+ # 提取英文单词
+ words = re.findall(r"[a-zA-Z']+", script_text)
+ words = [word.lower().strip("'") for word in words]
+ # 过滤停用词
+ stop_words = set(VALIDATION_CONFIG['vocab_validation'].get('stop_words', []))
+ words = [word for word in words if word not in stop_words and len(word) > 1]
+ # 词形还原后再查
+ out_of_vocab = []
+ seen = set()
+ for word in words:
+ lemma = simple_lemmatize(word)
+ if lemma not in L1_WORD_LIST and word not in L1_WORD_LIST and word not in seen:
+ out_of_vocab.append(word)
+ seen.add(word)
+ # 根据配置的允许比例判断是否超标
+ total_words = len([w for w in words if w not in stop_words and len(w) > 1])
+ if total_words == 0:
+ return []
+ oov_ratio = len(out_of_vocab) / total_words
+ max_ratio_key = f"{stage}_allow_OOV_ratio"
+ max_ratio = VALIDATION_CONFIG['vocab_validation'].get(max_ratio_key, 0.15)
+ if oov_ratio > max_ratio:
+ out_of_vocab.append(f"(超纲率 {oov_ratio:.1%} 超过允许上限 {max_ratio:.0%})")
+ return out_of_vocab
+
+def validate_output(content, stage):
+ """代码侧校验: 句长、词汇、AR等级等"""
+ warnings = []
+ script_lines = extract_script_lines(content)
+ if not script_lines:
+ return {'warnings': ['未检测到有效台词行']}
+
+ # 句长校验
+ stage_word_limits = {
+ 'S1': (4, 7), 'S2': (7, 10), 'S3': (10, 15), 'S4': (15, 20)
+ }
+ min_len, max_len = stage_word_limits.get(stage, (4, 20))
+ for line in script_lines:
+ words = re.findall(r"[a-zA-Z']+", line)
+ word_count = len(words)
+ if word_count > max_len + 3: # 容差3个词
+ warnings.append(f"句长超标: \"{line[:40]}...\" 共{word_count}词,{stage}要求{min_len}-{max_len}词")
+
+ # 禁用连接词校验
+ adult_connectors = ['actually', 'in fact', 'however', 'therefore', 'furthermore', 'moreover', 'nevertheless']
+ all_text = ' '.join(script_lines).lower()
+ for conn in adult_connectors:
+ if conn in all_text:
+ warnings.append(f"成人化连接词: 检测到 '{conn}',{stage} 禁止使用")
+
+ # S1 禁止复合从句
+ if stage == 'S1':
+ s1_banned = ['because', 'when', 'before', 'after', 'if', 'although', 'while']
+ for word in s1_banned:
+ if re.search(r'\b' + word + r'\b', all_text):
+ warnings.append(f"S1禁止从句连词: 检测到 '{word}'")
+
+ # S1/S2 禁止完成时/虚拟语气
+ if stage in ['S1', 'S2']:
+ complex_patterns = [r"\bhave\s+\w+ed\b", r"\bhas\s+\w+ed\b", r"\bwould\b", r"\bcould\b", r"\bshould\b"]
+ for pat in complex_patterns:
+ match = re.search(pat, all_text)
+ if match:
+ warnings.append(f"复杂时态/语气: 检测到 '{match.group()}',{stage} 禁止使用")
+
+ # 重复台词检测
+ seen_lines = {}
+ for line in script_lines:
+ normalized = line.strip().lower()
+ if normalized in seen_lines:
+ warnings.append(f"重复台词: \"{line[:40]}\" 连续出现{seen_lines[normalized] + 1}次")
+ seen_lines[normalized] = seen_lines.get(normalized, 0) + 1
+
+ return {'warnings': warnings}
+
+def save_result(output_dir, filename, content):
+ """保存结果到文件"""
+ output_dir = Path(output_dir)
+ output_dir.mkdir(parents=True, exist_ok=True)
+ output_file = output_dir / f"result_{filename}"
+ with open(output_file, "w", encoding="utf-8") as f:
+ f.write(content)
+ return output_file
+
+def main():
+ parser = argparse.ArgumentParser(description="4-8岁儿童英文台词标准化生产工具(带全流程监控)")
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument("--input", type=str, help="直接输入待处理的剧本文本")
+ group.add_argument("--path", type=str, help="待处理的单个剧本文件路径或包含多个剧本的目录路径")
+ group.add_argument("--feishu-url", type=str, help="飞书文档链接,从飞书表格读取台词并写回结果")
+ parser.add_argument("--stage", type=str, choices=["S1", "S2", "S3", "S4"], required=True, help="目标难度等级 S1/S2/S3/S4")
+ parser.add_argument("--output", type=str, help="结果输出目录,不指定则直接打印到控制台")
+ parser.add_argument("--verbose", action="store_true", default=True, help="显示每个节点的详细过程日志(默认开启)")
+ parser.add_argument("--quiet", action="store_true", help="静默模式,只输出最终结果")
+ parser.add_argument("--trace", action="store_true", help="保存中间产物到output目录(需配合--output使用)")
+ # 飞书模式参数
+ parser.add_argument("--source-col", type=str, default="中文台词", help="飞书表格源列名称(默认: 中文台词)")
+ parser.add_argument("--target-col", type=str, default="英文台词", help="飞书表格目标列名称(默认: 英文台词)")
+ parser.add_argument("--table-index", type=int, default=0, help="文档中第几个表格(从0开始)")
+ args = parser.parse_args()
+
+ verbose = not args.quiet
+
+ # 飞书模式:读取飞书表格 → pipeline处理 → 写回飞书
+ if args.feishu_url:
+ run_feishu_mode(args)
+ return
+
+ # 处理输入
+ if args.input:
+ input_list = [("direct_input", args.input)]
+ else:
+ input_list = load_input(args.path)
+
+ # 批量生成
+ results = []
+ for filename, text in input_list:
+ print(f"\n🚀 正在处理: {filename}")
+ result = generate_single_script(text, args.stage, verbose=verbose)
+ results.append((filename, str(result)))
+ if not verbose:
+ print(result)
+ # 保存结果
+ if args.output:
+ save_path = save_result(args.output, filename, str(result))
+ print(f"💾 结果已保存到: {save_path}")
+ # --trace: 保存中间产物
+ if args.trace and hasattr(result, 'logger'):
+ trace_dir = Path(args.output) / "trace"
+ trace_dir.mkdir(parents=True, exist_ok=True)
+ intermediates = result.logger.get_intermediate_outputs()
+ for node_name, data in intermediates.items():
+ trace_file = trace_dir / f"{filename}_{node_name}.txt"
+ with open(trace_file, "w", encoding="utf-8") as f:
+ f.write(f"节点: {node_name}\n")
+ f.write(f"状态: {data['status']}\n")
+ f.write(f"耗时: {data['duration']:.1f}s\n")
+ if data['warnings']:
+ f.write(f"警告: {'; '.join(data['warnings'])}\n")
+ f.write(f"\n--- 输出内容 ---\n{data['content']}\n")
+ print(f"📝 中间产物已保存到: {trace_dir}/")
+
+ print(f"\n✅ 全部处理完成,共处理{len(results)}个剧本")
+
+def _preprocess_feishu_cell(text: str) -> str:
+ """
+ 预处理飞书单元格文本,解决台词粘连问题。
+ 1. 清洗HTML换行标签(
/
等)→ 真实换行
+ 2. 智能拆分粘连台词行(如 "Tom: Hi!Dan: Hello!" → 两行)
+ 3. 清理多余空行
+ """
+ # Step 1: HTML标签清洗
+ text = re.sub(r'
', '\n', text, flags=re.IGNORECASE)
+ text = re.sub(r'?p\s*>', '\n', text, flags=re.IGNORECASE)
+
+ # Step 2: 如果已有正常换行则直接返回
+ lines = [l.strip() for l in text.split('\n') if l.strip()]
+ if len(lines) > 1:
+ return '\n'.join(lines)
+
+ # Step 3: 单行文本 → 智能拆分粘连台词
+ # 匹配模式:句末标点紧跟角色名冒号(如 "okay?Dan:" 或 "fine.Grace:")
+ # 在角色名前插入换行,角色名支持中英文
+ text = re.sub(
+ r'([.!?。!?"\))])\s*(?=[A-Z][a-zA-Z]*\s*[::]|[\u4e00-\u9fff]+\s*[::]|User\s*[::]|Mum\s*[::]|Dad\s*[::])',
+ r'\1\n',
+ text
+ )
+ lines = [l.strip() for l in text.split('\n') if l.strip()]
+ return '\n'.join(lines)
+
+
+def run_feishu_mode(args):
+ """飞书模式:从飞书表格读取台词 → pipeline处理 → 写回飞书"""
+ # 导入飞书 I/O 能力
+ feishu_io_path = Path(__file__).parent.parent.parent / "feishu-table-translate-fill" / "scripts"
+ sys.path.insert(0, str(feishu_io_path))
+ try:
+ from feishu_io import FeishuDocIO
+ except ImportError:
+ print("❌ 无法加载飞书 I/O 模块")
+ print(f" 请确认 feishu-table-translate-fill 技能已部署在: {feishu_io_path}")
+ print(" 或设置环境变量 FEISHU_IO_PATH 指向 feishu_io.py 所在目录")
+ sys.exit(1)
+
+ verbose = not args.quiet
+ io = FeishuDocIO()
+
+ # Step 1: 读取飞书表格
+ print(f"\n📄 正在读取飞书文档...")
+ print(f" 源列: {args.source_col} | 目标列: {args.target_col}")
+ try:
+ rows = io.read_table_column(
+ url=args.feishu_url,
+ column_name=args.source_col,
+ table_index=args.table_index
+ )
+ except Exception as e:
+ print(f"❌ 读取飞书文档失败: {e}")
+ sys.exit(1)
+
+ print(f"✅ 读取到 {len(rows)} 行台词")
+
+ # Step 2: 通过 pipeline 逐行处理
+ results = []
+ for i, row in enumerate(rows):
+ text = row["content"]
+ if not text.strip():
+ print(f" 行{row['row']}: 跳过(空内容)")
+ continue
+ # 输入预处理:清洗HTML标签 + 智能拆分粘连台词
+ text = _preprocess_feishu_cell(text)
+ print(f"\n🚀 处理行 {row['row']} ({i+1}/{len(rows)})")
+ result = generate_single_script(text, args.stage, verbose=verbose)
+
+ # 提取纯台词部分(去掉追踪报告)
+ result_text = str(result)
+ # 截取到追踪报告之前的内容
+ trace_marker = "\n" + "=" * 60 + "\n📋"
+ if trace_marker in result_text:
+ clean_text = result_text[:result_text.index(trace_marker)].strip()
+ else:
+ clean_text = result_text.strip()
+ # 去掉开头的 Stage 标题行
+ lines = clean_text.split("\n")
+ if lines and lines[0].startswith("【"):
+ clean_text = "\n".join(lines[1:]).strip()
+
+ results.append({"row": row["row"], "content": clean_text})
+
+ if not results:
+ print("\n⚠️ 没有生成任何结果")
+ return
+
+ # Step 3: 写回飞书表格
+ print(f"\n✍️ 正在写回飞书文档 ({len(results)} 行)...")
+ try:
+ stats = io.write_table_column(
+ url=args.feishu_url,
+ column_name=args.target_col,
+ data=results,
+ table_index=args.table_index
+ )
+ print(f"\n✅ 写回完成: 成功{stats['success']} 跳过{stats.get('skipped',0)} 失败{stats['failed']}")
+ except Exception as e:
+ print(f"❌ 写回飞书失败: {e}")
+ # 保存到本地作为备份
+ backup_file = Path(args.output or ".") / "feishu_backup_results.json"
+ import json as _json
+ with open(backup_file, "w", encoding="utf-8") as f:
+ _json.dump(results, f, ensure_ascii=False, indent=2)
+ print(f" 结果已备份到: {backup_file}")
+
+ # 同时保存到本地
+ if args.output:
+ output_dir = Path(args.output)
+ output_dir.mkdir(parents=True, exist_ok=True)
+ for item in results:
+ save_path = output_dir / f"result_row{item['row']}.txt"
+ with open(save_path, "w", encoding="utf-8") as f:
+ f.write(item["content"])
+ print(f"💾 本地副本已保存到: {output_dir}/")
+
+ print(f"\n✅ 飞书模式处理完成,共处理 {len(results)} 行台词")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/skills/unit_challenge/core/unit_challenge_core/SKILL.md b/business_production/单元挑战/skills/unit_challenge/core/unit_challenge_core/SKILL.md
similarity index 100%
rename from skills/unit_challenge/core/unit_challenge_core/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/core/unit_challenge_core/SKILL.md
diff --git a/skills/unit_challenge/master/unit_challenge_master/SKILL.md b/business_production/单元挑战/skills/unit_challenge/master/unit_challenge_master/SKILL.md
similarity index 100%
rename from skills/unit_challenge/master/unit_challenge_master/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/master/unit_challenge_master/SKILL.md
diff --git a/skills/unit_challenge/questions/listening/L1/listening-drag/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/listening/L1/listening-drag/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/listening/L1/listening-drag/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L1/listening-drag/SKILL.md
diff --git a/skills/unit_challenge/questions/listening/L1/listening-drag/references/ability_tags.md b/business_production/单元挑战/skills/unit_challenge/questions/listening/L1/listening-drag/references/ability_tags.md
similarity index 100%
rename from skills/unit_challenge/questions/listening/L1/listening-drag/references/ability_tags.md
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L1/listening-drag/references/ability_tags.md
diff --git a/skills/unit_challenge/questions/listening/L1/listening-drag/references/difficulty_mapping.md b/business_production/单元挑战/skills/unit_challenge/questions/listening/L1/listening-drag/references/difficulty_mapping.md
similarity index 100%
rename from skills/unit_challenge/questions/listening/L1/listening-drag/references/difficulty_mapping.md
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L1/listening-drag/references/difficulty_mapping.md
diff --git a/skills/unit_challenge/questions/listening/L1/listening-drag/references/l1_word_list.json b/business_production/单元挑战/skills/unit_challenge/questions/listening/L1/listening-drag/references/l1_word_list.json
similarity index 100%
rename from skills/unit_challenge/questions/listening/L1/listening-drag/references/l1_word_list.json
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L1/listening-drag/references/l1_word_list.json
diff --git a/skills/unit_challenge/questions/listening/L1/listening-drag/scripts/generate_listening_drag.py b/business_production/单元挑战/skills/unit_challenge/questions/listening/L1/listening-drag/scripts/generate_listening_drag.py
similarity index 100%
rename from skills/unit_challenge/questions/listening/L1/listening-drag/scripts/generate_listening_drag.py
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L1/listening-drag/scripts/generate_listening_drag.py
diff --git a/skills/unit_challenge/questions/listening/L1/listening_picture_selection/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/listening/L1/listening_picture_selection/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/listening/L1/listening_picture_selection/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L1/listening_picture_selection/SKILL.md
diff --git a/skills/unit_challenge/questions/listening/L2/listening-choicePic/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening-choicePic/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/listening/L2/listening-choicePic/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening-choicePic/SKILL.md
diff --git a/skills/unit_challenge/questions/listening/L2/listening-choicePic/references/ability_tags.md b/business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening-choicePic/references/ability_tags.md
similarity index 100%
rename from skills/unit_challenge/questions/listening/L2/listening-choicePic/references/ability_tags.md
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening-choicePic/references/ability_tags.md
diff --git a/skills/unit_challenge/questions/listening/L2/listening-choicePic/references/difficulty_mapping.md b/business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening-choicePic/references/difficulty_mapping.md
similarity index 100%
rename from skills/unit_challenge/questions/listening/L2/listening-choicePic/references/difficulty_mapping.md
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening-choicePic/references/difficulty_mapping.md
diff --git a/skills/unit_challenge/questions/listening/L2/listening-choicePic/references/l1_word_list.json b/business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening-choicePic/references/l1_word_list.json
similarity index 100%
rename from skills/unit_challenge/questions/listening/L2/listening-choicePic/references/l1_word_list.json
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening-choicePic/references/l1_word_list.json
diff --git a/skills/unit_challenge/questions/listening/L2/listening-choicePic/scripts/generate_listening_choicePic.py b/business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening-choicePic/scripts/generate_listening_choicePic.py
similarity index 100%
rename from skills/unit_challenge/questions/listening/L2/listening-choicePic/scripts/generate_listening_choicePic.py
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening-choicePic/scripts/generate_listening_choicePic.py
diff --git a/skills/unit_challenge/questions/listening/L2/listening_form_fill/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening_form_fill/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/listening/L2/listening_form_fill/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening_form_fill/SKILL.md
diff --git a/skills/unit_challenge/questions/listening/L2/listening_info_match/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening_info_match/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/listening/L2/listening_info_match/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening_info_match/SKILL.md
diff --git a/skills/unit_challenge/questions/listening/L2/listening_long_conversation/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening_long_conversation/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/listening/L2/listening_long_conversation/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening_long_conversation/SKILL.md
diff --git a/skills/unit_challenge/questions/listening/L2/listening_short_conversation/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening_short_conversation/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/listening/L2/listening_short_conversation/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/listening/L2/listening_short_conversation/SKILL.md
diff --git a/skills/unit_challenge/questions/reading/reading_cloze/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/reading/reading_cloze/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/reading/reading_cloze/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/reading/reading_cloze/SKILL.md
diff --git a/skills/unit_challenge/questions/reading/reading_info_match/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/reading/reading_info_match/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/reading/reading_info_match/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/reading/reading_info_match/SKILL.md
diff --git a/skills/unit_challenge/questions/reading/reading_long_passage/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/reading/reading_long_passage/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/reading/reading_long_passage/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/reading/reading_long_passage/SKILL.md
diff --git a/skills/unit_challenge/questions/reading/reading_open_fill/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/reading/reading_open_fill/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/reading/reading_open_fill/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/reading/reading_open_fill/SKILL.md
diff --git a/skills/unit_challenge/questions/reading/reading_paragraph_match/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/reading/reading_paragraph_match/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/reading/reading_paragraph_match/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/reading/reading_paragraph_match/SKILL.md
diff --git a/skills/unit_challenge/questions/speaking/L1/speaking_pic_qa/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/speaking/L1/speaking_pic_qa/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/speaking/L1/speaking_pic_qa/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/speaking/L1/speaking_pic_qa/SKILL.md
diff --git a/skills/unit_challenge/questions/speaking/L2/speaking_topic_discussion/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/speaking/L2/speaking_topic_discussion/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/speaking/L2/speaking_topic_discussion/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/speaking/L2/speaking_topic_discussion/SKILL.md
diff --git a/skills/unit_challenge/questions/speaking/common/speaking_daily_qa/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/speaking/common/speaking_daily_qa/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/speaking/common/speaking_daily_qa/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/speaking/common/speaking_daily_qa/SKILL.md
diff --git a/skills/unit_challenge/questions/writing/writing_email_reply/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/writing/writing_email_reply/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/writing/writing_email_reply/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/writing/writing_email_reply/SKILL.md
diff --git a/skills/unit_challenge/questions/writing/writing_picture_writing/SKILL.md b/business_production/单元挑战/skills/unit_challenge/questions/writing/writing_picture_writing/SKILL.md
similarity index 100%
rename from skills/unit_challenge/questions/writing/writing_picture_writing/SKILL.md
rename to business_production/单元挑战/skills/unit_challenge/questions/writing/writing_picture_writing/SKILL.md
diff --git a/11-4组件生成结果.md b/business_production/课程组件/final/11-4组件生成结果.md
similarity index 100%
rename from 11-4组件生成结果.md
rename to business_production/课程组件/final/11-4组件生成结果.md
diff --git a/11-5_组件配置已填充.xlsx b/business_production/课程组件/final/11-5_组件配置已填充.xlsx
similarity index 100%
rename from 11-5_组件配置已填充.xlsx
rename to business_production/课程组件/final/11-5_组件配置已填充.xlsx
diff --git a/11-5_组件配置已填充_GPT5.4.xlsx b/business_production/课程组件/final/11-5_组件配置已填充_GPT5.4.xlsx
similarity index 100%
rename from 11-5_组件配置已填充_GPT5.4.xlsx
rename to business_production/课程组件/final/11-5_组件配置已填充_GPT5.4.xlsx
diff --git a/12-4_已填充组件.xlsx b/business_production/课程组件/final/12-4_已填充组件.xlsx
similarity index 100%
rename from 12-4_已填充组件.xlsx
rename to business_production/课程组件/final/12-4_已填充组件.xlsx
diff --git a/skills/component-design/SKILL.md b/business_production/课程组件/skills/component-design/SKILL.md
similarity index 100%
rename from skills/component-design/SKILL.md
rename to business_production/课程组件/skills/component-design/SKILL.md
diff --git a/skills/dialogue-interaction-config/SKILL.md b/business_production/课程组件/skills/dialogue-interaction-config/SKILL.md
similarity index 100%
rename from skills/dialogue-interaction-config/SKILL.md
rename to business_production/课程组件/skills/dialogue-interaction-config/SKILL.md
diff --git a/skills/vala-interactive-components/SKILL.md b/business_production/课程组件/skills/vala-interactive-components/SKILL.md
similarity index 100%
rename from skills/vala-interactive-components/SKILL.md
rename to business_production/课程组件/skills/vala-interactive-components/SKILL.md
diff --git a/memory/2026-04-24-request-timed-out-before-a-res.md b/memory/2026-04-24-request-timed-out-before-a-res.md
new file mode 100644
index 0000000..5e1211f
--- /dev/null
+++ b/memory/2026-04-24-request-timed-out-before-a-res.md
@@ -0,0 +1,124 @@
+# Session: 2026-04-24 07:07:11 UTC
+
+- **Session Key**: agent:xiaoyan:feishu:direct:ou_5af74c1fb96042e33cc0f16b5ca02cf4
+- **Session ID**: 1fb32997-2021-41db-9968-838feabb2380
+- **Source**: feishu
+
+## Conversation Summary
+
+assistant: 继续第4批(行31-40):
+assistant: 最后一批(行41-43):
+assistant: 全部完成!汇总结果:
+- ✅ **表格1**(全屏插入图场景):1行翻译已补全完整
+- ✅ **表格2**(Wood家场景):43行全部处理完毕
+ - 成功写入:39行
+ - 智能跳过:4行(已有翻译内容或无需翻译的行)
+ - 失败:0行
+
+**全部翻译已精准写入到文档「英文翻译」列对应的单元格中**,100%行列对齐,零错位。
+
+最终通过飞书文档Block API实现了单元格级别的精准写入,彻底解决了之前的定位精度问题。您可以打开文档确认:https://makee-interactive.feishu.cn/wiki/P7LZwEAJjilWd8k66LycXMWIn4g
+user: System: [2026-04-24 12:10:30 GMT+8] Feishu[xiaoyan] DM | 刘彦江 (ou_5af74c1fb96042e33cc0f16b5ca02cf4) [msg:om_x100b519a9d983080b48e5a1ca5a0346]
+
+Conversation info (untrusted metadata):
+```json
+{
+ "message_id": "om_x100b519a9d983080b48e5a1ca5a0346",
+ "sender_id": "ou_5af74c1fb96042e33cc0f16b5ca02cf4",
+ "sender": "刘彦江",
+ "timestamp": "Fri 2026-04-24 12:10 GMT+8"
+}
+```
+
+Sender (untrusted metadata):
+```json
+{
+ "label": "刘彦江 (ou_5af74c1fb96042e33cc0f16b5ca02cf4)",
+ "id": "ou_5af74c1fb96042e33cc0f16b5ca02cf4",
+ "name": "刘彦江"
+}
+```
+
+这个路径沉淀到 skill “feishu-table-translate-fill”中
+assistant:
+
+用户要求把刚才成功的技术路径(通过飞书文档Block API精准写入单元格)沉淀到skill中。我需要更新SKILL.md和脚本,把完整的实现路径固化下来。
+
+首先更新SKILL.md,加入Block API精准写入的技术路径说明,然后把write_translations.py这个脚本整理好放到scripts目录下。
+assistant: 现在把验证通过的写入脚本整理后保存到skills目录:
+assistant: 现在确认完整的skill目录结构:
+assistant: 已完成技术路径的沉淀。最终Skill结构:
+
+```
+feishu-table-translate-fill/
+├── SKILL.md # 技能说明:核心技术路径 + 使用流程 + 校验规则
+├── scripts/
+│ ├── write_translations.py # 核心脚本:Block API 精准单元格写入(已验证)
+│ └── translate_fill.py # 编排脚本:翻译 + 规则校验 + 写入调度
+└── references/
+ └── usage.md # 使用说明 + 常见问题
+```
+
+沉淀的核心技术路径:
+1. **获取凭证** → tenant_access_token
+2. **获取文档Block树** → 识别所有表格(block_type=31),按行列数定位目标表格
+3. **获取表格子Blocks** → 按 `row * total_cols + col` 公式精准定位目标列的每个单元格 block_id
+4. **逐行写入** → 检查是否已有内容 → 清理空子blocks → 创建文本block写入翻译 → 0.3s间隔避免限频
+5. **分批处理** → 每批10行,确保稳定性
+
+后续使用时只需修改脚本中的 `DOC_ID`、`TABLE_BLOCK_ID` 和 `translations` 列表即可复用。
+user: System: [2026-04-24 12:13:27 GMT+8] Feishu[xiaoyan] DM | 刘彦江 (ou_5af74c1fb96042e33cc0f16b5ca02cf4) [msg:om_x100b519a90abf490b4b4541a183b1aa]
+
+Conversation info (untrusted metadata):
+```json
+{
+ "message_id": "om_x100b519a90abf490b4b4541a183b1aa",
+ "sender_id": "ou_5af74c1fb96042e33cc0f16b5ca02cf4",
+ "sender": "刘彦江",
+ "timestamp": "Fri 2026-04-24 12:13 GMT+8"
+}
+```
+
+Sender (untrusted metadata):
+```json
+{
+ "label": "刘彦江 (ou_5af74c1fb96042e33cc0f16b5ca02cf4)",
+ "id": "ou_5af74c1fb96042e33cc0f16b5ca02cf4",
+ "name": "刘彦江"
+}
+```
+
+https://makee-interactive.feishu.cn/wiki/P7LZwEAJjilWd8k66LycXMWIn4g?from=from_copylink
+
+阅读文档中【剧本】目录下的内容
+【剧情内容】列是故事的剧本,其中标色部分为知识点,比如黄色 “Let me help”
+【中文台词】列为该段剧本对应的台词部分(纯英文、中英混杂两种情况)
+【英文翻译】列是目标列,结合【剧情内容】和【中文台词】,把纯英文台词写于此列
+
+注意:不能修改文档中的表格格式,若必须要改才能完成,要与我确认
+assistant:
+
+好,用户再次给了同样的需求,但这次加了重要约束:**不能修改文档中的表格格式**。我需要重新获取最新文档内容,确认当前状态(之前已经写入了一些翻译),然后把剩余空的英文翻译列填完,使用已验证的Block API路径。
+
+首先获取最新文档内容,确认哪些行还需要填充。
+assistant: 文档格式已经调整了——现在是**表格0**(5行x6列)和**表格4**(50行x6列),之前写入的翻译似乎在旧表格里,新表格的翻译列几乎全空。
+
+先获取文档的中文台词列内容,确认每行对应的翻译:
+assistant: 表格结构更清楚了。表格4是主表格(50行x6列),里面包含了场景信息行和数据行。表格0是制作人员表,不需要翻译。
+
+现在继续读取表格4剩余行的中文台词:
+assistant: 表格结构清楚了。共49行,已翻译2行(表头行),待翻译42行(有中文台词内容的行),另外5行是场景/角色/表头信息无需翻译。
+
+现在使用Block API精准写入,**不修改任何表格格式**,仅向空的英文翻译单元格写入文本:
+assistant: 第1批全部成功!继续:
+assistant: 全部完成!42行翻译全部精准写入,0失败。
+
+**汇总**:
+- 第1批(行3-17):12行 ✅
+- 第2批(行18-32):14行 ✅
+- 第3批(行33-49):16行 ✅
+- **共42行翻译精准写入,成功率100%,表格格式零修改**
+
+写入方式:仅通过Block API向空的英文翻译单元格内创建文本block,未触碰表格结构、行列数、列宽、合并单元格等任何格式属性。
+
+文档链接:https://makee-interactive.feishu.cn/wiki/P7LZwEAJjilWd8k66LycXMWIn4g
diff --git a/memory/2026-04-24.md b/memory/2026-04-24.md
new file mode 100644
index 0000000..d5fc79d
--- /dev/null
+++ b/memory/2026-04-24.md
@@ -0,0 +1,7 @@
+## 2026-04-24 工作记录
+### 完成的任务
+1. ✅ 完成L1-S2-U13-L4《沙漠之花》剧本中英文台词对齐,已将所有英文翻译写入文档【英文翻译】列,文档链接:https://makee-interactive.feishu.cn/wiki/K5E1wzwk7it9t7kXvcbc6Xugnhc
+2. ✅ 完成L1-S2-U13-L5《飞天袜子》剧本中英文台词对齐,已将所有英文翻译写入文档【英文翻译】列,文档链接:https://makee-interactive.feishu.cn/wiki/ILzAwqcmpiN4JDkCxqwcsd1cnlh
+
+### 业务规则确认
+- 剧本输入格式现状:内部编剧习惯分为纯英文、中英混合两种,无纯中文输入场景,翻译skill可移除纯中文处理逻辑
diff --git a/memory/2026-04-27-request-timed-out-before-a-res.md b/memory/2026-04-27-request-timed-out-before-a-res.md
new file mode 100644
index 0000000..42af3c4
--- /dev/null
+++ b/memory/2026-04-27-request-timed-out-before-a-res.md
@@ -0,0 +1,43 @@
+# Session: 2026-04-27 08:30:29 UTC
+
+- **Session Key**: agent:xiaoyan:feishu:direct:ou_270924c22bf8d63cf356dbd37614055f
+- **Session ID**: 5c884370-b7a0-45aa-b210-c45bf7baa28a
+- **Source**: feishu
+
+## Conversation Summary
+
+assistant: ✅ New session started · model: volcengine/doubao-seed-2-0-pro-260215
+user: System: [2026-04-21 17:14:58 GMT+8] Feishu[xiaoyan] DM | 李若松 (ou_270924c22bf8d63cf356dbd37614055f) [msg:om_x100b51404fefc4b4b2b853f13f7888b]
+System: [2026-04-21 17:15:00 GMT+8] Model switched to doubao (volcengine/doubao-seed-2-0-pro-260215).
+System: [2026-04-21 17:15:03 GMT+8] Feishu[xiaoyan] DM | 李若松 (ou_270924c22bf8d63cf356dbd37614055f) [msg:om_x100b51404fa16cacb2b8f3add558e42]
+
+A new session was started via /new or /reset. Run your Session Startup sequence - read the required files before responding to the user. Then greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning.
+Current time: Tuesday, April 21st, 2026 — 5:15 PM (Asia/Shanghai) / 2026-04-21 09:15 UTC
+assistant: 📝 你好,我是教研助理小研。请问需要我提供什么教研相关的支持?
+user: System: [2026-04-21 17:15:40 GMT+8] Feishu[xiaoyan] DM | 李若松 (ou_270924c22bf8d63cf356dbd37614055f) [msg:om_x100b51404d0c88a8b3dcbaf38e0799e]
+
+Conversation info (untrusted metadata):
+```json
+{
+ "message_id": "om_x100b51404d0c88a8b3dcbaf38e0799e",
+ "sender_id": "ou_270924c22bf8d63cf356dbd37614055f",
+ "sender": "李若松",
+ "timestamp": "Tue 2026-04-21 17:15 GMT+8"
+}
+```
+
+Sender (untrusted metadata):
+```json
+{
+ "label": "李若松 (ou_270924c22bf8d63cf356dbd37614055f)",
+ "id": "ou_270924c22bf8d63cf356dbd37614055f",
+ "name": "李若松"
+}
+```
+
+https://makee-interactive.feishu.cn/wiki/SXw2wYSYtiNT5ok5CkSc4OHLn5f 在这个文档最后的剧本表格中 再写入一行测试内容。
+assistant: ✅ 已在文档最后的剧本表格中成功写入一行测试内容,写入位置:第197行,写入内容如下:
+```
+["测试类型", "测试组件配置", "TEST001", "测试剧情描述", "测试角色名", "测试台词", "测试角色", "测试润色台词", "测试组件配置2", "测试知识点"]
+```
+已回读验证,内容正确。
diff --git a/11-5_meixu.csv b/output/11-5_meixu.csv
similarity index 100%
rename from 11-5_meixu.csv
rename to output/11-5_meixu.csv
diff --git a/22163_data.csv b/output/22163_data.csv
similarity index 100%
rename from 22163_data.csv
rename to output/22163_data.csv
diff --git a/22163_data.xlsx b/output/22163_data.xlsx
similarity index 100%
rename from 22163_data.xlsx
rename to output/22163_data.xlsx
diff --git a/all_blocks.json b/output/all_blocks.json
similarity index 100%
rename from all_blocks.json
rename to output/all_blocks.json
diff --git a/all_standard_words.txt b/output/all_standard_words.txt
similarity index 100%
rename from all_standard_words.txt
rename to output/all_standard_words.txt
diff --git a/all_standard_words_raw.txt b/output/all_standard_words_raw.txt
similarity index 100%
rename from all_standard_words_raw.txt
rename to output/all_standard_words_raw.txt
diff --git a/skills/kids-english-script-production/kids-english-script-production/references/l1_word_list.json b/output/l1_word_list.json
similarity index 100%
rename from skills/kids-english-script-production/kids-english-script-production/references/l1_word_list.json
rename to output/l1_word_list.json
diff --git a/raw_content.md b/output/raw_content.md
similarity index 100%
rename from raw_content.md
rename to output/raw_content.md
diff --git a/standard_words.txt b/output/standard_words.txt
similarity index 100%
rename from standard_words.txt
rename to output/standard_words.txt
diff --git a/pdf_pages/page-01.png b/pdf_pages/page-01.png
deleted file mode 100644
index 9af23cb..0000000
Binary files a/pdf_pages/page-01.png and /dev/null differ
diff --git a/pdf_pages/page-02.png b/pdf_pages/page-02.png
deleted file mode 100644
index cfe17b4..0000000
Binary files a/pdf_pages/page-02.png and /dev/null differ
diff --git a/pdf_pages/page-03.png b/pdf_pages/page-03.png
deleted file mode 100644
index 25e43f5..0000000
Binary files a/pdf_pages/page-03.png and /dev/null differ
diff --git a/pdf_pages/page-04.png b/pdf_pages/page-04.png
deleted file mode 100644
index 91da0e5..0000000
Binary files a/pdf_pages/page-04.png and /dev/null differ
diff --git a/pdf_pages/page-05.png b/pdf_pages/page-05.png
deleted file mode 100644
index c7e5140..0000000
Binary files a/pdf_pages/page-05.png and /dev/null differ
diff --git a/pdf_pages/page-06.png b/pdf_pages/page-06.png
deleted file mode 100644
index 81174a4..0000000
Binary files a/pdf_pages/page-06.png and /dev/null differ
diff --git a/pdf_pages/page-07.png b/pdf_pages/page-07.png
deleted file mode 100644
index 65e6209..0000000
Binary files a/pdf_pages/page-07.png and /dev/null differ
diff --git a/pdf_pages/page-08.png b/pdf_pages/page-08.png
deleted file mode 100644
index 19e8da3..0000000
Binary files a/pdf_pages/page-08.png and /dev/null differ
diff --git a/pdf_pages/page-09.png b/pdf_pages/page-09.png
deleted file mode 100644
index d58d8ec..0000000
Binary files a/pdf_pages/page-09.png and /dev/null differ
diff --git a/pdf_pages/page-10.png b/pdf_pages/page-10.png
deleted file mode 100644
index 0125a74..0000000
Binary files a/pdf_pages/page-10.png and /dev/null differ
diff --git a/fill_components.py b/scripts/fill_components.py
similarity index 100%
rename from fill_components.py
rename to scripts/fill_components.py
diff --git a/fill_components_by_id.py b/scripts/fill_components_by_id.py
similarity index 100%
rename from fill_components_by_id.py
rename to scripts/fill_components_by_id.py
diff --git a/fill_components_by_id_fixed.py b/scripts/fill_components_by_id_fixed.py
similarity index 100%
rename from fill_components_by_id_fixed.py
rename to scripts/fill_components_by_id_fixed.py
diff --git a/get_all_blocks.sh b/scripts/get_all_blocks.sh
similarity index 100%
rename from get_all_blocks.sh
rename to scripts/get_all_blocks.sh
diff --git a/read_excel.py b/scripts/read_excel.py
similarity index 100%
rename from read_excel.py
rename to scripts/read_excel.py
diff --git a/skills/dialogue-core-navigation-config/SKILL.md b/skills/dialogue-core-navigation-config/SKILL.md
new file mode 100644
index 0000000..ac59104
--- /dev/null
+++ b/skills/dialogue-core-navigation-config/SKILL.md
@@ -0,0 +1,110 @@
+---
+name: dialogue-core-navigation-config
+version: 1.0.0
+description: 核心互动导览类组件配置标准化规范,用于剧本中核心互动/导览配置类型组件的标准化生成
+metadata:
+ requires:
+ permissions: ["sheets:spreadsheet"]
+ identity: bot
+---
+# 核心互动导览组件配置规范
+## 适用场景
+当【类型】列内容为【核心互动】【导览配置】时,使用本规范生成组件配置
+## 配置格式
+### 字段要求
+1. 【任务标题】:用户看到的任务名,建议字数在10字以内
+2. 【任务背景】:
+ - 用户在任务开始前看到的背景描述,目的是告诉孩子为什么需要参加这个任务
+ - 建议字数在25字左右,以第二人称书写
+ - 人名、地名均写成英文
+3. 【任务描述】:
+ - 用户在任务进行时看到的目标,简洁明确,15字左右描述
+ - 以第二人称描述,格式为「你需要…」
+ - 简单题可笼统描述,难题可具象描述
+4. 【通关知识】:
+ - 用无序列表分类(若无分类可不填)
+ - 每个分类下多个句子结构,与知识点一致,每个句子先写英文后写译文
+5. 【对话角色】:
+ - 对话的角色名,需要与资源库中的名称完全一致,用于匹配对应的立绘和音色
+ - 如有多个相同名称,可使用「角色名#id」方式锁定明确资源
+6. 【角色背景】:
+ - NPC参与任务的背景描述,用于给角色扮演的NPC提供明确的情境信息,用户看不到该内容
+ - 以第二人称书写,格式为「你是谁,你做了什么」
+ - 以剧情信息为主,无需增加太多人物性格、语气等生动化描述,可分条罗列
+7. 【互动问题】:
+ - 包含NPC提问的问题、问题的答案、以及该问答对应的图片编号
+ - 同一个图片对应多个问题时,编号重复
+ - 问题和答案只写最简单版本,示例对话中可适度包装让对话更自然流畅
+ - **图片命名规则**:
+ - 底图:`[编号]_question.png`,示例:`0102003_question.png`
+ - 叠加图:`[编号]_option_[序号].png`,示例:`0102003_option_00.png`
+ - ⚠️ 注意:需要XML格式
+8. 【回合设定】:每个问题设置1-2个回合
+9. 【通过规则】:
+ - 对话完成后判断所有问题是否通过,只要有一个没通过则判定为Oops
+ - 列出用户需要说出的所有正确答案内容
+10. 【示例对话】:
+ - 以NPC进行开场白和结尾句,对话流畅自然,避免无意义回合
+ - 语言难度低,符合1-3年级学生水平
+ - 对用户回答有引导,但不能直接用Yes/No回复
+ - 可增加NPC回应性话术让对话更自然
+## 完整示例
+```
+【任务标题】
+找同色工具修机器人
+
+【任务背景】
+为快速修复破损的机器人,你需要找出合适工具复原工具箱。
+
+【任务描述】
+你需要找出成套的工具,复原工具箱
+
+【通关知识】
+- It's... 它是……
+- Yes, I think it's... 是的,我认为它是……
+- It seems that... 它看起来是……
+
+【对话角色】
+SecurityA
+
+【角色背景】
+你是机器人SecurityA,你需要修复一个破损的机器人,但需要完整的工具箱,所以你需要让用户帮你找出工具箱中所需要的工具。
+
+【互动问题】
+#图片编号:00
+Q: Let's find tools together. What's the color of the pliers?
+A: It's blue.
+
+#图片编号:00
+Q: What's the color of the ruler?
+A: It's yellow.
+
+#图片编号:01
+Q:What's the color of the hammer?
+A:And how about the screwdriver?
+
+#图片编号:02
+Q: And how about the screwdriver?
+A: Blue. Yes, I think it's blue.
+
+【回合设定】
+每个问题1-2个回合
+
+【通过规则】
+用户说出:
+1. 钳子是蓝色的
+2. 尺子是黄色的
+3. 锤子是橙色的
+4. 螺丝刀是蓝色的
+
+【示例对话】
+NPC: Let's find tools together. What's the color of the pliers?
+User: It's blue.
+NPC: What's the color of the ruler?
+User: It's yellow.
+NPC: What's the color of the hammer?
+User: It's orange.
+NPC: And how about the screwdriver?
+User: Blue. Yes, I think it's blue.
+NPC: It seems that the pliers and the screwdriver belong to the same set of toolkit. Good job!
+```
diff --git a/skills/dialogue-fill-in-blanks-config/SKILL.md b/skills/dialogue-fill-in-blanks-config/SKILL.md
new file mode 100644
index 0000000..a457f33
--- /dev/null
+++ b/skills/dialogue-fill-in-blanks-config/SKILL.md
@@ -0,0 +1,67 @@
+---
+name: dialogue-fill-in-blanks-config
+version: 1.0.0
+description: 对话挖空类互动组件配置标准化规范,用于剧本中对话挖空类型组件的标准化生成
+metadata:
+ requires:
+ permissions: ["sheets:spreadsheet"]
+ identity: bot
+---
+# 对话挖空组件配置规范
+## 适用场景
+当【类型】列内容为【对话挖空】时,使用本规范生成组件配置
+## 配置格式
+### 字段要求
+1. 【任务标题】:结合剧情上下文设计语义化标题,点明互动核心,例如「介绍照片」
+2. 【资源配置】:
+ - 类型为「对话挖空」时,填写:无
+ - 类型为「对话挖空-配图」时,填写:
+ ```
+ 图片时机:
+ 互动内容
+ 互动反馈
+
+ 音频载体:Pioneer Band
+ ```
+3. 【情境引入】:填写挖空前的对话上下文,格式为「角色名 : 对话内容」
+4. 【互动内容】:
+ - 包含带挖空(__标记)的句子,末尾标注「(音频)」
+ - 列出选项,正确选项标注「(正确)」,错误选项无需标注
+ - 版本1(单空、单内容组)格式示例:
+ ```
+ This is my picture. It's ___, green and orange.(音频)
+ 选项1:blue(正确)
+ 选项2:black
+ ```
+5. 【互动反馈】:
+ - 反馈台词要求:根据剧情上下文生成符合角色人设、场景逻辑的合适台词
+ - 正确反馈格式:「正确 角色名 : 对话内容」
+ - 错误反馈格式:「错误 角色名 : 对话内容」
+6. 【后置对话】:默认值为「无」
+## 完整示例
+```
+【任务标题】
+介绍照片
+
+【资源配置】
+图片时机:
+互动内容
+互动反馈
+
+音频载体:Pioneer Band
+
+【情境引入】
+Johnny : What's That?
+
+【互动内容】
+This is my picture. It's ___, green and orange.(音频)
+选项1:blue(正确)
+选项2:black
+
+【互动反馈】
+正确 Johnny :It looks nice!
+错误 Johnny :Come on! It's not what you said.
+
+【后置对话】
+无
+```
diff --git a/skills/dialogue-image-description-config/SKILL.md b/skills/dialogue-image-description-config/SKILL.md
new file mode 100644
index 0000000..0498f62
--- /dev/null
+++ b/skills/dialogue-image-description-config/SKILL.md
@@ -0,0 +1,68 @@
+---
+name: dialogue-image-description-config
+version: 1.0.0
+description: 看图说话类互动组件配置标准化规范,用于剧本中看图说话/看图说话-配图类型组件的标准化生成
+metadata:
+ requires:
+ permissions: ["sheets:spreadsheet"]
+ identity: bot
+---
+# 看图说话组件配置规范
+## 适用场景
+当【类型】列内容为【看图说话】或【看图说话-配图】时,使用本规范生成组件配置
+## 配置格式
+### 字段要求
+1. 【任务标题】:结合图片内容设计语义化标题,点明看图说话的核心主题,10字以内
+2. 【资源配置】:
+ - 类型为「看图说话」时,填写:无
+ - 类型为「看图说话-配图」时,填写:
+ ```
+ 图片时机:
+ 互动内容
+ 互动反馈
+
+ 音频载体:Pioneer Band
+ ```
+3. 【情境引入】:描述图片场景背景,说明对话发生的上下文
+4. 【互动内容】:
+ - 首先说明图片内容和互动要求
+ - 列出对话流程:NPC提问内容、正确回答内容
+ - 辅助信息:提供必要的词汇、句型提示
+5. 【互动反馈】:
+ - 正确反馈:NPC的正向回应台词,符合角色人设
+ - 错误反馈:NPC的引导性台词,鼓励用户重新尝试
+6. 【后置对话】:默认值为「无」,如有后续剧情可填写
+## 示例
+```
+【任务标题】
+描述机器人故障
+
+【资源配置】
+图片时机:
+互动内容
+互动反馈
+
+音频载体:Pioneer Band
+
+【情境引入】
+机器人身体、嘴巴、脑袋冒烟,神秘人Vicky打电话来询问故障情况,仅出声音不出镜。
+
+【互动内容】
+图片内容:冒烟的故障机器人
+互动要求:回答Vicky的三个问题,描述机器人的故障
+对话流程:
+Vicky: What's your robot's problem? Is its body broken?
+正确回答:Yes, its body is broken.
+Vicky: Is its head broken?
+正确回答:Yes, its head is broken.
+Vicky: Is its mouth broken?
+正确回答:Yes, its mouth is broken.
+辅助信息:使用句型 "Yes, its [身体部位] is broken." 描述故障。
+
+【互动反馈】
+正确 Vicky: Okay, I'm on my way!
+错误 Vicky: Try again, tell me what's wrong with the robot.
+
+【后置对话】
+无
+```
diff --git a/skills/dialogue-reading-config/SKILL.md b/skills/dialogue-reading-config/SKILL.md
new file mode 100644
index 0000000..e77b6f3
--- /dev/null
+++ b/skills/dialogue-reading-config/SKILL.md
@@ -0,0 +1,41 @@
+---
+name: dialogue-reading-config
+version: 1.0.0
+description: 对话朗读类互动组件配置标准化规范,用于剧本中对话朗读/对话朗读-配图类型组件的标准化生成
+metadata:
+ requires:
+ permissions: ["sheets:spreadsheet"]
+ identity: bot
+---
+# 对话朗读组件配置规范
+## 适用场景
+当【类型】列内容为【对话朗读】或【对话朗读-配图】时,使用本规范生成组件配置
+## 配置格式
+### 字段要求
+1. 【任务标题】:
+ - 设计规则:结合剧情上下文,提炼互动核心内容,使用中文口语化表述,符合低龄用户认知
+ - 核心要素:包含互动对象+核心动作/状态,避免笼统的「对话X」编号
+ - 示例:「机器人的头坏了」「帮机器人拼接头和身体」
+2. 【资源配置】:
+ - 类型为「对话朗读」时,填写:图片时机:无
+ - 类型为「对话朗读-配图」时,填写:图片时机:互动内容
+3. 【情景引入】:默认值为「无」
+4. 【互动内容】:填写带有知识点的朗读句子(与原台词保持一致)
+5. 【后置对话】:默认值为「无」
+## 示例
+### 对话朗读-配图类型示例
+```
+【任务标题】机器人的头坏了
+【资源配置】图片时机:互动内容
+【情景引入】无
+【互动内容】Its head is broken.
+【后置对话】无
+```
+### 对话朗读类型示例
+```
+【任务标题】帮机器人拼接头和身体
+【资源配置】图片时机:无
+【情景引入】无
+【互动内容】Eva, can you put the head and body together?
+【后置对话】无
+```
diff --git a/skills/dialogue-sentence-building-config/SKILL.md b/skills/dialogue-sentence-building-config/SKILL.md
new file mode 100644
index 0000000..dcde526
--- /dev/null
+++ b/skills/dialogue-sentence-building-config/SKILL.md
@@ -0,0 +1,69 @@
+---
+name: dialogue-sentence-building-config
+version: 1.0.0
+description: 对话组句类互动组件配置标准化规范,用于剧本中对话组句/对话组句-配图类型组件的标准化生成
+metadata:
+ requires:
+ permissions: ["sheets:spreadsheet"]
+ identity: bot
+---
+# 对话组句组件配置规范
+## 适用场景
+当【类型】列内容为【对话组句】或【对话组句-配图】时,使用本规范生成组件配置
+## 配置格式
+### 字段要求
+1. 【任务标题】:结合剧情上下文设计语义化标题,点明组句的核心场景,例如「提醒 Otis 系好安全带」
+2. 【资源配置】:
+ - 类型为「对话组句」时,填写:无
+ - 类型为「对话组句-配图」时,填写:
+ ```
+ 图片时机:
+ 互动内容
+ 互动反馈
+
+ 音频载体:Pioneer Band
+ ```
+3. 【情境引入】:填写组题出现前的对话上下文,格式为「角色名 : 对话内容」
+4. 【互动内容】:
+ - 第一行:题目中文描述,说明组句的目标场景
+ - 第二行:末尾标注「(音频)」
+ - 第三部分:列出所有可选单词/短语选项,编号为「选项1:XX」「选项2:XX」依次排列
+ - 第四部分:答案,格式为「答案:XX」(填写完整的正确句子)
+ - 第五部分:辅助信息,提供语法提示、句子结构说明等帮助内容
+5. 【互动反馈】:
+ - 反馈台词要求:根据剧情上下文生成符合角色人设、场景逻辑的合适台词
+ - 正确反馈格式:「正确 角色名 : 对话内容」
+ - 错误反馈格式:「错误 角色名 : 对话内容」
+6. 【后置对话】:默认值为「无」
+## 完整示例
+```
+【任务标题】
+提醒 Otis 系好安全带
+
+【资源配置】
+图片时机:
+互动内容
+互动反馈
+
+音频载体:Pioneer Band
+
+【情境引入】
+User: Let me ask Smith if Maeve is really a visitor.
+
+【互动内容】
+题目:询问 Smith:Maeve 真的是你邀请的客人吗?
+(音频)
+选项1:is
+选项2:the visitor
+选项3:you invited
+选项4:Maeve
+答案:Is Maeve the visitor you invited?
+辅助信息:可以用 is 引导的一般疑问句询问;you invited 是 visitor 的后置定语
+
+【互动反馈】
+正确 User : Got it!Sent successfully!
+错误 User : Oh no, this isn't a correct sentence.
+
+【后置对话】
+无
+```
diff --git a/skills/dialogue-single-choice-image-config/SKILL.md b/skills/dialogue-single-choice-image-config/SKILL.md
new file mode 100644
index 0000000..c7aa2c6
--- /dev/null
+++ b/skills/dialogue-single-choice-image-config/SKILL.md
@@ -0,0 +1,52 @@
+---
+name: dialogue-single-choice-image-config
+version: 1.0.0
+description: 图片单选类互动组件配置标准化规范,用于剧本中图片单选/图片单选-配图类型组件的标准化生成
+metadata:
+ requires:
+ permissions: ["sheets:spreadsheet"]
+ identity: bot
+---
+# 图片单选组件配置规范
+## 适用场景
+当【类型】列内容为【图片单选】或【图片单选-配图】时,使用本规范生成组件配置
+## 配置格式
+### 字段要求
+1. 【任务标题】:结合剧情上下文设计语义化标题,点明互动核心,例如「查看公告板说明书」
+2. 【情境引入】:填写题目出现前的对话上下文,格式为「角色名 : 对话内容」
+3. 【互动内容】:
+ - 第一行:题目描述,末尾标注「(音频)」,目标对象用$符号包裹,例如`Find a $headset$ in the picture. (音频)`
+ - 第二部分:列出选项,编号为「00」「01」「02」... 依次排列
+ - 第三部分:答案,格式为「答案:XX」(填写对应选项编号)
+ - 第四部分:辅助信息,格式为「辅助信息:XX 指“XXX”」,为目标词汇提供中文释义
+4. 【互动反馈】:
+ - 反馈台词要求:根据剧情上下文生成符合角色人设、场景逻辑的合适台词
+ - 正确反馈格式:「正确 角色名 : 对话内容」
+ - 错误反馈格式:「错误 角色名 : 对话内容」
+5. 【后置对话】:默认值为「无」
+## 完整示例
+```
+【任务标题】
+查看公告板说明书
+
+【情境引入】
+Sarah: We've got a message from Rebecca.
+User: Let's take a look.
+
+【互动内容】
+Find a $headset$ in the picture. (音频)
+选项:
+00
+01
+02
+答案:
+01
+辅助信息:headset 指“耳机”。
+
+【互动反馈】
+正确 Sarah :What a cool headset!
+错误 Sarah:That's not a headset.
+
+【后置对话】
+无
+```
diff --git a/skills/interactive-component-json/SKILL.md b/skills/interactive-component-json/SKILL.md
new file mode 100644
index 0000000..60e1f2b
--- /dev/null
+++ b/skills/interactive-component-json/SKILL.md
@@ -0,0 +1,127 @@
+---
+name: interactive-component-json
+version: 0.1.0
+description: >
+ 互动组件配置JSON生成器(全量版)。覆盖约30种互动组件类型,从规范化的原始剧本格式出发,
+ 经过组件类型匹配、知识点匹配、AI衍生字段生产,最终输出符合系统要求的组件配置JSON,
+ 并持久化到本地SQLite数据库。组件类型采用注册机制,支持逐步扩展。
+metadata:
+ requires:
+ tools: ["exec", "read", "write"]
+ identity: bot
+---
+
+# 互动组件配置JSON生成器
+
+## 概述
+
+本skill实现从**原始剧本格式** → **结构化组件配置JSON**的完整生产流水线。
+覆盖全部约30种互动组件类型,采用**注册机制**逐步扩展。
+
+### 核心流程
+
+```
+原始剧本文本
+ ↓
+[1] 组件类型识别与匹配(基于注册表)
+ ↓
+[2] 知识点提取与匹配(词库/句型库校验)
+ ↓
+[3] AI衍生字段生产(基于组件类型专属prompt)
+ ↓
+[4] 组件配置JSON生成(按schema校验)
+ ↓
+[5] 写入本地SQLite数据库
+ ↓
+(后续) HTML预览 / 推送公司数据库
+```
+
+## 目录结构
+
+```
+interactive-component-json/
+├── SKILL.md # 本文件 - skill入口与总览
+├── references/ # 参考文档
+│ ├── component_registry.md # 🔑 组件类型注册表(全量,逐步填充)
+│ ├── script_format_spec.md # 原始剧本格式规范(输入格式定义)
+│ └── json_output_spec.md # 最终JSON输出格式规范(输出格式定义)
+├── prompts/ # AI衍生字段生产的提示词(按组件类型分文件)
+│ ├── README.md # 提示词目录说明
+│ └── {component_type}.md # 各组件类型的衍生字段prompt(逐步添加)
+├── schemas/ # JSON Schema定义(按组件类型)
+│ ├── common.json # 公共字段schema
+│ └── {component_type}.json # 各组件类型的专属schema(逐步添加)
+├── scripts/ # 自动化脚本
+│ ├── db_manager.py # SQLite数据库管理(建表/读写/查询)
+│ ├── parse_script.py # 剧本文本解析器
+│ ├── match_component.py # 组件类型匹配器
+│ ├── match_knowledge.py # 知识点匹配器
+│ ├── generate_json.py # JSON配置生成器
+│ └── validate_json.py # JSON校验器
+└── db/
+ └── components.db # SQLite数据库(运行时生成)
+```
+
+## 组件类型注册机制
+
+### 设计理念
+- 不硬编码组件类型列表,通过**注册表**(`references/component_registry.md`)统一管理
+- 每新增一种组件类型,需要注册3项:
+ 1. 在注册表中添加类型定义(标识、名称、匹配规则、字段列表)
+ 2. 在 `prompts/` 中添加对应的AI衍生prompt
+ 3. 在 `schemas/` 中添加对应的JSON Schema
+- 注册表是**唯一的类型真相源**,所有脚本从注册表读取类型信息
+
+### 当前注册进度
+详见 `references/component_registry.md`
+
+## 数据库设计(SQLite)
+
+### 表结构
+
+1. **`components`** — 组件主表
+ - component_id, script_id, component_index
+ - component_type, component_subtype, level, unit_id
+ - raw_text, parsed_data(JSON), knowledge_points(JSON)
+ - ai_derived_fields(JSON), final_config_json(JSON)
+ - status: draft → parsed → matched → generated → validated → exported
+ - created_at, updated_at
+
+2. **`generation_logs`** — 生成日志表
+ - log_id, component_id, step, input/output摘要, model_used, success, error
+
+### 操作入口
+```bash
+python3 scripts/db_manager.py init # 初始化
+python3 scripts/db_manager.py stats # 统计
+python3 scripts/db_manager.py list # 列表
+```
+
+## 开发计划
+
+### Phase 1: 基础框架 ← 当前阶段
+- [x] 目录结构
+- [x] 数据库DDL与基础操作
+- [x] 组件注册表框架
+- [ ] 逐步注册组件类型(随参考文件提供)
+
+### Phase 2: 输入输出格式定义
+- [ ] 原始剧本格式规范(需提供样例)
+- [ ] 目标JSON格式规范(需提供样例)
+
+### Phase 3: 解析与匹配引擎
+- [ ] 剧本文本解析器
+- [ ] 组件类型匹配器
+- [ ] 知识点匹配器
+
+### Phase 4: AI衍生字段生产
+- [ ] 各组件类型的prompt模板(逐步添加)
+- [ ] AI调用流程
+
+### Phase 5: JSON生成与校验
+- [ ] JSON Schema(逐步添加)
+- [ ] 配置JSON生成器 + 校验器
+
+### Phase 6: 扩展
+- [ ] HTML预览
+- [ ] 公司数据库推送
diff --git a/skills/interactive-component-json/db/components.db b/skills/interactive-component-json/db/components.db
new file mode 100644
index 0000000..e2d7f94
Binary files /dev/null and b/skills/interactive-component-json/db/components.db differ
diff --git a/skills/interactive-component-json/prompts/README.md b/skills/interactive-component-json/prompts/README.md
new file mode 100644
index 0000000..b436c6e
--- /dev/null
+++ b/skills/interactive-component-json/prompts/README.md
@@ -0,0 +1,33 @@
+# Prompts 目录说明
+
+本目录存放各组件类型的**AI衍生字段生产提示词**。
+
+## 命名规则
+- 文件名 = 组件类型标识(与注册表中的 `type_id` 一致)+ `.md`
+- 每个文件包含:该组件类型需要AI生成的字段列表、生成规则、few-shot示例
+
+## 当前文件
+随组件类型注册逐步添加,与 `references/component_registry.md` 保持同步。
+
+## 提示词模板结构
+
+每个prompt文件应包含以下部分:
+
+```markdown
+# {组件中文名称} AI衍生字段生产提示词
+
+## 组件类型
+type_id: {type_id}
+
+## 输入字段
+(从剧本解析后获得的结构化数据,明确有哪些字段可用)
+
+## 需要生成的衍生字段
+(列出AI需要生成的具体字段及其规范)
+
+## 生成规则与约束
+(语言风格、难度控制、格式约束、词库范围等)
+
+## Few-shot 示例
+(输入→输出的完整示例,至少2组)
+```
diff --git a/skills/interactive-component-json/references/component_registry.md b/skills/interactive-component-json/references/component_registry.md
new file mode 100644
index 0000000..fc30881
--- /dev/null
+++ b/skills/interactive-component-json/references/component_registry.md
@@ -0,0 +1,207 @@
+# 互动组件类型注册表
+
+> 本文件是所有组件类型的**唯一真相源**。新增组件类型时在此注册。
+
+## 注册状态总览
+
+| # | 类型标识 | 中文名称 | 注册状态 | prompt | schema |
+|---|---------|---------|---------|--------|--------|
+| 1 | `dialogue_reading` | 对话朗读 / 对话朗读-配图 | ✅ 已注册 | 🔲 | 🔲 |
+| 2 | `dialogue_fill_in_blanks` | 对话挖空 / 对话挖空-配图 | ✅ 已注册 | 🔲 | 🔲 |
+| 3 | `dialogue_sentence_building` | 对话组句 / 对话组句-配图 | ✅ 已注册 | 🔲 | 🔲 |
+| 4 | `dialogue_single_choice_image` | 图片单选 / 图片单选-配图 | ✅ 已注册 | 🔲 | 🔲 |
+| 5 | `dialogue_image_description` | 看图说话 / 看图说话-配图 | ✅ 已注册 | 🔲 | 🔲 |
+| 6 | `dialogue_core_navigation` | 核心互动 / 导览配置 | ✅ 已注册 | 🔲 | 🔲 |
+| 7 | `image_interaction` | 图片互动(坐标点选) | ✅ 已注册 | 🔲 | 🔲 |
+| 8 | `image_unordered` | 图片互动-图片无序 | ✅ 已注册 | 🔲 | 🔲 |
+| 9 | `image_ordered` | 图片互动-图片有序 | ✅ 已注册 | 🔲 | 🔲 |
+| 10-30 | *待注册* | *随参考文件逐步添加* | 🔲 | 🔲 | 🔲 |
+
+**当前已注册:9 / ~30**
+
+---
+
+## 已注册组件详情
+
+### 1. dialogue_reading — 对话朗读
+
+**匹配规则:** 【类型】= "对话朗读" 或 "对话朗读-配图"
+**子类型:** basic(对话朗读)/ with_image(对话朗读-配图)
+**来源规范:** `skills/dialogue-reading-config/SKILL.md`
+
+**字段列表:**
+| 字段 | 必填 | 来源 | 说明 |
+|------|------|------|------|
+| 任务标题 | ✅ | 剧本 | 10字以内,语义化 |
+| 资源配置 | ✅ | 规则 | basic→"图片时机:无" / with_image→"图片时机:互动内容" |
+| 情景引入 | ❌ | 剧本 | 默认"无" |
+| 互动内容 | ✅ | 剧本 | 朗读句子 |
+| 后置对话 | ❌ | 剧本 | 默认"无" |
+
+---
+
+### 2. dialogue_fill_in_blanks — 对话挖空
+
+**匹配规则:** 【类型】= "对话挖空" 或 "对话挖空-配图"
+**子类型:** basic / with_image
+**来源规范:** `skills/dialogue-fill-in-blanks-config/SKILL.md`
+
+**字段列表:**
+| 字段 | 必填 | 来源 | 说明 |
+|------|------|------|------|
+| 任务标题 | ✅ | 剧本+AI | 语义化标题 |
+| 资源配置 | ✅ | 规则 | basic→"无" / with_image→图片时机+音频载体 |
+| 情境引入 | ❌ | 剧本 | 对话上下文 |
+| 互动内容 | ✅ | 剧本 | 含__标记的句子+选项+正确标记 |
+| 互动反馈 | ✅ | 剧本+AI | 正确/错误反馈台词 |
+| 后置对话 | ❌ | 剧本 | 默认"无" |
+
+---
+
+### 3. dialogue_sentence_building — 对话组句
+
+**匹配规则:** 【类型】= "对话组句" 或 "对话组句-配图"
+**子类型:** basic / with_image
+**来源规范:** `skills/dialogue-sentence-building-config/SKILL.md`
+
+**字段列表:**
+| 字段 | 必填 | 来源 | 说明 |
+|------|------|------|------|
+| 任务标题 | ✅ | 剧本+AI | 语义化标题 |
+| 资源配置 | ✅ | 规则 | basic→"无" / with_image→图片时机+音频载体 |
+| 情境引入 | ❌ | 剧本 | 对话上下文 |
+| 互动内容 | ✅ | 剧本 | 题目描述+选项单词+答案+辅助信息 |
+| 互动反馈 | ✅ | 剧本+AI | 正确/错误反馈 |
+| 后置对话 | ❌ | 剧本 | 默认"无" |
+
+---
+
+### 4. dialogue_single_choice_image — 图片单选
+
+**匹配规则:** 【类型】= "图片单选" 或 "图片单选-配图"
+**子类型:** basic / with_image
+**来源规范:** `skills/dialogue-single-choice-image-config/SKILL.md`
+
+**字段列表:**
+| 字段 | 必填 | 来源 | 说明 |
+|------|------|------|------|
+| 任务标题 | ✅ | 剧本+AI | 语义化标题 |
+| 情境引入 | ❌ | 剧本 | 对话上下文 |
+| 互动内容 | ✅ | 剧本 | 题干($高亮$)+选项编号+答案编号+辅助信息 |
+| 互动反馈 | ✅ | 剧本+AI | 正确/错误反馈 |
+| 后置对话 | ❌ | 剧本 | 默认"无" |
+
+---
+
+### 5. dialogue_image_description — 看图说话
+
+**匹配规则:** 【类型】= "看图说话" 或 "看图说话-配图"
+**子类型:** basic / with_image
+**来源规范:** `skills/dialogue-image-description-config/SKILL.md`
+
+**字段列表:**
+| 字段 | 必填 | 来源 | 说明 |
+|------|------|------|------|
+| 任务标题 | ✅ | 剧本+AI | 10字以内 |
+| 资源配置 | ✅ | 规则 | basic→"无" / with_image→图片时机+音频载体 |
+| 情境引入 | ❌ | 剧本 | 场景背景 |
+| 互动内容 | ✅ | 剧本 | 图片内容+对话流程+辅助信息 |
+| 互动反馈 | ✅ | 剧本+AI | 正确/错误反馈 |
+| 后置对话 | ❌ | 剧本 | 默认"无" |
+
+---
+
+### 6. dialogue_core_navigation — 核心互动/导览配置
+
+**匹配规则:** 【类型】= "核心互动" 或 "导览配置"
+**子类型:** 值本身
+**来源规范:** `skills/dialogue-core-navigation-config/SKILL.md`
+
+**字段列表:**
+| 字段 | 必填 | 来源 | 说明 |
+|------|------|------|------|
+| 任务标题 | ✅ | 剧本 | 10字以内 |
+| 任务背景 | ✅ | 剧本+AI | 25字左右,二人称 |
+| 任务描述 | ✅ | 剧本+AI | 15字左右,"你需要…" |
+| 通关知识 | ✅ | 剧本 | 知识点列表 |
+| 对话角色 | ✅ | 剧本 | 角色名(匹配资源库) |
+| 角色背景 | ✅ | 剧本+AI | NPC背景,二人称 |
+| 互动问题 | ✅ | 剧本 | Q&A+图片编号 |
+| 回合设定 | ✅ | 剧本 | 每问题1-2回合 |
+| 通过规则 | ✅ | 剧本+AI | 判定条件 |
+| 示例对话 | ✅ | 剧本+AI | NPC开头和结尾 |
+
+---
+
+### 7. image_interaction — 图片互动(坐标点选)
+
+**匹配规则:** 【类型】= "图片互动"(不含"无序""有序")
+**来源规范:** `business_knowledge/教研生产配置修改规范.md`
+
+**字段列表:**
+| 字段 | 必填 | 来源 | 说明 |
+|------|------|------|------|
+| 任务标题 | ✅ | 剧本 | |
+| 情境引入 | ❌ | 剧本 | |
+| 互动内容 | ✅ | 剧本 | 题干($高亮$)+答案坐标[x1,y1,x2,y2]+辅助信息 |
+| 互动反馈 | ❌ | 剧本+AI | |
+| 后置对话 | ❌ | 剧本 | |
+| 图片资源 | ✅ | 命名规则 | {ID}.png |
+
+---
+
+### 8. image_unordered — 图片互动-图片无序
+
+**匹配规则:** 【类型】含 "图片无序"
+**来源规范:** `business_knowledge/教研生产配置修改规范.md`
+
+**字段列表:**
+| 字段 | 必填 | 来源 | 说明 |
+|------|------|------|------|
+| 任务标题 | ✅ | 剧本 | |
+| 情境引入 | ❌ | 剧本 | |
+| 互动内容 | ✅ | 剧本 | 题干+选项编号+答案编号(无序)+辅助信息 |
+| 互动反馈 | ❌ | 剧本+AI | |
+| 后置对话 | ❌ | 剧本 | |
+| 图片资源 | ✅ | 命名规则 | question/option/answer |
+
+---
+
+### 9. image_ordered — 图片互动-图片有序
+
+**匹配规则:** 【类型】含 "图片有序"
+**来源规范:** `business_knowledge/教研生产配置修改规范.md`
+
+**字段列表:**
+| 字段 | 必填 | 来源 | 说明 |
+|------|------|------|------|
+| 任务标题 | ✅ | 剧本 | |
+| 情境引入 | ❌ | 剧本 | |
+| 互动内容 | ✅ | 剧本 | 题干+选项编号+答案编号(有序)+辅助信息 |
+| 互动反馈 | ❌ | 剧本+AI | |
+| 后置对话 | ❌ | 剧本 | |
+| 图片资源 | ✅ | 命名规则 | question/option/answer |
+
+---
+
+## 新增组件注册模板
+
+新增一种组件类型时,复制以下模板填充:
+
+```markdown
+### N. {type_id} — {中文名称}
+
+**匹配规则:** 【类型】= "{剧本中的类型值}"
+**子类型:** {如有}
+**来源规范:** {参考文档路径}
+
+**字段列表:**
+| 字段 | 必填 | 来源 | 说明 |
+|------|------|------|------|
+| ... | ... | ... | ... |
+
+**AI衍生字段:**
+| 字段 | 生成规则 | prompt文件 |
+|------|---------|-----------|
+| ... | ... | prompts/{type_id}.md |
+```
diff --git a/skills/interactive-component-json/references/json_output_spec.md b/skills/interactive-component-json/references/json_output_spec.md
new file mode 100644
index 0000000..bad0981
--- /dev/null
+++ b/skills/interactive-component-json/references/json_output_spec.md
@@ -0,0 +1,19 @@
+# 最终JSON输出格式规范(输出格式定义)
+
+> ⚠️ 待填充:需要提供公司系统接收的实际JSON格式样例
+
+## 预期内容
+
+1. **目标系统**:公司数据库/后台系统接收的JSON格式
+2. **字段映射**:教研配置字段 → 系统JSON字段的对应关系
+3. **数据类型**:每个字段的类型约束
+4. **嵌套结构**:JSON的层级结构定义
+5. **枚举值**:各字段的可选值范围
+
+## 待确认问题
+
+- [ ] 最终JSON是推送到哪个系统/API?
+- [ ] 是否有现成的JSON Schema或接口文档?
+- [ ] 不同组件类型的JSON结构差异有多大?
+- [ ] 是否有版本字段或时间戳要求?
+- [ ] 图片资源的引用方式(URL?文件名?token?)
diff --git a/skills/interactive-component-json/references/script_format_spec.md b/skills/interactive-component-json/references/script_format_spec.md
new file mode 100644
index 0000000..04e1d19
--- /dev/null
+++ b/skills/interactive-component-json/references/script_format_spec.md
@@ -0,0 +1,18 @@
+# 原始剧本格式规范(输入格式定义)
+
+> ⚠️ 待填充:需要提供一份实际的原始剧本样例,明确输入格式
+
+## 预期内容
+
+1. **剧本来源**:飞书文档(知识库)中的结构化内容
+2. **整体结构**:每个剧本包含多个场景/回合,每个回合可能包含0-N个互动组件
+3. **组件标记方式**:通过【类型】列标识组件类型
+4. **知识点标注**:在组件配置中或关联列中标注对应的知识点
+
+## 待确认问题
+
+- [ ] 剧本是以飞书文档还是电子表格形式存在?
+- [ ] 组件在剧本中的定位方式(行号?标记?)
+- [ ] 一个剧本文档中通常包含多少个互动组件?
+- [ ] 知识点是在组件配置中直接标注,还是单独一列?
+- [ ] 是否有组件ID的编号规则需要遵循?
diff --git a/skills/interactive-component-json/scripts/db_manager.py b/skills/interactive-component-json/scripts/db_manager.py
new file mode 100644
index 0000000..dded8eb
--- /dev/null
+++ b/skills/interactive-component-json/scripts/db_manager.py
@@ -0,0 +1,233 @@
+#!/usr/bin/env python3
+"""
+互动组件配置 SQLite 数据库管理器
+负责建表、CRUD操作、查询、导出
+"""
+
+import sqlite3
+import json
+import os
+from datetime import datetime
+
+DB_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "db", "components.db")
+
+
+def get_connection():
+ """获取数据库连接"""
+ os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
+ conn = sqlite3.connect(DB_PATH)
+ conn.row_factory = sqlite3.Row
+ conn.execute("PRAGMA journal_mode=WAL")
+ conn.execute("PRAGMA foreign_keys=ON")
+ return conn
+
+
+def init_db():
+ """初始化数据库表结构"""
+ conn = get_connection()
+ cursor = conn.cursor()
+
+ # 组件主表
+ cursor.execute("""
+ CREATE TABLE IF NOT EXISTS components (
+ component_id INTEGER PRIMARY KEY AUTOINCREMENT,
+ script_id TEXT NOT NULL, -- 剧本标识(如 S01E01、文档token等)
+ component_index INTEGER DEFAULT 0, -- 组件在剧本中的顺序号
+ component_type TEXT NOT NULL, -- 组件类型标识(dialogue_reading等)
+ component_subtype TEXT DEFAULT 'basic', -- 子类型(basic/with_image/核心互动/导览配置)
+ level TEXT DEFAULT 'L1', -- 级别(L1/L2)
+ unit_id TEXT, -- 单元标识
+ raw_text TEXT, -- 原始剧本文本
+ parsed_data TEXT, -- JSON: 解析后的结构化中间数据
+ knowledge_points TEXT, -- JSON: 匹配到的知识点
+ ai_derived_fields TEXT, -- JSON: AI衍生字段
+ final_config_json TEXT, -- JSON: 最终配置
+ status TEXT DEFAULT 'draft'
+ CHECK(status IN ('draft','parsed','matched','generated','validated','exported')),
+ created_at TEXT DEFAULT (datetime('now','localtime')),
+ updated_at TEXT DEFAULT (datetime('now','localtime'))
+ )
+ """)
+
+ # 生成日志表
+ cursor.execute("""
+ CREATE TABLE IF NOT EXISTS generation_logs (
+ log_id INTEGER PRIMARY KEY AUTOINCREMENT,
+ component_id INTEGER NOT NULL,
+ step TEXT NOT NULL, -- parse/match_type/match_knowledge/ai_derive/generate_json/validate
+ input_summary TEXT, -- 输入摘要
+ output_summary TEXT, -- 输出摘要
+ model_used TEXT, -- 使用的AI模型(如有)
+ success INTEGER DEFAULT 1, -- 1=成功 0=失败
+ error_message TEXT, -- 失败时的错误信息
+ duration_ms INTEGER, -- 耗时(毫秒)
+ created_at TEXT DEFAULT (datetime('now','localtime')),
+ FOREIGN KEY (component_id) REFERENCES components(component_id) ON DELETE CASCADE
+ )
+ """)
+
+ # 索引
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_components_script ON components(script_id)")
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_components_type ON components(component_type)")
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_components_status ON components(status)")
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_logs_component ON generation_logs(component_id)")
+
+ conn.commit()
+ conn.close()
+ print(f"✅ 数据库初始化完成: {DB_PATH}")
+
+
+# ============ CRUD 操作 ============
+
+def insert_component(script_id, component_type, component_subtype="basic",
+ level="L1", unit_id=None, raw_text=None, component_index=0):
+ """插入一条新的组件记录,返回 component_id"""
+ conn = get_connection()
+ cursor = conn.cursor()
+ cursor.execute("""
+ INSERT INTO components (script_id, component_index, component_type, component_subtype,
+ level, unit_id, raw_text)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """, (script_id, component_index, component_type, component_subtype,
+ level, unit_id, raw_text))
+ component_id = cursor.lastrowid
+ conn.commit()
+ conn.close()
+ return component_id
+
+
+def update_component_field(component_id, field, value):
+ """更新组件的某个字段(支持 parsed_data, knowledge_points, ai_derived_fields, final_config_json, status)"""
+ allowed_fields = {'parsed_data', 'knowledge_points', 'ai_derived_fields',
+ 'final_config_json', 'status', 'raw_text'}
+ if field not in allowed_fields:
+ raise ValueError(f"不允许更新的字段: {field}")
+
+ conn = get_connection()
+ cursor = conn.cursor()
+ cursor.execute(f"""
+ UPDATE components SET {field} = ?, updated_at = datetime('now','localtime')
+ WHERE component_id = ?
+ """, (value, component_id))
+ conn.commit()
+ conn.close()
+
+
+def get_component(component_id):
+ """获取单个组件"""
+ conn = get_connection()
+ row = conn.execute("SELECT * FROM components WHERE component_id = ?",
+ (component_id,)).fetchone()
+ conn.close()
+ return dict(row) if row else None
+
+
+def list_components(script_id=None, component_type=None, status=None, limit=100):
+ """查询组件列表"""
+ conn = get_connection()
+ query = "SELECT * FROM components WHERE 1=1"
+ params = []
+ if script_id:
+ query += " AND script_id = ?"
+ params.append(script_id)
+ if component_type:
+ query += " AND component_type = ?"
+ params.append(component_type)
+ if status:
+ query += " AND status = ?"
+ params.append(status)
+ query += " ORDER BY script_id, component_index LIMIT ?"
+ params.append(limit)
+
+ rows = conn.execute(query, params).fetchall()
+ conn.close()
+ return [dict(r) for r in rows]
+
+
+def insert_log(component_id, step, input_summary=None, output_summary=None,
+ model_used=None, success=True, error_message=None, duration_ms=None):
+ """插入一条生成日志"""
+ conn = get_connection()
+ conn.execute("""
+ INSERT INTO generation_logs (component_id, step, input_summary, output_summary,
+ model_used, success, error_message, duration_ms)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ """, (component_id, step, input_summary, output_summary,
+ model_used, 1 if success else 0, error_message, duration_ms))
+ conn.commit()
+ conn.close()
+
+
+def get_logs(component_id):
+ """获取某组件的所有日志"""
+ conn = get_connection()
+ rows = conn.execute("""
+ SELECT * FROM generation_logs WHERE component_id = ?
+ ORDER BY created_at
+ """, (component_id,)).fetchall()
+ conn.close()
+ return [dict(r) for r in rows]
+
+
+def get_stats():
+ """获取数据库统计信息"""
+ conn = get_connection()
+ stats = {}
+ stats['total'] = conn.execute("SELECT COUNT(*) FROM components").fetchone()[0]
+ for status in ('draft', 'parsed', 'matched', 'generated', 'validated', 'exported'):
+ stats[status] = conn.execute(
+ "SELECT COUNT(*) FROM components WHERE status = ?", (status,)
+ ).fetchone()[0]
+ stats['by_type'] = {}
+ rows = conn.execute(
+ "SELECT component_type, COUNT(*) as cnt FROM components GROUP BY component_type"
+ ).fetchall()
+ for r in rows:
+ stats['by_type'][r['component_type']] = r['cnt']
+ conn.close()
+ return stats
+
+
+def export_final_json(script_id=None, status='validated'):
+ """导出最终配置JSON"""
+ components = list_components(script_id=script_id, status=status, limit=10000)
+ result = []
+ for c in components:
+ if c['final_config_json']:
+ entry = {
+ 'component_id': c['component_id'],
+ 'script_id': c['script_id'],
+ 'component_index': c['component_index'],
+ 'component_type': c['component_type'],
+ 'component_subtype': c['component_subtype'],
+ 'config': json.loads(c['final_config_json'])
+ }
+ result.append(entry)
+ return result
+
+
+# ============ CLI入口 ============
+
+if __name__ == "__main__":
+ import sys
+ if len(sys.argv) < 2:
+ print("用法: python3 db_manager.py
S1=4-5岁零基础
S2=5-6岁入门
S3=6-7岁进阶
S4=7-8岁提升 |
-| --output | 否 | 目录路径 | 结果输出目录,指定后自动保存所有结果到该目录,不指定则直接打印到控制台 |
-
-## 错误处理规则
-- 输入路径不存在/无txt文件:直接报错退出,给出明确提示
-- 配置文件加载失败:报错退出,提示检查配置文件格式
-- LLM调用失败:单个剧本生成失败不影响其他批量任务,给出错误提示
-- 参数不合法:直接输出参数说明,提示正确用法
-
-## 使用示例
-```bash
-# 1. 直接输入文本生成,结果打印到控制台
-openclaw skill run kids-english-script-production --input "角色A: 光有水不行,得先拿上毛巾。角色B: 好的,我现在去拿" --stage S2
-
-# 2. 处理单个文件,结果保存到output目录
-openclaw skill run kids-english-script-production --path ./script.txt --stage S3 --output ./result
-
-# 3. 批量处理目录下所有txt剧本,结果保存到output目录
-openclaw skill run kids-english-script-production --path ./scripts_dir --stage S1 --output ./batch_result
-```
diff --git a/skills/kids-english-script-production/kids-english-script-production/assets/skill.yml b/skills/kids-english-script-production/kids-english-script-production/assets/skill.yml
deleted file mode 100644
index 8ceafa9..0000000
--- a/skills/kids-english-script-production/kids-english-script-production/assets/skill.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-name: kids-english-script-production
-description: 4-8岁儿童英文台词标准化生产工具,支持纯中/纯英/中英混合输入,自动生成分级合规地道台词
-version: 1.1.0
-author: shark
-entry: python3 scripts/gen_script.py
-parameters:
- - name: input
- type: string
- description: 直接输入待处理的剧本文本
- required: false
- - name: path
- type: string
- description: 待处理的单个剧本文件路径或包含多个剧本的目录路径
- required: false
- - name: stage
- type: string
- description: 目标难度等级 S1/S2/S3/S4
- required: true
- - name: output
- type: string
- description: 结果输出目录,指定后自动保存结果
- required: false
-tags:
- - 内容生产
- - 英语课程
- - 台词生成
- - 批量处理
diff --git a/skills/kids-english-script-production/kids-english-script-production/examples/sample_script.txt b/skills/kids-english-script-production/kids-english-script-production/examples/sample_script.txt
deleted file mode 100644
index cf20390..0000000
--- a/skills/kids-english-script-production/kids-english-script-production/examples/sample_script.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-用户: 光有水不行,得先拿上毛巾。
-Ben: 好的,我现在去院子里拿毛巾,顺便把水桶也拿过来。
-用户: 太棒了,我们快点把飞船擦干净,不然天黑就完不成了!
-Ben: 没问题,飞船的能量 core 出了点小问题,我们擦完再一起修。
diff --git a/skills/kids-english-script-production/kids-english-script-production/scripts/gen_script.py b/skills/kids-english-script-production/kids-english-script-production/scripts/gen_script.py
deleted file mode 100644
index 1543aa8..0000000
--- a/skills/kids-english-script-production/kids-english-script-production/scripts/gen_script.py
+++ /dev/null
@@ -1,212 +0,0 @@
-#!/usr/bin/env python3
-import argparse
-import sys
-import os
-import yaml
-from openai import OpenAI
-from pathlib import Path
-
-# 加载配置
-BASE_DIR = Path(__file__).parent.parent
-ASSETS_DIR = BASE_DIR / "assets"
-
-# 加载外部配置文件(所有可调优参数全部在assets目录下的yaml文件,无需改代码)
-try:
- # 基础配置
- with open(ASSETS_DIR / "sci_fi_map.yaml", "r", encoding="utf-8") as f:
- SCI_FI_WORD_MAP = yaml.safe_load(f)
- with open(ASSETS_DIR / "stage_config.yaml", "r", encoding="utf-8") as f:
- STAGE_CONFIG = yaml.safe_load(f)
- # 调优配置
- with open(ASSETS_DIR / "expression_map.yaml", "r", encoding="utf-8") as f:
- EXPRESSION_MAP = yaml.safe_load(f)
- with open(ASSETS_DIR / "prompt_config.yaml", "r", encoding="utf-8") as f:
- PROMPT_CONFIG = yaml.safe_load(f)
- with open(ASSETS_DIR / "validation_config.yaml", "r", encoding="utf-8") as f:
- VALIDATION_CONFIG = yaml.safe_load(f)
- # 词表配置
- with open(BASE_DIR / "references" / "l1_word_list.json", "r", encoding="utf-8") as f:
- L1_WORD_LIST = set([word.lower() for word in yaml.safe_load(f)])
-except Exception as e:
- print(f"❌ 配置文件加载失败,请检查yaml格式是否正确: {str(e)}")
- sys.exit(1)
-
-# 初始化LLM客户端,配置从环境变量读取
-try:
- client = OpenAI(
- api_key=os.getenv("OPENAI_API_KEY", "your-api-key"),
- base_url=os.getenv("OPENAI_BASE_URL", "https://ark.cn-beijing.volces.com/api/v3")
- )
- MODEL = os.getenv("OPENAI_MODEL", "volcengine/doubao-seed-2-0-pro-260215")
-except Exception as e:
- print(f"❌ LLM客户端初始化失败: {str(e)}")
- sys.exit(1)
-
-def load_input(input_path):
- """加载输入内容,支持单个文件或目录批量加载"""
- input_path = Path(input_path)
- if not input_path.exists():
- print(f"❌ 输入路径不存在: {input_path}")
- sys.exit(1)
-
- if input_path.is_file():
- with open(input_path, "r", encoding="utf-8") as f:
- return [(input_path.name, f.read())]
- elif input_path.is_dir():
- # 批量加载目录下所有txt文件
- script_files = list(input_path.glob("*.txt"))
- if not script_files:
- print(f"❌ 目录下没有找到txt格式的剧本文件: {input_path}")
- sys.exit(1)
- results = []
- for f in script_files:
- with open(f, "r", encoding="utf-8") as fp:
- results.append((f.name, fp.read()))
- return results
- else:
- print(f"❌ 不支持的输入类型: {input_path}")
- sys.exit(1)
-
-def get_prompt(input_text, stage):
- """生成Prompt,所有可调规则从配置文件读取,无需改代码"""
- sci_fi_map_str = "\n".join([f"{k} → {v}" for k, v in SCI_FI_WORD_MAP.items()])
- # 动态加载配置规则
- emotion_map_rule = "优先使用以下映射匹配情绪词:" + "、".join([f"{k}→{v}" for k,v in EXPRESSION_MAP['emotion_map'].items()]) if PROMPT_CONFIG['naturalization']['enable_emotion_word'] else "不使用自定义情绪词映射"
- synonym_replace_rule = "可使用以下同义口语替换(不改变原意):" + "、".join([f"{k}→{v}" for k,v in EXPRESSION_MAP['synonym_replace'].items()]) if PROMPT_CONFIG['naturalization']['enable_synonym_replace'] else "不使用同义替换"
- split_rule = "包含2个及以上信息的句子拆成单信息短句" if PROMPT_CONFIG['naturalization']['enable_long_sentence_split'] else "不拆分长句"
- repeat_rule = "允许自然重复(比如It is dirty. Very dirty.)" if PROMPT_CONFIG['naturalization']['allow_repeat_expression'] else "不允许重复表达"
- exclamation_rule = "情绪强烈的句子可用感叹号" if PROMPT_CONFIG['naturalization']['enable_exclamation_mark'] else "统一使用句号"
- fidelity_rule = "100%忠于原剧本内容:禁止新增任何原剧本没有的信息、禁止删除任何原剧本已有的信息" if PROMPT_CONFIG['script_fidelity']['strictly_no_add'] and PROMPT_CONFIG['script_fidelity']['strictly_no_delete'] else "允许适当调整细节"
-
- return f"""
-你是专为4-8岁儿童打造的英文台词生产专家,严格遵守以下所有规则生成内容,绝对不允许违反:
-### 剧本忠实度规则(最高优先级,绝对不能违反)
-{fidelity_rule}
-
-### 第一步:输入归一
-当前输入是:{input_text}
-不管输入是纯中文/纯英文/中英混合,你首先统一转成标准中文「角色: 台词」格式,完整保留所有剧情、动作、角色关系、道具、事件触发点信息,不能丢失任何核心内容。
-
-### 第二步:中文AR预处理
-严格遵守4个保留机制(绝对不能改):
-1. 保留完整事件动词链
-2. 保留所有事件触发点
-3. 保留完整道具逻辑链
-4. 保留原有角色关系
-按以下7条规则拆成单信息短句,1句仅表达1个信息,不改变剧情:
-1. 复杂句拆成短句
-2. 因果拆分,保留事实不保留连接词
-3. 目的拆分,不删目的信息
-4. 多步动作拆成单动作句
-5. 条件+行为全拆分,去掉假设逻辑
-6. 情绪与事实拆分,不修改情绪
-7. 去复杂推理,只留可见事实
-
-### 第三步:分级英文生成
-目标Stage:{stage}
-对应要求:{STAGE_CONFIG[stage]["rules"]}
-蓝思值要求:{STAGE_CONFIG[stage]["lexile"]}
-
-自然化要求(**严格遵守剧本忠实度规则,禁止新增/删减任何原剧本没有的内容**):
-1. 情绪词映射规则:{emotion_map_rule}
-2. 同义替换规则:{synonym_replace_rule}
-3. 长句拆分规则:{split_rule}
-4. 重复表达规则:{repeat_rule}
-5. 标点规则:{exclamation_rule}
-6. 绝对禁止成人化连接词(actually/in fact/however等)
-7. 完全符合母语小朋友说话习惯,绝对不能有翻译腔
-8. 科幻词汇自动按以下映射替换:
-{sci_fi_map_str}
-
-### 第四步:自动校验
-生成后自行校验以下4项:
-1. AR等级合规:S1禁止AR3/AR4,S2禁止AR4
-2. 难度合规:词汇/句法/句长/蓝思值完全匹配对应Stage要求,无超纲
-3. 自然度合规:无翻译腔,符合4-8岁儿童母语表达习惯
-4. 内容合规:无敏感内容,无中式英语
-
-### 输出格式(严格按照格式输出,不要其他内容)
-【Stage {stage} 英文台词(适配{STAGE_CONFIG[stage]["age"]})】
-角色A: 台词内容
-角色B: 台词内容
-...
-【蓝思值】:[估算值]L
-【校验结果】:通过/待优化
-【优化建议】:无/具体建议
-"""
-
-def generate_single_script(input_text, stage):
- """生成单个剧本的台词"""
- try:
- prompt = get_prompt(input_text, stage)
- response = client.chat.completions.create(
- model=MODEL,
- messages=[{"role": "user", "content": prompt}],
- temperature=0.3,
- max_tokens=2000,
- timeout=30
- )
- result = response.choices[0].message.content
- # 增加超纲词校验
- oov_words = check_out_of_vocab(result, stage)
- if oov_words and stage in ["S1", "S2"]:
- result += f"\n【超纲词提醒】:{', '.join(oov_words)}(请确认是否需要替换)"
- return result
- except Exception as e:
- return f"❌ 生成失败: {str(e)}"
-
-def check_out_of_vocab(script_content, stage):
- """检查超纲词汇,规则从配置文件读取"""
- if not VALIDATION_CONFIG['vocab_validation']['enable_OOV_remind'] or stage not in ["S1", "S2"]:
- return []
- # 提取所有英文单词
- import re
- words = re.findall(r"[a-zA-Z']+", script_content)
- words = [word.lower().strip("'") for word in words]
- # 过滤配置里定义的停用词
- stop_words = set(VALIDATION_CONFIG['vocab_validation']['stop_words'])
- words = [word for word in words if word not in stop_words and len(word) > 1]
- # 找超纲词
- out_of_vocab = list(set([word for word in words if word not in L1_WORD_LIST]))
- return out_of_vocab
-
-def save_result(output_dir, filename, content):
- """保存结果到文件"""
- output_dir = Path(output_dir)
- output_dir.mkdir(parents=True, exist_ok=True)
- output_file = output_dir / f"result_{filename}"
- with open(output_file, "w", encoding="utf-8") as f:
- f.write(content)
- return output_file
-
-def main():
- parser = argparse.ArgumentParser(description="4-8岁儿童英文台词标准化生产工具")
- group = parser.add_mutually_exclusive_group(required=True)
- group.add_argument("--input", type=str, help="直接输入待处理的剧本文本")
- group.add_argument("--path", type=str, help="待处理的单个剧本文件路径或包含多个剧本的目录路径")
- parser.add_argument("--stage", type=str, choices=["S1", "S2", "S3", "S4"], required=True, help="目标难度等级 S1/S2/S3/S4")
- parser.add_argument("--output", type=str, help="结果输出目录,不指定则直接打印到控制台")
- args = parser.parse_args()
-
- # 处理输入
- if args.input:
- input_list = [("direct_input", args.input)]
- else:
- input_list = load_input(args.path)
-
- # 批量生成
- results = []
- for filename, text in input_list:
- print(f"\n🚀 正在处理: {filename}")
- result = generate_single_script(text, args.stage)
- results.append((filename, result))
- print(result)
- # 保存结果
- if args.output:
- save_path = save_result(args.output, filename, result)
- print(f"💾 结果已保存到: {save_path}")
-
- print(f"\n✅ 全部处理完成,共处理{len(results)}个剧本")
-
-if __name__ == "__main__":
- main()
diff --git a/skills/script_writing/validation_config.yaml b/skills/script_writing/validation_config.yaml
deleted file mode 100644
index dd78217..0000000
--- a/skills/script_writing/validation_config.yaml
+++ /dev/null
@@ -1,45 +0,0 @@
-# 英文台词校验规则配置
-# 版本:2026-04-01
----
-## 一、基础通用校验规则(强制执行)
-basic_rules:
- sentence_spec:
- - 默认保留be going to标准表达,无特殊标注(如指定学龄前场景)禁止使用gonna等过度口语化表达
- vocabulary_control:
- - 低龄学习场景禁止使用超纲词,动作指令优先选择基础词汇(如用look at替代focus)
- redundancy_check:
- - 禁止同一句台词连续重复出现2次及以上,此类排版错误直接标注
- confirmed_optimization:
- - "Today, we must train!" 统一优化为 "Let's start training!"
-
-## 二、核心精校5大法则(A1级别内容强制遵循)
-core_principles:
- simplification:
- name: 极简降维法则
- rules:
- - 严格执行"一句一意",复杂嵌套句型拆分为独立简单句,禁止复合从句
- - 时态仅允许使用一般现在时、一般过去时、基础将来时(will/be going to),禁止完成时、虚拟语气
- chunking:
- name: 语块优先法则
- rules:
- - 上下文/画面信息充足时可省略冗余成分,保留核心语块
- - 允许使用母语儿童常用地道祈使短句(如Game start!/Watch this!/Silly me!),禁止生造表达
- tpr_action:
- name: TPR动作强绑定法则
- rules:
- - 引导交互/动作的台词以基础动词原形开头(Look/Listen/Hit/Run等)
- - 确保台词与画面动作/UI组件完全同步,利用视觉辅助听力解码
- target_focus:
- name: 目标大纲无痕植入法则
- rules:
- - 核心词汇/句型通过剧情冲突(找不到/受伤/失误等)自然重复,禁止生硬植入
- - 目标句型通过NPC提问引导输出,禁止直接生硬陈述
- emotional_resonance:
- name: 情绪夸张法则
- rules:
- - 允许使用低认知负荷语气词传递情绪(Phew!/Ouch!/Oops!/Aha!/Waaaaah!等)
- - 情绪表达直接使用A1级形容词(sad/happy/angry等),禁止复杂心理描写
-
-## 三、校验输出标准
-output_standard:
- - 所有问题标注需包含:上下文引用+问题类型说明+具体优化方案,确保可直接落地修改
diff --git a/temp/L1写作模块收尾.md b/temp/L1写作模块收尾.md
deleted file mode 100644
index 635ea73..0000000
--- a/temp/L1写作模块收尾.md
+++ /dev/null
@@ -1,304 +0,0 @@
-
----
-### 题型6:L1 - Movers - 写作 简单句表达(3-5词句子)
-#### 1. 标准结构(与真实考试完全一致)
-- 题量:5道/套
-- 呈现形式:给出图片或提示词,要求用3-5个单词组成完整句子
-- 考察形式:根据提示写出完整简单句,语法正确
-- 答题要求:句子长度3-5个单词,结构完整、语法正确
-#### 2. 能力标签绑定&触发机制
-