💻 源码
"""
终端输出模块。

负责把演示过程打印得清晰易读,包括:
- 项目是否真实调用 API 的说明
- 各文件分工与运行流程
- 每道题三种模式的对比结果
"""

import sys
import textwrap

from config import API_KEY, MODEL_NAME, SEED, TEMPERATURE

ROLE_LABELS = {
    "system": "system(系统角色:设定行为规则)",
    "user": "user(用户角色:本次提问)",
    "assistant": "assistant(助手角色:示例回答,供模型模仿)",
}


def _configure_stdout() -> None:
    """尽量让 Windows 终端正确显示中文。"""
    if hasattr(sys.stdout, "reconfigure"):
        try:
            sys.stdout.reconfigure(encoding="utf-8")
        except Exception:
            pass


_configure_stdout()


def print_banner(title: str) -> None:
    line = "=" * 60
    print(f"\n{line}\n  {title}\n{line}")


def print_section(title: str) -> None:
    print(f"\n{'-' * 60}\n>> {title}\n{'-' * 60}")


def print_kv(label: str, value: str, indent: int = 0) -> None:
    prefix = " " * indent
    print(f"{prefix}{label}")
    wrapped = textwrap.indent(textwrap.fill(value, width=72), "  ")
    print(wrapped)


def print_model_reply(reply: str) -> None:
    print("\n【模型回复】")
    print(textwrap.indent(reply, "  "))


def print_messages(messages: list[dict[str, str]], summary: str = "") -> None:
    """打印本次发送给模型的完整 messages,便于对比三种模式的提示词差异。"""
    print("\n【发送给模型的提示词】")
    if summary:
        print(f"  结构说明:{summary}")
    print(f"  共 {len(messages)} 条消息:")
    for i, msg in enumerate(messages, start=1):
        role = msg["role"]
        label = ROLE_LABELS.get(role, role)
        content = msg["content"].strip()
        print(f"\n  --- 消息 {i} · {label} ---")
        print(textwrap.indent(content, "    "))


def _mask_api_key(key: str | None) -> str:
    if not key:
        return "未配置"
    if len(key) <= 8:
        return "已配置(长度较短,已隐藏)"
    return f"已配置({key[:4]}...{key[-4:]})"


def print_runtime_info() -> None:
    """启动时打印运行环境与真实性说明。"""
    print_section("运行环境(真实 API 调用,非模拟)")
    print_kv("调用方式", "dashscope.Generation.call() → 阿里云百炼云端")
    print_kv("模型", MODEL_NAME)
    print_kv("API Key", _mask_api_key(API_KEY))
    print_kv("参数", f"temperature={TEMPERATURE}, seed={SEED}")
    print_kv(
        "是否真实运行",
        "是。每次「模型回复」都是实时请求百炼接口返回的结果,"
        "会消耗 Token;断网或 Key 无效时会直接报错,没有写死的假答案。",
    )


def print_project_structure() -> None:
    """打印项目构建方式,帮助学生理解代码分工。"""
    print_section("项目是怎么构建的")
    lines = [
        "main.py       入口:组织演示流程,打印对比小结",
        "demo.py       核心:三种模式组装 messages 并调用模型",
        "prompts.py    提示词:无CoT / 零样本CoT / 少样本CoT 模板(改效果主要改这里)",
        "questions.py  题库:3 道经典演示题",
        "llm_client.py API层:唯一真正联网发请求的地方",
        "config.py     配置:从 os.getenv 读取 DASHSCOPE_API_KEY",
    ]
    for line in lines:
        print(f"  {line}")


def print_run_flow() -> None:
    """打印单道题的执行步骤。"""
    print_section("单道题运行流程(每题调用 3 次百炼 API)")
    steps = [
        "1. main.py 从 questions.py 取出题目",
        "2. 模式 A:prompts.py(无CoT) → demo.py 拼 messages → llm_client.py 发请求",
        "3. 模式 B:prompts.py(零样本CoT) → demo.py 拼 messages → llm_client.py 发请求",
        "4. 模式 C:prompts.py(少样本CoT+示例) → demo.py 拼 messages → llm_client.py 发请求",
        "5. main.py 提取「最终答案」,判断正确性与是否有分步推理",
        "6. 打印 A/B/C 对比小结",
    ]
    for step in steps:
        print(f"  {step}")


def print_intro() -> None:
    print_banner("CoT 思维链(Chain-of-Thought)教学演示")
    print(
        textwrap.fill(
            "CoT 的核心:不要急着给最终答案,先让模型把中间推理步骤写出来。"
            "这样复杂题更不容易跳步出错,推理过程也便于检查和纠错。",
            width=72,
        )
    )
    print("\n本演示对比三种写法:")
    print("  1) 无 CoT      → 直接要答案(容易跳步)")
    print("  2) 零样本 CoT  → 不给示例,只要求分步思考")
    print("  3) 少样本 CoT  → 给一个带推理过程的示例,再让模型模仿")
    print_runtime_info()
    print_project_structure()
    print_run_flow()
    print("\n网页版演示:python web_app.py  →  http://127.0.0.1:8765")
    print("详细说明见:docs/项目说明.md")