Compare commits

..

56 Commits

Author SHA1 Message Date
f9a01269d2 Merge pull request 'refactor: 优化数据库迁移文件约束和注释' (#211) from refatcor/table-structure into main
All checks were successful
Deploy / deploy (push) Successful in 39s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/211
2026-06-23 23:42:40 +08:00
hhs
74f1a8eb34 refactor: 优化数据库迁移文件约束和注释
**迁移文件优化**:
- 001_users.up.sql: 添加完整表和列注释,说明密码哈希算法 (bcrypt) 和 token 哈希算法 (SHA-256)
- 002_messages.up.sql: 收紧 role 字段至 VARCHAR(10),添加 tokens_used 非负约束,补充完整注释
- 003_sessions.up.sql: 收紧 title 字段至 VARCHAR(100) 并添加长度约束 (1-100),详细说明 config JSONB 结构
- 004_user_scenarios.up.sql: 修复 greeting 字段冲突 (VARCHAR(500)),扩大 icon 至 VARCHAR(20) 支持复合 Emoji,prompt 改为 TEXT 无上限,优化约束逻辑

**后端代码修改**:
- auth.go: 移除用户名最小长度限制 (3 字符),仅保留最大长度 64

**前端国际化修改**:
- 更新三种语言的登录/注册表单 placeholder 文本,移除字符长度要求提示
  - zh-CN: "请输入用户名" / "请输入密码"
  - en-US: "Enter username" / "Enter password"
  - ja-JP: "ユーザー名を入力" / "パスワードを入力"

**删除文件**:
- 移除临时修复迁移文件 (005_fix_user_scenarios_description.*)
- 删除临时诊断脚本 (fix_user_scenarios.sql, check_constraints.sql)

所有约束修改都是放宽限制,不影响现有数据。
2026-06-23 23:41:49 +08:00
4dd4138197 Merge pull request 'fix: 减少自定义情景中数据库表对 prompt 的约束' (#210) from fix/database into main
All checks were successful
Deploy / deploy (push) Successful in 33s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/210
2026-06-23 22:58:56 +08:00
hhs
5ef059eb40 fix: 减少自定义情景中数据库表对 prompt 的约束 2026-06-23 22:58:02 +08:00
9523fe6650 Merge pull request 'fix: 修复无法正常自定义情景的问题' (#209) from fix/frontend into main
All checks were successful
Deploy / deploy (push) Successful in 39s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/209
2026-06-23 11:11:08 +08:00
hhs
a219934f4b fix: 修复无法正常自定义情景的问题 2026-06-23 11:10:37 +08:00
72a8d4803e Merge pull request 'fix: 修复前端语法问题' (#208) from fix/frontend into main
All checks were successful
Deploy / deploy (push) Successful in 28s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/208
2026-06-22 21:59:14 +08:00
hhs
dcf53a3783 fix: 修复前端语法问题 2026-06-22 21:58:49 +08:00
b5ec6551bf Merge pull request 'styles:美化样式' (#207) from feat/styles into main
Some checks failed
Deploy / deploy (push) Failing after 17s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/207
2026-06-22 19:32:47 +08:00
79d321c78a fix: 自建情景在聊天面板与系统消息中正确显示图标和名称 2026-06-22 16:02:02 +08:00
57bd8c72b8 Merge pull request 'feat: v2 版本' (#206) from v2 into main
All checks were successful
Deploy / deploy (push) Successful in 18s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/206
2026-06-22 16:01:27 +08:00
d640b5b41b Merge pull request 'docs: 优化首页 README 文档' (#205) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 18s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/205
2026-06-22 15:47:39 +08:00
f1b9e4fcc1 fix: 系统消息最大宽度限制与居中对齐 2026-06-22 15:08:26 +08:00
18aa5f1949 refactor: 场景配置与弹窗样式改用 CSS 变量,适配白色主题 2026-06-22 15:03:58 +08:00
a16628119a Merge pull request 'docs: 优化首页部分内容重复' (#204) from docs/readme into develop
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/204
2026-06-22 14:50:11 +08:00
hhs
3888941f85 docs: 优化首页部分内容重复 2026-06-22 14:49:52 +08:00
140a178993 fix: 侧栏收起时使用 visibility 隐藏避免闪烁 2026-06-22 14:43:04 +08:00
c00b8a83d9 Merge pull request 'fix: 优化侧栏弹出延迟问题' (#200) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 28s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/200
2026-06-22 13:17:18 +08:00
c3808ff38c Merge pull request 'fix: 修复“设置”侧栏双重展开的问题' (#198) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 29s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/198
2026-06-22 13:03:04 +08:00
70ee212ea2 Merge pull request 'fix: 修复前端侧边栏无法平滑弹出的问题' (#196) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 28s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/196
2026-06-22 12:55:43 +08:00
c4b68b77d1 Merge pull request 'fix: 修复空会话返回 null 导致前端崩溃的问题' (#194) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 43s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/194
2026-06-22 12:40:37 +08:00
964c5c967e Merge pull request 'fix: 修复前端侧边栏弹出异常问题' (#192) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 47s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/192
2026-06-22 11:20:01 +08:00
881c3f9853 Merge pull request 'fix: 修复前端 TypeScript 编译错误' (#190) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 23s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/190
2026-06-22 00:30:00 +08:00
f9e68b96b9 Merge pull request 'refactor: 重构视频面板 UI,情景选择改为卡片网格,控制按钮改为 SVG 图标' (#188) from develop into v2
Some checks failed
Deploy / deploy (push) Failing after 11s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/188
2026-06-22 00:14:16 +08:00
7a705744d0 Merge pull request 'feat: 实现日志追踪链路' (#186) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 29s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/186
2026-06-21 23:24:17 +08:00
b4fbf8625b Merge pull request 'feat: 实现摄像头和麦克风设备选择功能' (#184) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 39s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/184
2026-06-21 23:21:35 +08:00
ad5d90e344 Merge pull request 'docs: 重构文档结构,规范编号并整合冗余内容' (#182) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 26s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/182
2026-06-21 14:52:06 +08:00
8b4acb3ce7 Merge pull request 'docs: 统一更新配置文件路径引用' (#180) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 28s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/180
2026-06-21 13:32:20 +08:00
d0f5f5c94d Merge pull request 'feat: 集成限流器到服务' (#178) from develop into v2
Some checks failed
Deploy / deploy (push) Failing after 4s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/178
2026-06-21 13:25:06 +08:00
023c834074 Merge pull request 'feat:部署修改' (#175) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 28s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/175
2026-06-20 23:38:29 +08:00
6af26ffc91 Merge pull request 'feat: 优化情景切换功能' (#173) from develop into v2
Some checks failed
Deploy / deploy (push) Failing after 29s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/173
2026-06-20 23:28:37 +08:00
e151c5b665 Merge pull request 'fix: apk 使用阿里云镜像 mirrors.aliyun.com 加速下载' (#171) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 42s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/171
2026-06-20 22:04:05 +08:00
23e0e22a12 Merge pull request 'fix: CI 环境安装 rsync + docker-cli + docker-cli-compose' (#169) from develop into v2
Some checks failed
Deploy / deploy (push) Has been cancelled
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/169
2026-06-20 21:47:57 +08:00
f8af2f0ccc Merge pull request 'fix: git clone 前清理残留的 /tmp/camtalk-deploy 目录' (#167) from develop into v2
Some checks failed
Deploy / deploy (push) Failing after 3m57s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/167
2026-06-20 21:39:17 +08:00
f5440c9e2d Merge pull request 'fix: 添加 rsync 安装,避免首次部署时 command not found' (#165) from develop into v2
Some checks failed
Deploy / deploy (push) Failing after 4m2s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/165
2026-06-20 21:33:39 +08:00
340c26b7a6 Merge pull request 'fix: 统一使用 /opt/camtalk/.env 路径,act_runner 挂载宿主机目录后直接读取' (#163) from develop into v2
Some checks failed
Deploy / deploy (push) Failing after 1s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/163
2026-06-20 21:32:05 +08:00
f86c2560cd Merge pull request 'fix: env_file 使用绝对路径 /root/camtalk/.env,确保 CI 容器内 docker compose 正确解析' (#161) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 12s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/161
2026-06-20 20:58:51 +08:00
b7d0edb6da Merge pull request 'docs: 更新架构设计和令牌桶限流相关文档' (#159) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 13s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/159
2026-06-20 20:54:51 +08:00
04215e7a53 Merge pull request 'feat: 优化对话历史功能、增加首页登录页面' (#156) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 32s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/156
2026-06-20 20:11:34 +08:00
bc7eb7409c Merge pull request 'fix: 通过 docker run -v 桥接同步 /opt/camtalk/.env,每次部署自动获取最新配置' (#154) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 2m52s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/154
2026-06-20 20:05:28 +08:00
0447fdacac Merge pull request 'fix: git fetch 改用公网 URL 而非 origin,避免 Docker 内网地址不可达导致卡死' (#152) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 15s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/152
2026-06-20 19:48:04 +08:00
32aea44f3b Merge pull request 'fix: env_file 改为相对路径,解决 CI 容器无法读取 /opt/camtalk/.env 的问题' (#149) from develop into v2
Some checks failed
Deploy / deploy (push) Has been cancelled
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/149
2026-06-20 19:31:24 +08:00
2a13ee9c89 Merge pull request 'fix: 精简 deploy.yml,checkout + ref + verify 三步确保拉取正确分支' (#147) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 2m12s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/147
2026-06-20 17:50:25 +08:00
ae100c4a75 Merge pull request 'fix: 恢复 checkout action 拉取代码,仅添加 ref 参数指定触发分支' (#145) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 2m10s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/145
2026-06-20 17:35:46 +08:00
97e125234b Merge pull request 'fix: 完善鉴权模块,修复 CI/CD 中 dubious ownership 错误' (#143) from develop into v2
Some checks failed
Deploy / deploy (push) Has been cancelled
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/143
2026-06-20 17:24:06 +08:00
dfed964f76 Merge pull request 'fix: CI/CD 改用 git clone 直接拉取代码,避免 rsync 同步失败' (#141) from develop into v2
Some checks failed
Deploy / deploy (push) Has been cancelled
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/141
2026-06-20 16:25:23 +08:00
1c65433d40 Merge pull request 'fix: checkout 指定触发分支,避免始终拉取 main 代码' (#140) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 16s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/140
2026-06-20 15:51:41 +08:00
15cd157f45 Merge pull request 'feat: 对话场景优化' (#139) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 24s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/139
2026-06-20 15:18:38 +08:00
1f7cd407a8 Merge pull request 'fix: CI 环境安装 rsync 依赖' (#137) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 1m56s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/137
2026-06-20 13:37:27 +08:00
52015fa6c6 Merge pull request 'fix: CI/CD 部署固定到 /root/camtalk 目录,确保 compose 能正确管理容器生命周期' (#135) from develop into v2
Some checks failed
Deploy / deploy (push) Failing after 4s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/135
2026-06-20 13:34:50 +08:00
de78d60959 Merge pull request 'refactor: 用 Eino 框架重构编排层' (#133) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 2m4s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/133
2026-06-20 12:01:03 +08:00
795ace75d6 Merge pull request 'fix: 统一 docker-compose 环境变量注入方式,修复 postgres/redis 配置读取问题' (#129) from feature/add-redis-docker into v2
All checks were successful
Deploy / deploy (push) Successful in 16s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/129
2026-06-20 00:27:20 +08:00
582f68f68b Merge pull request 'fix: 修复 Dockerfile 运行阶段缺少 config.yaml 导致容器启动 panic' (#127) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 15s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/127
2026-06-19 23:24:45 +08:00
556666c046 Merge pull request 'fix: 修复 Redis 密码为空时容器启动失败的问题' (#125) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 21s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/125
2026-06-19 23:19:23 +08:00
04dac0b673 Merge pull request 'feat: 添加 Redis Docker 容器编排及三级存储架构' (#123) from develop into v2
Some checks failed
Deploy / deploy (push) Failing after 5m12s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/123
2026-06-19 23:09:39 +08:00
f38fbf0527 Merge pull request 'refactor: 统一配置文件系统并重构项目设计文档' (#121) from develop into v2
All checks were successful
Deploy / deploy (push) Successful in 11s
Reviewed-on: http://8.161.227.145:3000/XEngineers/CamTalk/pulls/121
2026-06-19 19:46:06 +08:00
13 changed files with 303 additions and 265 deletions

1
.gitignore vendored
View File

@@ -27,3 +27,4 @@ Thumbs.db
.claudian/
修改过程笔记/
学习复盘/
docs/follow-up/

199
README.md
View File

@@ -214,14 +214,16 @@ CamTalk/
- **PostgreSQL** >= 15可选 Docker
- **Redis** >= 7可选用于缓存加速
### 1. 克隆项目
### 本地开发
#### 1. 克隆项目
```bash
git clone https://github.com/yourusername/CamTalk.git
cd CamTalk
```
### 2. 配置环境变量
#### 2. 配置环境变量
```bash
# 复制环境变量模板
@@ -235,7 +237,7 @@ cp backend/.env.example backend/.env
# - CAMTALK_AI_TTS_API_KEYMiMo/OpenAI API Key
```
### 3. 启动后端
#### 3. 启动后端
```bash
cd backend
@@ -250,7 +252,7 @@ go run ./cmd/server migrate
go run ./cmd/server
```
### 4. 启动前端
#### 4. 启动前端
```bash
cd frontend
@@ -262,27 +264,93 @@ npm install
npm run dev
```
### 5. 访问应用
#### 5. 访问应用
打开浏览器访问 [http://localhost:5173](http://localhost:5173),注册账号后即可开始使用。
---
### 🐳 Docker 快速部署(生产环境)
#### 6. 代码检查与测试
```bash
# 一键启动全部服务frontend + backend + postgres + redis
# 安装 Go 代码检查工具
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# 运行后端代码检查
cd backend
golangci-lint run
# 后端单元测试
go test ./...
# 后端集成测试(需要 PostgreSQL
go test -tags=integration ./...
# 前端代码检查
cd frontend
npm run lint
# 前端测试
npm test
```
### 远程部署
#### 方式一Docker Compose推荐
```bash
# 1. 克隆代码到服务器
git clone https://github.com/yourusername/CamTalk.git
cd CamTalk
# 2. 配置环境变量
cp backend/.env.example backend/.env
# 编辑 .env 文件,填入生产环境配置
# 3. 一键部署frontend + backend + postgres + redis
./deploy.sh up
# 停止服务
./deploy.sh down
# 查看日志
# 4. 查看日志
./deploy.sh logs
# 5. 停止服务
./deploy.sh down
```
部署完成后访问 [http://localhost:9000](http://localhost:9000)
#### 方式二:手动部署
```bash
# 1. 构建前端
cd frontend
npm install
npm run build # 输出到 dist/
# 2. 构建后端
cd backend
go build -o camtalk ./cmd/server
# 3. 配置 Nginx
# 参考 nginx.conf.example 配置反向代理
# 4. 启动服务
APP_ENV=prod ./camtalk
# 5. 使用 systemd 管理(可选)
sudo systemctl enable camtalk
sudo systemctl start camtalk
```
#### 环境变量检查清单
部署前确保已配置以下环境变量:
-`CAMTALK_AUTH_JWT_SECRET`(使用 `openssl rand -hex 32` 生成)
-`CAMTALK_STORAGE_DSN`PostgreSQL 连接字符串)
-`CAMTALK_AI_LLM_API_KEY`DashScope API Key
-`CAMTALK_AI_STT_API_KEY`STT 服务 API Key
-`CAMTALK_AI_TTS_API_KEY`TTS 服务 API Key
-`APP_ENV=prod`(启用生产环境配置)
### 配置优先级
```
@@ -443,10 +511,6 @@ CamTalk 采用 **JWT 双 token 轮转 + Refresh Token Rotation** 安全机制:
**水平扩展**Gateway 无状态设计,会话状态存储在 Redis/PostgreSQL支持多实例部署。
## 📖 文档
|------|------|
| [01-架构设计](docs/01-架构设计.md) | 三层架构、技术栈、数据库设计、部署方案 |
| [02-接口文档](docs/02-接口文档.md) | WebSocket 协议、REST API、AI 服务层、编排器、配置管理 |
## 📖 文档
### 核心设计文档
@@ -472,107 +536,6 @@ CamTalk 采用 **JWT 双 token 轮转 + Refresh Token Rotation** 安全机制:
| [09-情景切换](docs/09-情景切换.md) | 情景模式设计与实现 |
| [12-自定义情景](docs/12-自定义情景.md) | 用户自定义情景功能(规划中) |
## 🛠️ 本地开发和远程部署指南
### 本地开发环境配置
#### 1. 代码检查工具
```bash
# 安装 Go 代码检查工具
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# 运行代码检查
cd backend
golangci-lint run
# 前端代码检查
cd frontend
npm run lint
```
#### 2. 运行测试
```bash
# 后端单元测试
cd backend
go test ./...
# 后端集成测试(需要 PostgreSQL
go test -tags=integration ./...
# 前端测试
cd frontend
npm test
```
#### 3. 数据库迁移
```bash
# 运行迁移(自动创建/更新表结构)
cd backend
go run ./cmd/server migrate
# 回滚迁移
go run ./cmd/server migrate:rollback
```
### 远程服务器部署
#### 方式一Docker Compose
```bash
# 1. 克隆代码到服务器
git clone https://github.com/yourusername/CamTalk.git
cd CamTalk
# 2. 配置环境变量
cp backend/.env.example backend/.env
# 编辑 .env 文件,填入生产环境配置
# 3. 一键部署
./deploy.sh up
# 4. 查看日志
./deploy.sh logs
# 5. 停止服务
./deploy.sh down
```
#### 方式二:手动部署
```bash
# 1. 构建前端
cd frontend
npm install
npm run build # 输出到 dist/
# 2. 构建后端
cd backend
go build -o camtalk ./cmd/server
# 3. 配置 Nginx
# 参考 nginx.conf.example 配置反向代理
# 4. 启动服务
APP_ENV=prod ./camtalk
# 5. 使用 systemd 管理(可选)
sudo systemctl enable camtalk
sudo systemctl start camtalk
```
#### 环境变量检查清单
部署前确保已配置以下环境变量:
-`CAMTALK_AUTH_JWT_SECRET`(使用 `openssl rand -hex 32` 生成)
-`CAMTALK_STORAGE_DSN`PostgreSQL 连接字符串)
-`CAMTALK_AI_LLM_API_KEY`DashScope API Key
-`CAMTALK_AI_STT_API_KEY`STT 服务 API Key
-`CAMTALK_AI_TTS_API_KEY`TTS 服务 API Key
-`APP_ENV=prod`(启用生产环境配置)
## 🐛 问题反馈

View File

@@ -208,8 +208,8 @@ func (h *AuthHandler) Logout(c *gin.Context) {
// validateCredentials 校验用户名和密码格式。
// 返回空字符串表示校验通过,否则返回错误描述。
func validateCredentials(username, password string) string {
if len(username) < 3 || len(username) > 64 {
return "username must be 3-64 characters"
if len(username) > 64 {
return "username must not exceed 64 characters"
}
if len(password) < 8 || len(password) > 72 {
return "password must be 8-72 characters"

View File

@@ -10,6 +10,14 @@ CREATE TABLE IF NOT EXISTS users (
-- 用户名索引(用于登录查询)
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
-- 表和列注释
COMMENT ON TABLE users IS '用户表,存储系统所有注册用户的基本信息';
COMMENT ON COLUMN users.id IS '用户唯一标识符 (UUID)';
COMMENT ON COLUMN users.username IS '用户名,最大 64 字符,全局唯一';
COMMENT ON COLUMN users.password_hash IS '密码哈希值,使用 bcrypt 算法cost=10';
COMMENT ON COLUMN users.created_at IS '用户注册时间';
COMMENT ON COLUMN users.updated_at IS '用户信息最后更新时间';
-- Refresh Token 表
CREATE TABLE IF NOT EXISTS refresh_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
@@ -24,3 +32,11 @@ CREATE INDEX IF NOT EXISTS idx_refresh_tokens_token_hash ON refresh_tokens(token
-- 用户 ID 索引(用于登出所有设备)
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens(user_id);
-- 表和列注释
COMMENT ON TABLE refresh_tokens IS 'Refresh Token 表,用于 JWT 双 token 机制的长期身份验证';
COMMENT ON COLUMN refresh_tokens.id IS 'Token 唯一标识符 (UUID)';
COMMENT ON COLUMN refresh_tokens.user_id IS '所属用户 ID外键关联 users 表,用户删除时级联删除';
COMMENT ON COLUMN refresh_tokens.token_hash IS 'Token 哈希值,使用 SHA-256 算法,十六进制编码 (64 字符)';
COMMENT ON COLUMN refresh_tokens.expires_at IS 'Token 过期时间,默认有效期 7 天';
COMMENT ON COLUMN refresh_tokens.created_at IS 'Token 创建时间';

View File

@@ -2,10 +2,12 @@
CREATE TABLE IF NOT EXISTS messages (
id BIGSERIAL PRIMARY KEY,
session_id UUID NOT NULL,
role VARCHAR(16) NOT NULL, -- "user" | "assistant" | "system"
role VARCHAR(10) NOT NULL,
content TEXT NOT NULL,
tokens_used INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT check_tokens_non_negative CHECK (tokens_used >= 0)
);
-- 按会话查询消息(分页核心索引)
@@ -15,3 +17,12 @@ CREATE INDEX IF NOT EXISTS idx_messages_session_id_created_at
-- 按会话查询最后一条消息
CREATE INDEX IF NOT EXISTS idx_messages_session_id_id_desc
ON messages(session_id, id DESC);
-- 表和列注释
COMMENT ON TABLE messages IS '消息表,存储所有会话的消息记录';
COMMENT ON COLUMN messages.id IS '消息唯一标识符,自增序列';
COMMENT ON COLUMN messages.session_id IS '所属会话 ID关联 sessions 表';
COMMENT ON COLUMN messages.role IS '消息角色,可选值: ''user'' (用户), ''assistant'' (AI 助手), ''system'' (系统)';
COMMENT ON COLUMN messages.content IS '消息内容,无长度限制';
COMMENT ON COLUMN messages.tokens_used IS '消息消耗的 token 数量,用于计费统计';
COMMENT ON COLUMN messages.created_at IS '消息创建时间';

View File

@@ -1,11 +1,22 @@
CREATE TABLE IF NOT EXISTS sessions (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
title VARCHAR(256) NOT NULL DEFAULT '新对话',
title VARCHAR(100) NOT NULL DEFAULT '新对话',
config JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT check_title_length CHECK (char_length(title) >= 1 AND char_length(title) <= 100)
);
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions (user_id);
CREATE INDEX IF NOT EXISTS idx_sessions_user_updated ON sessions (user_id, updated_at DESC);
-- 表和列注释
COMMENT ON TABLE sessions IS '会话表,存储用户的对话会话信息';
COMMENT ON COLUMN sessions.id IS '会话唯一标识符 (UUID)';
COMMENT ON COLUMN sessions.user_id IS '所属用户 ID关联 users 表';
COMMENT ON COLUMN sessions.title IS '会话标题,默认为"新对话",长度 1-100 字符';
COMMENT ON COLUMN sessions.config IS '会话配置 (JSONB),包含: tts_enabled (布尔), detail_level (''low''/''high''), language (语言代码), scenario (情景 ID)';
COMMENT ON COLUMN sessions.created_at IS '会话创建时间';
COMMENT ON COLUMN sessions.updated_at IS '会话最后更新时间';

View File

@@ -5,18 +5,19 @@ 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) NOT NULL,
icon VARCHAR(20) DEFAULT '',
description VARCHAR(100),
prompt TEXT NOT NULL,
greeting VARCHAR(200),
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 (char_length(description) >= 5 AND char_length(description) <= 100),
CONSTRAINT check_prompt_length CHECK (char_length(prompt) >= 50 AND char_length(prompt) <= 2000)
CONSTRAINT check_description_length CHECK (description IS NULL OR char_length(description) <= 100),
CONSTRAINT check_prompt_length CHECK (char_length(prompt) >= 10),
CONSTRAINT check_greeting_length CHECK (greeting IS NULL OR char_length(greeting) <= 500)
);
-- 为用户 ID 创建索引,加速查询
@@ -25,12 +26,15 @@ 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);
COMMENT ON TABLE user_scenarios IS '用户自建情景表';
COMMENT ON COLUMN user_scenarios.id IS '情景唯一标识';
COMMENT ON COLUMN user_scenarios.user_id IS '所属用户 ID外键关联 users 表';
COMMENT ON COLUMN user_scenarios.name IS '情景名称,如"创意写作导师"';
COMMENT ON COLUMN user_scenarios.icon IS 'Emoji 图标,如"🎨"';
COMMENT ON COLUMN user_scenarios.description IS '简短描述,显示在情景卡片上';
COMMENT ON COLUMN user_scenarios.prompt IS '角色 System Prompt定义 AI 行为';
COMMENT ON COLUMN user_scenarios.greeting IS '首句引导,可选';
COMMENT ON COLUMN user_scenarios.language IS '默认语言,如 zh-CN、en-US';
-- 表和列注释
COMMENT ON TABLE user_scenarios IS '用户自建情景表,存储用户创建的 AI 对话情景配置';
COMMENT ON COLUMN user_scenarios.id IS '情景唯一标识符 (UUID)';
COMMENT ON COLUMN user_scenarios.user_id IS '所属用户 ID外键关联 users 表,用户删除时级联删除';
COMMENT ON COLUMN user_scenarios.name IS '情景名称 (2-50 字符),如"创意写作导师"';
COMMENT ON COLUMN user_scenarios.icon IS 'Emoji 图标 (最多 20 字符),支持复合 Emoji如"🎨"';
COMMENT ON COLUMN user_scenarios.description IS '简短描述 (最多 100 字符),可选,显示在情景卡片上';
COMMENT ON COLUMN user_scenarios.prompt IS '角色 System Prompt (最少 10 字符,无上限),定义 AI 行为和对话风格';
COMMENT ON COLUMN user_scenarios.greeting IS '首句引导 (最多 500 字符)可选AI 的开场白';
COMMENT ON COLUMN user_scenarios.language IS '默认语言代码 (如 zh-CN、en-US、ja-JP)';
COMMENT ON COLUMN user_scenarios.created_at IS '情景创建时间';
COMMENT ON COLUMN user_scenarios.updated_at IS '情景最后更新时间';

View File

@@ -241,17 +241,20 @@ body {
border-right: 1px solid var(--color-border);
overflow: hidden;
box-shadow: 8px 0 40px rgba(10, 10, 15, 0.4);
transition: transform 0.2s ease-out;
transition: transform 0.2s ease-out, visibility 0s linear 0.2s;
}
.sidebar--collapsed {
transform: translateX(-100%);
pointer-events: none;
visibility: hidden;
}
.sidebar--open {
transform: translateX(0);
pointer-events: auto;
visibility: visible;
transition: transform 0.2s ease-out, visibility 0s linear 0s;
}
.sidebar__header {
@@ -912,6 +915,8 @@ body {
gap: 10px;
padding: 6px 0;
background: transparent;
max-width: 100%;
align-self: center;
}
.chat-message--system::before,
@@ -1389,6 +1394,11 @@ body {
to { opacity: 1; }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* ---- Scrollbar ---- */
::-webkit-scrollbar {
@@ -2203,7 +2213,7 @@ body {
}
/* ============================================================
Custom Scenarios Styles
Custom Scenarios Styles — 白色商务风格(使用 CSS 变量)
============================================================ */
/* Scenario list */
@@ -2219,16 +2229,16 @@ body {
align-items: center;
gap: 10px;
padding: 10px 12px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
background: var(--color-surface-2);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
cursor: pointer;
transition: all 0.2s;
transition: all var(--transition-fast);
}
.scenario-item:hover {
background: rgba(255, 255, 255, 0.06);
border-color: rgba(255, 255, 255, 0.15);
background: var(--color-surface-3);
border-color: var(--color-surface-3);
}
.scenario-item input[type="radio"] {
@@ -2243,12 +2253,12 @@ body {
.scenario-item__name {
flex: 1;
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
color: var(--color-text);
}
.scenario-item__desc {
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
color: var(--color-text-muted);
margin-top: 2px;
}
@@ -2281,28 +2291,31 @@ body {
.scenario-action-btn {
padding: 4px 8px;
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 4px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
font-size: 14px;
color: var(--color-text-muted);
cursor: pointer;
transition: all 0.2s;
transition: all var(--transition-fast);
}
.scenario-action-btn:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.2);
background: var(--color-surface-2);
border-color: var(--color-surface-3);
color: var(--color-text);
}
.scenario-action-btn--confirm {
background: rgba(248, 113, 113, 0.15);
border-color: rgba(248, 113, 113, 0.3);
background: rgba(248, 113, 113, 0.1);
border-color: rgba(248, 113, 113, 0.2);
color: var(--color-error);
}
.scenario-empty {
padding: 16px;
text-align: center;
font-size: 13px;
color: rgba(255, 255, 255, 0.4);
color: var(--color-text-muted);
font-style: italic;
}
@@ -2311,17 +2324,17 @@ body {
margin-left: auto;
padding: 4px 12px;
font-size: 12px;
background: rgba(196, 97, 47, 0.15);
border: 1px solid rgba(196, 97, 47, 0.3);
background: rgba(59, 130, 246, 0.08);
border: 1px solid rgba(59, 130, 246, 0.2);
border-radius: 999px;
color: #C4612F;
color: var(--color-primary);
cursor: pointer;
transition: all 0.2s;
transition: all var(--transition-fast);
}
.config-create-btn:hover {
background: rgba(196, 97, 47, 0.25);
border-color: rgba(196, 97, 47, 0.5);
background: rgba(59, 130, 246, 0.16);
border-color: rgba(59, 130, 246, 0.4);
}
.config-group__title {
@@ -2337,25 +2350,30 @@ body {
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
background: rgba(10, 10, 15, 0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
animation: fadeIn 0.2s;
}
[data-theme="light"] .modal-overlay {
background: rgba(0, 0, 0, 0.3);
}
.modal {
background: #1F2421;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius);
width: 90%;
max-width: 600px;
max-height: 85vh;
display: flex;
flex-direction: column;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
box-shadow: 0 16px 48px rgba(10, 10, 15, 0.25);
animation: slideUp 0.3s;
}
@@ -2368,20 +2386,21 @@ body {
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
border-bottom: 1px solid var(--color-border);
}
.modal__title {
font-size: 18px;
font-weight: 500;
color: rgba(255, 255, 255, 0.95);
font-weight: 600;
color: var(--color-text);
letter-spacing: -0.01em;
}
.modal__close {
background: transparent;
border: none;
font-size: 24px;
color: rgba(255, 255, 255, 0.6);
font-size: 20px;
color: var(--color-text-muted);
cursor: pointer;
padding: 0;
width: 32px;
@@ -2389,13 +2408,13 @@ body {
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
transition: all 0.2s;
border-radius: var(--radius-sm);
transition: all var(--transition-fast);
}
.modal__close:hover {
background: rgba(255, 255, 255, 0.08);
color: rgba(255, 255, 255, 0.9);
background: var(--color-surface-2);
color: var(--color-text);
}
.modal__body {
@@ -2409,7 +2428,7 @@ body {
gap: 12px;
justify-content: flex-end;
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
border-top: 1px solid var(--color-border);
margin-top: 8px;
}
@@ -2424,12 +2443,12 @@ body {
gap: 8px;
font-size: 14px;
font-weight: 500;
color: rgba(255, 255, 255, 0.85);
color: var(--color-text);
margin-bottom: 8px;
}
.form-required {
color: #C4612F;
color: var(--color-primary);
}
.form-input,
@@ -2437,21 +2456,28 @@ body {
.form-select {
width: 100%;
padding: 10px 12px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
color: rgba(255, 255, 255, 0.9);
background: var(--color-surface-2);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
color: var(--color-text);
font-size: 14px;
font-family: inherit;
transition: all 0.2s;
transition: all var(--transition-fast);
}
.form-input::placeholder,
.form-textarea::placeholder {
color: var(--color-text-muted);
opacity: 0.7;
}
.form-input:focus,
.form-textarea:focus,
.form-select:focus {
outline: none;
border-color: rgba(196, 97, 47, 0.5);
background: rgba(255, 255, 255, 0.08);
border-color: var(--color-primary);
background: var(--color-surface);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.form-textarea {
@@ -2465,15 +2491,15 @@ body {
display: block;
margin-top: 4px;
font-size: 12px;
color: rgba(255, 255, 255, 0.4);
color: var(--color-text-muted);
}
.form-error {
padding: 12px;
background: rgba(248, 113, 113, 0.15);
border: 1px solid rgba(248, 113, 113, 0.3);
border-radius: 6px;
color: #f87171;
background: rgba(248, 113, 113, 0.08);
border: 1px solid rgba(248, 113, 113, 0.2);
border-radius: var(--radius-sm);
color: var(--color-error);
font-size: 13px;
margin-top: 12px;
}
@@ -2493,129 +2519,112 @@ body {
align-items: center;
justify-content: center;
font-size: 22px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
background: var(--color-surface-2);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
cursor: pointer;
transition: all 0.2s;
transition: all var(--transition-fast);
}
.icon-picker__item:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.2);
background: var(--color-surface-3);
border-color: var(--color-surface-3);
transform: scale(1.05);
}
.icon-picker__item--active {
background: rgba(196, 97, 47, 0.2);
border-color: rgba(196, 97, 47, 0.5);
background: rgba(59, 130, 246, 0.1);
border-color: rgba(59, 130, 246, 0.4);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15);
}
/* Prompt guide */
.form-guide-btn {
background: transparent;
border: none;
color: #C4612F;
color: var(--color-primary);
font-size: 12px;
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
border-radius: var(--radius-sm);
margin-left: auto;
transition: all 0.2s;
transition: all var(--transition-fast);
}
.form-guide-btn:hover {
background: rgba(196, 97, 47, 0.1);
background: rgba(59, 130, 246, 0.06);
}
.form-guide {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
background: var(--color-surface-2);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
padding: 16px;
margin-bottom: 12px;
font-size: 13px;
line-height: 1.6;
color: var(--color-text);
}
.form-guide strong {
color: rgba(255, 255, 255, 0.9);
color: var(--color-text);
}
.form-guide ul {
margin: 8px 0;
padding-left: 20px;
color: rgba(255, 255, 255, 0.7);
color: var(--color-text-muted);
}
.form-guide__code {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 6px;
background: var(--color-surface-3);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
padding: 12px;
margin-top: 8px;
overflow-x: auto;
font-size: 12px;
line-height: 1.5;
color: rgba(255, 255, 255, 0.8);
color: var(--color-text);
}
/* Buttons */
.btn {
/* Buttons (modal-scoped overrides — only affect modal context) */
.modal .btn {
padding: 10px 20px;
border-radius: 6px;
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 500;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
transition: all var(--transition-fast);
border: 1px solid transparent;
}
.btn:disabled {
.modal .btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn--primary {
background: #C4612F;
.modal .btn--primary {
background: var(--color-primary);
color: white;
border-color: #C4612F;
border-color: var(--color-primary);
}
.btn--primary:hover:not(:disabled) {
background: #A94E22;
border-color: #A94E22;
.modal .btn--primary:hover:not(:disabled) {
background: var(--color-primary-hover);
border-color: var(--color-primary-hover);
}
.btn--secondary {
background: transparent;
color: rgba(255, 255, 255, 0.7);
border-color: rgba(255, 255, 255, 0.2);
.modal .btn--secondary {
background: var(--color-surface-2);
color: var(--color-text-muted);
border-color: var(--color-border);
}
.btn--secondary:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.3);
color: rgba(255, 255, 255, 0.9);
.modal .btn--secondary:hover:not(:disabled) {
background: var(--color-surface-3);
border-color: var(--color-surface-3);
color: var(--color-text);
}
/* Animations */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
transform: translateY(20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
/* Animations — fadeIn & slideUp defined in main animation section above */

View File

@@ -21,6 +21,15 @@ import { loadConfig, loadTheme, saveTheme } from "./lib/storage";
import { I18nContext, parseLocale, t } from "./lib/i18n";
import { renderScenarioIconById } from "./lib/scenarioIcons";
import type { Locale } from "./lib/i18n";
/** SVG 图标 ID → emoji 映射(用于纯文本场景,如系统消息) */
const ICON_ID_TO_EMOJI: Record<string, string> = {
sparkles: "✨", theater: "🎭", palette: "🎨", target: "🎯", film: "🎬",
"book-open": "📖", book: "📚", edit: "✏️", clipboard: "📋", "map-pin": "📍",
search: "🔍", lightbulb: "💡", briefcase: "💼", code: "💻", "graduation-cap": "🎓",
mic: "🎤", music: "🎵", globe: "🌐", heart: "❤️", shield: "🛡️",
zap: "⚡", coffee: "☕", tool: "🔧", "message-circle": "💬",
};
import type { Theme } from "./types";
import type { UserScenario } from "./lib/api/scenarios";
import "./App.css";
@@ -228,10 +237,14 @@ function AppContent() {
}
}
updateConfig(updates);
// 插入系统提示消息
const scenarioName = sc
? `${sc.icon} ${sc.nameKey ? tr(sc.nameKey) : sc.name}`
// 插入系统提示消息(自建情景的 icon 是 SVG ID需转为 emoji
const iconText = sc
? (ICON_ID_TO_EMOJI[sc.icon] || sc.icon)
: "";
const displayName = sc
? (sc.nameKey ? tr(sc.nameKey) : sc.name)
: scenarioId;
const scenarioName = iconText ? `${iconText} ${displayName}` : displayName;
setMessages(prev => [...prev, {
id: uuidv4(),
role: "system",
@@ -607,6 +620,7 @@ function AppContent() {
currentReply={currentReply}
connectionStatus={connectionStatus}
currentScenario={config.scenario}
allScenarios={allScenarios}
isProcessing={isProcessing}
isVADReady={isVADReady}
vadError={vadError ?? undefined}

View File

@@ -7,6 +7,7 @@
import { useEffect, useRef, useState, type ReactNode } from "react";
import { useI18n } from "../../lib/i18n";
import { scenarios } from "../../lib/scenarios";
import type { ExtendedScenario } from "../../hooks/useScenarios";
import type { ChatMessage } from "../../types";
import type { ConnectionStatus } from "../../lib/websocket";
@@ -15,6 +16,7 @@ interface ChatPanelProps {
currentReply?: string;
connectionStatus: ConnectionStatus;
currentScenario?: string;
allScenarios?: ExtendedScenario[];
isProcessing?: boolean;
isVADReady?: boolean;
vadError?: string | null;
@@ -43,6 +45,7 @@ export function ChatPanel({
currentReply,
connectionStatus,
currentScenario,
allScenarios,
isProcessing,
isVADReady,
vadError,
@@ -103,6 +106,10 @@ export function ChatPanel({
}
};
// 当前情景对象(优先从 allScenarios 查找,含自建情景;兜底用系统情景)
const scenarioList = allScenarios || scenarios;
const activeScenarioObj = scenarioList.find(sc => sc.id === activeScenario);
return (
<div className="chat-panel">
<div className="chat-panel__messages" ref={containerRef}>
@@ -111,11 +118,13 @@ export function ChatPanel({
<div className="chat-panel__scenario-hint">
<div className="scenario-hint-card">
<span className="scenario-hint-card__icon">
{scenarios.find(s => s.id === activeScenario)?.icon}
{activeScenarioObj?.icon}
</span>
<div className="scenario-hint-card__text">
<strong>{t(scenarios.find(s => s.id === activeScenario)?.nameKey || "")}</strong>
<p>{t(`scenario.${activeScenario}.hint`)}</p>
<strong>{activeScenarioObj ? (activeScenarioObj.nameKey ? t(activeScenarioObj.nameKey) : ('name' in activeScenarioObj ? activeScenarioObj.name : '')) : ""}</strong>
{activeScenarioObj && (activeScenarioObj.descKey || ('description' in activeScenarioObj && activeScenarioObj.description)) && (
<p>{activeScenarioObj.descKey ? t(activeScenarioObj.descKey) : ('description' in activeScenarioObj ? activeScenarioObj.description : '')}</p>
)}
</div>
</div>
</div>
@@ -124,7 +133,7 @@ export function ChatPanel({
{/* 空状态:欢迎信息 */}
{isEmpty && (
<div className="chat-panel__welcome">
<span className="chat-panel__welcome-icon">{scenarios.find(s => s.id === activeScenario)?.icon || <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>}</span>
<span className="chat-panel__welcome-icon">{activeScenarioObj?.icon || <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>}</span>
<p>{isConnected ? t("chat.welcome.prompt") : t("chat.empty.prompt")}</p>
<span className="chat-panel__welcome-hint">{isConnected ? t("chat.welcome.hint") : t("chat.empty.hint")}</span>

View File

@@ -155,9 +155,9 @@ export const enUS: TranslationMap = {
"auth.login": "Sign In",
"auth.register": "Sign Up",
"auth.username": "Username",
"auth.username.placeholder": "3-64 characters",
"auth.username.placeholder": "Enter username",
"auth.password": "Password",
"auth.password.placeholder": "8-72 characters",
"auth.password.placeholder": "Enter password",
"auth.submitting": "Please wait...",
"auth.noAccount": "Don't have an account?",
"auth.hasAccount": "Already have an account?",

View File

@@ -155,9 +155,9 @@ export const jaJP: TranslationMap = {
"auth.login": "ログイン",
"auth.register": "新規登録",
"auth.username": "ユーザー名",
"auth.username.placeholder": "3〜64文字",
"auth.username.placeholder": "ユーザー名を入力",
"auth.password": "パスワード",
"auth.password.placeholder": "8〜72文字",
"auth.password.placeholder": "パスワードを入力",
"auth.submitting": "お待ちください...",
"auth.noAccount": "アカウントをお持ちでないですか?",
"auth.hasAccount": "すでにアカウントをお持ちですか?",

View File

@@ -155,9 +155,9 @@ export const zhCN: TranslationMap = {
"auth.login": "登录",
"auth.register": "注册",
"auth.username": "用户名",
"auth.username.placeholder": "3-64 个字符",
"auth.username.placeholder": "请输入用户名",
"auth.password": "密码",
"auth.password.placeholder": "8-72 个字符",
"auth.password.placeholder": "请输入密码",
"auth.submitting": "请稍候...",
"auth.noAccount": "还没有账号?",
"auth.hasAccount": "已有账号?",