9.8 KiB
9.8 KiB
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 中新增以下字段:
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 数据迁移
为现有角色卡补充默认字段值:
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/ 中新增或扩展角色卡片组件:
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 或在组件级别定义样式:
/* 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 沙箱:
const FrontendCardSandbox: React.FC<{ htmlContent: string }> = ({ htmlContent }) => {
return (
<iframe
srcDoc={htmlContent}
sandbox="allow-scripts"
title="前端卡内容"
className="frontend-card-iframe"
/>
);
};
10.3.4 postMessage 通信
定义消息类型:
// 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 条目:
{
"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
# 请求
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 扩展
# 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 → 进阶 → 完整三阶段逐步落地。