Files
CamTalk/frontend/src/lib/api/scenarios.ts
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

121 lines
3.2 KiB
TypeScript

// ============================================================
// scenarios API — 用户自建情景 API 调用
// 职责:封装 /api/scenarios 的 CRUD 操作
// ============================================================
// 开发环境通过 Vite 代理,生产环境使用同域名
const API_BASE = "";
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;
}
export interface CreateScenarioRequest {
name: string;
icon?: string;
description?: string;
prompt: string;
greeting?: string;
language?: string;
}
export interface UpdateScenarioRequest {
name?: string;
icon?: string;
description?: string;
prompt?: string;
greeting?: string;
language?: string;
}
export interface ScenariosListResponse {
scenarios: UserScenario[];
total: number;
}
// 获取用户的所有自建情景
export async function listUserScenarios(token: string): Promise<ScenariosListResponse> {
const res = await fetch(`${API_BASE}/api/scenarios`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) {
const err = await res.json().catch(() => ({ error: "Network error" }));
throw new Error(err.error || "Failed to list scenarios");
}
return res.json();
}
// 创建新情景
export async function createUserScenario(
token: string,
data: CreateScenarioRequest
): Promise<UserScenario> {
const res = await fetch(`${API_BASE}/api/scenarios`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(data),
});
if (!res.ok) {
const err = await res.json().catch(() => ({ error: "Network error" }));
throw new Error(err.error || "Failed to create scenario");
}
return res.json();
}
// 获取单个情景详情
export async function getUserScenario(token: string, id: string): Promise<UserScenario> {
const res = await fetch(`${API_BASE}/api/scenarios/${id}`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) {
const err = await res.json().catch(() => ({ error: "Network error" }));
throw new Error(err.error || "Failed to get scenario");
}
return res.json();
}
// 更新情景
export async function updateUserScenario(
token: string,
id: string,
data: UpdateScenarioRequest
): Promise<UserScenario> {
const res = await fetch(`${API_BASE}/api/scenarios/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(data),
});
if (!res.ok) {
const err = await res.json().catch(() => ({ error: "Network error" }));
throw new Error(err.error || "Failed to update scenario");
}
return res.json();
}
// 删除情景
export async function deleteUserScenario(token: string, id: string): Promise<void> {
const res = await fetch(`${API_BASE}/api/scenarios/${id}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) {
const err = await res.json().catch(() => ({ error: "Network error" }));
throw new Error(err.error || "Failed to delete scenario");
}
}