#!/usr/bin/env python3
"""
看图说话 (core_speaking_image) 多步 LLM prompt 模板

生成流水线：
  Step 1: 学习流程配置 (intermediate text)
  Step 2: 语音识别热词 (intermediate text)
  Step 3: taskData JSON
  Step 4: imageInfo JSON
  Step 5: optionList JSON
  Step 6: questionList JSON
  Step 7: configData JSON (depends on questionList)
  Step 8: learningData JSON (depends on 学习流程配置)
"""

# ── Step 1: 学习流程配置 ──
STEP1_LEARNING_FLOW = r"""你是一个英语学习任务的教学流程设计助手。请你根据以下提供的信息，生成一个结构化的学习引导内容，用于引导孩子逐轮完成一个英语对话任务。

# 你的任务
参考 {用户任务描述} 和 {示例对话}，理解整个对话互动流程，然后生成一个 markdown 格式的教学流程。

你需要从{示例对话}中提取每一轮 NPC 的发言和孩子的回应（即一问一答为一回合），自动识别总共需要生成多少个回合。通常每个对话任务包含 3~5 回合。

中间回合(通常1~3回合)：
- {{npc角色名}}：NPC 的英文发言（来自示例对话）
- Brainy：用中文解释该发言的意思，并引导孩子理解当前情境要做什么
- 选项：提供三个英文选项（1 正确[标识在选项内容后] + 2 干扰），每个选项附带 Brainy 的反馈
  - 若选项正确，Brainy 鼓励孩子朗读该句
  - 若选项错误，先用中文简要说明该英文句子的实际含义；然后指出它与当前画面/情境不符（假设有一张配套插图）；最后提醒用户回到当前问题，并建议"仔细看图"或"听清问题"。

结尾回合:
- {{npc角色名}}：NPC 的英文发言（来自示例对话）
- Brainy：友好地向用户表示任务已经完成。

注意：
所有{{npc角色名}}从{对话信息}中的对话角色抽取
生成的选项反馈如果里面有英文的话前后需要空格，后面如果是标点符号则不需要空格。

# 输出格式
```
# 中间回合1
{{npc角色名}}：（英文原句）
Brainy：（对 NPC 的话进行中文解释与情境引导）
用户选项:
（选项 A）
  Brainy：（选项 A 的反馈）
（选项 B）
  Brainy：（选项 B 的反馈）
（选项 C）
  Brainy：（选项 C 的反馈）
{{npc角色名}}：（英文原句）

# 中间回合2
... ...

# 结尾回合
Brainy：（任务完成说明）
```

#其他规则
在输出的文本中，如果涉及英文专有词汇的中文翻译，当该英文词汇在 {专有词汇} 表中时， 请使用  {专有词汇}  表中的中文翻译。


# 输出样例
以下是一个完整的输出样例：
```
# 中间回合1
Luggage: Hello! Welcome to use our check-in service. Do you have anything to check in?
Brainy: 托运机器人向你打招呼，并询问你是否需要托运任何物品。现在你需要告诉机器人你想要托运的物品。
用户选项:
（选项 A）I'd like to check in my sword. [正确]
Brainy: 太棒了！你正确地表达了想要托运的物品是剑。请大声朗读这句话！
（选项 B）I'm going to Planet Vala.
Brainy: 这句话的意思是你要去 Planet Vala，但机器人现在问的是你要托运什么物品，而不是你的目的地。请再仔细听清问题，告诉机器人你想要托运的物品。
（选项 C）My flight number is 1221.
Brainy: 这句话的意思是你的航班号是1221，但机器人现在问的是你要托运什么，而不是航班信息。请再仔细听清问题，告诉机器人你想要托运的物品。

# 中间回合2
Luggage: OK. May I ask where you are going?
Brainy: 机器人已经知道你要托运的物品是剑，现在它询问你的目的地。你需要告诉机器人你要去的地方。
用户选项:
（选项 A）I'd like to check in my sword.
Brainy: 这句话的意思是你想托运一把剑，但机器人现在问的是你要去哪里。请再仔细听清问题，告诉机器人你的目的地。
（选项 B）I'm going to Planet Vala. [正确]
Brainy: 太棒了！你正确地表达了你的目的地是 Planet Vala。请大声朗读这句话！
（选项 C）My flight number is 1221.
Brainy: 这句话的意思是你的航班号是1221，但机器人现在问的是你要去哪个地方，而不是航班号。请再仔细听清问题，告诉机器人你的目的地。

# 中间回合3
Luggage: Please tell me your flight information.
Brainy: 机器人已经知道你的目的地，现在它询问你的航班信息。你需要告诉机器人你的航班号。
用户选项:
（选项 A）I'd like to check in my sword.
Brainy: 这句话的意思是你想托运一把剑，但机器人现在需要的是你的航班信息。请再仔细听清问题，告诉机器人你的航班号。
（选项 B）I'm going to Planet Vala.
Brainy: 这句话的意思是你要去 Planet Vala，但机器人现在问的是你的航班号。请再仔细听清问题，告诉机器人你的航班号。
（选项 C）My flight number is 1221. [正确]
Brainy: 太棒了！你正确地表达了你的航班号是1221。请大声朗读这句话！

# 结尾回合
Luggage: Thank you. I've finished the check-in for your sword. Have a nice journey.
Brainy: 机器人已经完成了你的剑的托运手续，并祝你旅途愉快。任务成功完成！
```

# 输入
{用户任务描述}:
$用户任务描述$

{示例对话}:
$示例对话$

{专有词汇}:
$专有词汇$

{对话信息}：
$对话信息$

# 输出
现在请按照上述信息和要求，直接输出目标内容。
如果 {用户任务描述} 或{示例对话}内容为空，则直接输出"无"。"""


# ── Step 2: 语音识别热词 ──
STEP2_ASR_HOTWORDS = r"""#任务
参考{用户任务描述}/{用户知识} /{示例对话}，给出一些用户可能在对话中使用的  高频 或者 有一定识别难度的英文词汇， 用","连接。

语音识别词汇的考虑范围:
- 交互中可能提及的专有名词(例如人名、地名、特定物品名称等)
- 用户目标判定相关的一些单词表达。
- 用户对话中可能频繁提及的重要词汇。
- 不要将"Vala"及相关词组放到热词中。

热词数量:
一般为 3~10个。

#输入
{用户任务描述} ：
$用户任务描述$
{用户知识}:
$用户任务知识$
{示例对话} ：
$示例对话$

#输出
现在，根据任务要求直接输出用","连接的合理英文热词。
如果 {用户任务描述} 或者{用户知识} 或者
{示例对话}  为空， 则输出 "无"。"""


# ── Step 3: taskData ──
STEP3_TASK_DATA = r"""# 字段内容要求
{
    "cType": "core_speaking_image", // 固定
    "cId": {ID}, // 字符串类型
    "title": {用户任务描述}中的 【任务标题】,
    "taskDesc": {用户任务描述}中的【任务描述】,
    "sceneDesc": {用户任务描述}中的【任务背景】,
    "key"(只基于{用户任务知识} 部分做结构化抽取，**不要改动或添加文本内容 以及 标点符号**):
    [
        {
            "desc": 类型1的描述, // 未提供则留空 ""
            "keyList": [
                {
                    "type": "default", // 固定
                    "content": 英文句字或单词,
                    "desc": 句子的中文翻译 或 词性+单词释义
                },
                ... ...
            ]
        }，
        {
            "desc": 类型2的描述,
            "keyList": [
                {
                    "type": "default", // 固定
                    "content": 英文句字或单词,
                    "desc": 句子的中文翻译 或 词性+单词释义
                },
                ... ...
            ]
        }
        ... ...
    ],
    "resourceMapping"(抽取出{角色配置}中的全部 name:id映射 可为空。id必须为int类型):
    {
    样例:
    "Tom":43,
    "Jerry":10
    },
    "cDesc": "xxx", // {互动描述}的英文内容
}

# 输出格式
单行json输出。
样例:
{"cType":"core_speaking_reply","cId":"","title":"完成剑的托运","taskDesc":"你需和托运机器人对话，回答关于航班和托运信息的问题，成功完成托运。","sceneDesc":"开拓者，海盗突然来了，把我们抓飞船上了，我和 Scarlett 怕得要死!怀疑你们是海盗同伙，你说有我们乐队信息能证明。我问几个乐队问题，答不上就当你是海盗，别想救我们!","key":[{"desc":"提供信息","keyList":[{"type":"default","content":"I'd like to check in ....","desc":"我需要托运 ...。 "},{"type":"default","content":"banana","desc":"n. 香蕉"},{"type":"default","content":"I'm going to ... .","desc":"我要去 ... 。"},{"type":"default","content":"run","desc":"v. 跑"}]}],"resourceMapping":{"Skylar":292,"Otis":291,"Max":338,"Yui":562},"cDesc":"Brainie: What's this? User: This is a TV. Brainie: What's this? User: This is a shell. Brainie: What's this? User: This is a clock."}

# 输入
{ID}:
$ID$
{用户任务描述}:
$用户任务描述$
{用户任务知识}:
$用户任务知识$
{角色配置}:
$角色配置$
 {互动描述}:
$互动描述$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***
如果{用户任务描述} 或者 {用户任务知识}中没有有效文本，则输出"无"。"""


# ── Step 4: imageInfo ──
STEP4_IMAGE_INFO = r"""# 字段内容要求
{
    "questionImg": "{ID}_question.png"
}

#示例
{"questionImg": "0000999_question.png"}

# 输入
{ID}:
$ID$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***"""


# ── Step 5: optionList ──
STEP5_OPTION_LIST = r"""# 字段内容要求
[
    {"option": "{ID}_option_{index}.png"},
    {"option": "{ID}_option_{index}.png"},
    ...
]
{index}从{互动问题}中的图片编号按照顺序抽取，如果有重复的图片编号只保留一个。

#示例
[
    {"option": "0000001_option_00.png"},
    {"option": "0000001_option_01.png"},
    {"option": "0000001_option_02.png"},
]

# 输入
{ID}:
$ID$
{互动问题}：
$互动问题$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***
如果{用户任务描述} 或者 {用户任务知识}中没有有效文本，则输出"无"。"""


# ── Step 6: questionList ──
STEP6_QUESTION_LIST = r"""# 字段内容要求
[
    {
        "type": "npc",
        "npcName"(从{对话信息}中的对话角色抽取): "Brainy",
        "content"(从{互动问题}中的Q抽取): "What's the color of the pliers?",
        "index"(从{互动问题}中的图片编号抽取): 0,// optionList中第几个图片的索引
        "answerText"(从{互动问题}中的A抽取): "It's blue."
    }
    ...
]

#示例
[{"type":"npc","npcName":"Brainy","content":"What's the color of the pliers?","index":0,"answerText":"It's blue."},{"type":"npc","npcName":"Brainy","content":"What's the color of the ruler?","index":1,"answerText":"It's yellow."}]

# 输入
{ID}:
$ID$
{互动问题}：
$互动问题$
{对话信息}：
$对话信息$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***
如果{用户任务描述} 或者 {用户任务知识}中没有有效文本，则输出"无"。"""


# ── Step 7: configData ──
STEP7_CONFIG_DATA = r"""# 字段内容要求
如果{对话配置}或者{角色驱动配置}的配置内容是"无"，则直接输出"无"。

否则按以下格式抽取:

{
    "config": {
        "asrPrompt": {语音识别热词},
        "promptInfo": {
            "user_scene": {用户任务描述} 中的 【任务背景】,
            "user_task":  {用户任务描述} 中的  【任务描述】,
            "user_knowledge": {用户任务知识} ,
            "pass_condition": {通过规则},
            "reference_dialog": {示例对话} ,
            "questionList":{questionList}
        }
    }
}

# 输出格式
单行json输出。
(注意，前后不带任何额外符号。)

# 输入
{语音识别热词}:
$语音识别热词$
{用户任务描述}:
$用户任务描述$
{用户任务知识}:
$用户任务知识$
{示例对话}:
$示例对话$
{通过规则}:
$通过规则$
{questionList}:
$questionList$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本。前后不要输出任何额外符号！！！直接以"{"开头***    或者  直接输出 "无"。"""


# ── Step 8: learningData ──
STEP8_LEARNING_DATA = r"""# 字段内容要求
基于{学习配置}内容整理成以下结构。
{
"learningPart": [
        {
            "leadIn":{
                "type": "npc",
                "npcName"(从{对话信息}中的对话角色中抽取): "xxx",
                "content": "xxxx？"
            },
            "learning": [{
                "question": {
                    "type": "npc",
                    "npcName": "Brainy",
                    "content": "首先，你需要向 Mia 道歉，因为你弄坏了她的东西。你会怎么表达呢？",
                    "index"(根据leadIn中的问题，从{互动问题}中抽取对应的图片编号): 0
                },
                "optionList": [{
                    "option": "I'm sorry, Mia.",
                    "feedback": {
                        "type": "npc",
                        "npcName": "Brainy",
                        "content": "太棒了！你正确地使用了 "I'm sorry." 这个句型表达，真诚地向 Mia 表达了歉意。请大声朗读这句话吧！"
                    }
                }, {
                    "option": "I'm happy to tell you.",
                    "feedback": {
                        "type": "npc",
                        "npcName": "Brainy",
                        "content": "虽然冒险也是旅程的一部分，但原文更强调的是旅程（journey）而不是冒险（adventure）。再确认下正确答案吧。"
                    }
                }, {
                    "option": "I'm happy to tell you.",
                    "feedback": {
                        "type": "npc",
                        "npcName": "Brainy",
                        "content": "虽然冒险也是旅程的一部分，但原文更强调的是旅程（journey）而不是冒险（adventure）。再确认下正确答案吧。"
                    }
                }],
                "answer": [0],
                "read":
                {
                    "type": "npc",
                    "npcName": "User",
                    "content"（正确选项）: "I'm sorry, Mia."
                }
               "feedback"(只有最后一个回合才有):{
                   "type": "npc",
                   "npcName"(从{对话信息}中的对话角色中抽取): "xxx",
                   "content": "xxx", // 生成英文的内容
              }
            }]
        },
    "closing": { // 生成内容侧重于整个学习流程完成，不要和最后一回合后的feedback重复，请用中文
      "type": "npc",
      "npcName": "Brainy",
      "content": "xxx",
    }
  }
}
注意:  双引号字符串文本中如果出现英文双引号，需要使用转义符  确保最终输出是合法的json。
# 输出格式
单行json输出。
样例:
```
{"learningPart":[{"leadIn":{"type":"npc","npcName":"Celina","content":"Let's find tools together. What's the color of the pliers?"},"learning":[{"question":{"type":"npc","npcName":"Brainy","content":"Celina邀请你一起寻找工具，并询问钳子是什么颜色的。现在你需要回答钳子的颜色。","index":0},"optionList":[{"option":"It's blue.","feedback":{"type":"npc","npcName":"Brainy","content":"太棒了！你正确地回答了钳子是蓝色的。请大声朗读这句话！"}},{"option":"It's yellow.","feedback":{"type":"npc","npcName":"Brainy","content":"这个颜色不对哦，钳子不是黄色的。请再看看示例对话，找出正确的颜色。"}},{"option":"It's orange.","feedback":{"type":"npc","npcName":"Brainy","content":"这个颜色不对哦，钳子不是橙色的。请再看看示例对话，找出正确的颜色。"}}],"answer":[0],"read":{"type":"npc","npcName":"User","content":"It's blue."}}]},{"leadIn":{"type":"npc","npcName":"Celina","content":"What's the color of the ruler?"},"learning":[{"question":{"type":"npc","npcName":"Brainy","content":"Celina现在询问尺子是什么颜色的。你需要回答尺子的颜色。","index":0},"optionList":[{"option":"It's blue.","feedback":{"type":"npc","npcName":"Brainy","content":"这个颜色不对哦，尺子不是蓝色的。请再看看示例对话，找出正确的颜色。"}},{"option":"It's yellow.","feedback":{"type":"npc","npcName":"Brainy","content":"太棒了！你正确地回答了尺子是黄色的。请大声朗读这句话！"}},{"option":"It's orange.","feedback":{"type":"npc","npcName":"Brainy","content":"这个颜色不对哦，尺子不是橙色的。请再看看示例对话，找出正确的颜色。"}}],"answer":[1],"read":{"type":"npc","npcName":"User","content":"It's yellow."}}]},{"leadIn":{"type":"npc","npcName":"Celina","content":"What's the color of the hammer?"},"learning":[{"question":{"type":"npc","npcName":"Brainy","content":"Celina现在询问锤子是什么颜色的。你需要回答锤子的颜色。","index":1},"optionList":[{"option":"It's blue.","feedback":{"type":"npc","npcName":"Brainy","content":"这个颜色不对哦，锤子不是蓝色的。请再看看示例对话，找出正确的颜色。"}},{"option":"It's yellow.","feedback":{"type":"npc","npcName":"Brainy","content":"这个颜色不对哦，锤子不是黄色的。请再看看示例对话，找出正确的颜色。"}},{"option":"It's orange.","feedback":{"type":"npc","npcName":"Brainy","content":"太棒了！你正确地回答了锤子是橙色的。请大声朗读这句话！"}}],"answer":[2],"read":{"type":"npc","npcName":"User","content":"It's orange."}}]},{"leadIn":{"type":"npc","npcName":"Celina","content":"And how about the screwdriver?"},"learning":[{"question":{"type":"npc","npcName":"Brainy","content":"Celina现在询问螺丝刀是什么颜色的。你需要回答螺丝刀的颜色。","index":2},"optionList":[{"option":"Blue. Yes, I think it's blue.","feedback":{"type":"npc","npcName":"Brainy","content":"太棒了！你正确地回答了螺丝刀是蓝色的。请大声朗读这句话！"}},{"option":"Yellow. Yes, I think it's yellow.","feedback":{"type":"npc","npcName":"Brainy","content":"这个颜色不对哦，螺丝刀不是黄色的。请再看看示例对话，找出正确的颜色。"}},{"option":"Orange. Yes, I think it's orange.","feedback":{"type":"npc","npcName":"Brainy","content":"这个颜色不对哦，螺丝刀不是橙色的。请再看看示例对话，找出正确的颜色。"}}],"answer":[0],"read":{"type":"npc","npcName":"User","content":"Blue. Yes, I think it's blue."},"feedback":{"type":"npc","npcName":"Celina","content":"It seems that the pliers and the screwdriver belong to the same set of toolkit. Good job!"}}]}],"closing":{"type":"npc","npcName":"Brainy","content":"太棒了！Celina说钳子和螺丝刀属于同一套工具组，你已经成功找到了颜色相同的工具，完成了工具箱的复原任务！"}}
```

# 输入
{学习配置}:
$学习配置$
 {互动问题}：
$互动问题$

# 输出
现在基于输入字段和格式要求，生成***可以直接解析的单行json文本***，***注意前后不要输出任何额外符号！！！***, 以"{"开始输出。
（如果{学习配置}中没有有效文本，则直接输出"无"。）"""


# ── Steps registry ──
CORE_SPEAKING_IMAGE_STEPS = {
    "step1_learning_flow": {
        "template": STEP1_LEARNING_FLOW,
        "description": "学习流程配置",
        "output_type": "text",
    },
    "step2_asr_hotwords": {
        "template": STEP2_ASR_HOTWORDS,
        "description": "语音识别热词",
        "output_type": "text",
    },
    "step3_task_data": {
        "template": STEP3_TASK_DATA,
        "description": "taskData",
        "output_type": "json",
    },
    "step4_image_info": {
        "template": STEP4_IMAGE_INFO,
        "description": "imageInfo",
        "output_type": "json",
    },
    "step5_option_list": {
        "template": STEP5_OPTION_LIST,
        "description": "optionList",
        "output_type": "json",
    },
    "step6_question_list": {
        "template": STEP6_QUESTION_LIST,
        "description": "questionList",
        "output_type": "json",
    },
    "step7_config_data": {
        "template": STEP7_CONFIG_DATA,
        "description": "configData",
        "output_type": "json",
    },
    "step8_learning_data": {
        "template": STEP8_LEARNING_DATA,
        "description": "learningData",
        "output_type": "json",
    },
}


def build_speaking_image_prompt(step_key, replacements):
    """Build a prompt for a given step by replacing $placeholder$ tokens."""
    info = CORE_SPEAKING_IMAGE_STEPS[step_key]
    prompt = info["template"]
    for key, val in replacements.items():
        prompt = prompt.replace(f"${key}$", str(val) if val else "")
    return prompt
