auto backup: 2026-06-05 08:10:01
This commit is contained in:
parent
01072f4003
commit
fa5d896a2c
@ -233,7 +233,7 @@ interactive-component-json/
|
||||
| 造句互动 | mid_sentence_makeSentence | WfHMbzBYPau6hZsmFD0cBwWuncc |
|
||||
| 发音互动 | mid_pron_pron | CeH5bO3FhaG5cNsPZ4Xc72axnrb |
|
||||
|
||||
### 核心互动组件(已实现12种,其余待定义)
|
||||
### 核心互动组件(已实现13种,其余待定义)
|
||||
- **合作阅读** `core_reading_order` — 7步LLM调用,输出 taskData/textData/sequenceData/learningData
|
||||
- **看图选词** `core_reading_imageDrag` — 5步LLM调用,输出 taskData/questionGroupData/studyData
|
||||
- **口语快答** `core_speaking_reply` — 7步LLM调用,输出 taskData/settingData/configData/learningData
|
||||
@ -246,8 +246,9 @@ interactive-component-json/
|
||||
- **看图撰写** `core_writing_imgWrite` — 6步LLM调用,输出 taskInfo/textInfo/studyInfo/evalInfo
|
||||
- **邮件组句** `core_writing_questionMakeSentence` — 6步LLM调用,输出 taskInfo/textInfo/studyInfo/evalInfo
|
||||
- **邮件撰写** `core_writing_questionWrite` — 6步LLM调用,输出 taskInfo/textInfo/studyInfo/evalInfo
|
||||
- **看图拼词** `core_writing_imgMakeWord` — 7步(Step5规则),输出 taskInfo/preDialog/imageInfo/questionList/studyInfo
|
||||
|
||||
其余7种详见 `scripts/match_component.py` 中的 `CORE_INTERACTION_TYPES`
|
||||
其余6种详见 `scripts/match_component.py` 中的 `CORE_INTERACTION_TYPES`
|
||||
|
||||
## LLM 配置
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -110,6 +110,18 @@ bitable数据可用于验证和参考。
|
||||
- 组句类inputType=sentence(optionList+answer);撰写类inputType=write(answerText+hint)
|
||||
- prompt模板: `prompts/core_writing_img_prompts.py`, `prompts/core_writing_question_prompts.py`
|
||||
|
||||
### v8.1 — 看图拼词(1种,已完成)
|
||||
|
||||
| 类型 | cType | LLM步数 | 输出字段 | bitable wiki |
|
||||
|------|-------|---------|---------|-------------|
|
||||
| 看图拼词 | core_writing_imgMakeWord | 7 (Step5规则) | taskInfo, preDialog, imageInfo, questionList, studyInfo | QDyhwiY7PiSER8kHGZncDsrinde |
|
||||
|
||||
- 输入: 单一教研配置块(【任务标题】【任务背景】【任务描述】【通关知识】【前置对话】【互动问题】)
|
||||
- 中间步骤: 学习流程配置(LLM生成) + 互动描述(LLM生成)
|
||||
- Step 5 imageInfo 为规则生成: `{"questionImg": "{cId}_question.png"}`
|
||||
- 独立7步流水线 `generate_core_writing_imgmakeword()`
|
||||
- prompt模板: `prompts/core_writing_imgmakeword_prompts.py`
|
||||
|
||||
### 其他未实现类型
|
||||
- 口语独白、口语探讨
|
||||
- 写作互动
|
||||
@ -246,3 +258,26 @@ bitable数据可用于验证和参考。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## v14 — 重新生成拆分 + 知识点推送失败判定(已完成)
|
||||
|
||||
### 重新生成拆分
|
||||
将原来的单一"重新生成"按钮拆分为两个独立操作:
|
||||
- **重新生成组件**: 只重新生成 jsonData/core 字段,保留现有 kpInfo 不变
|
||||
- **重新生成知识点**: 只重新生成 kpInfo,保留现有 jsonData/core 字段不变
|
||||
|
||||
实现方式:
|
||||
- 前端拆分为 `regenerateComponentJson(idx)` 和 `regenerateComponentKp(idx)` 两个函数
|
||||
- 后端 `/api/regenerate` 通过 `mode` 参数区分:`mode='component'` 或 `mode='kp'`
|
||||
- `mode='component'`: 调用 `generate_component()` 但跳过 kpInfo
|
||||
- `mode='kp'`: 只调用 `generate_kp_info()`
|
||||
- `knowledge_text` 嵌入到 HTML 报告的 COMPONENTS 前端数据中,供重新生成知识点使用
|
||||
|
||||
### 知识点推送失败判定
|
||||
- 单个组件"知识推送"按钮: 无知识点或所有 kpId 为空时,标记为推送**失败**(原来算成功/info)
|
||||
- 批量"推送全部知识点": 无知识点或 kpId 为空时,计入 failCount 并显示失败详情(原来静默跳过或算成功)
|
||||
|
||||
### 涉及文件
|
||||
- `scripts/pipeline.py`: report entry 添加 `knowledge_text` 字段
|
||||
- `scripts/html_report.py`: 前端按钮/面板/JS 逻辑拆分 + 推送失败判定
|
||||
- `scripts/proxy_server.py`: `_do_regenerate` 按 mode 分流
|
||||
|
||||
Binary file not shown.
@ -0,0 +1,340 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
看图拼词 (core_writing_imgMakeWord) 多步 LLM prompt 模板
|
||||
|
||||
生成流水线(7步,Step 5 为规则生成):
|
||||
Step 1: 学习流程配置 (intermediate text)
|
||||
Step 2: 互动描述 (intermediate text)
|
||||
Step 3: taskInfo → JSON
|
||||
Step 4: preDialog → JSON
|
||||
Step 5: imageInfo → 规则生成 (不需要 prompt)
|
||||
Step 6: questionList → JSON
|
||||
Step 7: studyInfo → JSON
|
||||
"""
|
||||
|
||||
# ── Step 1: 学习流程配置 ──
|
||||
STEP1_LEARNING_FLOW = r"""你是一名儿童英语学习助手,负责为学习者生成互动式的巩固练习题。请根据以下信息生成练习内容:
|
||||
|
||||
**输入信息**
|
||||
$任务信息$
|
||||
|
||||
**生成要求**
|
||||
|
||||
1. 为每个【通关知识】的知识词汇生成一个小题,包含以下结构:
|
||||
|
||||
```
|
||||
# 小题 X
|
||||
## 互动题目
|
||||
1. 抄写题
|
||||
Brainy:如图所示,首先/接着/最后要帮 Sarah 整理的物品是"中文词",该词的英文是"英文词"。先来抄写一下这个词吧!
|
||||
|
||||
2. 听写题
|
||||
Brainy:很好。接下来,记一下"英文词"的拼写,是"字母拆分"。然后看着图片,试试听写出"中文词"这个词吧!
|
||||
|
||||
Brainy:没错!"中文词"对应的英文词汇是"英文词",你的拼写完全正确!
|
||||
```
|
||||
|
||||
2. 每个知识词汇生成一题,顺序与知识词汇列表一致。
|
||||
|
||||
3. 拼写拆解用字母加"-"表示,多个单词间用空格分隔(如 teddy bear → t-e-d-d-y b-e-a-r)。
|
||||
|
||||
4. 最后生成一个"完结反馈":
|
||||
|
||||
```
|
||||
Brainy:太棒了!你复习了所有的关键知识点,拼对了每一个词,相信下一次你一定可以做得更好!
|
||||
```
|
||||
|
||||
5. 输出中文引号全部使用英文引号"",不要使用中文引号"「」"。
|
||||
|
||||
6. 不输出额外说明或过程,只生成完整互动题内容。
|
||||
|
||||
### 三、示例输出(参考格式)
|
||||
```
|
||||
# 小题 1
|
||||
## 互动题目
|
||||
1. 抄写题
|
||||
Brainy:如图所示,首先要帮 Sally 整理的物品是"照片","照片"的英文是"photo"。先来抄写一下这个词吧!
|
||||
|
||||
2. 听写题
|
||||
Brainy:很好。接下来,记一下"photo"的拼写,是"p-h-o-t-o"。然后看着图片,试试听写出"照片"这个词吧!
|
||||
|
||||
Brainy:没错!"照片"对应的英文词汇是"photo",你的拼写完全正确!
|
||||
|
||||
# 小题 2
|
||||
## 互动题目
|
||||
1. 抄写题
|
||||
Brainy:如图所示,接着要帮 Sally 整理的物品是"奖牌","奖牌"的英文是"medal"。先来抄写一下这个词吧!
|
||||
|
||||
2. 听写题
|
||||
Brainy:很好。接下来,记一下"medal"的拼写,是"m-e-d-a-l"。然后看着图片,试试听写出"奖牌"这个词吧!
|
||||
|
||||
Brainy:没错!"奖牌"对应的英文词汇是"medal",你的拼写完全正确!
|
||||
|
||||
|
||||
# 完结反馈
|
||||
Brainy:太棒了!你复习了所有的关键知识点,拼对了每一个词,相信下一次你一定可以做得更好!
|
||||
```
|
||||
|
||||
#输入
|
||||
任务信息:
|
||||
$任务信息$
|
||||
|
||||
# 输出
|
||||
现在请按照上述信息和要求,直接输出目标内容。"""
|
||||
|
||||
|
||||
# ── Step 2: 互动描述 ──
|
||||
STEP2_INTERACTION_DESC = r"""# 任务目标
|
||||
你是一个儿童英语学习内容生成助手。现在需要根据给定的任务信息、知识词汇和前置对话,生成一句符合场景的英文描述句,并附上对应中文翻译。
|
||||
|
||||
# 输入信息
|
||||
* 任务信息:
|
||||
$任务信息$
|
||||
|
||||
* 前置对话:
|
||||
$前置对话$
|
||||
|
||||
# 生成要求
|
||||
只输出:
|
||||
一句完整、自然、符合儿童英语学习场景的英文描述句
|
||||
对应中文翻译(放在括号中)
|
||||
英文句子要求:
|
||||
必须结合「任务名称」与「知识」中的词汇
|
||||
尽量自然使用多个知识词汇
|
||||
句子简洁、口语化、符合少儿英语水平
|
||||
与「前置对话」场景保持一致
|
||||
中文翻译要求:
|
||||
与英文句子语义完全对应
|
||||
表达自然通顺
|
||||
输出格式固定如下(禁止输出任何解释):
|
||||
English sentence.(中文翻译。)
|
||||
|
||||
# 输出示例
|
||||
Dear Kate, I'd like to say thank you in this letter. Thank you for taking care of me. I have to go away now. Let's meet in the holidays.(亲爱的凯特,我想在这封信里向你表达感谢。感谢你对我的照顾。我现在必须离开了。咱们假期见。)"""
|
||||
|
||||
|
||||
# ── Step 3: taskInfo ──
|
||||
STEP3_TASK_INFO = r"""# 字段内容要求
|
||||
{
|
||||
"cId": "{ID}", // 字符串类型
|
||||
"cType": "core_writing_imgMakeWord",
|
||||
"title": {任务信息}中的【任务名称】,
|
||||
"sceneDesc": {任务信息}中的【任务背景】,
|
||||
"cDesc": "xxx", // {互动描述}的全部内容
|
||||
"key": [ // {任务信息}中的【通关知识】
|
||||
{
|
||||
"desc": "",
|
||||
"keyList": [
|
||||
{
|
||||
"type": "default", //固定
|
||||
"content": 知识点内容(英文部分),
|
||||
"desc": 知识点解释(中文部分,如果是单词前面会带有英文缩写的词性标识)
|
||||
},
|
||||
... ...
|
||||
]
|
||||
},
|
||||
...
|
||||
],
|
||||
"resourceMapping"(抽取出{角色配置}中的全部 name:id映射 可为空。id必须为int类型):
|
||||
{
|
||||
样例:
|
||||
"Tom":43,
|
||||
"Jerry":10
|
||||
}
|
||||
}
|
||||
|
||||
# 输出格式
|
||||
- 整个输出必须在一行内完成,**不要换行**。
|
||||
- **不要使用 markdown 代码块**(如 ```json)包裹输出。
|
||||
|
||||
样例:
|
||||
{"cId":"0000999","cType":"core_writing_imgMakeWord","title":"帮助sarah整理物品","sceneDesc":"场景描述,场景描述,看图说话场景描述","cDesc":"xxxxxx","key":[{"desc":"","keyList":[{"type":"default","content":"passport"},{"type":"default","content":"suitcase"},{"type":"default","content":"clothes"},{"type":"default","content":"pack"}]}],"resourceMapping":{"brainy":291,"jack":210}}
|
||||
|
||||
# 输入
|
||||
{ID}:
|
||||
$ID$
|
||||
|
||||
{任务信息}:
|
||||
$任务信息$
|
||||
|
||||
{互动描述}:
|
||||
$互动描述$
|
||||
|
||||
{角色配置}:
|
||||
$角色配置$
|
||||
|
||||
# 输出
|
||||
现在基于输入字段和格式要求,生成***可以直接解析的单行json文本***,***注意前后不要输出任何额外符号!!!***
|
||||
如果{任务配置}中没有有效文本,则输出"无"。"""
|
||||
|
||||
|
||||
# ── Step 4: preDialog ──
|
||||
STEP4_PRE_DIALOG = r"""# 字段内容要求
|
||||
[ // 从{互动配置}中的【前置对话】中抽取
|
||||
{
|
||||
"type": "npc",
|
||||
"npcName": "xxx",
|
||||
"content": "xxxxxxxx"
|
||||
},
|
||||
{
|
||||
"type": "user", // 如果是user则没有npcName
|
||||
"content": "xxxxxxxx"
|
||||
},
|
||||
......
|
||||
]
|
||||
|
||||
# 输出格式
|
||||
- 整个输出必须在一行内完成,**不要换行**。
|
||||
- **不要使用 markdown 代码块**(如 ```json)包裹输出。
|
||||
|
||||
样例:
|
||||
[{"type":"npc","npcName":"Brainy","content":"OK,let's try to the scene the scene together."},{"type":"npc","npcName":"Amy","content":"OK,let's try to the scene the scene together."},{"type":"npc","npcName":"Brainy","content":"OK,let's try to the scene the scene together."}]
|
||||
|
||||
# 输入
|
||||
{互动配置}:
|
||||
$前置对话$
|
||||
|
||||
# 输出
|
||||
现在基于输入字段和格式要求,生成***可以直接解析的单行json文本***,***注意前后不要输出任何额外符号!!!***
|
||||
如果{任务配置}中没有有效文本,则输出"无"。"""
|
||||
|
||||
|
||||
# ── Step 6: questionList ──
|
||||
STEP6_QUESTION_LIST = r"""# 字段内容要求
|
||||
[ // 从{互动配置}中的内容抽取为下面格式,有几个题目则有几个item
|
||||
{
|
||||
"desc": "(x)", // 题干中抽取
|
||||
"imageInfo": {
|
||||
"questionImg": "{ID}_question_{图片编号}.png" // 图片编号从【互动问题】中该小题对应的抽取
|
||||
},
|
||||
"optionList": [ // 选项中抽取
|
||||
{
|
||||
"option": "a"
|
||||
},
|
||||
{
|
||||
"option": "b"
|
||||
},
|
||||
......
|
||||
],
|
||||
"answerText": "xxxxx" // 答案中抽取
|
||||
},
|
||||
......
|
||||
]
|
||||
|
||||
# 输出格式
|
||||
- 整个输出必须在一行内完成,**不要换行**。
|
||||
- **不要使用 markdown 代码块**(如 ```json)包裹输出。
|
||||
|
||||
样例:
|
||||
[{"desc":"(5)","imageInfo":{"questionImg":"012310_question_00.png"},"optionList":[{"option":"o"},{"option":"h"},{"option":"x"},{"option":"P"},{"option":"t"},{"option":"o"},{"option":"k"}],"answerText":"Photo"},{"desc":"(5)","imageInfo":{"questionImg":"012310_question_01.png"},"optionList":[{"option":"m"},{"option":"d"},{"option":"l"},{"option":"q"},{"option":"e"},{"option":"d"},{"option":"m"},{"option":"a"}],"answerText":"medal"},{"desc":"(6)","imageInfo":{"questionImg":"012310_question_02.png"},"optionList":[{"option":"a"},{"option":"j"},{"option":"k"},{"option":"t"},{"option":"e"},{"option":"v"},{"option":"c"},{"option":"z"},{"option":"a"}],"answerText":"jacket"},{"desc":"(5) (4)","imageInfo":{"questionImg":"012310_question_03.png"},"optionList":[{"option":"b"},{"option":"e"},{"option":"t"},{"option":"r"},{"option":"y"},{"option":"a"},{"option":"d"},{"option":"e"},{"option":"t"},{"option":"d"},{"option":"y"}],"answerText":"teddy bear"}]
|
||||
|
||||
# 输入
|
||||
{互动配置}:
|
||||
$互动问题$
|
||||
|
||||
{ID}:
|
||||
$ID$
|
||||
|
||||
# 输出
|
||||
现在基于输入字段和格式要求,生成***可以直接解析的单行json文本***,***注意前后不要输出任何额外符号!!!***
|
||||
如果{任务配置}中没有有效文本,则输出"无"。"""
|
||||
|
||||
|
||||
# ── Step 7: studyInfo ──
|
||||
STEP7_STUDY_INFO = r"""# 字段内容要求
|
||||
{
|
||||
"learningPart": [ // {学习流程配置}中有几个小题则learningPart中有几个元素
|
||||
{
|
||||
"type": "npc",
|
||||
"npcName": "Brainy",
|
||||
"content": "xxxx" // {任务信息}里【通关知识】和该小题对应的单词
|
||||
"image": "{ID}_question_{两位自增索引}.png" // 索引从00开始
|
||||
"learning": [
|
||||
{
|
||||
"question": {
|
||||
"type": "npc",
|
||||
"npcName": "Brainy",
|
||||
"content": "xxx", // 抄写题题干
|
||||
"desc": "xxx", // {任务信息}里【通关知识】和该小题对应的单词
|
||||
"answerText": "xxx", // {任务信息}里【通关知识】和该小题对应的单词
|
||||
},
|
||||
"optionList": [ // 单词的所有字母
|
||||
{"option": "a"},
|
||||
{"option": "b"},
|
||||
......
|
||||
]
|
||||
},
|
||||
{
|
||||
"question": {
|
||||
"type": "npc",
|
||||
"npcName": "Brainy",
|
||||
"content": "xxx", // 听写题题干
|
||||
"desc": "(x)", // x为{任务信息}里【通关知识】和该小题对应的单词的字母个数
|
||||
"answerText": "xxx", // {任务信息}里【通关知识】和该小题对应的单词
|
||||
},
|
||||
"optionList": [ // 单词的所有字母
|
||||
{"option": "a"},
|
||||
{"option": "b"},
|
||||
......
|
||||
],
|
||||
"feedback": {
|
||||
"type": "npc",
|
||||
"npcName": "Brainy",
|
||||
"content": "xxxxxx"
|
||||
}
|
||||
},
|
||||
{
|
||||
......
|
||||
"image": "{ID}_question_01.png"
|
||||
......
|
||||
},
|
||||
......
|
||||
"closing": { //{学习流程配置}中的 完结反馈中提取
|
||||
"type": "npc",
|
||||
"npcName": "Brainy",
|
||||
"content": "xxxxx"
|
||||
}
|
||||
}
|
||||
|
||||
# 输出格式
|
||||
- 整个输出必须在一行内完成,**不要换行**。
|
||||
- **不要使用 markdown 代码块**(如 ```json)包裹输出。
|
||||
|
||||
样例:
|
||||
{"learningPart":[{"type":"npc","npcName":"Brainy","content":"Photo","image":"012310_question_00.png","learning":[{"question":{"type":"npc","npcName":"Brainy","content":"如图所示,首先要帮 Sally 整理的物品是「照片」,「照片」的英文是「photo」。先来抄写一下这个词吧!","desc":"Photo","answerText":"Photo"},"optionList":[{"option":"P"},{"option":"h"},{"option":"o"},{"option":"t"},{"option":"o"}]},{"question":{"type":"npc","npcName":"Brainy","content":"很好。接下来,记一下「photo」的拼写,是「p-h-o-t-o」。然后看着图片,试试听写出「照片」这个词吧!","desc":"(5)","answerText":"Photo"},"optionList":[{"option":"P"},{"option":"h"},{"option":"o"},{"option":"t"},{"option":"o"}],"feedback":{"type":"npc","npcName":"Brainy","content":"没错!「照片」对应的英文词汇是「photo」,你的拼写完全正确!"}}]},{"type":"npc","npcName":"Brainy","content":"medal","image":"012310_question_01.png","learning":[{"question":{"type":"npc","npcName":"Brainy","content":" 如图所示,接着要帮 Sally 整理的物品是「奖牌」,「奖牌」的英文是「medal」。先来抄写一下这个词吧!","desc":"medal","answerText":"medal"},"optionList":[{"option":"m"},{"option":"e"},{"option":"d"},{"option":"a"},{"option":"l"}]},{"question":{"type":"npc","npcName":"Brainy","content":"很好。接下来,记一下「medal」的拼写,是「m-e-d-a-l」。然后看着图片,试试听写出「奖牌」这个词吧!","desc":"(5)","answerText":"medal"},"optionList":[{"option":"m"},{"option":"e"},{"option":"d"},{"option":"a"},{"option":"l"}],"feedback":{"type":"npc","npcName":"Brainy","content":"没错!「奖牌」对应的英文词汇是「medal」,你的拼写完全正确!"}}]},{"type":"npc","npcName":"Brainy","content":"jetcak","image":"012310_question_02.png","learning":[{"question":{"type":"npc","npcName":"Brainy","content":" 如图所示,接着要帮 Sally 整理的物品是「奖牌」,「奖牌」的英文是「medal」。先来抄写一下这个词吧!","desc":"jetcak","answerText":"jetcak"},"optionList":[{"option":"j"},{"option":"e"},{"option":"t"},{"option":"c"},{"option":"a"},{"option":"k"}]},{"question":{"type":"npc","npcName":"Brainy","content":"很好。接下来,记一下「medal」的拼写,是「m-e-d-a-l」。然后看着图片,试试听写出「奖牌」这个词吧!","desc":"(6)","answerText":"jetcak"},"optionList":[{"option":"j"},{"option":"e"},{"option":"t"},{"option":"c"},{"option":"a"},{"option":"k"}],"feedback":{"type":"npc","npcName":"Brainy","content":"没错!「夹克」对应的英文词汇是「jetcak」,你的拼写完全正确!"}}]},{"type":"npc","npcName":"Brainy","content":"teddy bear","image":"012310_question_03.png","learning":[{"question":{"type":"npc","npcName":"Brainy","content":"很好。接下来,记一下「medal」的拼写,是「m-e-d-a-l」。然后看着图片,试试听写出「奖牌」这个词吧!","desc":"teddy bear","answerText":"teddy bear"},"optionList":[{"option":"t"},{"option":"e"},{"option":"d"},{"option":"d"},{"option":"y"},{"option":"b"},{"option":"e"},{"option":"a"},{"option":"r"}]},{"question":{"type":"npc","npcName":"Brainy","content":"很好。接下来,记一下「medal」的拼写,是「m-e-d-a-l」。然后看着图片,试试听写出「奖牌」这个词吧!","desc":"(5) (4)","answerText":"teddy bear"},"optionList":[{"option":"t"},{"option":"e"},{"option":"d"},{"option":"d"},{"option":"y"},{"option":"b"},{"option":"e"},{"option":"a"},{"option":"r"}],"feedback":{"type":"npc","npcName":"Brainy","content":"没错!「泰迪熊」对应的英文词汇是「teddy bear」,你的拼写完全正确!"}}]}],"closing":{"type":"npc","npcName":"Brainy","content":"太棒了!你复习了所有的关键知识点,拼对了每一个词,相信下一次你一定可以做得更好!"}}
|
||||
|
||||
# 输入
|
||||
{学习流程配置}:
|
||||
$学习流程配置$
|
||||
|
||||
{任务信息}:
|
||||
$任务信息$
|
||||
|
||||
{ID}:
|
||||
$ID$
|
||||
|
||||
# 输出
|
||||
现在基于输入字段和格式要求,生成***可以直接解析的单行json文本***,***注意前后不要输出任何额外符号!!!***直接以"{"开头,输出一定保持单行!如果{学习流程配置}中没有有效文本,则输出"无"。"""
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# Steps Registry & Builder
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
CORE_WRITING_IMGMAKEWORD_STEPS = {
|
||||
"step1_learning_flow": {"template": STEP1_LEARNING_FLOW, "description": "学习流程配置", "output_type": "text"},
|
||||
"step2_interaction_desc": {"template": STEP2_INTERACTION_DESC, "description": "互动描述", "output_type": "text"},
|
||||
"step3_task_info": {"template": STEP3_TASK_INFO, "description": "taskInfo", "output_type": "json"},
|
||||
"step4_pre_dialog": {"template": STEP4_PRE_DIALOG, "description": "preDialog", "output_type": "json"},
|
||||
"step6_question_list": {"template": STEP6_QUESTION_LIST, "description": "questionList", "output_type": "json"},
|
||||
"step7_study_info": {"template": STEP7_STUDY_INFO, "description": "studyInfo", "output_type": "json"},
|
||||
}
|
||||
|
||||
|
||||
def build_writing_imgmakeword_prompt(step_key, replacements):
|
||||
"""Build a prompt for a given step by replacing $placeholder$ tokens."""
|
||||
info = CORE_WRITING_IMGMAKEWORD_STEPS[step_key]
|
||||
prompt = info["template"]
|
||||
for key, val in replacements.items():
|
||||
prompt = prompt.replace(f"${key}$", str(val) if val else "")
|
||||
return prompt
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -130,6 +130,11 @@ from prompts.core_writing_question_prompts import (
|
||||
build_writing_question_prompt,
|
||||
)
|
||||
|
||||
from prompts.core_writing_imgmakeword_prompts import (
|
||||
CORE_WRITING_IMGMAKEWORD_STEPS,
|
||||
build_writing_imgmakeword_prompt,
|
||||
)
|
||||
|
||||
# Fallback: 旧版 few-shot 方式(对无 prompt 模板的类型)
|
||||
FALLBACK_SYSTEM_PROMPT = """你是一个互动组件配置JSON生成专家。根据教研配置文本生成对应的 jsonData JSON。
|
||||
|
||||
@ -382,7 +387,7 @@ def generate_kp_info(cType, cId, knowledge_text, llm_client=None, level=None):
|
||||
return match_knowledge_points(knowledge_text, cType, cId, level=level, llm_client=llm_client)
|
||||
|
||||
|
||||
CORE_TYPES = {"core_reading_order", "core_reading_imageDrag", "core_speaking_reply", "core_speaking_inquiry", "core_speaking_image", "core_listening_order", "core_listening_drag", "core_listening_choose", "core_writing_imgMakeSentence", "core_writing_imgWrite", "core_writing_questionMakeSentence", "core_writing_questionWrite"}
|
||||
CORE_TYPES = {"core_reading_order", "core_reading_imageDrag", "core_speaking_reply", "core_speaking_inquiry", "core_speaking_image", "core_listening_order", "core_listening_drag", "core_listening_choose", "core_writing_imgMakeSentence", "core_writing_imgWrite", "core_writing_questionMakeSentence", "core_writing_questionWrite", "core_writing_imgMakeWord"}
|
||||
|
||||
|
||||
def parse_core_reading_fields(teaching_config):
|
||||
@ -1995,6 +2000,162 @@ def generate_core_writing_question_write(cId, teaching_config, character_map=Non
|
||||
return _generate_core_writing(cId, "core_writing_questionWrite", teaching_config, character_map, llm_client, level=level)
|
||||
|
||||
|
||||
# ============ 看图拼词 (core_writing_imgMakeWord) ============
|
||||
|
||||
def parse_core_writing_imgmakeword_fields(teaching_config):
|
||||
"""
|
||||
从组件配置文本中提取 core_writing_imgMakeWord 所需的输入字段。
|
||||
|
||||
教研配置为单一文本块,包含 【任务标题】【任务背景】【任务描述】【通关知识】【前置对话】【互动问题】 等 sections。
|
||||
|
||||
Returns:
|
||||
dict: 字段名 → 值
|
||||
"""
|
||||
fields = parse_teaching_config_fields(teaching_config)
|
||||
|
||||
任务标题 = fields.get("任务标题", "")
|
||||
任务背景 = fields.get("任务背景", "")
|
||||
任务描述 = fields.get("任务描述", "")
|
||||
通关知识 = fields.get("通关知识", "")
|
||||
前置对话 = fields.get("前置对话", "")
|
||||
互动问题 = fields.get("互动问题", "")
|
||||
|
||||
# 构建任务信息(composite)
|
||||
任务信息_parts = []
|
||||
if 任务标题:
|
||||
任务信息_parts.append(f"【任务标题】\n{任务标题}")
|
||||
if 任务背景:
|
||||
任务信息_parts.append(f"【任务背景】\n{任务背景}")
|
||||
if 任务描述:
|
||||
任务信息_parts.append(f"【任务描述】\n{任务描述}")
|
||||
if 通关知识:
|
||||
任务信息_parts.append(f"【通关知识】\n{通关知识}")
|
||||
任务信息 = "\n\n".join(任务信息_parts)
|
||||
|
||||
return {
|
||||
"任务标题": 任务标题,
|
||||
"任务背景": 任务背景,
|
||||
"任务描述": 任务描述,
|
||||
"通关知识": 通关知识,
|
||||
"前置对话": 前置对话,
|
||||
"互动问题": 互动问题,
|
||||
"任务信息": 任务信息,
|
||||
}
|
||||
|
||||
|
||||
def generate_core_writing_imgmakeword(cId, teaching_config, character_map=None, llm_client=None, level=None):
|
||||
"""
|
||||
看图拼词(core_writing_imgMakeWord)7步生成流水线。
|
||||
|
||||
Step 1: 学习流程配置 → 中间文本
|
||||
Step 2: 互动描述 → 中间文本
|
||||
Step 3: taskInfo → JSON
|
||||
Step 4: preDialog → JSON
|
||||
Step 5: imageInfo → 规则生成
|
||||
Step 6: questionList → JSON
|
||||
Step 7: studyInfo → JSON
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"taskInfo": dict, "preDialog": list,
|
||||
"imageInfo": dict, "questionList": list,
|
||||
"studyInfo": dict, "kpInfo": None,
|
||||
"intermediate": {"学习流程配置": str, "互动描述": str}
|
||||
}
|
||||
"""
|
||||
if llm_client is None:
|
||||
from llm_client import get_client
|
||||
llm_client = get_client()
|
||||
|
||||
cType = "core_writing_imgMakeWord"
|
||||
|
||||
# 解析输入字段
|
||||
fields = parse_core_writing_imgmakeword_fields(teaching_config)
|
||||
|
||||
# 角色配置:优先教研配置,回退到 character_map
|
||||
角色配置 = ""
|
||||
if character_map:
|
||||
角色配置 = "\n".join(f"{k}:{v}" for k, v in character_map.items())
|
||||
|
||||
logger.info(f"[core_writing_imgMakeWord] 开始7步生成: cId={cId}")
|
||||
|
||||
# ── Step 1: 学习流程配置 ──
|
||||
logger.info(f"[core_writing_imgMakeWord] Step 1/7: 学习流程配置")
|
||||
prompt1 = build_writing_imgmakeword_prompt("step1_learning_flow", {
|
||||
"任务信息": fields["任务信息"],
|
||||
})
|
||||
学习流程配置, usage1 = llm_client.call("", prompt1, max_tokens=8192, temperature=0.3)
|
||||
学习流程配置 = 学习流程配置.strip()
|
||||
logger.info(f" Step 1 完成: {len(学习流程配置)} 字符, tokens={usage1}")
|
||||
|
||||
# ── Step 2: 互动描述 ──
|
||||
logger.info(f"[core_writing_imgMakeWord] Step 2/7: 互动描述")
|
||||
prompt2 = build_writing_imgmakeword_prompt("step2_interaction_desc", {
|
||||
"任务信息": fields["任务信息"],
|
||||
"前置对话": fields["前置对话"],
|
||||
})
|
||||
互动描述, usage2 = llm_client.call("", prompt2, max_tokens=4096, temperature=0.3)
|
||||
互动描述 = 互动描述.strip()
|
||||
logger.info(f" Step 2 完成: {len(互动描述)} 字符, tokens={usage2}")
|
||||
|
||||
# ── Step 3: taskInfo ──
|
||||
logger.info(f"[core_writing_imgMakeWord] Step 3/7: taskInfo")
|
||||
prompt3 = build_writing_imgmakeword_prompt("step3_task_info", {
|
||||
"ID": str(cId),
|
||||
"任务信息": fields["任务信息"],
|
||||
"互动描述": 互动描述,
|
||||
"角色配置": 角色配置,
|
||||
})
|
||||
taskInfo, usage3 = llm_client.call_for_json("", prompt3, max_tokens=4096, temperature=0.1)
|
||||
logger.info(f" Step 3 完成: taskInfo keys={list(taskInfo.keys()) if isinstance(taskInfo, dict) else type(taskInfo)}, tokens={usage3}")
|
||||
|
||||
# ── Step 4: preDialog ──
|
||||
logger.info(f"[core_writing_imgMakeWord] Step 4/7: preDialog")
|
||||
prompt4 = build_writing_imgmakeword_prompt("step4_pre_dialog", {
|
||||
"前置对话": fields["前置对话"],
|
||||
})
|
||||
preDialog, usage4 = llm_client.call_for_json("", prompt4, max_tokens=4096, temperature=0.1)
|
||||
logger.info(f" Step 4 完成: preDialog len={len(preDialog) if isinstance(preDialog, list) else 'dict'}, tokens={usage4}")
|
||||
|
||||
# ── Step 5: imageInfo (规则生成) ──
|
||||
logger.info(f"[core_writing_imgMakeWord] Step 5/7: imageInfo (规则生成)")
|
||||
imageInfo = {"questionImg": f"{cId}_question.png"}
|
||||
logger.info(f" Step 5 完成: imageInfo={imageInfo}")
|
||||
|
||||
# ── Step 6: questionList ──
|
||||
logger.info(f"[core_writing_imgMakeWord] Step 6/7: questionList")
|
||||
prompt6 = build_writing_imgmakeword_prompt("step6_question_list", {
|
||||
"互动问题": fields["互动问题"],
|
||||
"ID": str(cId),
|
||||
})
|
||||
questionList, usage6 = llm_client.call_for_json("", prompt6, max_tokens=8192, temperature=0.1)
|
||||
logger.info(f" Step 6 完成: questionList len={len(questionList) if isinstance(questionList, list) else 'dict'}, tokens={usage6}")
|
||||
|
||||
# ── Step 7: studyInfo ──
|
||||
logger.info(f"[core_writing_imgMakeWord] Step 7/7: studyInfo")
|
||||
prompt7 = build_writing_imgmakeword_prompt("step7_study_info", {
|
||||
"学习流程配置": 学习流程配置,
|
||||
"任务信息": fields["任务信息"],
|
||||
"ID": str(cId),
|
||||
})
|
||||
studyInfo, usage7 = llm_client.call_for_json("", prompt7, max_tokens=16384, temperature=0.1)
|
||||
logger.info(f" Step 7 完成: studyInfo keys={list(studyInfo.keys()) if isinstance(studyInfo, dict) else type(studyInfo)}, tokens={usage7}")
|
||||
|
||||
logger.info(f"[core_writing_imgMakeWord] 7步生成完成: cId={cId}")
|
||||
return {
|
||||
"taskInfo": taskInfo,
|
||||
"preDialog": preDialog,
|
||||
"imageInfo": imageInfo,
|
||||
"questionList": questionList,
|
||||
"studyInfo": studyInfo,
|
||||
"kpInfo": None,
|
||||
"intermediate": {
|
||||
"学习流程配置": 学习流程配置,
|
||||
"互动描述": 互动描述,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def generate_component(component, character_map=None, section_char_map=None, llm_client=None, level=None):
|
||||
"""
|
||||
一站式生成单个组件的 jsonData + kpInfo
|
||||
@ -2171,6 +2332,20 @@ def generate_component(component, character_map=None, section_char_map=None, llm
|
||||
"intermediate": result["intermediate"],
|
||||
}
|
||||
|
||||
elif cType == "core_writing_imgMakeWord":
|
||||
result = generate_core_writing_imgmakeword(**_core_call_args)
|
||||
core_out = {
|
||||
**_core_base,
|
||||
"jsonData": result["taskInfo"],
|
||||
"taskInfo": result["taskInfo"],
|
||||
"preDialog": result["preDialog"],
|
||||
"imageInfo": result["imageInfo"],
|
||||
"questionList": result["questionList"],
|
||||
"studyInfo": result["studyInfo"],
|
||||
"kpInfo": result["kpInfo"],
|
||||
"intermediate": result["intermediate"],
|
||||
}
|
||||
|
||||
else:
|
||||
raise ValueError(f"核心互动类型 {cType} 尚未实现生成器")
|
||||
|
||||
|
||||
@ -53,6 +53,7 @@ def _build_component_data(result):
|
||||
"status": status,
|
||||
"raw_config": result.get("raw_config", ""),
|
||||
"config_info": result.get("config_info", ""),
|
||||
"knowledge_text": result.get("knowledge_text", ""),
|
||||
}
|
||||
|
||||
if status not in ("generated", "saved"):
|
||||
@ -254,9 +255,12 @@ def _render_component(idx, result):
|
||||
知识推送<span class="push-icon" id="kp-status-{idx}"></span>
|
||||
</button>'''
|
||||
|
||||
# Regenerate button (for all components)
|
||||
regen_btn = f'''<button class="push-btn regen-btn" data-idx="{idx}" onclick="event.stopPropagation();toggleRegenPanel({idx})">
|
||||
重新生成<span class="push-icon" id="regen-status-{idx}"></span>
|
||||
# Regenerate buttons (split: component + kp)
|
||||
regen_btn = f'''<button class="push-btn regen-btn" data-idx="{idx}" onclick="event.stopPropagation();toggleRegenPanel({idx},'component')">
|
||||
重新生成组件<span class="push-icon" id="regen-status-{idx}"></span>
|
||||
</button>
|
||||
<button class="push-btn regen-kp-btn" data-idx="{idx}" onclick="event.stopPropagation();toggleRegenPanel({idx},'kp')">
|
||||
重新生成知识点<span class="push-icon" id="regen-kp-status-{idx}"></span>
|
||||
</button>'''
|
||||
|
||||
# Build body
|
||||
@ -303,17 +307,25 @@ def _render_component(idx, result):
|
||||
reason = html.escape(result.get("reason", ""))
|
||||
body_html = f'<div class="skip-msg">跳过: {reason}</div>'
|
||||
|
||||
# Regenerate panel (editable teaching_config) — for all components
|
||||
# Regenerate panel — split into component regen + kp regen
|
||||
raw_config = result.get("raw_config", "")
|
||||
escaped_config = html.escape(raw_config)
|
||||
config_rows = min(max(raw_config.count('\n') + 1, 5), 25)
|
||||
knowledge_text = result.get("knowledge_text", "")
|
||||
escaped_kp_text = html.escape(knowledge_text)
|
||||
kp_rows = min(max(knowledge_text.count('\n') + 1, 3), 15)
|
||||
# For failed/skipped: show config panel expanded; for success: collapsed
|
||||
regen_display = "block" if status in ("failed", "skipped") else "none"
|
||||
regen_panel = f'''
|
||||
<div class="regen-panel" id="regen-panel-{idx}" style="display:{regen_display};">
|
||||
<div class="regen-label">原始组件配置 <span class="regen-hint">(可编辑后点击"执行重新生成")</span></div>
|
||||
<div class="regen-panel" id="regen-panel-{idx}" style="display:none;">
|
||||
<div class="regen-label">原始组件配置 <span class="regen-hint">(可编辑后点击"执行重新生成组件")</span></div>
|
||||
<textarea class="regen-textarea" id="regen-config-{idx}" rows="{config_rows}">{escaped_config}</textarea>
|
||||
<button class="regen-exec-btn" id="regen-exec-{idx}" onclick="regenerateComponent({idx})">执行重新生成</button>
|
||||
<button class="regen-exec-btn" id="regen-exec-{idx}" onclick="regenerateComponentJson({idx})">执行重新生成组件</button>
|
||||
</div>
|
||||
<div class="regen-panel regen-kp-panel" id="regen-kp-panel-{idx}" style="display:none;">
|
||||
<div class="regen-label regen-kp-label">知识点文本 <span class="regen-hint">(来自sheet知识点列,可编辑后点击"执行重新生成知识点")</span></div>
|
||||
<textarea class="regen-textarea regen-kp-textarea" id="regen-kp-text-{idx}" rows="{kp_rows}">{escaped_kp_text}</textarea>
|
||||
<button class="regen-exec-btn regen-kp-exec-btn" id="regen-kp-exec-{idx}" onclick="regenerateComponentKp({idx})">执行重新生成知识点</button>
|
||||
</div>'''
|
||||
|
||||
return f'''
|
||||
@ -465,6 +477,8 @@ body {{ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto San
|
||||
.kp-btn:hover {{ background:#dcfce7; }}
|
||||
.regen-btn {{ background:#fef3c7; color:#d97706; border-color:#fde68a; }}
|
||||
.regen-btn:hover {{ background:#fde68a; }}
|
||||
.regen-kp-btn {{ background:#fdf2f8; color:#db2777; border-color:#fbcfe8; }}
|
||||
.regen-kp-btn:hover {{ background:#fce7f3; }}
|
||||
.push-btn:disabled {{ opacity:0.5; cursor:not-allowed; }}
|
||||
.push-icon {{ display:inline-block; width:8px; height:8px; border-radius:50%; background:#94a3b8; }}
|
||||
.push-icon.ok {{ background:#10b981; }}
|
||||
@ -485,6 +499,12 @@ body {{ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Noto San
|
||||
cursor:pointer; transition:background 0.2s; }}
|
||||
.regen-exec-btn:hover {{ background:#b45309; }}
|
||||
.regen-exec-btn:disabled {{ opacity:0.5; cursor:not-allowed; }}
|
||||
.regen-kp-panel {{ background:#fdf2f8; border-color:#fbcfe8; }}
|
||||
.regen-kp-label {{ color:#9d174d; }}
|
||||
.regen-kp-textarea {{ border-color:#fbcfe8; }}
|
||||
.regen-kp-textarea:focus {{ border-color:#db2777; box-shadow:0 0 0 2px rgba(219,39,119,0.15); }}
|
||||
.regen-kp-exec-btn {{ background:#db2777; }}
|
||||
.regen-kp-exec-btn:hover {{ background:#9d174d; }}
|
||||
|
||||
/* JSON sections */
|
||||
.json-section {{ margin-bottom:12px; }}
|
||||
@ -1009,8 +1029,9 @@ async function pushComponent(idx) {{
|
||||
async function pushKp(idx) {{
|
||||
const body = buildKpPushBody(idx);
|
||||
if (!body) {{
|
||||
showToast('组件 #' + idx + ' 无知识点数据', 'info');
|
||||
return true;
|
||||
setStatus('kp-status-' + idx, 'fail');
|
||||
showToast('组件 #' + idx + ' 知识点推送失败: 无知识点或kpId为空', 'error');
|
||||
return false;
|
||||
}}
|
||||
|
||||
setStatus('kp-status-' + idx, 'loading');
|
||||
@ -1079,7 +1100,11 @@ async function _runPushAll(mode) {{
|
||||
const r2 = await doPush(body2);
|
||||
if (r2.ok) {{ setStatus('kp-status-' + idx, 'ok'); okCount++; }}
|
||||
else {{ setStatus('kp-status-' + idx, 'fail'); failCount++; _batchErrors.push('[' + _batchContext + '] 知识点推送失败: ' + r2.error); }}
|
||||
}} else {{ if (mode === 'both') okCount++; }}
|
||||
}} else {{
|
||||
setStatus('kp-status-' + idx, 'fail');
|
||||
failCount++;
|
||||
_batchErrors.push('[' + _batchContext + '] 知识点推送失败: 无知识点或kpId为空');
|
||||
}}
|
||||
done++; updateProgress();
|
||||
}}
|
||||
}}
|
||||
@ -1103,18 +1128,34 @@ function pushAllComponents() {{ _runPushAll('config'); }}
|
||||
function pushAllKpAll() {{ _runPushAll('kp'); }}
|
||||
|
||||
// ============ Regenerate ============
|
||||
function toggleRegenPanel(idx) {{
|
||||
const panel = document.getElementById('regen-panel-' + idx);
|
||||
if (!panel) return;
|
||||
const showing = panel.style.display !== 'block';
|
||||
panel.style.display = showing ? 'block' : 'none';
|
||||
function toggleRegenPanel(idx, mode) {{
|
||||
const compPanel = document.getElementById('regen-panel-' + idx);
|
||||
const kpPanel = document.getElementById('regen-kp-panel-' + idx);
|
||||
|
||||
if (mode === 'kp') {{
|
||||
if (compPanel) compPanel.style.display = 'none';
|
||||
if (kpPanel) {{
|
||||
const showing = kpPanel.style.display !== 'block';
|
||||
kpPanel.style.display = showing ? 'block' : 'none';
|
||||
if (showing) {{
|
||||
const card = document.getElementById('card-' + idx);
|
||||
if (card) card.classList.add('open');
|
||||
}}
|
||||
}}
|
||||
}} else {{
|
||||
if (kpPanel) kpPanel.style.display = 'none';
|
||||
if (compPanel) {{
|
||||
const showing = compPanel.style.display !== 'block';
|
||||
compPanel.style.display = showing ? 'block' : 'none';
|
||||
if (showing) {{
|
||||
const card = document.getElementById('card-' + idx);
|
||||
if (card) card.classList.add('open');
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
async function regenerateComponent(idx) {{
|
||||
async function regenerateComponentJson(idx) {{
|
||||
const comp = COMPONENTS[idx - 1];
|
||||
const ta = document.getElementById('regen-config-' + idx);
|
||||
const btn = document.getElementById('regen-exec-' + idx);
|
||||
@ -1137,6 +1178,7 @@ async function regenerateComponent(idx) {{
|
||||
method: 'POST',
|
||||
headers: {{ 'Content-Type': 'application/json' }},
|
||||
body: JSON.stringify({{
|
||||
mode: 'component',
|
||||
teaching_config: teachingConfig,
|
||||
type_name: comp.type_name,
|
||||
cId: comp.cId,
|
||||
@ -1153,7 +1195,7 @@ async function regenerateComponent(idx) {{
|
||||
throw new Error(data.error || ('HTTP ' + resp.status));
|
||||
}}
|
||||
|
||||
// Update COMPONENTS data
|
||||
// Update COMPONENTS data (only jsonData/core fields, keep kpInfo)
|
||||
comp.status = 'generated';
|
||||
comp.category = data.category || comp.category;
|
||||
comp.cType = data.cType || comp.cType;
|
||||
@ -1167,13 +1209,15 @@ async function regenerateComponent(idx) {{
|
||||
}} else {{
|
||||
if (data.jsonData) comp.jsonData = data.jsonData;
|
||||
}}
|
||||
if (data.kpInfo) comp.kpInfo = data.kpInfo;
|
||||
// kpInfo not updated in component-only mode
|
||||
|
||||
// Rebuild body HTML
|
||||
rebuildComponentBody(idx, data);
|
||||
// Rebuild body HTML (pass existing kpInfo from comp, not from data)
|
||||
const displayData = {{ ...data }};
|
||||
if (!displayData.kpInfo && comp.kpInfo) displayData.kpInfo = comp.kpInfo;
|
||||
rebuildComponentBody(idx, displayData);
|
||||
|
||||
setStatus('regen-status-' + idx, 'ok');
|
||||
btn.textContent = '执行重新生成';
|
||||
btn.textContent = '执行重新生成组件';
|
||||
btn.disabled = false;
|
||||
|
||||
// Update status label
|
||||
@ -1183,12 +1227,83 @@ async function regenerateComponent(idx) {{
|
||||
statusEl.textContent = '生成成功';
|
||||
}}
|
||||
|
||||
showToast('组件 #' + idx + ' 重新生成成功', 'success');
|
||||
showToast('组件 #' + idx + ' 组件配置重新生成成功', 'success');
|
||||
}} catch(e) {{
|
||||
setStatus('regen-status-' + idx, 'fail');
|
||||
btn.textContent = '执行重新生成';
|
||||
btn.textContent = '执行重新生成组件';
|
||||
btn.disabled = false;
|
||||
showToast('组件 #' + idx + ' 重新生成失败: ' + e.message, 'error');
|
||||
showToast('组件 #' + idx + ' 组件重新生成失败: ' + e.message, 'error');
|
||||
}}
|
||||
}}
|
||||
|
||||
async function regenerateComponentKp(idx) {{
|
||||
const comp = COMPONENTS[idx - 1];
|
||||
const ta = document.getElementById('regen-kp-text-' + idx);
|
||||
const btn = document.getElementById('regen-kp-exec-' + idx);
|
||||
if (!ta || !btn) return;
|
||||
|
||||
const knowledgeText = ta.value.trim();
|
||||
if (!knowledgeText) {{
|
||||
showToast('知识点文本不能为空', 'error');
|
||||
return;
|
||||
}}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = '生成中...';
|
||||
setStatus('regen-kp-status-' + idx, 'loading');
|
||||
|
||||
const regenUrl = getRegenUrl();
|
||||
|
||||
try {{
|
||||
const resp = await fetch(regenUrl, {{
|
||||
method: 'POST',
|
||||
headers: {{ 'Content-Type': 'application/json' }},
|
||||
body: JSON.stringify({{
|
||||
mode: 'kp',
|
||||
type_name: comp.type_name,
|
||||
cId: comp.cId,
|
||||
knowledge_text: knowledgeText,
|
||||
level: LEVEL,
|
||||
}}),
|
||||
}});
|
||||
|
||||
const data = await resp.json();
|
||||
|
||||
if (!resp.ok) {{
|
||||
throw new Error(data.error || ('HTTP ' + resp.status));
|
||||
}}
|
||||
|
||||
// Update kpInfo only
|
||||
if (data.kpInfo) {{
|
||||
comp.kpInfo = data.kpInfo;
|
||||
}} else {{
|
||||
comp.kpInfo = null;
|
||||
}}
|
||||
comp.knowledge_text = knowledgeText;
|
||||
|
||||
// Rebuild body HTML (keep existing jsonData/core fields)
|
||||
const displayData = {{}};
|
||||
if (comp.category === 'core') {{
|
||||
const fields = PUSH_FIELD_MAP[comp.cType] || [];
|
||||
for (const key of fields) {{
|
||||
if (comp[key]) displayData[key] = comp[key];
|
||||
}}
|
||||
}} else {{
|
||||
if (comp.jsonData) displayData.jsonData = comp.jsonData;
|
||||
}}
|
||||
displayData.kpInfo = comp.kpInfo;
|
||||
rebuildComponentBody(idx, displayData);
|
||||
|
||||
setStatus('regen-kp-status-' + idx, 'ok');
|
||||
btn.textContent = '执行重新生成知识点';
|
||||
btn.disabled = false;
|
||||
|
||||
showToast('组件 #' + idx + ' 知识点重新生成成功', 'success');
|
||||
}} catch(e) {{
|
||||
setStatus('regen-kp-status-' + idx, 'fail');
|
||||
btn.textContent = '执行重新生成知识点';
|
||||
btn.disabled = false;
|
||||
showToast('组件 #' + idx + ' 知识点重新生成失败: ' + e.message, 'error');
|
||||
}}
|
||||
}}
|
||||
|
||||
@ -1200,8 +1315,9 @@ function rebuildComponentBody(idx, data) {{
|
||||
// Clear old content above regen-result (error-msg, json-sections etc)
|
||||
const body = document.getElementById('body-' + idx);
|
||||
const regenPanel = document.getElementById('regen-panel-' + idx);
|
||||
// Remove everything except regen-result and regen-panel
|
||||
while (body.firstChild && body.firstChild !== resultDiv && body.firstChild !== regenPanel) {{
|
||||
const regenKpPanel = document.getElementById('regen-kp-panel-' + idx);
|
||||
// Remove everything except regen-result and regen panels
|
||||
while (body.firstChild && body.firstChild !== resultDiv && body.firstChild !== regenPanel && body.firstChild !== regenKpPanel) {{
|
||||
body.removeChild(body.firstChild);
|
||||
}}
|
||||
|
||||
@ -1301,7 +1417,7 @@ function ensurePushButtons(idx) {{
|
||||
}}
|
||||
|
||||
// ============ LocalStorage Auto-Save ============
|
||||
const SAVE_KEY = 'icj_' + document.title;
|
||||
const SAVE_KEY = 'icj_' + document.title + '_{timestamp}';
|
||||
|
||||
function saveEdits() {{
|
||||
const state = {{}};
|
||||
|
||||
@ -267,7 +267,26 @@ def _extract_english_word_only(text):
|
||||
|
||||
# ============ 单条知识点匹配 ============
|
||||
|
||||
def _match_single_kp(kp_text, level=None, context="", llm_client=None):
|
||||
def _filter_candidates_by_unit(candidates, cId):
|
||||
"""
|
||||
根据组件ID前4位过滤候选知识点,确保知识点属于同一单元。
|
||||
kpid前4位与cId前4位相同才属于同一单元。
|
||||
|
||||
Args:
|
||||
candidates: 粗召回的候选列表
|
||||
cId: 组件ID
|
||||
|
||||
Returns:
|
||||
list: 过滤后的候选列表(仅保留同单元的知识点)
|
||||
"""
|
||||
if not cId or len(str(cId)) < 4:
|
||||
return candidates
|
||||
unit_prefix = str(cId)[:4]
|
||||
filtered = [c for c in candidates if c.get("kp_id") and str(c["kp_id"])[:4] == unit_prefix]
|
||||
return filtered
|
||||
|
||||
|
||||
def _match_single_kp(kp_text, level=None, context="", llm_client=None, cId=None):
|
||||
"""
|
||||
匹配单个知识点文本到数据库记录。
|
||||
|
||||
@ -309,6 +328,14 @@ def _match_single_kp(kp_text, level=None, context="", llm_client=None):
|
||||
logger.info(f"未找到匹配: '{kp_text}' (level={level})")
|
||||
return default_result
|
||||
|
||||
# 根据组件ID前4位过滤候选,确保知识点属于同一单元
|
||||
if cId:
|
||||
filtered = _filter_candidates_by_unit(candidates, cId)
|
||||
if not filtered:
|
||||
logger.info(f"单元过滤后无匹配: '{kp_text}' (cId前4位={str(cId)[:4]})")
|
||||
return default_result
|
||||
candidates = filtered
|
||||
|
||||
# 保存候选列表供 HTML 下拉展示
|
||||
candidates_for_ui = [
|
||||
{"kp_id": c["kp_id"], "type": c.get("type", ""), "title": c.get("title", ""),
|
||||
@ -378,7 +405,7 @@ def match_knowledge_points(knowledge_text, cType, cId, level=None, llm_client=No
|
||||
logger.debug(f"跳过无英文字符行: '{stripped}'")
|
||||
continue
|
||||
|
||||
kp_entry = _match_single_kp(stripped, level, context, llm_client)
|
||||
kp_entry = _match_single_kp(stripped, level, context, llm_client, cId=cId)
|
||||
kp_list.append(kp_entry)
|
||||
|
||||
if not kp_list:
|
||||
|
||||
@ -51,6 +51,7 @@ CORE_INTERACTION_TYPES = {
|
||||
"合作听力": {"cType": "core_listening_order", "bitable_wiki": "FrxtwNRQDizqiikPkATcBzTCnYe", "db_table": "tblBuFU6wVKdBh8B"},
|
||||
"听力拖拽": {"cType": "core_listening_drag", "bitable_wiki": "K3QrwQnWqiPBm1krhnNcWDTqnhe", "db_table": "tblOOAsgYoZWsTGJ"},
|
||||
"听力选择": {"cType": "core_listening_choose", "bitable_wiki": "Kwrcw6A4jip2sxkdLn4czd1knvf", "db_table": "tblH4xNpAmKJ7gEq"},
|
||||
"看图拼词": {"cType": "core_writing_imgMakeWord", "bitable_wiki": "QDyhwiY7PiSER8kHGZncDsrinde", "db_table": "tblyPBqx9IRYQ3a9"},
|
||||
"写作互动": {"cType": None, "bitable_wiki": "DVkcwVr2giswTckcgD0cpHwNnv2"},
|
||||
"看图组句": {"cType": "core_writing_imgMakeSentence", "bitable_wiki": "BkmtwUBwMiHd5Ak7VS6ccE9SnHd", "db_table": "tblpILmFc9ec17DY"},
|
||||
"看图撰写": {"cType": "core_writing_imgWrite", "bitable_wiki": "KwPHwnaqdiWlvNkDm5fcFreDnQh", "db_table": "tblAMaM3g7500Lmm"},
|
||||
|
||||
@ -390,6 +390,7 @@ def process_script(wiki_url_or_token, db_path=None, dry_run=False, target_cids=N
|
||||
"result": result,
|
||||
"raw_config": comp.get("teaching_config", ""),
|
||||
"config_info": comp.get("config_info", ""),
|
||||
"knowledge_text": comp.get("knowledge_text", ""),
|
||||
}
|
||||
|
||||
if dry_run:
|
||||
@ -505,6 +506,7 @@ def process_script(wiki_url_or_token, db_path=None, dry_run=False, target_cids=N
|
||||
"kpInfo": result_data.get("kpInfo"),
|
||||
"raw_config": res.get("raw_config", ""),
|
||||
"config_info": res.get("config_info", ""),
|
||||
"knowledge_text": res.get("knowledge_text", ""),
|
||||
}
|
||||
if status == "saved":
|
||||
entry["component_id"] = res.get("component_id")
|
||||
|
||||
@ -159,35 +159,66 @@ rate_limiter = RateLimiter(CONFIG['rate_limit_per_minute'])
|
||||
|
||||
# --- Regenerate Handler ---
|
||||
def _do_regenerate(body_data):
|
||||
"""调用 generate_component 重新生成单个组件"""
|
||||
"""调用 generate_component 或 generate_kp_info 重新生成单个组件/知识点"""
|
||||
if str(SCRIPTS_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SCRIPTS_DIR))
|
||||
|
||||
mode = body_data.get("mode", "component")
|
||||
type_name = body_data.get("type_name", "")
|
||||
cId = body_data.get("cId", "")
|
||||
level = body_data.get("level") or None
|
||||
|
||||
if not type_name:
|
||||
return 400, {"error": "缺少 type_name"}
|
||||
|
||||
if mode == "kp":
|
||||
# 仅重新生成知识点
|
||||
from generate_json import generate_kp_info
|
||||
from match_component import match_component_type
|
||||
from llm_client import LLMClient
|
||||
|
||||
knowledge_text = body_data.get("knowledge_text", "")
|
||||
if not knowledge_text.strip():
|
||||
return 400, {"error": "缺少 knowledge_text"}
|
||||
|
||||
try:
|
||||
type_info = match_component_type(type_name)
|
||||
cType = type_info["cType"]
|
||||
except ValueError as e:
|
||||
return 400, {"error": str(e)}
|
||||
|
||||
try:
|
||||
llm_client = LLMClient()
|
||||
kp_info = generate_kp_info(cType, cId, knowledge_text, llm_client, level=level)
|
||||
return 200, {"kpInfo": kp_info, "cType": cType}
|
||||
except Exception as e:
|
||||
logger.error(f"Regenerate KP failed: {traceback.format_exc()}")
|
||||
return 500, {"error": str(e)}
|
||||
else:
|
||||
# 仅重新生成组件配置(不生成 kpInfo)
|
||||
from generate_json import generate_component
|
||||
from llm_client import LLMClient
|
||||
|
||||
teaching_config = body_data.get("teaching_config", "")
|
||||
type_name = body_data.get("type_name", "")
|
||||
cId = body_data.get("cId", "")
|
||||
character_map = body_data.get("character_map") or {}
|
||||
section_char_map = body_data.get("section_char_map") or []
|
||||
level = body_data.get("level") or None
|
||||
|
||||
if not teaching_config or not type_name:
|
||||
return 400, {"error": "缺少 teaching_config 或 type_name"}
|
||||
if not teaching_config:
|
||||
return 400, {"error": "缺少 teaching_config"}
|
||||
|
||||
component = {
|
||||
"type_name": type_name,
|
||||
"cId": cId,
|
||||
"teaching_config": teaching_config,
|
||||
"has_image": body_data.get("has_image", False),
|
||||
"knowledge_text": body_data.get("knowledge_text", ""),
|
||||
"knowledge_text": "", # 强制为空,跳过 kpInfo 生成
|
||||
"config_info": body_data.get("config_info", ""),
|
||||
}
|
||||
|
||||
try:
|
||||
llm_client = LLMClient()
|
||||
result = generate_component(component, character_map=character_map, section_char_map=section_char_map, llm_client=llm_client, level=level)
|
||||
result["kpInfo"] = None # 确保不返回 kpInfo
|
||||
return 200, result
|
||||
except Exception as e:
|
||||
logger.error(f"Regenerate failed: {traceback.format_exc()}")
|
||||
@ -316,8 +347,8 @@ class ProxyHandler(BaseHTTPRequestHandler):
|
||||
self._send_json(err[0], err[1])
|
||||
return
|
||||
|
||||
if not isinstance(body_data, dict) or not body_data.get('teaching_config'):
|
||||
self._send_json(400, {'error': 'body must contain teaching_config'})
|
||||
if not isinstance(body_data, dict) or not body_data.get('type_name'):
|
||||
self._send_json(400, {'error': 'body must contain type_name'})
|
||||
return
|
||||
|
||||
logger.info(f'[{client_ip}] Regenerating: type_name={body_data.get("type_name")}, cId={body_data.get("cId")}')
|
||||
|
||||
@ -84,3 +84,11 @@
|
||||
{"type":"memory.recall.recorded","timestamp":"2026-06-01T10:44:47.489Z","query":"王璐辰 反馈 组件配置 中互动 核心互动 问题","resultCount":2,"results":[{"path":"memory/2026-04-30.md","startLine":50,"endLine":63,"score":1},{"path":"memory/2026-05-07.md","startLine":354,"endLine":368,"score":1}]}
|
||||
{"type":"memory.recall.recorded","timestamp":"2026-06-02T01:49:17.841Z","query":"英文台词生产 L1-S2-U17-L1 摩托怪客","resultCount":6,"results":[{"path":"memory/2026-05-28.md","startLine":46,"endLine":62,"score":1},{"path":"memory/2026-05-28.md","startLine":60,"endLine":66,"score":1},{"path":"memory/2026-05-29.md","startLine":1,"endLine":14,"score":1},{"path":"memory/2026-05-28.md","startLine":35,"endLine":49,"score":1},{"path":"memory/2026-05-18.md","startLine":791,"endLine":810,"score":1},{"path":"memory/2026-05-08.md","startLine":33,"endLine":55,"score":1}]}
|
||||
{"type":"memory.recall.recorded","timestamp":"2026-06-02T07:02:08.195Z","query":"interactive-component-json 组件配置-json skill","resultCount":1,"results":[{"path":"memory/2026-05-20.md","startLine":40,"endLine":59,"score":1}]}
|
||||
{"type":"memory.recall.recorded","timestamp":"2026-06-04T03:23:22.968Z","query":"主题图片 L1 L2 theme picture","resultCount":2,"results":[{"path":"memory/2026-05-12.md","startLine":141,"endLine":155,"score":1},{"path":"memory/2026-05-12.md","startLine":132,"endLine":143,"score":1}]}
|
||||
{"type":"memory.recall.recorded","timestamp":"2026-06-04T03:23:36.292Z","query":"主题 topic theme L1 L2 图片 单元","resultCount":2,"results":[{"path":"memory/2026-05-12.md","startLine":141,"endLine":155,"score":1},{"path":"memory/2026-05-12.md","startLine":132,"endLine":143,"score":1}]}
|
||||
{"type":"memory.recall.recorded","timestamp":"2026-06-04T03:24:24.356Z","query":"主题图片 theme image 单元主题 topic picture L1 L2 课程","resultCount":2,"results":[{"path":"memory/2026-05-12.md","startLine":141,"endLine":155,"score":1},{"path":"memory/2026-05-12.md","startLine":132,"endLine":143,"score":1}]}
|
||||
{"type":"memory.recall.recorded","timestamp":"2026-06-04T03:43:35.423Z","query":"瓦拉能力体系 Vala 产品能力","resultCount":2,"results":[{"path":"memory/2026-05-18.md","startLine":702,"endLine":726,"score":1},{"path":"memory/2026-05-18.md","startLine":672,"endLine":707,"score":1}]}
|
||||
{"type":"memory.recall.recorded","timestamp":"2026-06-04T06:32:47.988Z","query":"组件生产 多维表格 bitable app_token 1112315","resultCount":2,"results":[{"path":"memory/2026-05-07.md","startLine":354,"endLine":368,"score":1},{"path":"memory/2026-04-22.md","startLine":1,"endLine":8,"score":1}]}
|
||||
{"type":"memory.recall.recorded","timestamp":"2026-06-04T06:33:43.353Z","query":"组件生产 bitable app_token 多维表格 互动组件","resultCount":2,"results":[{"path":"memory/2026-05-07.md","startLine":354,"endLine":368,"score":1},{"path":"memory/2026-04-22.md","startLine":1,"endLine":8,"score":1}]}
|
||||
{"type":"memory.recall.recorded","timestamp":"2026-06-04T06:34:06.417Z","query":"组件生产 多维表格 app_token CMHSbUUjka","resultCount":2,"results":[{"path":"memory/2026-05-07.md","startLine":354,"endLine":368,"score":1},{"path":"memory/2026-04-22.md","startLine":1,"endLine":8,"score":1}]}
|
||||
{"type":"memory.recall.recorded","timestamp":"2026-06-04T09:21:44.866Z","query":"儿童英语学习通病 常见错误 不同阶段 学习难点","resultCount":2,"results":[{"path":"memory/2026-05-11.md","startLine":78,"endLine":89,"score":1},{"path":"memory/2026-05-26.md","startLine":161,"endLine":187,"score":1}]}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"updatedAt": "2026-06-02T07:02:08.195Z",
|
||||
"updatedAt": "2026-06-04T09:21:44.866Z",
|
||||
"entries": {
|
||||
"memory:memory/2026-05-07.md:57:74": {
|
||||
"key": "memory:memory/2026-05-07.md:57:74",
|
||||
@ -876,13 +876,13 @@
|
||||
"endLine": 368,
|
||||
"source": "memory",
|
||||
"snippet": "- **需求:** 将 020102(I am...)和 020103(I am ready / Thank you)两套题合并为一个 `{first:..., second:...}` JSON,统一 questionSetID=0000001 - **状态:** ✅ 已完成 - **核心考点分析(用户强调):** 需分析每个句型的核心考点(孩子最容易犯错的地方),挖空对准核心考点 - I am/from 组:am(系动词第一人称)、from(介词选择)、student(a+名词结构) - Thank you for 组:for(介词选择,非 you)、helping(for+动名词,非 help/to help) - **输出文件:** `output/writing_pic_qa_combined.json` ### 刘彦江 — 组件配置-json 请求(L1-S2-U13-L4 沙漠之花) - **时间:** 16:45 ~ 17:51 - **文档:** `https://makee-interactive.feishu.cn/wiki/K5E1wzwk7it9t7kXvcbc6Xugnhc` - **状态:** ⚠️ 未完成 — pipeline 识别到 0 组件 - **根因:** 剧本文档的13个组件数据存储在 markdown 内联表格中(lark-table,5列×36行),而非内嵌 Sheet。当前 pipeline 的 parse_script 只从内嵌 Sheet 读取组件数据,不支持 markdown 表格组件解析 - **已识别组件(ma",
|
||||
"recallCount": 12,
|
||||
"recallCount": 15,
|
||||
"dailyCount": 0,
|
||||
"groundedCount": 0,
|
||||
"totalScore": 12,
|
||||
"totalScore": 15,
|
||||
"maxScore": 1,
|
||||
"firstRecalledAt": "2026-05-15T07:13:08.147Z",
|
||||
"lastRecalledAt": "2026-06-01T10:44:47.489Z",
|
||||
"lastRecalledAt": "2026-06-04T06:34:06.417Z",
|
||||
"queryHashes": [
|
||||
"08364c8746ab",
|
||||
"4f08741ab4fd",
|
||||
@ -895,7 +895,10 @@
|
||||
"8742c0bf4e2b",
|
||||
"eb0902db0156",
|
||||
"229e5d3943bb",
|
||||
"f53c9769e692"
|
||||
"f53c9769e692",
|
||||
"5d025e291984",
|
||||
"472b243ec77c",
|
||||
"fd4c8aca2cb6"
|
||||
],
|
||||
"recallDays": [
|
||||
"2026-05-15",
|
||||
@ -904,7 +907,8 @@
|
||||
"2026-05-21",
|
||||
"2026-05-25",
|
||||
"2026-05-28",
|
||||
"2026-06-01"
|
||||
"2026-06-01",
|
||||
"2026-06-04"
|
||||
],
|
||||
"conceptTags": [
|
||||
"am/from",
|
||||
@ -924,13 +928,13 @@
|
||||
"endLine": 8,
|
||||
"source": "memory",
|
||||
"snippet": "[李应瑛 2026-04-22 提出要求] 所有需要包含对话的内容(如剧本、互动组件等)必须要有【后置对话】字段,无后置对话时填写“无”。 [李应瑛 2026-04-22 确认规则] 剧本内嵌表格组件填写位置规则:仅当表格第一列(A列)明确标注为对话类类型(对话朗读/对话挖空/对话选读/对话组句等)时,才在同一行的H列(【组件】列)填写对应的组件内容,其他类型行(TL/场景/角色/图片/非对话类等)无需填写。 [李应瑛 2026-04-22 确认格式规则] 对话类组件字段换行规则:每个结构单独占一行,格式为: 【任务标题】xxx 【情境引入】xxx 【互动内容】xxx 【后置对话】xxx 单元格内使用\\n作为换行符实现,后续所有组件均遵循此格式。",
|
||||
"recallCount": 10,
|
||||
"recallCount": 13,
|
||||
"dailyCount": 0,
|
||||
"groundedCount": 0,
|
||||
"totalScore": 10,
|
||||
"totalScore": 13,
|
||||
"maxScore": 1,
|
||||
"firstRecalledAt": "2026-05-15T07:13:08.147Z",
|
||||
"lastRecalledAt": "2026-06-01T10:44:19.887Z",
|
||||
"lastRecalledAt": "2026-06-04T06:34:06.417Z",
|
||||
"queryHashes": [
|
||||
"08364c8746ab",
|
||||
"4f08741ab4fd",
|
||||
@ -941,7 +945,10 @@
|
||||
"d6a04b711fd9",
|
||||
"8742c0bf4e2b",
|
||||
"eb0902db0156",
|
||||
"229e5d3943bb"
|
||||
"229e5d3943bb",
|
||||
"5d025e291984",
|
||||
"472b243ec77c",
|
||||
"fd4c8aca2cb6"
|
||||
],
|
||||
"recallDays": [
|
||||
"2026-05-15",
|
||||
@ -950,7 +957,8 @@
|
||||
"2026-05-21",
|
||||
"2026-05-25",
|
||||
"2026-05-28",
|
||||
"2026-06-01"
|
||||
"2026-06-01",
|
||||
"2026-06-04"
|
||||
],
|
||||
"conceptTags": [
|
||||
"对话朗读/对话挖空/对话选读/对话组句等",
|
||||
@ -1396,19 +1404,21 @@
|
||||
"endLine": 726,
|
||||
"source": "memory",
|
||||
"snippet": "- 颜色题: \"What colour is it?\",物体题: \"What's this?\" - ability: 图文匹配 - explanation: 含回答要点、图片内容、能力、评估标准、回答指导 ### 审校结果 - ✅ 全部27题 jsonData 结构完整、explanation 全中文、题目格式正确 - ✅ 题目集合ID一致、图片编号连续(00-10/00-11/00-09) - ✅ 题目配置(人类可读)格式规范 - ⚠️ 口语-P4表无「审校结果」列,无法标记审校状态 - ℹ️ 110101题组2 \"colour\"出现2次(调色盘+彩虹)— 按规格生成 - ℹ️ 110201 \"mice\"(复数) 使用 \"What are these?\" 区分于 \"mouse\"(单数) 使用 \"What's this?\" - ℹ️ 以下词汇未在L1词库中检索到(词库仅160词/声称980词,疑似文件不完整):jacket, T-shirt, hair, monster, colour, bread, cake, candy, chocolate, ice cream, mice, mouse — 按用户指定词汇生产,未做替换 ### Bitable信息 - App Token: CMHSbUUjka3TrUsaxxEc297ongf - Table ID: tblsD2dxaRpLmkXD - 操作方式: Python subprocess + curl(避免shell转义) ## [李应瑛] L1-S2-U17-L3 紧急拜访 组件配置(2026-05-18 20:5",
|
||||
"recallCount": 2,
|
||||
"recallCount": 3,
|
||||
"dailyCount": 0,
|
||||
"groundedCount": 0,
|
||||
"totalScore": 2,
|
||||
"totalScore": 3,
|
||||
"maxScore": 1,
|
||||
"firstRecalledAt": "2026-05-19T03:08:27.099Z",
|
||||
"lastRecalledAt": "2026-05-19T03:08:50.132Z",
|
||||
"lastRecalledAt": "2026-06-04T03:43:35.423Z",
|
||||
"queryHashes": [
|
||||
"34bf2e5fbdee",
|
||||
"3f9471df9e50"
|
||||
"3f9471df9e50",
|
||||
"19f5f93f3465"
|
||||
],
|
||||
"recallDays": [
|
||||
"2026-05-19"
|
||||
"2026-05-19",
|
||||
"2026-06-04"
|
||||
],
|
||||
"conceptTags": [
|
||||
"00-10/00-11/00-09",
|
||||
@ -1428,19 +1438,21 @@
|
||||
"endLine": 707,
|
||||
"source": "memory",
|
||||
"snippet": "- `/tmp/unit_challenge_ability_doc.md` — 对外分享版(已发送文件 + 飞书文档) --- ## L1-S2-U18-L5 双龙会 台词润色审校([王璐辰]) ### 审校结果 - 语法/拼写硬错误:5处(Rows 41, 55, 126, 153, 178) - 表达优化建议:4处(Rows 77, 84, 63, 207) - 一致性遗漏:4行无润色 + dragon/Loong 混用不统一 - D列知识点:6个知识点,共20次 ### 交互模式收获 - 用户(王璐辰)偏好直接在 bitable 中写入统计表格 → 后续同类需求优先写入原表 ## [刘彦江] 口语-P4-看图识物 内容生产(2026-05-18 19:34) ### 生产范围(3条记录,全部双题组,全部回填验证通过 ✅) | ID | record_id | 题组1 | 题组2 | 状态 | |----|-----------|-------|-------|------| | 100001 | recvjYhcXkYXIM | 6题(颜色) | 5题(衣物) | ✅ | | 110101 | recvjYhdvUxDgs | 6题(身体) | 6题(颜色) | ✅ | | 110201 | recvjYhe4opOGm | 6题(食物) | 4题(动物) | ✅ | ### 题型结构 **口语-P4 (speaking_pic_recognize)**: - textTitle: \"Look and answer.\" - 每词1题,配1图(白底中央展",
|
||||
"recallCount": 2,
|
||||
"recallCount": 3,
|
||||
"dailyCount": 0,
|
||||
"groundedCount": 0,
|
||||
"totalScore": 2,
|
||||
"totalScore": 3,
|
||||
"maxScore": 1,
|
||||
"firstRecalledAt": "2026-05-19T03:08:27.099Z",
|
||||
"lastRecalledAt": "2026-05-19T03:08:50.132Z",
|
||||
"lastRecalledAt": "2026-06-04T03:43:35.423Z",
|
||||
"queryHashes": [
|
||||
"34bf2e5fbdee",
|
||||
"3f9471df9e50"
|
||||
"3f9471df9e50",
|
||||
"19f5f93f3465"
|
||||
],
|
||||
"recallDays": [
|
||||
"2026-05-19"
|
||||
"2026-05-19",
|
||||
"2026-06-04"
|
||||
],
|
||||
"conceptTags": [
|
||||
"l1-s2-u18-l5",
|
||||
@ -2979,6 +2991,134 @@
|
||||
"token",
|
||||
"u1zewhzaaie07tkjmoochurgnih"
|
||||
]
|
||||
},
|
||||
"memory:memory/2026-05-12.md:141:155": {
|
||||
"key": "memory:memory/2026-05-12.md:141:155",
|
||||
"path": "memory/2026-05-12.md",
|
||||
"startLine": 141,
|
||||
"endLine": 155,
|
||||
"source": "memory",
|
||||
"snippet": "- **最终结果:10/10 条记录 sentenceMeaningMeaning 均可 json.loads 正确解析,每条含 2 题,无残留垃圾数据** - **受影响记录:** 1213004, 1213006, 1213010, 1214008, 1215005, 1216001, 1216004, 1216007, 1216008, 1216010 - **审校结果同步更新:** 移除所有 10 条的 `❌ JSON 解析失败` 标记 - **注意:** Q1 explanation 在重建时词表述可能微调(内容含义一致),如需精确原始 wording 可从中文列恢复后人工微调 ## 经验教训 ### bitable 写入需严格流程管控(2026-05-12) - 批量更新 JSON 字段时必须:写入前完整提取原始数据 → 写入后逐字段 diff 验证 → 发现异常立即回滚 - 本次 JSON 修复中首次写入不完整导致 Q2 丢失,原因:修复脚本未正确处理多题 JSON 结构 - **教训:** 任何涉及 JSON 重建/修复的批量操作,先单条试运行验证通过后再批量执行,不可并行甩出多条 ### sentenceMeaningMeaning 的 explanation 引号规范(2026-05-12) - explanation 字段中的中文双引号极易被误用为 ASCII `\"` (U+0022) - 建议生产侧统一使用中文引号 `\"` (U+201C) 和 `\"` (U+201D),或在 explanation 写入前做引号归一化预处理",
|
||||
"recallCount": 3,
|
||||
"dailyCount": 0,
|
||||
"groundedCount": 0,
|
||||
"totalScore": 3,
|
||||
"maxScore": 1,
|
||||
"firstRecalledAt": "2026-06-04T03:23:22.968Z",
|
||||
"lastRecalledAt": "2026-06-04T03:24:24.356Z",
|
||||
"queryHashes": [
|
||||
"defa0daee9d7",
|
||||
"92343053bee2",
|
||||
"302eafc578a7"
|
||||
],
|
||||
"recallDays": [
|
||||
"2026-06-04"
|
||||
],
|
||||
"conceptTags": [
|
||||
"10/10",
|
||||
"json.loads",
|
||||
"重建/修复的批量操作",
|
||||
"最终",
|
||||
"结果",
|
||||
"记录",
|
||||
"sentencemeaningmeaning",
|
||||
"均可"
|
||||
]
|
||||
},
|
||||
"memory:memory/2026-05-12.md:132:143": {
|
||||
"key": "memory:memory/2026-05-12.md:132:143",
|
||||
"path": "memory/2026-05-12.md",
|
||||
"startLine": 132,
|
||||
"endLine": 143,
|
||||
"source": "memory",
|
||||
"snippet": "- 检查题型:句意选择题 ×30、句子补全题、听句作答题、场景选择题、看图选择题 - 验证方法:提取 explanation 中的英文引句 vs question 中的英文句子、正则提取\"空处应填X\" vs options[answer] - 结果:✅ **0 条实质性不对应** - **10 条 sentenceMeaningMeaning JSON 修复:** - **根因:** explanation 文本中的 ASCII `\"` (U+0022) 被当作中文双引号使用,JSON 解析器将其视为字符串终止符 - **修复策略:** 从「句意选择题」中文列提取英文原句和选项 → json.dumps 重建标准 JSON(只修格式,不修内容) - **1216007 特殊处理:** 原始 JSON 损坏严重,需从中文列完全重建 - **二次修复(Q2 丢失问题):** 首次不完全修复导致 9 条记录的 Q2 被覆盖丢失,且 Q1 被 JSON 垃圾污染 → 从中文列全面重建 Q1+Q2 - **最终结果:10/10 条记录 sentenceMeaningMeaning 均可 json.loads 正确解析,每条含 2 题,无残留垃圾数据** - **受影响记录:** 1213004, 1213006, 1213010, 1214008, 1215005, 1216001, 1216004, 1216007, 1216008, 1216010 - **审校结果同步更新:** 移除所有 10 条的 `❌ JSON 解析失败` 标记",
|
||||
"recallCount": 3,
|
||||
"dailyCount": 0,
|
||||
"groundedCount": 0,
|
||||
"totalScore": 3,
|
||||
"maxScore": 1,
|
||||
"firstRecalledAt": "2026-06-04T03:23:22.968Z",
|
||||
"lastRecalledAt": "2026-06-04T03:24:24.356Z",
|
||||
"queryHashes": [
|
||||
"defa0daee9d7",
|
||||
"92343053bee2",
|
||||
"302eafc578a7"
|
||||
],
|
||||
"recallDays": [
|
||||
"2026-06-04"
|
||||
],
|
||||
"conceptTags": [
|
||||
"json.dumps",
|
||||
"10/10",
|
||||
"json.loads",
|
||||
"检查",
|
||||
"选择",
|
||||
"句子",
|
||||
"作答",
|
||||
"场景"
|
||||
]
|
||||
},
|
||||
"memory:memory/2026-05-11.md:78:89": {
|
||||
"key": "memory:memory/2026-05-11.md:78:89",
|
||||
"path": "memory/2026-05-11.md",
|
||||
"startLine": 78,
|
||||
"endLine": 89,
|
||||
"source": "memory",
|
||||
"snippet": "- **状态:** ✅ 全部完成(两阶段延迟写入策略,避免自动化覆盖) - **脚本:** `scripts/batch_reading_matchInfo.py` | ID | 题组1 | 题组2 | record_id | |----|-------|-------|-----------| | 021301 | actor/beard/different/brave/neighbour(人物) | dinner/cooking/fire/album/diary(生活) | recvjjk7oAT1YN | | 021401 | plan/repair/newspaper/backpack/seat(日程) | garden/melon/water/plant/summer(自然) | recvjjk7TU4ZFs | | 021501 | visitor/welcome/invite/laugh/guest(社交) | mistake/instruction/reason/technology/brush(学习) | recvjjk8mju8ou | | 021601 | record/event/enter/return/sort(管理) | handbag/camera/gate/wheel/entrance(场所) | recvjjk8Q5ZMIu | | 021701 | driver/street/market/guidebook/shopping(出行) | machine/draw/basketball/bookcase/invention(活动) | r",
|
||||
"recallCount": 1,
|
||||
"dailyCount": 0,
|
||||
"groundedCount": 0,
|
||||
"totalScore": 1,
|
||||
"maxScore": 1,
|
||||
"firstRecalledAt": "2026-06-04T09:21:44.866Z",
|
||||
"lastRecalledAt": "2026-06-04T09:21:44.866Z",
|
||||
"queryHashes": [
|
||||
"3d95a503721c"
|
||||
],
|
||||
"recallDays": [
|
||||
"2026-06-04"
|
||||
],
|
||||
"conceptTags": [
|
||||
"record-id",
|
||||
"dinner/cooking/fire/album/diary",
|
||||
"garden/melon/water/plant/summer",
|
||||
"record/event/enter/return/sort",
|
||||
"状态",
|
||||
"全部",
|
||||
"完成",
|
||||
"阶段"
|
||||
]
|
||||
},
|
||||
"memory:memory/2026-05-26.md:161:187": {
|
||||
"key": "memory:memory/2026-05-26.md:161:187",
|
||||
"path": "memory/2026-05-26.md",
|
||||
"startLine": 161,
|
||||
"endLine": 187,
|
||||
"source": "memory",
|
||||
"snippet": "- 文档:`https://makee-interactive.feishu.cn/wiki/XoGZwgrRMiGk9vkYNWmcegs9nyh`(L1-S2-U19-L1 客厅游戏) - Bot当前无该知识空间权限,无法写入 - 已完成 5 个组件配置文本生成,待权限恢复后写入 G 列 ### 修正格式规范(用户指定) - 不含【组件ID】字段 - 【互动内容】顶部加「题目:选择正确的单词补全句子」 - 挖空用 `___`(3个下划线) - 选项后加「辅助信息」行(如 `afternoon 意为下午`) - 【后置对话】缺省写「空」,不用「无」 - 【互动反馈】正确:「正确:无」;错误:「错误 角色名: 台词」 - 任务标题和反馈需贴合剧情 - 有【教研图】的组件:资源配置需写「图片时机」 ### 5个组件配置摘要 | ID | 句子 | 正确答案 | 干扰项 | 图片时机 | |----|------|---------|--------|---------| | 1219102 | This ___ , we go to space! | afternoon | evening | 无 | | 1219105 | This ___ is the captain's seat! | armchair | sofa | 互动内容 | | 1219110 | Push the ___ , Dad! | armchair | table | 互动内容 | | 1219111 | The ___ is a glowing star! | lamp | armchair |",
|
||||
"recallCount": 1,
|
||||
"dailyCount": 0,
|
||||
"groundedCount": 0,
|
||||
"totalScore": 1,
|
||||
"maxScore": 1,
|
||||
"firstRecalledAt": "2026-06-04T09:21:44.866Z",
|
||||
"lastRecalledAt": "2026-06-04T09:21:44.866Z",
|
||||
"queryHashes": [
|
||||
"3d95a503721c"
|
||||
],
|
||||
"recallDays": [
|
||||
"2026-06-04"
|
||||
],
|
||||
"conceptTags": [
|
||||
"l1-s2-u19-l1",
|
||||
"https",
|
||||
"makee",
|
||||
"interactive.feishu.cn",
|
||||
"wiki",
|
||||
"xogzwgrrmigk9vkynwmcegs9nyh",
|
||||
"u19",
|
||||
"客厅"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
45
memory/2026-06-04-1218.md
Normal file
45
memory/2026-06-04-1218.md
Normal file
@ -0,0 +1,45 @@
|
||||
# Session: 2026-06-04 12:18:44 GMT+8
|
||||
|
||||
- **Session Key**: agent:xiaoyan:feishu:direct:ou_8b79b7f711e42a4004f3f3c225a1a99c
|
||||
- **Session ID**: bed05a1e-fc0d-4ea2-b319-ed5f913c4a60
|
||||
- **Source**: feishu
|
||||
|
||||
## Conversation Summary
|
||||
|
||||
user: https://makee-interactive.feishu.cn/wiki/PgWrwllW2ikQKgkLTYKcmKFonXc组件配置-json
|
||||
assistant: 文档是「L1-S2-U18-L1 中国周」的组件配置文档,包含 17 个互动组件。我先读取相关技能和嵌入的 Sheet 数据。
|
||||
|
||||
|
||||
assistant: 文档已读取,标题是「L1-S2-U18-L1 中国周」,包含 17 个互动组件。消息含"组件配置-json",需要调用 `interactive-component-json` 技能。
|
||||
|
||||
|
||||
assistant: 匹配到 `interactive-component-json` 技能。文档「L1-S2-U18-L1 中国周」包含 17 个互动组件,启动流水线处理。
|
||||
|
||||
|
||||
assistant: 处理完成。17/17 组件全部成功,HTML 报告已发送给你。
|
||||
|
||||
**L1-S2-U18-L1 中国周 — 组件配置 JSON 生成结果:**
|
||||
|
||||
| # | 组件ID | 类型 | cType |
|
||||
|---|--------|------|-------|
|
||||
| 1 | 1218101 | 对话朗读 | mid_dialog_repeat |
|
||||
| 2 | 1218102 | 图片单选 | mid_image_choose |
|
||||
| 3 | 1218103 | 对话朗读 | mid_dialog_repeat |
|
||||
| 4 | 1218104 | 图片单选 | mid_image_choose |
|
||||
| 5 | 1218105 | 对话挖空 | mid_dialog_fillin |
|
||||
| 6 | 1218106 | 对话朗读 | mid_dialog_repeat |
|
||||
| 7 | 1218107 | 图片多选 | mid_image_multiple |
|
||||
| 8 | 1218108 | 对话挖空 | mid_dialog_fillin |
|
||||
| 9 | 1218109 | 对话朗读 | mid_dialog_repeat |
|
||||
| 10 | 1218110 | 对话朗读 | mid_dialog_repeat |
|
||||
| 11 | 1218111 | 对话选择 | mid_dialog_choose |
|
||||
| 12 | 1218112 | 对话挖空 | mid_dialog_fillin |
|
||||
| 13 | 1218113 | 对话组句 | mid_dialog_sentence |
|
||||
| 14 | 1218114 | 对话朗读 | mid_dialog_repeat |
|
||||
| 15 | 1218115 | 对话组句 | mid_dialog_sentence |
|
||||
| 16 | 1218116 | 听力拖拽 | core_listening_drag |
|
||||
| 17 | 1218117 | 对话选读 | mid_dialog_select |
|
||||
|
||||
⚠️ 1 个注意项:组件 1218115(对话组句)的知识点 `proud of` 在 L1 词库中未匹配到 kpId,需人工确认。
|
||||
|
||||
HTML 报告内嵌了推送按钮,你可以在浏览器中打开后直接推送配置到远程数据库。
|
||||
Loading…
Reference in New Issue
Block a user