修正了进度展示
parent
5a34df3944
commit
f616cb3fc3
|
|
@ -1,106 +0,0 @@
|
||||||
# 音频处理功能部署说明
|
|
||||||
|
|
||||||
## 1. 安装Python依赖
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /Users/jiliu/工作/projects/imeeting/backend
|
|
||||||
source venv/bin/activate
|
|
||||||
pip install tinytag
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. 执行数据库迁移
|
|
||||||
|
|
||||||
### 2.1 创建用户日志表
|
|
||||||
```bash
|
|
||||||
mysql -u root -p imeeting < sql/migrations/create_user_logs_table.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 为audio_files表添加duration字段
|
|
||||||
```bash
|
|
||||||
mysql -u root -p imeeting < sql/migrations/add_duration_to_audio_files.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. 功能说明
|
|
||||||
|
|
||||||
### 3.1 用户日志表 (user_logs)
|
|
||||||
- 记录用户登录活动
|
|
||||||
- 字段:log_id, user_id, action_type, ip_address, user_agent, metadata, created_at
|
|
||||||
- 管理后台可查询用户最后登录时间
|
|
||||||
|
|
||||||
### 3.2 音频处理优化
|
|
||||||
**音频时长计算:**
|
|
||||||
- 使用TinyTag库读取音频文件时长
|
|
||||||
- 纯Python实现,无需外部依赖
|
|
||||||
- 支持mp3, m4a, mp4, wav, ogg, flac等常见格式
|
|
||||||
- 能处理特殊/损坏元数据的文件
|
|
||||||
- 时长保存到audio_files.duration字段(单位:秒)
|
|
||||||
- 时长读取功能位于`app/utils/audio_parser.py`
|
|
||||||
|
|
||||||
### 3.3 管理后台用户统计
|
|
||||||
- 排除没有会议的用户
|
|
||||||
- 显示字段:
|
|
||||||
- ID
|
|
||||||
- 用户名
|
|
||||||
- 姓名
|
|
||||||
- 注册时间
|
|
||||||
- 最新登录时间(从user_logs表查询)
|
|
||||||
- 会议数量
|
|
||||||
- 会议时长(从audio_files.duration汇总,格式:Xh Ym)
|
|
||||||
|
|
||||||
## 4. 代码变更清单
|
|
||||||
|
|
||||||
### 后端变更:
|
|
||||||
- `/backend/app/utils/audio_parser.py` - 新增音频时长解析工具(使用TinyTag)
|
|
||||||
- `/backend/app/api/endpoints/meetings.py` - 完整文件上传时调用audio_parser获取时长,修复Safari播放问题
|
|
||||||
- `/backend/app/api/endpoints/audio.py` - Stream上传完成时调用audio_parser获取时长
|
|
||||||
- `/backend/app/services/audio_service.py` - 增加duration参数
|
|
||||||
- `/backend/app/api/endpoints/auth.py` - 登录时记录日志
|
|
||||||
- `/backend/app/api/endpoints/admin_dashboard.py` - 更新用户统计查询
|
|
||||||
- `/backend/app/models/models.py` - 新增UserLog模型
|
|
||||||
|
|
||||||
### 前端变更:
|
|
||||||
- `/frontend/src/pages/HomePage.jsx` - 密码显示/隐藏切换
|
|
||||||
- `/frontend/src/pages/HomePage.css` - 密码切换按钮样式
|
|
||||||
- `/frontend/src/pages/AdminDashboard.jsx` - 用户列表增加会议时长列
|
|
||||||
- `/frontend/src/pages/MeetingPreview.jsx` - 新增转录标签页,修复Safari音频播放
|
|
||||||
- `/frontend/src/pages/MeetingDetails.jsx` - 修复Safari音频播放问题
|
|
||||||
|
|
||||||
### 数据库变更:
|
|
||||||
- `sql/migrations/create_user_logs_table.sql` - 创建用户日志表
|
|
||||||
- `sql/migrations/add_duration_to_audio_files.sql` - 添加音频时长字段
|
|
||||||
|
|
||||||
## 5. 测试检查清单
|
|
||||||
|
|
||||||
- [ ] 安装Python依赖成功
|
|
||||||
- [ ] 数据库迁移执行成功
|
|
||||||
- [ ] 用户登录后,user_logs表有记录
|
|
||||||
- [ ] 上传音频文件,Safari可以正常播放
|
|
||||||
- [ ] 上传音频后,audio_files.duration有正确的时长值
|
|
||||||
- [ ] 管理后台用户列表显示正确(排除无会议用户)
|
|
||||||
- [ ] 用户列表中会议时长统计正确
|
|
||||||
- [ ] 登录页面密码显示/隐藏按钮正常工作
|
|
||||||
- [ ] 会议预览页面音频播放正常(Safari/Chrome)
|
|
||||||
- [ ] 会议详情页面音频播放正常(Safari/Chrome)
|
|
||||||
|
|
||||||
## 6. 注意事项
|
|
||||||
|
|
||||||
1. **音频时长计算**:
|
|
||||||
- 使用TinyTag纯Python库
|
|
||||||
- 无需系统依赖,跨平台兼容
|
|
||||||
- 计算失败时duration为0,不影响其他功能
|
|
||||||
- 支持所有TinyTag支持的格式
|
|
||||||
|
|
||||||
2. **Safari音频播放兼容性**:
|
|
||||||
- 使用直接src属性而非source子元素
|
|
||||||
- preload="metadata"模式
|
|
||||||
- 后端支持HTTP Range请求
|
|
||||||
- 正确的MIME类型由后端自动设置
|
|
||||||
|
|
||||||
3. **用户日志**:
|
|
||||||
- 登录日志记录失败不影响登录流程
|
|
||||||
- IP地址优先从X-Forwarded-For获取(考虑代理场景)
|
|
||||||
|
|
||||||
4. **管理后台统计**:
|
|
||||||
- 使用INNER JOIN过滤无会议用户
|
|
||||||
- 时长汇总从audio_files.duration计算
|
|
||||||
- 最后登录时间从user_logs子查询获取
|
|
||||||
|
|
@ -1,167 +0,0 @@
|
||||||
# 提示词模版选择功能实现总结
|
|
||||||
|
|
||||||
## 功能概述
|
|
||||||
实现了用户在创建会议时可以选择使用的总结模版功能。支持两种场景:
|
|
||||||
1. 手动生成会议总结时选择模版
|
|
||||||
2. 上传音频文件时选择模版,自动总结时使用该模版
|
|
||||||
|
|
||||||
## 实现的功能点
|
|
||||||
|
|
||||||
### 1. 新增API接口 ✅
|
|
||||||
**文件**: `app/api/endpoints/prompts.py`
|
|
||||||
- 新增 `GET /prompts/active/{task_type}` 接口
|
|
||||||
- 功能:获取指定任务类型的所有启用状态的提示词模版
|
|
||||||
- 返回字段:id, name, is_default
|
|
||||||
- 按默认模版优先、创建时间倒序排列
|
|
||||||
|
|
||||||
### 2. 修改LLM服务 ✅
|
|
||||||
**文件**: `app/services/llm_service.py`
|
|
||||||
- 修改 `get_task_prompt()` 方法,增加 `prompt_id` 可选参数
|
|
||||||
- 逻辑:
|
|
||||||
- 如果指定 prompt_id,查询该ID对应的提示词(需验证task_type和is_active)
|
|
||||||
- 如果不指定,使用默认提示词(is_default=TRUE)
|
|
||||||
- 如果都查不到,返回代码中的默认值
|
|
||||||
|
|
||||||
### 3. 修改会议总结服务 ✅
|
|
||||||
**文件**: `app/services/async_meeting_service.py`
|
|
||||||
|
|
||||||
#### 3.1 修改任务创建
|
|
||||||
- `start_summary_generation()`: 增加 `prompt_id` 参数
|
|
||||||
- 将 prompt_id 存储到 Redis 和数据库
|
|
||||||
|
|
||||||
#### 3.2 修改任务处理
|
|
||||||
- `_process_task()`: 从 Redis 读取 prompt_id,传递给 `_build_prompt()`
|
|
||||||
- `_build_prompt()`: 增加 `prompt_id` 参数,传递给 `llm_service.get_task_prompt()`
|
|
||||||
- `_save_task_to_db()`: 增加 `prompt_id` 参数,存储到数据库
|
|
||||||
|
|
||||||
#### 3.3 修改自动总结监控
|
|
||||||
- `monitor_and_auto_summarize()`: 增加 `prompt_id` 参数
|
|
||||||
- 在转录完成后启动总结任务时,传递 prompt_id
|
|
||||||
|
|
||||||
### 4. 修改音频服务 ✅
|
|
||||||
**文件**: `app/services/audio_service.py`
|
|
||||||
- `handle_audio_upload()`: 增加 `prompt_id` 参数
|
|
||||||
- 将 prompt_id 传递给 `monitor_and_auto_summarize()`
|
|
||||||
|
|
||||||
### 5. 修改会议API接口 ✅
|
|
||||||
**文件**: `app/api/endpoints/meetings.py`
|
|
||||||
|
|
||||||
#### 5.1 手动生成总结
|
|
||||||
- `GenerateSummaryRequest` 模型:增加 `prompt_id` 字段
|
|
||||||
- `POST /meetings/{meeting_id}/generate-summary-async`: 传递 prompt_id 给服务层
|
|
||||||
|
|
||||||
#### 5.2 音频上传
|
|
||||||
- `POST /meetings/upload-audio`: 增加 `prompt_id` 表单参数
|
|
||||||
- 将 prompt_id 传递给 `handle_audio_upload()`
|
|
||||||
|
|
||||||
### 6. 数据库迁移 ✅
|
|
||||||
**文件**: `sql/add_prompt_id_to_llm_tasks.sql`
|
|
||||||
- 为 `llm_tasks` 表添加 `prompt_id` 列
|
|
||||||
- 类型:int(11)
|
|
||||||
- 可空:YES
|
|
||||||
- 默认值:NULL
|
|
||||||
- 索引:idx_prompt_id
|
|
||||||
|
|
||||||
## 数据流向
|
|
||||||
|
|
||||||
### 手动生成总结
|
|
||||||
```
|
|
||||||
前端 → POST /meetings/{id}/generate-summary-async (prompt_id)
|
|
||||||
→ async_meeting_service.start_summary_generation(prompt_id)
|
|
||||||
→ 存储到 Redis 和 DB (llm_tasks.prompt_id)
|
|
||||||
→ _process_task() 读取 prompt_id
|
|
||||||
→ _build_prompt(prompt_id)
|
|
||||||
→ llm_service.get_task_prompt('MEETING_TASK', prompt_id)
|
|
||||||
→ 获取指定模版或默认模版
|
|
||||||
```
|
|
||||||
|
|
||||||
### 音频上传自动总结
|
|
||||||
```
|
|
||||||
前端 → POST /meetings/upload-audio (prompt_id, auto_summarize=true)
|
|
||||||
→ handle_audio_upload(prompt_id)
|
|
||||||
→ transcription_service.start_transcription()
|
|
||||||
→ monitor_and_auto_summarize(prompt_id)
|
|
||||||
→ 等待转录完成
|
|
||||||
→ start_summary_generation(prompt_id)
|
|
||||||
→ (后续流程同手动生成总结)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 向后兼容性
|
|
||||||
|
|
||||||
所有新增的 `prompt_id` 参数都是可选的(Optional[int] = None),确保:
|
|
||||||
1. 不传递 prompt_id 时,自动使用默认模版
|
|
||||||
2. 现有代码无需修改即可正常工作
|
|
||||||
3. 数据库中 prompt_id 允许为 NULL
|
|
||||||
|
|
||||||
## 测试结果
|
|
||||||
|
|
||||||
执行 `test_prompt_id_feature.py` 测试脚本,所有测试通过:
|
|
||||||
- ✅ 获取启用的提示词列表 (6个模版)
|
|
||||||
- ✅ 通过prompt_id获取提示词内容
|
|
||||||
- ✅ 获取默认提示词(不指定prompt_id)
|
|
||||||
- ✅ 验证方法签名支持prompt_id参数
|
|
||||||
- ✅ 验证数据库schema包含prompt_id列
|
|
||||||
- ✅ 验证API端点定义正确
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### 1. 获取启用的会议任务模版列表
|
|
||||||
```bash
|
|
||||||
GET /api/prompts/active/MEETING_TASK
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
返回:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": "200",
|
|
||||||
"message": "获取启用模版列表成功",
|
|
||||||
"data": {
|
|
||||||
"prompts": [
|
|
||||||
{"id": 1, "name": "默认会议总结", "is_default": true},
|
|
||||||
{"id": 5, "name": "产品会议总结", "is_default": false}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 手动生成总结时指定模版
|
|
||||||
```bash
|
|
||||||
POST /api/meetings/123/generate-summary-async
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"user_prompt": "重点关注技术讨论",
|
|
||||||
"prompt_id": 5
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 上传音频时指定模版
|
|
||||||
```bash
|
|
||||||
POST /api/meetings/upload-audio
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
Content-Type: multipart/form-data
|
|
||||||
|
|
||||||
- audio_file: <file>
|
|
||||||
- meeting_id: 123
|
|
||||||
- auto_summarize: true
|
|
||||||
- prompt_id: 5
|
|
||||||
```
|
|
||||||
|
|
||||||
## 文件变更列表
|
|
||||||
|
|
||||||
1. `app/api/endpoints/prompts.py` - 新增API接口
|
|
||||||
2. `app/api/endpoints/meetings.py` - 修改两个端点
|
|
||||||
3. `app/services/llm_service.py` - 修改get_task_prompt方法
|
|
||||||
4. `app/services/async_meeting_service.py` - 修改4个方法
|
|
||||||
5. `app/services/audio_service.py` - 修改handle_audio_upload方法
|
|
||||||
6. `sql/add_prompt_id_to_llm_tasks.sql` - 数据库迁移脚本
|
|
||||||
7. `test_prompt_id_feature.py` - 测试脚本
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. prompt_id 会与 task_type 一起验证,防止使用错误类型的模版
|
|
||||||
2. 如果指定的 prompt_id 不存在或未启用,会自动使用默认模版
|
|
||||||
3. 历史任务记录保留 prompt_id,即使对应的提示词被删除
|
|
||||||
4. Redis 中 prompt_id 存储为字符串,使用时需转换为 int
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
# 知识库提示词模版选择功能实现总结
|
|
||||||
|
|
||||||
## 功能概述
|
|
||||||
为知识库生成功能添加了提示词模版选择支持,用户在创建知识库时可以选择使用的生成模版。
|
|
||||||
|
|
||||||
## 实现的功能点
|
|
||||||
|
|
||||||
### 1. 修改请求模型 ✅
|
|
||||||
**文件**: `app/models/models.py`
|
|
||||||
- `CreateKnowledgeBaseRequest` 模型增加 `prompt_id` 字段
|
|
||||||
- 类型:Optional[int] = None
|
|
||||||
- 不指定时使用默认模版
|
|
||||||
|
|
||||||
### 2. 修改知识库异步服务 ✅
|
|
||||||
**文件**: `app/services/async_knowledge_base_service.py`
|
|
||||||
|
|
||||||
#### 2.1 修改任务创建
|
|
||||||
- `start_generation()`: 增加 `prompt_id` 参数
|
|
||||||
- 将 prompt_id 存储到 Redis 和数据库
|
|
||||||
- 支持通过 cursor 参数直接插入(事务场景)
|
|
||||||
|
|
||||||
#### 2.2 修改任务处理
|
|
||||||
- `_process_task()`: 从 Redis 读取 prompt_id,传递给 `_build_prompt()`
|
|
||||||
- 处理空字符串情况,转换为 None
|
|
||||||
|
|
||||||
#### 2.3 修改提示词构建
|
|
||||||
- `_build_prompt()`: 增加 `prompt_id` 参数
|
|
||||||
- 调用 `llm_service.get_task_prompt('KNOWLEDGE_TASK', prompt_id=prompt_id)`
|
|
||||||
- 支持获取指定模版或默认模版
|
|
||||||
|
|
||||||
#### 2.4 修改数据库保存
|
|
||||||
- `_save_task_to_db()`: 增加 `prompt_id` 参数
|
|
||||||
- 插入时包含 prompt_id 字段
|
|
||||||
|
|
||||||
### 3. 修改API接口 ✅
|
|
||||||
**文件**: `app/api/endpoints/knowledge_base.py`
|
|
||||||
- `create_knowledge_base`: 从请求中获取 `prompt_id`
|
|
||||||
- 调用 `async_kb_service.start_generation()` 时传递 `prompt_id`
|
|
||||||
|
|
||||||
### 4. 数据库字段 ✅
|
|
||||||
- `knowledge_base_tasks` 表已包含 `prompt_id` 列(用户已添加)
|
|
||||||
- 类型:int
|
|
||||||
- 可空:NO(默认值:0)
|
|
||||||
|
|
||||||
## 数据流向
|
|
||||||
|
|
||||||
```
|
|
||||||
前端 → POST /api/knowledge-bases (prompt_id)
|
|
||||||
→ CreateKnowledgeBaseRequest (prompt_id)
|
|
||||||
→ async_kb_service.start_generation(prompt_id)
|
|
||||||
→ 存储到 Redis 和 DB (knowledge_base_tasks.prompt_id)
|
|
||||||
→ _process_task() 读取 prompt_id
|
|
||||||
→ _build_prompt(prompt_id)
|
|
||||||
→ llm_service.get_task_prompt('KNOWLEDGE_TASK', prompt_id)
|
|
||||||
→ 获取指定模版或默认模版
|
|
||||||
```
|
|
||||||
|
|
||||||
## 向后兼容性
|
|
||||||
|
|
||||||
所有新增的 `prompt_id` 参数都是可选的(Optional[int] = None),确保:
|
|
||||||
1. 不传递 prompt_id 时,自动使用默认模版
|
|
||||||
2. 现有代码无需修改即可正常工作
|
|
||||||
3. 数据库中 prompt_id 有默认值 0
|
|
||||||
|
|
||||||
## 测试结果
|
|
||||||
|
|
||||||
执行 `test_kb_prompt_id_feature.py` 测试脚本,所有测试通过:
|
|
||||||
- ✅ 获取启用的知识库提示词列表 (3个模版)
|
|
||||||
- ✅ 通过prompt_id获取提示词内容
|
|
||||||
- ✅ 获取默认提示词(不指定prompt_id)
|
|
||||||
- ✅ 验证方法签名支持prompt_id参数
|
|
||||||
- ✅ 验证数据库schema包含prompt_id列
|
|
||||||
- ✅ 验证API模型定义正确
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### 1. 获取启用的知识库任务模版列表
|
|
||||||
```bash
|
|
||||||
GET /api/prompts/active/KNOWLEDGE_TASK
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
```
|
|
||||||
|
|
||||||
返回:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"code": "200",
|
|
||||||
"message": "获取启用模版列表成功",
|
|
||||||
"data": {
|
|
||||||
"prompts": [
|
|
||||||
{"id": 2, "name": "默认知识库生成", "is_default": true},
|
|
||||||
{"id": 13, "name": "分析总结模版", "is_default": false}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 创建知识库时指定模版
|
|
||||||
```bash
|
|
||||||
POST /api/knowledge-bases
|
|
||||||
Authorization: Bearer <token>
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"title": "产品会议知识库",
|
|
||||||
"is_shared": false,
|
|
||||||
"user_prompt": "重点提取产品功能相关信息",
|
|
||||||
"source_meeting_ids": "1,2,3",
|
|
||||||
"tags": "产品,功能",
|
|
||||||
"prompt_id": 13
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 文件变更列表
|
|
||||||
|
|
||||||
1. `app/models/models.py` - 修改CreateKnowledgeBaseRequest模型
|
|
||||||
2. `app/services/async_knowledge_base_service.py` - 修改5个方法
|
|
||||||
3. `app/api/endpoints/knowledge_base.py` - 修改create_knowledge_base端点
|
|
||||||
4. `test_kb_prompt_id_feature.py` - 测试脚本
|
|
||||||
|
|
||||||
## 与会议总结功能的一致性
|
|
||||||
|
|
||||||
知识库的实现与会议总结功能保持一致:
|
|
||||||
- 相同的prompt_id传递机制
|
|
||||||
- 相同的Redis存储格式(字符串)
|
|
||||||
- 相同的数据库字段类型
|
|
||||||
- 相同的向后兼容策略
|
|
||||||
- 相同的验证逻辑(task_type + is_active)
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. prompt_id 会与 task_type='KNOWLEDGE_TASK' 一起验证
|
|
||||||
2. 如果指定的 prompt_id 不存在或未启用,会自动使用默认模版
|
|
||||||
3. 历史任务记录保留 prompt_id,即使对应的提示词被删除
|
|
||||||
4. Redis 中 prompt_id 存储为字符串,使用时需转换为 int
|
|
||||||
5. 数据库 prompt_id 默认值为 0(表示未指定)
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
知识库提示词模版选择功能已完全实现并通过测试,与会议总结功能保持一致的设计和实现方式。用户现在可以在创建知识库时选择不同的生成模版,以满足不同场景的需求。
|
|
||||||
|
|
@ -1,126 +1,12 @@
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from app.core.auth import get_current_admin_user, get_current_user
|
from app.core.auth import get_current_admin_user, get_current_user
|
||||||
from app.core.config import LLM_CONFIG, DEFAULT_RESET_PASSWORD, MAX_FILE_SIZE, VOICEPRINT_CONFIG, TIMELINE_PAGESIZE
|
|
||||||
from app.core.response import create_api_response
|
from app.core.response import create_api_response
|
||||||
from app.core.database import get_db_connection
|
from app.core.database import get_db_connection
|
||||||
from app.models.models import MenuInfo, MenuListResponse, RolePermissionInfo, UpdateRolePermissionsRequest, RoleInfo
|
from app.models.models import MenuInfo, MenuListResponse, RolePermissionInfo, UpdateRolePermissionsRequest, RoleInfo
|
||||||
from pydantic import BaseModel
|
|
||||||
from typing import List
|
from typing import List
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
# 配置文件路径
|
|
||||||
CONFIG_FILE = Path(__file__).parent.parent.parent.parent / "config" / "system_config.json"
|
|
||||||
|
|
||||||
class SystemConfigModel(BaseModel):
|
|
||||||
model_name: str
|
|
||||||
template_text: str
|
|
||||||
DEFAULT_RESET_PASSWORD: str
|
|
||||||
MAX_FILE_SIZE: int # 字节为单位
|
|
||||||
TIMELINE_PAGESIZE: int # 分页数量
|
|
||||||
|
|
||||||
def load_config_from_file():
|
|
||||||
"""从文件加载配置,如果文件不存在则返回默认配置"""
|
|
||||||
try:
|
|
||||||
if CONFIG_FILE.exists():
|
|
||||||
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
|
|
||||||
return json.load(f)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 返回默认配置
|
|
||||||
return {
|
|
||||||
'model_name': LLM_CONFIG['model_name'],
|
|
||||||
'template_text': VOICEPRINT_CONFIG['template_text'],
|
|
||||||
'DEFAULT_RESET_PASSWORD': DEFAULT_RESET_PASSWORD,
|
|
||||||
'MAX_FILE_SIZE': MAX_FILE_SIZE,
|
|
||||||
'TIMELINE_PAGESIZE': TIMELINE_PAGESIZE
|
|
||||||
}
|
|
||||||
|
|
||||||
def save_config_to_file(config_data):
|
|
||||||
"""将配置保存到文件"""
|
|
||||||
try:
|
|
||||||
CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(config_data, f, ensure_ascii=False, indent=2)
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"保存配置文件失败: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
@router.get("/admin/system-config")
|
|
||||||
async def get_system_config(current_user=Depends(get_current_user)):
|
|
||||||
"""
|
|
||||||
获取系统配置
|
|
||||||
普通用户也可以获取
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
config = load_config_from_file()
|
|
||||||
response_data = {
|
|
||||||
'model_name': config.get('model_name', LLM_CONFIG['model_name']),
|
|
||||||
'template_text': config.get('template_text', VOICEPRINT_CONFIG['template_text']),
|
|
||||||
'DEFAULT_RESET_PASSWORD': config.get('DEFAULT_RESET_PASSWORD', DEFAULT_RESET_PASSWORD),
|
|
||||||
'MAX_FILE_SIZE': config.get('MAX_FILE_SIZE', MAX_FILE_SIZE),
|
|
||||||
'TIMELINE_PAGESIZE': config.get('TIMELINE_PAGESIZE', TIMELINE_PAGESIZE),
|
|
||||||
}
|
|
||||||
return create_api_response(code="200", message="配置获取成功", data=response_data)
|
|
||||||
except Exception as e:
|
|
||||||
return create_api_response(code="500", message=f"获取配置失败: {str(e)}")
|
|
||||||
|
|
||||||
@router.put("/admin/system-config")
|
|
||||||
async def update_system_config(
|
|
||||||
config: SystemConfigModel,
|
|
||||||
current_user=Depends(get_current_admin_user)
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
更新系统配置
|
|
||||||
只有管理员才能访问
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
config_data = {
|
|
||||||
'model_name': config.model_name,
|
|
||||||
'template_text': config.template_text,
|
|
||||||
'DEFAULT_RESET_PASSWORD': config.DEFAULT_RESET_PASSWORD,
|
|
||||||
'MAX_FILE_SIZE': config.MAX_FILE_SIZE,
|
|
||||||
'TIMELINE_PAGESIZE': config.TIMELINE_PAGESIZE
|
|
||||||
}
|
|
||||||
|
|
||||||
if not save_config_to_file(config_data):
|
|
||||||
return create_api_response(code="500", message="配置保存到文件失败")
|
|
||||||
|
|
||||||
# 更新运行时配置
|
|
||||||
LLM_CONFIG['model_name'] = config.model_name
|
|
||||||
VOICEPRINT_CONFIG['template_text'] = config.template_text
|
|
||||||
import app.core.config as config_module
|
|
||||||
config_module.DEFAULT_RESET_PASSWORD = config.DEFAULT_RESET_PASSWORD
|
|
||||||
config_module.MAX_FILE_SIZE = config.MAX_FILE_SIZE
|
|
||||||
config_module.TIMELINE_PAGESIZE = config.TIMELINE_PAGESIZE
|
|
||||||
|
|
||||||
return create_api_response(
|
|
||||||
code="200",
|
|
||||||
message="配置更新成功,重启服务后完全生效",
|
|
||||||
data=config_data
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
return create_api_response(code="500", message=f"更新配置失败: {str(e)}")
|
|
||||||
|
|
||||||
# 在应用启动时加载配置
|
|
||||||
def load_system_config():
|
|
||||||
"""在应用启动时调用,加载保存的配置"""
|
|
||||||
try:
|
|
||||||
config = load_config_from_file()
|
|
||||||
LLM_CONFIG['model_name'] = config.get('model_name', LLM_CONFIG['model_name'])
|
|
||||||
VOICEPRINT_CONFIG['template_text'] = config.get('template_text', VOICEPRINT_CONFIG['template_text'])
|
|
||||||
import app.core.config as config_module
|
|
||||||
config_module.DEFAULT_RESET_PASSWORD = config.get('DEFAULT_RESET_PASSWORD', DEFAULT_RESET_PASSWORD)
|
|
||||||
config_module.MAX_FILE_SIZE = config.get('MAX_FILE_SIZE', MAX_FILE_SIZE)
|
|
||||||
config_module.TIMELINE_PAGESIZE = config.get('TIMELINE_PAGESIZE', TIMELINE_PAGESIZE)
|
|
||||||
print(f"系统配置加载成功: model={config.get('model_name')}, pagesize={config.get('TIMELINE_PAGESIZE')}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"加载系统配置失败,使用默认配置: {e}")
|
|
||||||
|
|
||||||
# ========== 菜单权限管理接口 ==========
|
# ========== 菜单权限管理接口 ==========
|
||||||
|
|
||||||
@router.get("/admin/menus")
|
@router.get("/admin/menus")
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ TEMP_UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# 配置常量
|
# 配置常量
|
||||||
MAX_CHUNK_SIZE = 2 * 1024 * 1024 # 2MB per chunk
|
MAX_CHUNK_SIZE = 2 * 1024 * 1024 # 2MB per chunk
|
||||||
MAX_TOTAL_SIZE = 500 * 1024 * 1024 # 500MB total
|
MAX_TOTAL_SIZE = 500 * 1024 * 1024 # 500MB total (流式上传不受系统参数控制)
|
||||||
MAX_DURATION = 3600 # 1 hour max recording
|
MAX_DURATION = 3600 # 1 hour max recording
|
||||||
SESSION_EXPIRE_HOURS = 1 # 会话1小时后过期
|
SESSION_EXPIRE_HOURS = 1 # 会话1小时后过期
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ async def get_dict_data_by_code(dict_type: str, dict_code: str):
|
||||||
query = """
|
query = """
|
||||||
SELECT id, dict_type, dict_code, parent_code, tree_path,
|
SELECT id, dict_type, dict_code, parent_code, tree_path,
|
||||||
label_cn, label_en, sort_order, extension_attr,
|
label_cn, label_en, sort_order, extension_attr,
|
||||||
is_default, status, create_time
|
is_default, status, create_time, update_time
|
||||||
FROM dict_data
|
FROM dict_data
|
||||||
WHERE dict_type = %s AND dict_code = %s
|
WHERE dict_type = %s AND dict_code = %s
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
|
|
@ -211,6 +211,17 @@ async def create_dict_data(
|
||||||
创建码表数据(仅管理员)
|
创建码表数据(仅管理员)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# 验证extension_attr的JSON格式
|
||||||
|
if request.extension_attr:
|
||||||
|
try:
|
||||||
|
# 尝试序列化,验证是否为有效的JSON对象
|
||||||
|
json.dumps(request.extension_attr)
|
||||||
|
except (TypeError, ValueError) as e:
|
||||||
|
return create_api_response(
|
||||||
|
code="400",
|
||||||
|
message=f"扩展属性格式错误,必须是有效的JSON对象: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
with get_db_connection() as conn:
|
with get_db_connection() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
|
@ -233,7 +244,7 @@ async def create_dict_data(
|
||||||
sort_order, extension_attr, is_default, status
|
sort_order, extension_attr, is_default, status
|
||||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
"""
|
"""
|
||||||
extension_json = json.dumps(request.extension_attr) if request.extension_attr else None
|
extension_json = json.dumps(request.extension_attr, ensure_ascii=False) if request.extension_attr else None
|
||||||
|
|
||||||
cursor.execute(query, (
|
cursor.execute(query, (
|
||||||
request.dict_type,
|
request.dict_type,
|
||||||
|
|
@ -274,6 +285,17 @@ async def update_dict_data(
|
||||||
更新码表数据(仅管理员)
|
更新码表数据(仅管理员)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# 验证extension_attr的JSON格式
|
||||||
|
if request.extension_attr is not None:
|
||||||
|
try:
|
||||||
|
# 尝试序列化,验证是否为有效的JSON对象
|
||||||
|
json.dumps(request.extension_attr)
|
||||||
|
except (TypeError, ValueError) as e:
|
||||||
|
return create_api_response(
|
||||||
|
code="400",
|
||||||
|
message=f"扩展属性格式错误,必须是有效的JSON对象: {str(e)}"
|
||||||
|
)
|
||||||
|
|
||||||
with get_db_connection() as conn:
|
with get_db_connection() as conn:
|
||||||
cursor = conn.cursor(dictionary=True)
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
|
||||||
|
|
@ -309,7 +331,7 @@ async def update_dict_data(
|
||||||
|
|
||||||
if request.extension_attr is not None:
|
if request.extension_attr is not None:
|
||||||
update_fields.append("extension_attr = %s")
|
update_fields.append("extension_attr = %s")
|
||||||
params.append(json.dumps(request.extension_attr))
|
params.append(json.dumps(request.extension_attr, ensure_ascii=False))
|
||||||
|
|
||||||
if request.is_default is not None:
|
if request.is_default is not None:
|
||||||
update_fields.append("is_default = %s")
|
update_fields.append("is_default = %s")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from app.core.database import get_db_connection
|
||||||
|
from app.core.auth import get_current_admin_user
|
||||||
|
from app.core.response import create_api_response
|
||||||
|
from app.core.config import QWEN_API_KEY
|
||||||
|
from app.services.system_config_service import SystemConfigService
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional, List
|
||||||
|
import json
|
||||||
|
import dashscope
|
||||||
|
from dashscope.audio.asr import VocabularyService
|
||||||
|
from datetime import datetime
|
||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
class HotWordItem(BaseModel):
|
||||||
|
id: int
|
||||||
|
text: str
|
||||||
|
weight: int
|
||||||
|
lang: str
|
||||||
|
status: int
|
||||||
|
create_time: datetime
|
||||||
|
update_time: datetime
|
||||||
|
|
||||||
|
class CreateHotWordRequest(BaseModel):
|
||||||
|
text: str
|
||||||
|
weight: int = 4
|
||||||
|
lang: str = "zh"
|
||||||
|
status: int = 1
|
||||||
|
|
||||||
|
class UpdateHotWordRequest(BaseModel):
|
||||||
|
text: Optional[str] = None
|
||||||
|
weight: Optional[int] = None
|
||||||
|
lang: Optional[str] = None
|
||||||
|
status: Optional[int] = None
|
||||||
|
|
||||||
|
@router.get("/admin/hot-words", response_model=dict)
|
||||||
|
async def list_hot_words(current_user: dict = Depends(get_current_admin_user)):
|
||||||
|
"""获取热词列表"""
|
||||||
|
try:
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
cursor.execute("SELECT * FROM hot_words ORDER BY update_time DESC")
|
||||||
|
items = cursor.fetchall()
|
||||||
|
cursor.close()
|
||||||
|
return create_api_response(code="200", message="获取成功", data=items)
|
||||||
|
except Exception as e:
|
||||||
|
return create_api_response(code="500", message=f"获取失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.post("/admin/hot-words", response_model=dict)
|
||||||
|
async def create_hot_word(request: CreateHotWordRequest, current_user: dict = Depends(get_current_admin_user)):
|
||||||
|
"""创建热词"""
|
||||||
|
try:
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
query = "INSERT INTO hot_words (text, weight, lang, status) VALUES (%s, %s, %s, %s)"
|
||||||
|
cursor.execute(query, (request.text, request.weight, request.lang, request.status))
|
||||||
|
new_id = cursor.lastrowid
|
||||||
|
conn.commit()
|
||||||
|
cursor.close()
|
||||||
|
return create_api_response(code="200", message="创建成功", data={"id": new_id})
|
||||||
|
except Exception as e:
|
||||||
|
return create_api_response(code="500", message=f"创建失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.put("/admin/hot-words/{id}", response_model=dict)
|
||||||
|
async def update_hot_word(id: int, request: UpdateHotWordRequest, current_user: dict = Depends(get_current_admin_user)):
|
||||||
|
"""更新热词"""
|
||||||
|
try:
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
update_fields = []
|
||||||
|
params = []
|
||||||
|
if request.text is not None:
|
||||||
|
update_fields.append("text = %s")
|
||||||
|
params.append(request.text)
|
||||||
|
if request.weight is not None:
|
||||||
|
update_fields.append("weight = %s")
|
||||||
|
params.append(request.weight)
|
||||||
|
if request.lang is not None:
|
||||||
|
update_fields.append("lang = %s")
|
||||||
|
params.append(request.lang)
|
||||||
|
if request.status is not None:
|
||||||
|
update_fields.append("status = %s")
|
||||||
|
params.append(request.status)
|
||||||
|
|
||||||
|
if not update_fields:
|
||||||
|
return create_api_response(code="400", message="无更新内容")
|
||||||
|
|
||||||
|
query = f"UPDATE hot_words SET {', '.join(update_fields)} WHERE id = %s"
|
||||||
|
params.append(id)
|
||||||
|
cursor.execute(query, tuple(params))
|
||||||
|
conn.commit()
|
||||||
|
cursor.close()
|
||||||
|
return create_api_response(code="200", message="更新成功")
|
||||||
|
except Exception as e:
|
||||||
|
return create_api_response(code="500", message=f"更新失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.delete("/admin/hot-words/{id}", response_model=dict)
|
||||||
|
async def delete_hot_word(id: int, current_user: dict = Depends(get_current_admin_user)):
|
||||||
|
"""删除热词"""
|
||||||
|
try:
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("DELETE FROM hot_words WHERE id = %s", (id,))
|
||||||
|
conn.commit()
|
||||||
|
cursor.close()
|
||||||
|
return create_api_response(code="200", message="删除成功")
|
||||||
|
except Exception as e:
|
||||||
|
return create_api_response(code="500", message=f"删除失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.post("/admin/hot-words/sync", response_model=dict)
|
||||||
|
async def sync_hot_words(current_user: dict = Depends(get_current_admin_user)):
|
||||||
|
"""同步热词到阿里云 DashScope"""
|
||||||
|
try:
|
||||||
|
dashscope.api_key = QWEN_API_KEY
|
||||||
|
|
||||||
|
# 1. 获取所有启用的热词
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
cursor.execute("SELECT text, weight, lang FROM hot_words WHERE status = 1")
|
||||||
|
hot_words = cursor.fetchall()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
# 2. 获取现有的 vocabulary_id
|
||||||
|
existing_vocab_id = SystemConfigService.get_asr_vocabulary_id()
|
||||||
|
|
||||||
|
# 构建热词列表
|
||||||
|
vocabulary_list = [{"text": hw['text'], "weight": hw['weight'], "lang": hw['lang']} for hw in hot_words]
|
||||||
|
|
||||||
|
if not vocabulary_list:
|
||||||
|
return create_api_response(code="400", message="没有启用的热词可同步")
|
||||||
|
|
||||||
|
# 3. 调用阿里云 API
|
||||||
|
service = VocabularyService()
|
||||||
|
vocab_id = existing_vocab_id
|
||||||
|
|
||||||
|
try:
|
||||||
|
if existing_vocab_id:
|
||||||
|
# 尝试更新现有的热词表
|
||||||
|
try:
|
||||||
|
service.update_vocabulary(
|
||||||
|
vocabulary_id=existing_vocab_id,
|
||||||
|
vocabulary=vocabulary_list
|
||||||
|
)
|
||||||
|
# 更新成功,保持原有ID
|
||||||
|
except Exception as update_error:
|
||||||
|
# 如果更新失败(如资源不存在),尝试创建新的
|
||||||
|
print(f"Update vocabulary failed: {update_error}, trying to create new one.")
|
||||||
|
existing_vocab_id = None # 重置,触发创建逻辑
|
||||||
|
|
||||||
|
if not existing_vocab_id:
|
||||||
|
# 创建新的热词表
|
||||||
|
vocab_id = service.create_vocabulary(
|
||||||
|
prefix='imeeting',
|
||||||
|
target_model='paraformer-v2',
|
||||||
|
vocabulary=vocabulary_list
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as api_error:
|
||||||
|
return create_api_response(code="500", message=f"同步到阿里云失败: {str(api_error)}")
|
||||||
|
|
||||||
|
# 4. 更新数据库中的 vocabulary_id
|
||||||
|
if vocab_id:
|
||||||
|
SystemConfigService.set_config(
|
||||||
|
SystemConfigService.ASR_VOCABULARY_ID,
|
||||||
|
vocab_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return create_api_response(code="200", message="同步成功", data={"vocabulary_id": vocab_id})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return create_api_response(code="500", message=f"同步异常: {str(e)}")
|
||||||
|
|
@ -8,6 +8,7 @@ from app.services.llm_service import LLMService
|
||||||
from app.services.async_transcription_service import AsyncTranscriptionService
|
from app.services.async_transcription_service import AsyncTranscriptionService
|
||||||
from app.services.async_meeting_service import async_meeting_service
|
from app.services.async_meeting_service import async_meeting_service
|
||||||
from app.services.audio_service import handle_audio_upload
|
from app.services.audio_service import handle_audio_upload
|
||||||
|
from app.services.system_config_service import SystemConfigService
|
||||||
from app.utils.audio_parser import get_audio_duration
|
from app.utils.audio_parser import get_audio_duration
|
||||||
from app.core.auth import get_current_user, get_optional_current_user
|
from app.core.auth import get_current_user, get_optional_current_user
|
||||||
from app.core.response import create_api_response
|
from app.core.response import create_api_response
|
||||||
|
|
@ -143,11 +144,9 @@ def get_meetings(
|
||||||
tags: Optional[str] = None,
|
tags: Optional[str] = None,
|
||||||
filter_type: str = "all"
|
filter_type: str = "all"
|
||||||
):
|
):
|
||||||
from app.core.config import TIMELINE_PAGESIZE
|
|
||||||
|
|
||||||
# 使用配置的默认页面大小
|
# 使用配置的默认页面大小
|
||||||
if page_size is None:
|
if page_size is None:
|
||||||
page_size = TIMELINE_PAGESIZE
|
page_size = SystemConfigService.get_timeline_pagesize(default=10)
|
||||||
|
|
||||||
with get_db_connection() as connection:
|
with get_db_connection() as connection:
|
||||||
cursor = connection.cursor(dictionary=True)
|
cursor = connection.cursor(dictionary=True)
|
||||||
|
|
@ -486,8 +485,14 @@ async def upload_audio(
|
||||||
"""
|
"""
|
||||||
auto_summarize_bool = auto_summarize.lower() in ("true", "1", "yes")
|
auto_summarize_bool = auto_summarize.lower() in ("true", "1", "yes")
|
||||||
|
|
||||||
# 打印接收到的 prompt_id
|
# 0. 如果没有传入 prompt_id,尝试获取默认模版ID
|
||||||
print(f"[Upload Audio] Meeting ID: {meeting_id}, Received prompt_id: {prompt_id}, Type: {type(prompt_id)}, Auto-summarize: {auto_summarize_bool}")
|
if prompt_id is None:
|
||||||
|
with get_db_connection() as connection:
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT id FROM prompts WHERE task_type = 'MEETING_TASK' AND is_default = TRUE AND is_active = TRUE LIMIT 1"
|
||||||
|
)
|
||||||
|
prompt_id = cursor.fetchone()[0]
|
||||||
|
|
||||||
# 1. 文件类型验证
|
# 1. 文件类型验证
|
||||||
file_extension = os.path.splitext(audio_file.filename)[1].lower()
|
file_extension = os.path.splitext(audio_file.filename)[1].lower()
|
||||||
|
|
@ -498,7 +503,7 @@ async def upload_audio(
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. 文件大小验证
|
# 2. 文件大小验证
|
||||||
max_file_size = getattr(config_module, 'MAX_FILE_SIZE', 100 * 1024 * 1024)
|
max_file_size = SystemConfigService.get_max_audio_size(default=100) * 1024 * 1024 # MB转字节
|
||||||
if audio_file.size > max_file_size:
|
if audio_file.size > max_file_size:
|
||||||
return create_api_response(
|
return create_api_response(
|
||||||
code="400",
|
code="400",
|
||||||
|
|
@ -987,6 +992,7 @@ def get_meeting_preview_data(meeting_id: int):
|
||||||
- 200: 会议已完成(summary已生成)
|
- 200: 会议已完成(summary已生成)
|
||||||
- 400: 会议处理中(转译或总结阶段)
|
- 400: 会议处理中(转译或总结阶段)
|
||||||
- 503: 处理失败(转译或总结失败)
|
- 503: 处理失败(转译或总结失败)
|
||||||
|
- 504: 数据异常(流程完成但summary未生成)
|
||||||
- 404: 会议不存在
|
- 404: 会议不存在
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
|
@ -1049,7 +1055,19 @@ def get_meeting_preview_data(meeting_id: int):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# 情况3: 全部完成 → 返回200,提供完整预览数据
|
# 情况3: 全部完成但Summary缺失 → 返回504
|
||||||
|
if overall_status == "completed" and not meeting['summary']:
|
||||||
|
return create_api_response(
|
||||||
|
code="504",
|
||||||
|
message="处理已完成,AI总结尚未同步,请稍后重试",
|
||||||
|
data={
|
||||||
|
"meeting_id": meeting_id,
|
||||||
|
"title": meeting['title'],
|
||||||
|
"processing_status": progress_info
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 情况4: 全部完成 → 返回200,提供完整预览数据
|
||||||
if overall_status == "completed" and meeting['summary']:
|
if overall_status == "completed" and meeting['summary']:
|
||||||
# 获取参会人员信息
|
# 获取参会人员信息
|
||||||
with get_db_connection() as connection:
|
with get_db_connection() as connection:
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from app.models.models import UserInfo, PasswordChangeRequest, UserListResponse,
|
||||||
from app.core.database import get_db_connection
|
from app.core.database import get_db_connection
|
||||||
from app.core.auth import get_current_user
|
from app.core.auth import get_current_user
|
||||||
from app.core.response import create_api_response
|
from app.core.response import create_api_response
|
||||||
|
from app.services.system_config_service import SystemConfigService
|
||||||
import app.core.config as config_module
|
import app.core.config as config_module
|
||||||
import hashlib
|
import hashlib
|
||||||
import datetime
|
import datetime
|
||||||
|
|
@ -46,7 +47,7 @@ def create_user(request: CreateUserRequest, current_user: dict = Depends(get_cur
|
||||||
if cursor.fetchone():
|
if cursor.fetchone():
|
||||||
return create_api_response(code="400", message="用户名已存在")
|
return create_api_response(code="400", message="用户名已存在")
|
||||||
|
|
||||||
password = request.password if request.password else config_module.DEFAULT_RESET_PASSWORD
|
password = request.password if request.password else SystemConfigService.get_default_reset_password()
|
||||||
hashed_password = hash_password(password)
|
hashed_password = hash_password(password)
|
||||||
|
|
||||||
query = "INSERT INTO users (username, password_hash, caption, email, role_id, created_at) VALUES (%s, %s, %s, %s, %s, %s)"
|
query = "INSERT INTO users (username, password_hash, caption, email, role_id, created_at) VALUES (%s, %s, %s, %s, %s, %s)"
|
||||||
|
|
@ -138,7 +139,7 @@ def reset_password(user_id: int, current_user: dict = Depends(get_current_user))
|
||||||
if not cursor.fetchone():
|
if not cursor.fetchone():
|
||||||
return create_api_response(code="404", message="用户不存在")
|
return create_api_response(code="404", message="用户不存在")
|
||||||
|
|
||||||
hashed_password = hash_password(config_module.DEFAULT_RESET_PASSWORD)
|
hashed_password = hash_password(SystemConfigService.get_default_reset_password())
|
||||||
|
|
||||||
query = "UPDATE users SET password_hash = %s WHERE user_id = %s"
|
query = "UPDATE users SET password_hash = %s WHERE user_id = %s"
|
||||||
cursor.execute(query, (hashed_password, user_id))
|
cursor.execute(query, (hashed_password, user_id))
|
||||||
|
|
@ -162,7 +163,7 @@ def get_all_users(
|
||||||
count_params = []
|
count_params = []
|
||||||
|
|
||||||
if role_id is not None:
|
if role_id is not None:
|
||||||
where_conditions.append("role_id = %s")
|
where_conditions.append("u.role_id = %s")
|
||||||
count_params.append(role_id)
|
count_params.append(role_id)
|
||||||
|
|
||||||
if search:
|
if search:
|
||||||
|
|
@ -171,7 +172,7 @@ def get_all_users(
|
||||||
count_params.extend([search_pattern, search_pattern])
|
count_params.extend([search_pattern, search_pattern])
|
||||||
|
|
||||||
# 统计查询
|
# 统计查询
|
||||||
count_query = "SELECT COUNT(*) as total FROM users"
|
count_query = "SELECT COUNT(*) as total FROM users u"
|
||||||
if where_conditions:
|
if where_conditions:
|
||||||
count_query += " WHERE " + " AND ".join(where_conditions)
|
count_query += " WHERE " + " AND ".join(where_conditions)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from app.models.models import VoiceprintStatus, VoiceprintTemplate
|
||||||
from app.core.auth import get_current_user
|
from app.core.auth import get_current_user
|
||||||
from app.core.response import create_api_response
|
from app.core.response import create_api_response
|
||||||
from app.services.voiceprint_service import voiceprint_service
|
from app.services.voiceprint_service import voiceprint_service
|
||||||
|
from app.services.system_config_service import SystemConfigService
|
||||||
import app.core.config as config_module
|
import app.core.config as config_module
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
@ -23,7 +24,16 @@ def get_voiceprint_template(current_user: dict = Depends(get_current_user)):
|
||||||
权限:需要登录
|
权限:需要登录
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
template_data = VoiceprintTemplate(**config_module.VOICEPRINT_CONFIG)
|
# 动态从数据库获取声纹模版
|
||||||
|
template_text = SystemConfigService.get_voiceprint_template(
|
||||||
|
default="我正在进行声纹采集,这段语音将用于身份识别和验证。\n\n声纹技术能够准确识别每个人独特的声音特征。"
|
||||||
|
)
|
||||||
|
template_data = VoiceprintTemplate(
|
||||||
|
template_text=template_text,
|
||||||
|
duration_seconds=config_module.VOICEPRINT_CONFIG.get('duration_seconds', 12),
|
||||||
|
sample_rate=config_module.VOICEPRINT_CONFIG.get('sample_rate', 16000),
|
||||||
|
channels=config_module.VOICEPRINT_CONFIG.get('channels', 1)
|
||||||
|
)
|
||||||
return create_api_response(code="200", message="获取朗读模板成功", data=template_data.dict())
|
return create_api_response(code="200", message="获取朗读模板成功", data=template_data.dict())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return create_api_response(code="500", message=f"获取朗读模板失败: {str(e)}")
|
return create_api_response(code="500", message=f"获取朗读模板失败: {str(e)}")
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@ CLIENT_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
# 数据库配置
|
# 数据库配置
|
||||||
DATABASE_CONFIG = {
|
DATABASE_CONFIG = {
|
||||||
'host': os.getenv('DB_HOST', '10.100.51.161'),
|
'host': os.getenv('DB_HOST', '10.100.51.51'),
|
||||||
'user': os.getenv('DB_USER', 'root'),
|
'user': os.getenv('DB_USER', 'root'),
|
||||||
'password': os.getenv('DB_PASSWORD', 'sagacity'),
|
'password': os.getenv('DB_PASSWORD', 'Unis@123'),
|
||||||
'database': os.getenv('DB_NAME', 'imeeting'),
|
'database': os.getenv('DB_NAME', 'imeeting_dev'),
|
||||||
'port': int(os.getenv('DB_PORT', '3306')),
|
'port': int(os.getenv('DB_PORT', '3306')),
|
||||||
'charset': 'utf8mb4'
|
'charset': 'utf8mb4'
|
||||||
}
|
}
|
||||||
|
|
@ -56,10 +56,10 @@ APP_CONFIG = {
|
||||||
|
|
||||||
# Redis配置
|
# Redis配置
|
||||||
REDIS_CONFIG = {
|
REDIS_CONFIG = {
|
||||||
'host': os.getenv('REDIS_HOST', '10.100.51.161'),
|
'host': os.getenv('REDIS_HOST', '10.100.51.51'),
|
||||||
'port': int(os.getenv('REDIS_PORT', '6379')),
|
'port': int(os.getenv('REDIS_PORT', '6379')),
|
||||||
'db': int(os.getenv('REDIS_DB', '0')),
|
'db': int(os.getenv('REDIS_DB', '0')),
|
||||||
'password': os.getenv('REDIS_PASSWORD', ''),
|
'password': os.getenv('REDIS_PASSWORD', 'Unis@123'),
|
||||||
'decode_responses': True
|
'decode_responses': True
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,8 @@ import uvicorn
|
||||||
from fastapi import FastAPI, Request, HTTPException
|
from fastapi import FastAPI, Request, HTTPException
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from app.api.endpoints import auth, users, meetings, tags, admin, admin_dashboard, tasks, prompts, knowledge_base, client_downloads, voiceprint, audio, dict_data
|
from app.api.endpoints import auth, users, meetings, tags, admin, admin_dashboard, tasks, prompts, knowledge_base, client_downloads, voiceprint, audio, dict_data, hot_words
|
||||||
from app.core.config import UPLOAD_DIR, API_CONFIG
|
from app.core.config import UPLOAD_DIR, API_CONFIG
|
||||||
from app.api.endpoints.admin import load_system_config
|
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="iMeeting API",
|
title="iMeeting API",
|
||||||
|
|
@ -23,9 +22,6 @@ app = FastAPI(
|
||||||
version="1.0.2"
|
version="1.0.2"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 加载系统配置
|
|
||||||
load_system_config()
|
|
||||||
|
|
||||||
# 添加CORS中间件
|
# 添加CORS中间件
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
|
|
@ -53,6 +49,7 @@ app.include_router(client_downloads.router, prefix="/api", tags=["ClientDownload
|
||||||
app.include_router(dict_data.router, prefix="/api", tags=["DictData"])
|
app.include_router(dict_data.router, prefix="/api", tags=["DictData"])
|
||||||
app.include_router(voiceprint.router, prefix="/api", tags=["Voiceprint"])
|
app.include_router(voiceprint.router, prefix="/api", tags=["Voiceprint"])
|
||||||
app.include_router(audio.router, prefix="/api", tags=["Audio"])
|
app.include_router(audio.router, prefix="/api", tags=["Audio"])
|
||||||
|
app.include_router(hot_words.router, prefix="/api", tags=["HotWords"])
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def read_root():
|
def read_root():
|
||||||
|
|
|
||||||
|
|
@ -404,23 +404,6 @@ class AsyncMeetingService:
|
||||||
try:
|
try:
|
||||||
with get_db_connection() as connection:
|
with get_db_connection() as connection:
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
|
||||||
# 如果没有指定 prompt_id,获取默认的会议总结模版ID
|
|
||||||
if prompt_id is None:
|
|
||||||
print(f"[Meeting Service] prompt_id is None, fetching default template for MEETING_TASK")
|
|
||||||
cursor.execute(
|
|
||||||
"SELECT id FROM prompts WHERE task_type = 'MEETING_TASK' AND is_default = TRUE AND is_active = TRUE LIMIT 1"
|
|
||||||
)
|
|
||||||
default_prompt = cursor.fetchone()
|
|
||||||
if default_prompt:
|
|
||||||
prompt_id = default_prompt[0]
|
|
||||||
print(f"[Meeting Service] Found default template ID: {prompt_id}")
|
|
||||||
else:
|
|
||||||
print(f"[Meeting Service] WARNING: No default template found for MEETING_TASK!")
|
|
||||||
else:
|
|
||||||
print(f"[Meeting Service] Using provided prompt_id: {prompt_id}")
|
|
||||||
|
|
||||||
print(f"[Meeting Service] Inserting task into llm_tasks - task_id: {task_id}, meeting_id: {meeting_id}, prompt_id: {prompt_id}")
|
|
||||||
insert_query = "INSERT INTO llm_tasks (task_id, meeting_id, user_prompt, prompt_id, status, progress, created_at) VALUES (%s, %s, %s, %s, 'pending', 0, NOW())"
|
insert_query = "INSERT INTO llm_tasks (task_id, meeting_id, user_prompt, prompt_id, status, progress, created_at) VALUES (%s, %s, %s, %s, 'pending', 0, NOW())"
|
||||||
cursor.execute(insert_query, (task_id, meeting_id, user_prompt, prompt_id))
|
cursor.execute(insert_query, (task_id, meeting_id, user_prompt, prompt_id))
|
||||||
connection.commit()
|
connection.commit()
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from dashscope.audio.asr import Transcription
|
||||||
|
|
||||||
from app.core.config import QWEN_API_KEY, REDIS_CONFIG, APP_CONFIG
|
from app.core.config import QWEN_API_KEY, REDIS_CONFIG, APP_CONFIG
|
||||||
from app.core.database import get_db_connection
|
from app.core.database import get_db_connection
|
||||||
|
from app.services.system_config_service import SystemConfigService
|
||||||
|
|
||||||
|
|
||||||
class AsyncTranscriptionService:
|
class AsyncTranscriptionService:
|
||||||
|
|
@ -58,17 +59,24 @@ class AsyncTranscriptionService:
|
||||||
# 2. 构造完整的文件URL
|
# 2. 构造完整的文件URL
|
||||||
file_url = f"{self.base_url}{audio_file_path}"
|
file_url = f"{self.base_url}{audio_file_path}"
|
||||||
|
|
||||||
print(f"Starting transcription for meeting_id: {meeting_id}, file_url: {file_url}")
|
# 获取热词表ID (asr_vocabulary_id)
|
||||||
|
vocabulary_id = SystemConfigService.get_asr_vocabulary_id()
|
||||||
|
|
||||||
|
print(f"Starting transcription for meeting_id: {meeting_id}, file_url: {file_url}, vocabulary_id: {vocabulary_id}")
|
||||||
|
|
||||||
# 3. 调用Paraformer异步API
|
# 3. 调用Paraformer异步API
|
||||||
task_response = Transcription.async_call(
|
call_params = {
|
||||||
model='paraformer-v2',
|
'model': 'paraformer-v2',
|
||||||
file_urls=[file_url],
|
'file_urls': [file_url],
|
||||||
language_hints=['zh', 'en'],
|
'language_hints': ['zh', 'en'],
|
||||||
disfluency_removal_enabled=True,
|
'disfluency_removal_enabled': True,
|
||||||
diarization_enabled=True,
|
'diarization_enabled': True,
|
||||||
speaker_count=10
|
'speaker_count': 10
|
||||||
)
|
}
|
||||||
|
if vocabulary_id:
|
||||||
|
call_params['vocabulary_id'] = vocabulary_id
|
||||||
|
|
||||||
|
task_response = Transcription.async_call(**call_params)
|
||||||
|
|
||||||
if task_response.status_code != HTTPStatus.OK:
|
if task_response.status_code != HTTPStatus.OK:
|
||||||
print(f"Failed to start transcription: {task_response.status_code}, {task_response.message}")
|
print(f"Failed to start transcription: {task_response.status_code}, {task_response.message}")
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from http import HTTPStatus
|
||||||
from typing import Optional, Dict, List, Generator
|
from typing import Optional, Dict, List, Generator
|
||||||
import app.core.config as config_module
|
import app.core.config as config_module
|
||||||
from app.core.database import get_db_connection
|
from app.core.database import get_db_connection
|
||||||
|
from app.services.system_config_service import SystemConfigService
|
||||||
|
|
||||||
|
|
||||||
class LLMService:
|
class LLMService:
|
||||||
|
|
@ -16,27 +17,28 @@ class LLMService:
|
||||||
@property
|
@property
|
||||||
def model_name(self):
|
def model_name(self):
|
||||||
"""动态获取模型名称"""
|
"""动态获取模型名称"""
|
||||||
return config_module.LLM_CONFIG["model_name"]
|
return SystemConfigService.get_llm_model_name(default="qwen-plus")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def system_prompt(self):
|
def system_prompt(self):
|
||||||
"""动态获取系统提示词"""
|
"""动态获取系统提示词(fallback,优先使用prompts表)"""
|
||||||
return config_module.LLM_CONFIG["system_prompt"]
|
# 保留config中的system_prompt作为后备
|
||||||
|
return config_module.LLM_CONFIG.get("system_prompt", "请根据提供的内容进行总结和分析。")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def time_out(self):
|
def time_out(self):
|
||||||
"""动态获取超时时间"""
|
"""动态获取超时时间"""
|
||||||
return config_module.LLM_CONFIG["time_out"]
|
return SystemConfigService.get_llm_timeout(default=120)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def temperature(self):
|
def temperature(self):
|
||||||
"""动态获取temperature"""
|
"""动态获取temperature"""
|
||||||
return config_module.LLM_CONFIG["temperature"]
|
return SystemConfigService.get_llm_temperature(default=0.7)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def top_p(self):
|
def top_p(self):
|
||||||
"""动态获取top_p"""
|
"""动态获取top_p"""
|
||||||
return config_module.LLM_CONFIG["top_p"]
|
return SystemConfigService.get_llm_top_p(default=0.9)
|
||||||
|
|
||||||
def get_task_prompt(self, task_type: str, cursor=None, prompt_id: Optional[int] = None) -> str:
|
def get_task_prompt(self, task_type: str, cursor=None, prompt_id: Optional[int] = None) -> str:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
import json
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from app.core.database import get_db_connection
|
||||||
|
|
||||||
|
|
||||||
|
class SystemConfigService:
|
||||||
|
"""系统配置服务 - 从 dict_data 表中读取和保存 system_config 类型的配置"""
|
||||||
|
|
||||||
|
DICT_TYPE = 'system_config'
|
||||||
|
|
||||||
|
# 配置键常量
|
||||||
|
ASR_VOCABULARY_ID = 'asr_vocabulary_id'
|
||||||
|
VOICEPRINT_TEMPLATE = 'voiceprint_template'
|
||||||
|
TIMELINE_PAGESIZE = 'timeline_pagesize'
|
||||||
|
DEFAULT_RESET_PASSWORD = 'default_reset_password'
|
||||||
|
MAX_AUDIO_SIZE = 'max_audio_size'
|
||||||
|
|
||||||
|
# LLM模型配置
|
||||||
|
LLM_MODEL_NAME = 'llm_model_name'
|
||||||
|
LLM_TIMEOUT = 'llm_timeout'
|
||||||
|
LLM_TEMPERATURE = 'llm_temperature'
|
||||||
|
LLM_TOP_P = 'llm_top_p'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_config(cls, dict_code: str, default_value: Any = None) -> Any:
|
||||||
|
"""
|
||||||
|
获取指定配置项的值
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dict_code: 配置项编码
|
||||||
|
default_value: 默认值,如果配置不存在则返回此值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
配置项的值
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
query = """
|
||||||
|
SELECT extension_attr
|
||||||
|
FROM dict_data
|
||||||
|
WHERE dict_type = %s AND dict_code = %s AND status = 1
|
||||||
|
LIMIT 1
|
||||||
|
"""
|
||||||
|
cursor.execute(query, (cls.DICT_TYPE, dict_code))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
if result and result['extension_attr']:
|
||||||
|
try:
|
||||||
|
ext_attr = json.loads(result['extension_attr']) if isinstance(result['extension_attr'], str) else result['extension_attr']
|
||||||
|
return ext_attr.get('value', default_value)
|
||||||
|
except (json.JSONDecodeError, AttributeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return default_value
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting config {dict_code}: {e}")
|
||||||
|
return default_value
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_config(cls, dict_code: str, value: Any, label_cn: str = None) -> bool:
|
||||||
|
"""
|
||||||
|
设置指定配置项的值
|
||||||
|
|
||||||
|
Args:
|
||||||
|
dict_code: 配置项编码
|
||||||
|
value: 配置值
|
||||||
|
label_cn: 配置项中文名称(仅在配置不存在时需要)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否设置成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
|
||||||
|
# 检查配置是否存在
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT id FROM dict_data WHERE dict_type = %s AND dict_code = %s",
|
||||||
|
(cls.DICT_TYPE, dict_code)
|
||||||
|
)
|
||||||
|
existing = cursor.fetchone()
|
||||||
|
|
||||||
|
extension_attr = json.dumps({"value": value}, ensure_ascii=False)
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
# 更新现有配置
|
||||||
|
update_query = """
|
||||||
|
UPDATE dict_data
|
||||||
|
SET extension_attr = %s, update_time = NOW()
|
||||||
|
WHERE dict_type = %s AND dict_code = %s
|
||||||
|
"""
|
||||||
|
cursor.execute(update_query, (extension_attr, cls.DICT_TYPE, dict_code))
|
||||||
|
else:
|
||||||
|
# 插入新配置
|
||||||
|
if not label_cn:
|
||||||
|
label_cn = dict_code
|
||||||
|
|
||||||
|
insert_query = """
|
||||||
|
INSERT INTO dict_data (
|
||||||
|
dict_type, dict_code, parent_code, label_cn,
|
||||||
|
extension_attr, status, sort_order
|
||||||
|
) VALUES (%s, %s, 'ROOT', %s, %s, 1, 0)
|
||||||
|
"""
|
||||||
|
cursor.execute(insert_query, (cls.DICT_TYPE, dict_code, label_cn, extension_attr))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
cursor.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error setting config {dict_code}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all_configs(cls) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
获取所有系统配置
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
配置字典 {dict_code: value}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
query = """
|
||||||
|
SELECT dict_code, label_cn, extension_attr
|
||||||
|
FROM dict_data
|
||||||
|
WHERE dict_type = %s AND status = 1
|
||||||
|
ORDER BY sort_order
|
||||||
|
"""
|
||||||
|
cursor.execute(query, (cls.DICT_TYPE,))
|
||||||
|
results = cursor.fetchall()
|
||||||
|
cursor.close()
|
||||||
|
|
||||||
|
configs = {}
|
||||||
|
for row in results:
|
||||||
|
if row['extension_attr']:
|
||||||
|
try:
|
||||||
|
ext_attr = json.loads(row['extension_attr']) if isinstance(row['extension_attr'], str) else row['extension_attr']
|
||||||
|
configs[row['dict_code']] = ext_attr.get('value')
|
||||||
|
except (json.JSONDecodeError, AttributeError):
|
||||||
|
configs[row['dict_code']] = None
|
||||||
|
else:
|
||||||
|
configs[row['dict_code']] = None
|
||||||
|
|
||||||
|
return configs
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting all configs: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def batch_set_configs(cls, configs: Dict[str, Any]) -> bool:
|
||||||
|
"""
|
||||||
|
批量设置配置项
|
||||||
|
|
||||||
|
Args:
|
||||||
|
configs: 配置字典 {dict_code: value}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否全部设置成功
|
||||||
|
"""
|
||||||
|
success = True
|
||||||
|
for dict_code, value in configs.items():
|
||||||
|
if not cls.set_config(dict_code, value):
|
||||||
|
success = False
|
||||||
|
return success
|
||||||
|
|
||||||
|
# 便捷方法:获取特定配置
|
||||||
|
@classmethod
|
||||||
|
def get_asr_vocabulary_id(cls) -> Optional[str]:
|
||||||
|
"""获取ASR热词字典ID"""
|
||||||
|
return cls.get_config(cls.ASR_VOCABULARY_ID)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_voiceprint_template(cls, default: str = "") -> str:
|
||||||
|
"""获取声纹采集模版"""
|
||||||
|
return cls.get_config(cls.VOICEPRINT_TEMPLATE, default)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_timeline_pagesize(cls, default: int = 10) -> int:
|
||||||
|
"""获取会议时间轴每页数量"""
|
||||||
|
value = cls.get_config(cls.TIMELINE_PAGESIZE, str(default))
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_default_reset_password(cls, default: str = "111111") -> str:
|
||||||
|
"""获取默认重置密码"""
|
||||||
|
return cls.get_config(cls.DEFAULT_RESET_PASSWORD, default)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_max_audio_size(cls, default: int = 100) -> int:
|
||||||
|
"""获取上传音频文件大小限制(MB)"""
|
||||||
|
value = cls.get_config(cls.MAX_AUDIO_SIZE, str(default))
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
# LLM模型配置获取方法
|
||||||
|
@classmethod
|
||||||
|
def get_llm_model_name(cls, default: str = "qwen-plus") -> str:
|
||||||
|
"""获取LLM模型名称"""
|
||||||
|
return cls.get_config(cls.LLM_MODEL_NAME, default)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_llm_timeout(cls, default: int = 120) -> int:
|
||||||
|
"""获取LLM超时时间(秒)"""
|
||||||
|
value = cls.get_config(cls.LLM_TIMEOUT, str(default))
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_llm_temperature(cls, default: float = 0.7) -> float:
|
||||||
|
"""获取LLM temperature参数"""
|
||||||
|
value = cls.get_config(cls.LLM_TEMPERATURE, str(default))
|
||||||
|
try:
|
||||||
|
return float(value)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_llm_top_p(cls, default: float = 0.9) -> float:
|
||||||
|
"""获取LLM top_p参数"""
|
||||||
|
value = cls.get_config(cls.LLM_TOP_P, str(default))
|
||||||
|
try:
|
||||||
|
return float(value)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return default
|
||||||
|
|
@ -17,7 +17,6 @@ class VoiceprintService:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.voiceprint_dir = config_module.VOICEPRINT_DIR
|
self.voiceprint_dir = config_module.VOICEPRINT_DIR
|
||||||
self.voiceprint_config = config_module.VOICEPRINT_CONFIG
|
|
||||||
|
|
||||||
def get_user_voiceprint_status(self, user_id: int) -> Dict:
|
def get_user_voiceprint_status(self, user_id: int) -> Dict:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
import mysql.connector
|
|
||||||
|
|
||||||
# 连接数据库
|
|
||||||
conn = mysql.connector.connect(
|
|
||||||
host="10.100.51.51",
|
|
||||||
port=3306,
|
|
||||||
user="root",
|
|
||||||
password="Unis@123",
|
|
||||||
database="imeeting_dev"
|
|
||||||
)
|
|
||||||
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
print("=" * 80)
|
|
||||||
print("dict_data 表结构:")
|
|
||||||
print("=" * 80)
|
|
||||||
cursor.execute("SHOW CREATE TABLE dict_data")
|
|
||||||
result = cursor.fetchone()
|
|
||||||
print(result[1])
|
|
||||||
print("\n")
|
|
||||||
|
|
||||||
print("=" * 80)
|
|
||||||
print("dict_data 表数据:")
|
|
||||||
print("=" * 80)
|
|
||||||
cursor.execute("SELECT * FROM dict_data ORDER BY dict_code, sort_order")
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
cursor.execute("SHOW COLUMNS FROM dict_data")
|
|
||||||
columns = [col[0] for col in cursor.fetchall()]
|
|
||||||
print(" | ".join(columns))
|
|
||||||
print("-" * 80)
|
|
||||||
for row in rows:
|
|
||||||
print(" | ".join(str(val) for val in row))
|
|
||||||
print("\n")
|
|
||||||
|
|
||||||
print("=" * 80)
|
|
||||||
print("client_download 表结构:")
|
|
||||||
print("=" * 80)
|
|
||||||
cursor.execute("SHOW CREATE TABLE client_download")
|
|
||||||
result = cursor.fetchone()
|
|
||||||
print(result[1])
|
|
||||||
print("\n")
|
|
||||||
|
|
||||||
print("=" * 80)
|
|
||||||
print("client_download 表数据:")
|
|
||||||
print("=" * 80)
|
|
||||||
cursor.execute("SELECT * FROM client_download")
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
cursor.execute("SHOW COLUMNS FROM client_download")
|
|
||||||
columns = [col[0] for col in cursor.fetchall()]
|
|
||||||
print(" | ".join(columns))
|
|
||||||
print("-" * 80)
|
|
||||||
for row in rows:
|
|
||||||
print(" | ".join(str(val) for val in row))
|
|
||||||
|
|
||||||
cursor.close()
|
|
||||||
conn.close()
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"model_name": "qwen-plus",
|
|
||||||
"template_text": "我正在进行声纹采集,这段语音将用于身份识别和验证。\n声纹技术能够识别每个人独特的声音特征,用于人声识别应用。",
|
|
||||||
"DEFAULT_RESET_PASSWORD": "123456",
|
|
||||||
"MAX_FILE_SIZE": 209715200,
|
|
||||||
"TIMELINE_PAGESIZE": 10
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS `hot_words` (
|
||||||
|
`id` INT NOT NULL AUTO_INCREMENT,
|
||||||
|
`text` VARCHAR(255) NOT NULL COMMENT '热词内容',
|
||||||
|
`weight` INT NOT NULL DEFAULT 4 COMMENT '词汇权重 (1-10)',
|
||||||
|
`lang` VARCHAR(20) NOT NULL DEFAULT 'zh' COMMENT '语言 (zh/en)',
|
||||||
|
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态 (1:启用, 0:禁用)',
|
||||||
|
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `idx_text_lang` (`text`, `lang`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统语音识别热词表';
|
||||||
|
|
||||||
|
-- 预留存储 Vocabulary ID 的配置项(如果不想用字典表存储配置,也可以在系统配置表中增加)
|
||||||
|
INSERT INTO `dict_data` (dict_type, dict_code, parent_code, label_cn, status)
|
||||||
|
VALUES ('system_config', 'asr_vocabulary_id', 'ROOT', '阿里云ASR热词表ID', 1)
|
||||||
|
ON DUPLICATE KEY UPDATE label_cn='阿里云ASR热词表ID';
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
"""
|
|
||||||
测试APK解析功能
|
|
||||||
"""
|
|
||||||
from app.utils.apk_parser import parse_apk_with_androguard
|
|
||||||
|
|
||||||
# 测试导入是否正常
|
|
||||||
try:
|
|
||||||
from androguard.core.apk import APK
|
|
||||||
print("✅ androguard 导入成功")
|
|
||||||
print(f" androguard 模块路径: {APK.__module__}")
|
|
||||||
except ImportError as e:
|
|
||||||
print(f"❌ androguard 导入失败: {e}")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
# 如果有测试APK文件,可以在这里测试解析
|
|
||||||
# apk_path = "path/to/your/test.apk"
|
|
||||||
# result = parse_apk_with_androguard(apk_path)
|
|
||||||
# print(f"解析结果: {result}")
|
|
||||||
|
|
||||||
print("\n📋 测试结果:")
|
|
||||||
print(" 1. androguard 库已成功安装")
|
|
||||||
print(" 2. 导入路径正确: androguard.core.apk.APK")
|
|
||||||
print(" 3. 可以正常使用 APK 解析功能")
|
|
||||||
print("\n💡 提示: 请重启后台服务以使更改生效")
|
|
||||||
Loading…
Reference in New Issue