# 情景切换功能

## 功能概述

情景切换功能允许用户选择不同的对话场景，AI 会根据选择的情景扮演不同的角色：

| 情景 | AI 角色 | 主要功能 |
|------|---------|---------|
| 🎯 模拟面试官 | 资深面试官 | 提出面试问题，评估候选人能力，给出反馈 |
| 📚 英语老师 | 英语外教 | 全英文对话，纠正语法错误，引导深入交流 |
| ⚔️ 辩论对手 | 辩论选手 | 站在反方立场，用逻辑和证据反驳观点 |
| 🌐 同声翻译 | 翻译员 | 实时中英互译，口语化翻译，无额外解释 |
| 💬 自由对话 | 视觉助手 | 通用视觉对话助手（默认） |

### 核心特性

1. **情景首句引导**：切换情景后，AI 自动发送第一句话引导用户进入角色
2. **情景提示卡片**：对话顶部显示当前情景模式的蓝色提示卡片
3. **增强 System Prompt**：每个情景有详细的角色定位、交互规则和约束
4. **多语言支持**：完整支持中文、英文、日文界面

---

## 技术实现

### 后端实现

#### 1. 情景 Prompt 定义

**文件**: `backend/internal/ai/llm/scenarios.go`

- 扩展 `scenarioPrompt` 结构体，新增首句引导字段（GreetingZH/EN/JA）
- 增强所有情景的 System Prompt（添加角色定位、交互规则、约束）
- 新增函数 `GetScenarioGreeting(scenarioID, language string) string`

**示例 Prompt**（模拟面试官）：

```go
"interviewer": {
    ZH: `你是一位资深面试官。你通过摄像头观察面试者...

【角色定位】
- 你是面试官，不是助手或顾问
- 你的目标是评估候选人的能力
- 保持专业、客观、礼貌

【交互规则】
1. 每次只问一个问题，等用户回答后再追问
2. 问题要有层次：自我介绍 → 专业问题 → 情景题
3. 对用户的回答给出简短点评，然后追问
...`,
    GreetingZH: "你好！我是今天的面试官。让我们先从自我介绍开始...",
}
```

#### 2. 首句引导推送

**文件**: `backend/internal/ws/handler.go`

在处理 `config` 消息时，如果切换到非自由对话情景，自动返回首句引导：

```go
case "config":
    // ... 更新配置 ...
    
    // 如果切换了情景（非自由对话），返回首句引导
    if scenarioID != "" && scenarioID != "free_chat" {
        greeting := llm.GetScenarioGreeting(scenarioID, sess.Config.Language)
        if greeting != "" {
            // 发送 llm_chunk 和 llm_done 消息
            // 追加到历史记录
        }
    }
```

#### 3. State 初始化

**文件**: `backend/internal/eino/adapter.go`

从 `PipelineInput` 复制元数据到 `PipelineState`，确保情景配置正确传递到所有节点：

```go
state := genLocalState(ctx)
state.SessionID = input.SessionID
state.RequestID = input.RequestID
state.ImageData = input.ImageData
state.Scenario = input.Scenario      // 关键：复制情景配置
state.Language = input.Language
state.DetailLevel = sess.Config.DetailLevel
state.TTSEnabled = input.TTSEnabled
ctx = WithPipelineState(ctx, state)
```

---

### 前端实现

#### 1. 情景提示卡片

**文件**: `frontend/src/components/ChatPanel/index.tsx`

在对话列表顶部（非空状态 + 非自由对话模式）添加情景提示卡片：

```tsx
{messages.length > 0 && !isFreeChat && (
  <div className="chat-panel__scenario-hint">
    <div className="scenario-hint-card">
      <span className="scenario-hint-card__icon">
        {scenarios.find(s => s.id === activeScenario)?.icon}
      </span>
      <div className="scenario-hint-card__text">
        <strong>{t(scenarios.find(s => s.id === activeScenario)?.nameKey || "")}</strong>
        <p>{t(`scenario.${activeScenario}.hint`)}</p>
      </div>
    </div>
  </div>
)}
```

**显示效果**：
- 蓝色渐变背景（135deg 从蓝到紫）
- 左侧大图标 + 右侧标题和说明
- 最大宽度 520px，响应式布局
- 柔和阴影和半透明边框

#### 2. WebSocket 消息发送

**文件**: `frontend/src/hooks/useVisionSession.ts`

发送 config 消息时包含 `scenario` 字段：

```typescript
send({
  type: "config",
  payload: {
    tts_enabled: config.ttsEnabled,
    detail_level: config.detailLevel,
    language: config.language,
    scenario: config.scenario,  // 情景配置
  },
});
```

#### 3. 样式实现

**文件**: `frontend/src/App.css`

情景提示卡片样式：

```css
.scenario-hint-card {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-radius: var(--radius-sm);
  background: linear-gradient(135deg, rgba(59, 130, 246, 0.08) 0%, rgba(99, 102, 241, 0.08) 100%);
  border: 1px solid rgba(59, 130, 246, 0.2);
  box-shadow: 0 2px 8px rgba(59, 130, 246, 0.06);
}
```

#### 4. 多语言翻译

**文件**: `frontend/src/lib/i18n/{zh-CN,en-US,ja-JP}.ts`

新增翻译 key：

```typescript
"scenario.interviewer.hint": "AI 会扮演面试官，逐步提出专业问题并点评你的回答",
"scenario.englishTeacher.hint": "AI 会用英语对话，纠正语法错误并引导深入交流",
"scenario.debate.hint": "AI 会站在反方立场，用逻辑和证据反驳你的观点",
"scenario.interpreter.hint": "AI 会实时翻译你的话（中英互译），无解释评论",
```

---

## 数据流

### WebSocket 协议

**客户端 → 服务端**（config 消息）：

```json
{
  "type": "config",
  "payload": {
    "tts_enabled": true,
    "detail_level": "low",
    "language": "zh-CN",
    "scenario": "interviewer"
  }
}
```

**服务端 → 客户端**（首句引导）：

```json
// llm_chunk
{
  "type": "llm_chunk",
  "request_id": "scenario_greeting",
  "delta": "你好！我是今天的面试官...",
  "role": "assistant"
}

// llm_done
{
  "type": "llm_done",
  "request_id": "scenario_greeting",
  "full_text": "你好！我是今天的面试官...",
  "tokens_used": {"prompt": 0, "completion": 0, "total": 0}
}
```

### System Prompt 构建流程

```
sess.Config.Scenario = "interviewer"
  ↓
PipelineInput.Scenario = "interviewer"
  ↓
PipelineState.Scenario = "interviewer" (adapter.go 复制)
  ↓
nodes_history.go 读取 state.Scenario
  ↓
scenarioPrompt := llm.GetScenarioPrompt("interviewer", "zh-CN")
  ↓
systemPrompt := llm.BuildSystemPrompt(language, detailLevel, scenarioPrompt)
  ↓
messages[0] = {Role: "system", Content: systemPrompt}
  ↓
ChatModel 接收到情景 Prompt
  ↓
LLM 按情景角色生成回复
```

---

## 使用指南

### 快速验证

1. **打开浏览器** → http://localhost:5173
2. **登录系统**
3. **切换情景** → 右侧配置面板 → 对话情景 → 模拟面试官
4. **观察现象**：
   - ✨ AI 立即说："你好！我是今天的面试官。让我们先从自我介绍开始..."
   - ✨ 对话框顶部显示蓝色提示卡片
5. **验证效果** → 发送："你是谁？"
   - ✅ **正确回复**："我是今天的面试官..."
   - ❌ **错误回复**："我是通义千问..."

### 功能测试清单

| 测试项 | 操作步骤 | 预期结果 |
|--------|---------|---------|
| **首句引导** | 切换到"模拟面试官" | AI 自动说："你好！我是今天的面试官..." |
| **情景生效** | 问 "你是谁？" | AI 回答："我是今天的面试官..." |
| **提示卡片** | 发送一条消息后查看顶部 | 显示蓝色卡片："🎯 模拟面试官 \| AI 会扮演面试官..." |
| **语言联动** | 切换到"英语老师" | 语言自动切换到 en-US，AI 用英语回复 |
| **持久化** | 切换情景后刷新页面 | 情景配置保持，首句仍在历史中 |
| **多情景** | 依次测试所有情景 | 每个情景 AI 回复风格明显不同 |

---

## 故障排查

### 如果情景不生效

1. **检查后端日志**：
   ```bash
   grep "config updated" /tmp/camtalk_server.log | tail -5
   grep "历史组装完成" /tmp/camtalk_server.log | tail -5
   ```
   
   - 如果 `scenario=` 是空的，说明前端未发送或后端未接收
   - 如果 `scenario=interviewer` 正确，但 AI 回复仍是通用的，可能是 LLM 模型问题

2. **检查前端 WebSocket 消息**（浏览器 DevTools → Network → WS）：
   ```json
   {
     "type": "config",
     "payload": {
       "scenario": "interviewer"  // 确认存在
     }
   }
   ```

3. **检查会话配置是否保存**：
   - 切换情景后，LocalStorage 中应该有 `camtalk_config`
   - 内容应包含 `"scenario": "interviewer"`

4. **清除缓存重试**：
   ```bash
   # 浏览器：清除 LocalStorage
   # 后端：重启服务
   # 前端：刷新页面
   ```

---

## 后续优化建议

### P2（强烈推荐）

1. **情景切换时创建新会话**
   - 避免历史对话干扰新情景
   - 弹窗确认："切换情景会创建新会话，当前对话将保存。是否继续？"
   - 实现难度：⭐⭐
   - 用户价值：⭐⭐⭐⭐

2. **进一步增强 System Prompt**
   - 增加示例对话（Few-shot Prompting）
   - 增加"禁止事项"列表
   - 实现难度：⭐
   - 效果提升：⭐⭐⭐

### P3（可选）

1. **情景专属 UI 主题色**
   - 面试官 → 深蓝色
   - 英语老师 → 绿色
   - 辩论 → 红色
   - 翻译 → 紫色

2. **切换动画与音效**
   - 切换时播放短音效
   - 聊天面板淡出淡入动画

---

## 修改文件清单

### 后端（3 个文件）

- `backend/internal/eino/adapter.go` — 修复 State 初始化
- `backend/internal/ws/handler.go` — 添加首句引导
- `backend/internal/ai/llm/scenarios.go` — 增强 Prompt + 首句

### 前端（5 个文件）

- `frontend/src/hooks/useVisionSession.ts` — 修复 scenario 发送
- `frontend/src/components/ChatPanel/index.tsx` — 添加提示卡片
- `frontend/src/App.css` — 卡片样式
- `frontend/src/lib/i18n/zh-CN.ts` — 中文翻译
- `frontend/src/lib/i18n/en-US.ts` — 英文翻译
- `frontend/src/lib/i18n/ja-JP.ts` — 日文翻译
