Files
CamTalk/docs/12-自定义情景.md
2026-06-21 19:31:17 +08:00

426 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 自建情景功能
## 概述
用户可以创建自己的情景,而不仅限于系统预置的 5 种情景。
**系统预置情景**(不可修改):
- 💬 自由对话
- 🎯 模拟面试官
- 📚 英语老师
- ⚔️ 辩论对手
- 🌐 同声翻译
**用户自建情景**(可增删改):
- 🎨 创意写作导师
- 🧘 心理咨询师
- 👨‍🍳 私人厨师
- 📖 历史学家
- ... (用户自由创建)
**用户旅程**
```
1. 用户点击"创建情景"按钮
2. 弹出创建对话框
3. 填写表单:
- 情景名称(必填)
- 情景图标(可选)
- 简短描述(可选)
- 角色 Prompt必填最少 10 字)
- 首句引导(可选)
4. 点击"创建"
5. 情景保存到数据库
6. 情景出现在选择列表中
7. 用户切换到自建情景
8. AI 按照用户设定的 Prompt 扮演角色
```
**核心特性**:完整 CRUD 操作(创建/查看/编辑/删除),通过 `user_id` 实现用户数据完全隔离Eino Graph 管线深度集成(动态加载自建情景 Prompt中文/英文/日文全覆盖Modal 对话框 + 图标选择器 + Prompt 编写指南,创建后立即可用无需刷新。
## 技术架构
### 数据流
**创建情景**
```
用户填写表单 → POST /api/scenarios → Handler 验证
→ Repository.Create → PostgreSQL 插入 → 返回情景对象
```
**AI 对话使用自建情景**
```
WebSocket 连接 → ServeWS 获取 userID
→ Eino Graph 初始化 → nodes_history 查询 user_scenarios
→ GetScenarioPrompt(customScenarios) → 构建 System Prompt
→ LLM 生成回复
```
### 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 添加 `UserID` |
| `backend/internal/eino/types.go` | PipelineInput 添加 `UserID` |
| `backend/internal/eino/graph.go` | 接受 `scenarioRepo` 参数 |
| `backend/internal/eino/adapter.go` | 设置 UserID |
| `backend/internal/eino/nodes_history.go` | 查询自建情景 |
| `backend/internal/ws/handler.go` | 首句引导支持自建情景 |
## 数据模型
### 数据库表结构
**表名**: `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 Prompt10-2000 字符) |
| `greeting` | 首句引导(可选,最多 500 字符) |
| `language` | 默认语言zh-CN / en-US / ja-JP |
### 后端数据模型
```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"`
}
```
### 前端数据结构
```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
### API 端点
| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| GET | `/api/scenarios` | 获取用户的所有自建情景 | 需登录 |
| POST | `/api/scenarios` | 创建新情景 | 需登录 |
| GET | `/api/scenarios/:id` | 获取单个情景详情 | 需登录 |
| PATCH | `/api/scenarios/:id` | 更新情景 | 需登录 |
| DELETE | `/api/scenarios/:id` | 删除情景 | 需登录 |
### API 示例
**创建情景**
```http
POST /api/scenarios
Authorization: Bearer <access_token>
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 <access_token>
```
响应 200 OK
```json
{
"scenarios": [],
"total": 3
}
```
## 前端实现
### 组件结构
```
frontend/src/
├── components/
│ ├── CreateScenarioModal/
│ │ └── index.tsx # 创建情景对话框
│ ├── EditScenarioModal/
│ │ └── index.tsx # 编辑情景对话框
│ └── ConfigPanel/
│ └── index.tsx # 设置面板(改造)
├── hooks/
│ └── useScenarios.ts # 情景管理 Hook
└── lib/
└── api/
└── scenarios.ts # API 调用封装
```
### 核心 Hook
```typescript
// 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,
};
}
```
### 创建情景表单
**表单字段**
- 名称必填2-50 字符)
- 图标可选24 个预设 emoji
- 描述(可选,最多 100 字符)
- Prompt必填10-2000 字符)
- 首句引导(可选,最多 500 字符)
- 语言(可选,默认 zh-CN
**表单验证**
- 实时字符计数
- 长度限制提示
- 必填项高亮
## 使用指南
### 后端 API 测试
```bash
# 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"
```
### 前端功能测试
1. 刷新浏览器Cmd+Shift+R
2. 登录账户
3. 打开设置面板(右上角齿轮)
4. 滚动到"我的情景"区域
5. 点击"+ 创建新情景"
6. 填写表单并提交
7. 验证列表中出现新情景
8. 切换到自建情景,验证首句引导
9. 发送消息,验证 AI 使用自建 Prompt
10. 编辑情景,验证数据预填充
11. 删除情景,验证二次确认
## 安全与限制
### 用户配额
```go
const MaxScenariosPerUser = 20 // 每个用户最多 20 个自建情景
```
### 权限控制
- 只能查看/编辑/删除自己的情景
- 系统预置情景不可编辑/删除
- 后端验证 `user_id` 匹配
### 数据验证
**后端**
- 名称2-50 字符
- 描述:可选,最多 100 字符
- Prompt10-2000 字符
- 首句引导:可选,最多 500 字符
**前端**
- 实时字符计数
- 超长提示
- 必填项高亮
## 未来优化方向
**V1.1**
- Prompt 模板库
- 实时预览效果
- 导入导出功能
- 情景搜索和筛选
**V2.0**
- 情景市场
- 情景分享链接
- AI 辅助优化 Prompt
- 协作编辑(团队情景)
## 参考资料
- [CLAUDE.md](../CLAUDE.md) — 项目开发指南
- [02-接口文档.md](./02-接口文档.md) — WebSocket 和 REST API
- [自建情景功能-权限隔离说明.md](./自建情景功能-权限隔离说明.md) — 安全设计