Files
CamTalk/docs/自建情景功能完整文档.md
cfy666 1079e22699 feat: 实现自建情景功能
## 功能概述
- 用户可创建、编辑、删除自定义情景
- 支持自定义情景名称、图标、描述、Prompt、首句引导
- 完整的权限隔离,用户只能管理自己的情景
- 深度集成 Eino 框架,动态加载自建情景 Prompt

## 后端实现
### 数据库
- 新增 user_scenarios 表
- 支持用户配额(最多 20 个)
- 字段验证:description 可选,prompt 最小 10 字符

### API
- GET /api/scenarios - 获取用户情景列表
- POST /api/scenarios - 创建情景
- GET /api/scenarios/:id - 获取详情
- PATCH /api/scenarios/:id - 更新情景
- DELETE /api/scenarios/:id - 删除情景

### Eino 集成
- PipelineState 添加 UserID 字段
- nodes_history 动态加载用户自建情景
- GetScenarioPrompt 支持自建情景优先级

## 前端实现
### 组件
- CreateScenarioModal - 创建情景对话框
- EditScenarioModal - 编辑情景对话框
- ConfigPanel 改造 - 分组显示系统预置和自建情景

### Hook
- useScenarios - 合并系统和自建情景,提供 CRUD 接口

### 国际化
- 中文、英文、日文翻译支持

## 问题修复
- 修复 CORS 问题:使用 Vite 代理
- 统一验证规则:description 可选,prompt 最小 10 字符
- 修复数据库约束:使用 NULLIF 处理空字符串

## 文件变更
新增文件: 13 个
修改文件: 14 个

详见文档: docs/自建情景功能完整文档.md
2026-06-21 15:38:28 +08:00

16 KiB
Raw Blame History

自建情景功能完整文档

最后更新: 2026-06-21
开发者: Claude Code + cfy
状态: 开发完成80%),待测试验证


📊 总体进度

当前状态: Phase 1-4 已完成
完成度: 🟢 80% (4/5 Phases)
剩余: Phase 5 测试验证


一、功能概述

核心功能

用户可以创建自己的情景,而不仅限于系统预置的 5 种情景:

系统预置情景(不可修改):

  • 💬 自由对话
  • 🎯 模拟面试官
  • 📚 英语老师
  • ⚔️ 辩论对手
  • 🌐 同声翻译

用户自建情景(可增删改):

  • 🎨 创意写作导师
  • 🧘 心理咨询师
  • 👨‍🍳 私人厨师
  • 📖 历史学家
  • ... (用户自由创建)

用户旅程

1. 用户点击"创建情景"按钮
   ↓
2. 弹出创建对话框
   ↓
3. 填写表单:
   - 情景名称(必填)
   - 情景图标(可选)
   - 简短描述(可选)
   - 角色 Prompt必填最少 10 字)
   - 首句引导(可选)
   ↓
4. 点击"创建"
   ↓
5. 情景保存到数据库
   ↓
6. 情景出现在选择列表中
   ↓
7. 用户切换到自建情景
   ↓
8. AI 按照用户设定的 Prompt 扮演角色

权限隔离: 每个用户只能看到和管理自己创建的情景,通过 user_id 实现数据隔离。


二、技术实现架构

2.1 数据流图

【创建情景】
用户填写表单 → POST /api/scenarios → Handler 验证
→ Repository.Create → PostgreSQL 插入 → 返回情景对象

【AI 对话使用自建情景】
WebSocket 连接 → ServeWS 获取 userID
→ Eino Graph 初始化 → nodes_history 查询 user_scenarios
→ GetScenarioPrompt(customScenarios) → 构建 System Prompt
→ LLM 生成回复

2.2 Eino 框架集成

数据传递链路:

JWT Token → userID
    ↓
Session.UserID
    ↓
PipelineInput.UserID
    ↓
PipelineState.UserID
    ↓
nodes_history.go: scenarioRepo.FindByUserID(userID)
    ↓
构建 customScenarios map[string]string
    ↓
llm.GetScenarioPrompt(scenarioID, language, customScenarios)
    ↓
LLM 使用自建情景 Prompt

关键修改文件:

  1. backend/internal/eino/state.go — PipelineState 添加 UserID
  2. backend/internal/eino/types.go — PipelineInput 添加 UserID
  3. backend/internal/eino/graph.go — 接受 scenarioRepo 参数
  4. backend/internal/eino/adapter.go — 设置 UserID
  5. backend/internal/eino/nodes_history.go — 查询自建情景
  6. backend/internal/ws/handler.go — 首句引导支持自建情景

三、数据模型设计

3.1 数据库表结构

表名: user_scenarios

CREATE TABLE user_scenarios (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    name VARCHAR(50) NOT NULL,
    icon VARCHAR(10) DEFAULT '✨',
    description VARCHAR(100),  -- 可选
    prompt TEXT NOT NULL,
    greeting VARCHAR(500),      -- 可选
    language VARCHAR(10) DEFAULT 'zh-CN',
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
    
    CONSTRAINT unique_user_scenario UNIQUE(user_id, name),
    CONSTRAINT check_name_length CHECK (char_length(name) >= 2 AND char_length(name) <= 50),
    CONSTRAINT check_description_length CHECK (description IS NULL OR char_length(description) <= 100),
    CONSTRAINT check_prompt_length CHECK (char_length(prompt) >= 10 AND char_length(prompt) <= 2000),
    CONSTRAINT check_greeting_length CHECK (greeting IS NULL OR char_length(greeting) <= 500)
);

CREATE INDEX idx_user_scenarios_user_id ON user_scenarios(user_id);
CREATE INDEX idx_user_scenarios_created_at ON user_scenarios(created_at DESC);

字段说明:

  • id: 情景唯一标识
  • user_id: 所属用户,实现数据隔离
  • name: 情景名称2-50 字符)
  • icon: Emoji 图标(默认
  • description: 简短描述(可选,最多 100 字符)
  • prompt: 角色 System Prompt10-2000 字符)
  • greeting: 首句引导(可选,最多 500 字符)
  • language: 默认语言zh-CN / en-US / ja-JP

3.2 后端数据模型

// backend/internal/models/user_scenario.go

type UserScenario struct {
    ID          string    `json:"id"`
    UserID      string    `json:"user_id"`
    Name        string    `json:"name"`
    Icon        string    `json:"icon"`
    Description string    `json:"description"`
    Prompt      string    `json:"prompt"`
    Greeting    string    `json:"greeting,omitempty"`
    Language    string    `json:"language"`
    CreatedAt   time.Time `json:"created_at"`
    UpdatedAt   time.Time `json:"updated_at"`
}

type CreateUserScenarioRequest struct {
    Name        string `json:"name" binding:"required,min=2,max=50"`
    Icon        string `json:"icon,omitempty"`
    Description string `json:"description,omitempty" binding:"omitempty,max=100"`
    Prompt      string `json:"prompt" binding:"required,min=10,max=2000"`
    Greeting    string `json:"greeting,omitempty" binding:"omitempty,max=500"`
    Language    string `json:"language,omitempty"`
}

3.3 前端数据结构

// frontend/src/lib/api/scenarios.ts

export interface UserScenario {
  id: string;
  user_id: string;
  name: string;
  icon: string;
  description: string;
  prompt: string;
  greeting?: string;
  language: string;
  created_at: string;
  updated_at: string;
}

// frontend/src/hooks/useScenarios.ts

export interface ExtendedScenario {
  id: string;
  icon: string;
  name: string;
  nameKey?: string;
  description?: string;
  descKey?: string;
  isCustom: boolean;
  prompt?: string;
  greeting?: string;
  language?: string;
}

四、REST API 设计

4.1 API 端点

方法 路径 说明 权限
GET /api/scenarios 获取用户的所有自建情景 需登录
POST /api/scenarios 创建新情景 需登录
GET /api/scenarios/:id 获取单个情景详情 需登录
PATCH /api/scenarios/:id 更新情景 需登录
DELETE /api/scenarios/:id 删除情景 需登录

4.2 API 示例

创建情景

POST /api/scenarios
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "name": "创意写作导师",
  "icon": "✨",
  "description": "帮助构思故事情节和写作技巧",
  "prompt": "你是一位创意写作导师,帮助用户构思故事情节、人物设定和写作技巧...",
  "greeting": "你好!我是你的创意写作导师。今天想聊聊什么故事创意呢?",
  "language": "zh-CN"
}

响应: 201 Created

{
  "id": "uuid-xxx",
  "user_id": "uuid-user",
  "name": "创意写作导师",
  "icon": "✨",
  ...
}

获取列表

GET /api/scenarios
Authorization: Bearer <access_token>

响应: 200 OK

{
  "scenarios": [...],
  "total": 3
}

五、前端实现

5.1 组件结构

frontend/src/
├── components/
│   ├── CreateScenarioModal/
│   │   └── index.tsx          # 创建情景对话框
│   ├── EditScenarioModal/
│   │   └── index.tsx          # 编辑情景对话框
│   └── ConfigPanel/
│       └── index.tsx          # 设置面板(改造)
├── hooks/
│   └── useScenarios.ts        # 情景管理 Hook
└── lib/
    └── api/
        └── scenarios.ts        # API 调用封装

5.2 核心 Hook

// useScenarios.ts

export function useScenarios(token: string | null) {
  const [allScenarios, setAllScenarios] = useState<ExtendedScenario[]>([]);
  
  // 合并系统预置 + 用户自建
  useEffect(() => {
    const systemScenarios = scenarios.map(s => ({...s, isCustom: false}));
    const customScenarios = customList.map(s => ({...s, isCustom: true}));
    setAllScenarios([...systemScenarios, ...customScenarios]);
  }, [customList]);
  
  return {
    allScenarios,
    createScenario,
    updateScenario,
    deleteScenario,
  };
}

5.3 创建情景表单

表单字段:

  • 名称必填2-50 字符)
  • 图标可选24 个预设 emoji
  • 描述(可选,最多 100 字符)
  • Prompt必填10-2000 字符)
  • 首句引导(可选,最多 500 字符)
  • 语言(可选,默认 zh-CN

表单验证:

  • 实时字符计数
  • 长度限制提示
  • 必填项高亮

六、实施进度

Phase 1: 后端基础100% 完成)

1.1 数据库迁移

  • 文件: backend/migrations/004_user_scenarios.up.sql
  • 创建 user_scenarios
  • 添加索引和约束

1.2 数据模型

  • 文件: backend/internal/models/user_scenario.go
  • 定义 UserScenario 结构体
  • 定义请求/响应模型

1.3 Repository 层

  • 文件: backend/internal/store/user_scenario_repository.go
  • 实现 UserScenarioRepository 接口
  • CRUD 操作 + 权限校验

1.4 REST API

  • 文件: backend/internal/api/user_scenario_handler.go
  • 5 个 HTTP 端点(创建/列表/详情/更新/删除)
  • 输入验证和错误处理

Phase 2: 后端集成100% 完成)

2.1 Prompt 加载逻辑

  • 修改: backend/internal/ai/llm/scenarios.go
  • GetScenarioPrompt 支持自建情景
  • GetScenarioGreeting 支持自建情景

2.2 Eino 框架集成

  • 修改 7 个文件,完整数据链路
  • PipelineState 添加 UserID
  • nodes_history 查询用户自建情景
  • 动态构建 System Prompt

Phase 3: 前端 UI100% 完成)

3.1 API 封装

  • 文件: frontend/src/lib/api/scenarios.ts
  • 5 个 API 调用函数

3.2 Hook 封装

  • 文件: frontend/src/hooks/useScenarios.ts
  • useScenarios Hook
  • 合并系统预置 + 自建情景

3.3 组件实现

  • CreateScenarioModal — 创建对话框
  • EditScenarioModal — 编辑对话框
  • ConfigPanel 改造 — 分组显示 + 编辑/删除

3.4 i18n 支持

  • 中文/英文/日文翻译(+40 条)

3.5 样式实现

  • Modal、表单、图标选择器样式

Phase 4: 前端集成100% 完成)

4.1 主应用集成

  • 文件: frontend/src/App.tsx
  • 集成 useScenarios Hook
  • 渲染 Modal 组件
  • 情景选择联动

4.2 编译验证

  • 前端: 669.96 kB JS + 55.80 kB CSS
  • 后端: 48MB 二进制

Phase 5: 测试验证(待进行)

5.1 后端测试

  • 数据库迁移验证
  • REST API CRUD 测试
  • 权限隔离测试
  • Eino Graph 自建情景加载测试

5.2 前端测试

  • 创建情景表单验证
  • 编辑情景数据预填充
  • 删除情景二次确认
  • 情景列表实时更新

5.3 集成测试

  • 创建自建情景后立即可用
  • 切换到自建情景显示首句引导
  • AI 对话使用自建 Prompt
  • 多用户并发隔离

七、已完成文件清单

新增文件13 个)

后端5 个):

  1. backend/migrations/004_user_scenarios.up.sql
  2. backend/migrations/004_user_scenarios.down.sql
  3. backend/internal/models/user_scenario.go
  4. backend/internal/store/user_scenario_repository.go
  5. backend/internal/api/user_scenario_handler.go

前端5 个): 6. frontend/src/lib/api/scenarios.ts 7. frontend/src/hooks/useScenarios.ts 8. frontend/src/components/CreateScenarioModal/index.tsx 9. frontend/src/components/EditScenarioModal/index.tsx

文档3 个): 10. docs/自建情景功能设计方案.md 11. docs/自建情景功能-权限隔离说明.md 12. docs/自建情景功能实施进度.md 13. docs/自建情景功能完整文档.md (本文件)

修改文件14 个)

后端8 个):

  1. backend/cmd/server/main.go — 注册 API 路由 + 传递 scenarioRepo
  2. backend/internal/ai/llm/scenarios.go — Prompt/Greeting 加载支持自建
  3. backend/internal/eino/state.go — 添加 UserID 字段
  4. backend/internal/eino/types.go — PipelineInput 添加 UserID
  5. backend/internal/eino/graph.go — 接受并传递 scenarioRepo
  6. backend/internal/eino/adapter.go — 复制 UserID 到 State
  7. backend/internal/eino/nodes_history.go — 加载自建情景
  8. backend/internal/ws/handler.go — 首句引导支持自建情景

前端6 个): 9. frontend/src/App.tsx — 集成自建情景管理 10. frontend/src/components/ConfigPanel/index.tsx — 分组显示 + 编辑/删除 11. frontend/src/lib/i18n/zh-CN.ts — 新增翻译 12. frontend/src/lib/i18n/en-US.ts — 新增翻译 13. frontend/src/lib/i18n/ja-JP.ts — 新增翻译 14. frontend/src/App.css — 新增样式


八、问题解决记录

8.1 CORS 错误

问题: 前端直接访问 http://localhost:8080 触发 CORS
解决: 将 API_BASE 改为空字符串,使用 Vite 代理

8.2 验证规则不一致

问题: 后端要求 description 必填,prompt 最小 50 字符
解决: 统一为 description 可选,prompt 最小 10 字符

8.3 数据库约束错误

问题: 空字符串 "" 不满足 char_length >= 1 约束
解决:

  1. 更新约束允许 description IS NULL
  2. Repository 使用 NULLIF($5, '') 将空字符串转为 NULL

九、测试指南

9.1 后端 API 测试

# 1. 注册用户
curl -X POST http://localhost:8080/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username":"testuser","password":"test12345"}'

# 2. 创建情景
TOKEN="<access_token>"
curl -X POST http://localhost:8080/api/scenarios \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "创意写作导师",
    "icon": "✨",
    "prompt": "你是一位创意写作导师...",
    "language": "zh-CN"
  }'

# 3. 获取列表
curl -X GET http://localhost:8080/api/scenarios \
  -H "Authorization: Bearer $TOKEN"

# 4. 更新情景
curl -X PATCH http://localhost:8080/api/scenarios/<id> \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"高级写作导师"}'

# 5. 删除情景
curl -X DELETE http://localhost:8080/api/scenarios/<id> \
  -H "Authorization: Bearer $TOKEN"

9.2 前端功能测试

操作步骤:

  1. 刷新浏览器Cmd+Shift+R
  2. 登录账户
  3. 打开设置面板(右上角齿轮)
  4. 滚动到"我的情景"区域
  5. 点击"+ 创建新情景"
  6. 填写表单并提交
  7. 验证列表中出现新情景
  8. 切换到自建情景,验证首句引导
  9. 发送消息,验证 AI 使用自建 Prompt
  10. 编辑情景,验证数据预填充
  11. 删除情景,验证二次确认

十、功能亮点

完整的 CRUD — 创建、查看、编辑、删除自建情景
权限隔离 — 用户数据完全隔离,无法互相访问
Eino 深度集成 — 在 Graph Pipeline 中动态加载自建情景
多语言支持 — 中文、英文、日文全覆盖
优雅的 UI — Modal 对话框 + 图标选择器 + Prompt 编写指南
实时生效 — 创建后立即可用,无需刷新
表单验证 — 字符计数、长度限制、必填项提示


十一、安全与限制

11.1 用户配额

const MaxScenariosPerUser = 20  // 每个用户最多 20 个自建情景

11.2 权限控制

  • 只能查看/编辑/删除自己的情景
  • 系统预置情景不可编辑/删除
  • 后端验证 user_id 匹配

11.3 数据验证

后端:

  • 名称: 2-50 字符
  • 描述: 可选,最多 100 字符
  • Prompt: 10-2000 字符
  • 首句: 可选,最多 500 字符

前端:

  • 实时字符计数
  • 超长提示
  • 必填项高亮

十二、未来优化方向

V1.1 功能(推荐)

  • Prompt 模板库
  • 实时预览效果
  • 导入导出功能
  • 情景搜索和筛选

V2.0 功能(长期)

  • 情景市场
  • 情景分享链接
  • AI 辅助优化 Prompt
  • 协作编辑(团队情景)

十三、参考资料


开发完成日期: 2026-06-21
下一步行动: 启动服务进行人工测试验证