# 自建情景功能完整文档 **最后更新**: 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` ```sql 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 后端数据模型 ```go // 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 前端数据结构 ```typescript // 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 示例 #### 创建情景 ```http POST /api/scenarios Authorization: Bearer Content-Type: application/json { "name": "创意写作导师", "icon": "✨", "description": "帮助构思故事情节和写作技巧", "prompt": "你是一位创意写作导师,帮助用户构思故事情节、人物设定和写作技巧...", "greeting": "你好!我是你的创意写作导师。今天想聊聊什么故事创意呢?", "language": "zh-CN" } ``` **响应**: 201 Created ```json { "id": "uuid-xxx", "user_id": "uuid-user", "name": "创意写作导师", "icon": "✨", ... } ``` #### 获取列表 ```http GET /api/scenarios Authorization: Bearer ``` **响应**: 200 OK ```json { "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 ```typescript // useScenarios.ts export function useScenarios(token: string | null) { const [allScenarios, setAllScenarios] = useState([]); // 合并系统预置 + 用户自建 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` - `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 测试 ```bash # 1. 注册用户 curl -X POST http://localhost:8080/api/auth/register \ -H "Content-Type: application/json" \ -d '{"username":"testuser","password":"test12345"}' # 2. 创建情景 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/ \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"高级写作导师"}' # 5. 删除情景 curl -X DELETE http://localhost:8080/api/scenarios/ \ -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 用户配额 ```go 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](../CLAUDE.md) — 项目开发指南 - [02-接口文档.md](./02-接口文档.md) — WebSocket 和 REST API - [自建情景功能-权限隔离说明.md](./自建情景功能-权限隔离说明.md) — 安全设计 --- **开发完成日期**: 2026-06-21 **下一步行动**: 启动服务进行人工测试验证