# Clannad_v3.1.png 案例分析与落地要点

## 目标角色卡概览

- **图像**：@docs/Clannad_v3.1.png（图片来源、版权信息见下方字段）
- **风格**：Clannad 系列日系校园少女题材，具有较强的情感表达与背景设定需求
- **已知需求**：在云酒馆系统中以“前端卡”方式呈现，并能注入背景信息、变量、互动 UI

---

## 10.1 数据字段与数据模型需求

| 字段名 | 说明 | 备注 |
|--------|------|------|
| portrait_url | 图片主地址 | 建议使用 CDN 托管，支持 webp/AVIF 高效格式 |
| portrait_alt | 无障碍文本描述 | 供屏幕阅读器使用，描述角色外观特征 |
| thumbnail_url | 缩略图地址 | 列表页展示使用，建议 200x200 以内 |
| portrait_caption | 图片简短说明 | 显示在图片下方的简短描述 |
| image_source | 版权信息/出处 | 记录图片来源、授权信息 |
| worldbook_id | 关联的 Worldbook | 用于注入背景设定 |
| worldbook_entries | 世界书条目集合 | 用于背景信息注入 |
| name | 角色名称 | 现有字段 |
| description | 角色描述 | 现有字段 |
| personality | 性格设定 | 现有字段 |
| scenario | 场景设定 | 现有字段 |
| first_mes | 开场白 | 现有字段 |
| mes_example | 对话示例 | 现有字段 |
| system_prompt | 系统提示词 | 现有字段 |
| extensions | 扩展数据 | MVU 相关字段 |
| variables | 变量定义 | MVU 相关字段 |

---

## 10.2 后端改动要点

### 10.2.1 数据模型扩展

在 `model/app/ai_character.go` 中新增以下字段：

```go
type AICharacter struct {
    // ... 现有字段 ...
    
    // 图片相关字段
    PortraitURL    string `json:"portraitUrl" gorm:"type:varchar(500)"`
    PortraitAlt    string `json:"portraitAlt" gorm:"type:varchar(200)"`
    ThumbnailURL   string `json:"thumbnailUrl" gorm:"type:varchar(500)"`
    PortraitCaption string `json:"portraitCaption" gorm:"type:varchar(200)"`
    ImageSource    string `json:"imageSource" gorm:"type:varchar(500)"` // 版权信息
    
    // Worldbook 关联
    WorldbookID    *uint  `json:"worldbookId"`
}
```

### 10.2.2 API 接口

- **上传图片**：`POST /api/v1/app/character/:id/portrait`
  - 鉴权：需要用户登录
  - 校验：文件大小（建议最大 5MB）、格式（支持 jpg/png/webp/gif）
  - 返回：portrait_url、thumbnail_url

- **获取角色图片信息**：`GET /api/v1/app/character/:id`
  - 返回字段包含 portrait_url、portrait_alt、thumbnail_url、portrait_caption、image_source

### 10.2.3 Worldbook 关联增强

在 `conversation.go` 的 system prompt 构建流程中，增加对角色关联 Worldbook 的背景注入：

- 读取 `character.WorldbookID`
- 通过 WorldbookEngine 查询对应的启用条目
- 将背景信息注入到 system_prompt 中（需控制 token budget）

### 10.2.4 数据迁移

为现有角色卡补充默认字段值：

```sql
ALTER TABLE ai_characters 
ADD COLUMN IF NOT EXISTS portrait_url VARCHAR(500),
ADD COLUMN IF NOT EXISTS portrait_alt VARCHAR(200),
ADD COLUMN IF NOT EXISTS thumbnail_url VARCHAR(500),
ADD COLUMN IF NOT EXISTS portrait_caption VARCHAR(200),
ADD COLUMN IF NOT EXISTS image_source VARCHAR(500),
ADD COLUMN IF NOT EXISTS worldbook_id UINT REFERENCES worldbooks(id);
```

---

## 10.3 前端实现要点

### 10.3.1 角色卡片组件

在 `web-app/src/components/` 中新增或扩展角色卡片组件：

```tsx
interface CharacterCardProps {
  portraitUrl?: string;
  portraitAlt?: string;
  thumbnailUrl?: string;
  portraitCaption?: string;
  // ... 其他字段
}

const CharacterPortrait: React.FC<CharacterCardProps> = ({
  portraitUrl,
  portraitAlt,
  thumbnailUrl,
  portraitCaption
}) => {
  if (!portraitUrl) return null;
  
  return (
    <div className="character-portrait">
      <img 
        src={portraitUrl} 
        alt={portraitAlt || '角色图像'}
        loading="lazy"
        className="portrait-image"
      />
      {portraitCaption && (
        <p className="portrait-caption">{portraitCaption}</p>
      )}
    </div>
  );
};
```

### 10.3.2 样式隔离

使用 CSS Modules 或在组件级别定义样式：

```css
/* CharacterPortrait.module.css */
.character-portrait {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.portrait-image {
  max-width: 300px;
  border-radius: 8px;
}

.portrait-caption {
  font-size: 14px;
  color: #666;
  margin-top: 8px;
}
```

### 10.3.3 沙箱渲染

对于需要执行脚本的前端卡，使用 iframe 沙箱：

```tsx
const FrontendCardSandbox: React.FC<{ htmlContent: string }> = ({ htmlContent }) => {
  return (
    <iframe
      srcDoc={htmlContent}
      sandbox="allow-scripts"
      title="前端卡内容"
      className="frontend-card-iframe"
    />
  );
};
```

### 10.3.4 postMessage 通信

定义消息类型：

```ts
// types/frontend-card.ts
interface FrontendCardMessage {
  type: 'GET_VARIABLE' | 'SET_VARIABLE' | 'SEND_MESSAGE' | 'TRIGGER_ACTION';
  payload: Record<string, any>;
}

interface FrontendCardResponse {
  type: 'VARIABLE_VALUE' | 'ACTION_RESULT' | 'ERROR';
  payload: Record<string, any>;
}
```

---

## 10.4 Worldbook 与背景注入

### 10.4.1 背景信息转 Worldbook 条目

将 Clannad 角色的背景故事、人物关系、剧情要点等转换为 Worldbook 条目：

```json
{
  "keys": ["冈崎汐", "汐", "女儿"],
  "content": "冈崎汐，主人公冈崎朋也的女儿。5岁时因事故去世，后来以幽灵的形式再次出现在父亲面前...",
  "enabled": true,
  "constant": true,
  "position": 1,
  "order": 1
}
```

### 10.4.2 Token Budget 控制

在 `buildContextManagedSystemPrompt` 中，对 Worldbook 注入的内容进行 token 预算控制：

- **优先级**：Worldbook 触发条目 > CharacterBook 内嵌条目 > MesExample
- **预算分配**：system_prompt 总预算的 20-30% 用于 Worldbook 背景注入
- **截断策略**：超出预算时，从最低优先级条目开始丢弃

---

## 10.5 安全性与合规

### 10.5.1 图片安全

- **来源限制**：只允许从受信任的 CDN 或本地上传
- **内容审查**：上传图片需经过内容审核（可接入第三方审核服务）
- **元数据清理**：清理图片 EXIF 信息，防止泄露拍摄设备、地点等隐私

### 10.5.2 脚本安全

- **沙箱隔离**：前端卡内的 JavaScript 必须在 iframe 沙箱中运行
- **API 白名单**：只暴露受限的 API 接口，映射到后端受控服务
- **CSP 策略**：

```
Content-Security-Policy: 
  default-src 'self'; 
  script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; 
  style-src 'self' 'unsafe-inline'; 
  img-src 'self' https://cdn.example.com data:; 
  connect-src 'self' https://api.example.com;
```

### 10.5.3 审核流程

- **新卡审核**：用户上传新角色卡后，需经过管理员审核
- **定期检查**：对已上线卡片进行定期安全扫描
- **举报机制**：提供用户举报入口

---

## 10.6 迁移策略

### 10.6.1 MVP 阶段（第一阶段）

- 只引入 portrait_url、portrait_alt、thumbnail_url 字段
- 前端仅展示图片，不改变现有对话逻辑
- 不支持前端卡脚本执行

### 10.6.2 进阶阶段（第二阶段）

- 接入 Worldbook 背景注入
- 支持 portrait_caption 显示
- 引入基础的 iframe 沙箱渲染

### 10.6.3 完整阶段（第三阶段）

- 支持完整的前端卡脚本执行
- 实现 postMessage 通信
- 支持 RPG、VN 等复杂交互系统

### 10.6.4 回滚策略

- 每个阶段保留回滚开关
- 遇到兼容性问题时，可一键回退到上一阶段
- 数据库字段支持版本标记

---

## 10.7 验证与测试

### 10.7.1 功能测试

| 测试用例 | 预期结果 |
|---------|---------|
| 上传角色图片 | 图片正确保存，返回正确的 portrait_url |
| 图片加载失败 | 显示默认占位图，alt 文本正确 |
| 无图片角色卡 | 正常显示，不崩溃 |
| Worldbook 注入 | 背景信息正确注入到对话上下文 |
| 懒加载图片 | 首屏不加载，滚动后加载 |

### 10.7.2 安全性测试

| 测试用例 | 预期结果 |
|---------|---------|
| XSS 注入（图片描述） | 被转义，不执行 |
| 恶意脚本（前卡内） | 在沙箱中被阻止，无法访问父窗口 |
| 跨域请求 | 被 CSP 拦截 |

### 10.7.3 性能测试

| 指标 | 目标 |
|-----|------|
| 图片首次加载 | < 1s（5G 网络） |
| 懒加载触发 | 视口进入后 200ms 内 |
| 内存占用 | 前端卡渲染时 < 50MB 额外 |

---

## 10.8 版权信息与来源

### 10.8.1 必填字段

- **image_source**：图片来源（如「官方美术集」「同人创作」）
- **license_type**：许可证类型（如「MIT」「CC BY-SA 4.0」「原创」）
- **original_artist**：原作者名称（若为同人创作）

---

## 10.9 附录/接口示例

### 10.9.1 角色图片上传 API

```bash
# 请求
POST /api/v1/app/character/1/portrait
Authorization: Bearer <token>
Content-Type: multipart/form-data

# 响应
{
  "portraitUrl": "https://cdn.example.com/characters/1/portrait.webp",
  "thumbnailUrl": "https://cdn.example.com/characters/1/thumb.webp",
  "portraitAlt": "粉色头发的少女，穿着校服"
}
```

### 10.9.2 角色详情 API 扩展

```bash
# GET /api/v1/app/character/1

{
  "id": 1,
  "name": "冈崎汐",
  "portraitUrl": "https://cdn.example.com/characters/1/portrait.webp",
  "portraitAlt": "粉色头发的少女，穿着校服",
  "thumbnailUrl": "https://cdn.example.com/characters/1/thumb.webp",
  "portraitCaption": "Clannad 主角冈崎朋也的女儿",
  "imageSource": "官方美术集",
  "worldbookId": 5,
  // ... 其他字段
}
```

---

## 结论

通过独立分析文档分离，便于版本管理与跨团队协作。请在实现过程中严格遵循安全沙箱与版权规范，确保云酒馆的扩展能力可控且可维护。

本落地要点覆盖了从数据模型、后端 API、前端渲染、Worldbook 集成、安全合规到迁移测试的完整流程，建议按 MVP → 进阶 → 完整三阶段逐步落地。
