## 功能概述 - 用户可创建、编辑、删除自定义情景 - 支持自定义情景名称、图标、描述、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
16 KiB
自建情景功能完整文档
最后更新: 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
关键修改文件:
backend/internal/eino/state.go— PipelineState 添加UserIDbackend/internal/eino/types.go— PipelineInput 添加UserIDbackend/internal/eino/graph.go— 接受scenarioRepo参数backend/internal/eino/adapter.go— 设置 UserIDbackend/internal/eino/nodes_history.go— 查询自建情景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 Prompt(10-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: 前端 UI(100% 完成)
3.1 API 封装 ✅
- 文件:
frontend/src/lib/api/scenarios.ts - 5 个 API 调用函数
3.2 Hook 封装 ✅
- 文件:
frontend/src/hooks/useScenarios.ts useScenariosHook- 合并系统预置 + 自建情景
3.3 组件实现 ✅
CreateScenarioModal— 创建对话框EditScenarioModal— 编辑对话框ConfigPanel改造 — 分组显示 + 编辑/删除
3.4 i18n 支持 ✅
- 中文/英文/日文翻译(+40 条)
3.5 样式实现 ✅
- Modal、表单、图标选择器样式
✅ Phase 4: 前端集成(100% 完成)
4.1 主应用集成 ✅
- 文件:
frontend/src/App.tsx - 集成
useScenariosHook - 渲染 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 个):
backend/migrations/004_user_scenarios.up.sqlbackend/migrations/004_user_scenarios.down.sqlbackend/internal/models/user_scenario.gobackend/internal/store/user_scenario_repository.gobackend/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 个):
backend/cmd/server/main.go— 注册 API 路由 + 传递 scenarioRepobackend/internal/ai/llm/scenarios.go— Prompt/Greeting 加载支持自建backend/internal/eino/state.go— 添加 UserID 字段backend/internal/eino/types.go— PipelineInput 添加 UserIDbackend/internal/eino/graph.go— 接受并传递 scenarioRepobackend/internal/eino/adapter.go— 复制 UserID 到 Statebackend/internal/eino/nodes_history.go— 加载自建情景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 约束
解决:
- 更新约束允许
description IS NULL - 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 前端功能测试
操作步骤:
- 刷新浏览器(Cmd+Shift+R)
- 登录账户
- 打开设置面板(右上角齿轮)
- 滚动到"我的情景"区域
- 点击"+ 创建新情景"
- 填写表单并提交
- 验证列表中出现新情景
- 切换到自建情景,验证首句引导
- 发送消息,验证 AI 使用自建 Prompt
- 编辑情景,验证数据预填充
- 删除情景,验证二次确认
十、功能亮点
✅ 完整的 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
- 协作编辑(团队情景)
十三、参考资料
- CLAUDE.md — 项目开发指南
- 02-接口文档.md — WebSocket 和 REST API
- 自建情景功能-权限隔离说明.md — 安全设计
开发完成日期: 2026-06-21
下一步行动: 启动服务进行人工测试验证