修改了会议获取列表

main
mula.liu 2025-11-07 17:12:29 +08:00
parent 2f36474f4d
commit 8f332145cb
7 changed files with 225 additions and 26 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
app.zip 100644

Binary file not shown.

BIN
app/.DS_Store vendored

Binary file not shown.

View File

@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends
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, MAX_IMAGE_SIZE
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 pydantic import BaseModel
import json
@ -13,10 +13,10 @@ CONFIG_FILE = Path(__file__).parent.parent.parent.parent / "config" / "system_co
class SystemConfigModel(BaseModel):
model_name: str
system_prompt: str
template_text: str
DEFAULT_RESET_PASSWORD: str
MAX_FILE_SIZE: int # 字节为单位
MAX_IMAGE_SIZE: int # 字节为单位
TIMELINE_PAGESIZE: int # 分页数量
def load_config_from_file():
"""从文件加载配置,如果文件不存在则返回默认配置"""
@ -30,10 +30,10 @@ def load_config_from_file():
# 返回默认配置
return {
'model_name': LLM_CONFIG['model_name'],
'system_prompt': LLM_CONFIG['system_prompt'],
'template_text': VOICEPRINT_CONFIG['template_text'],
'DEFAULT_RESET_PASSWORD': DEFAULT_RESET_PASSWORD,
'MAX_FILE_SIZE': MAX_FILE_SIZE,
'MAX_IMAGE_SIZE': MAX_IMAGE_SIZE
'TIMELINE_PAGESIZE': TIMELINE_PAGESIZE
}
def save_config_to_file(config_data):
@ -57,10 +57,10 @@ async def get_system_config(current_user=Depends(get_current_user)):
config = load_config_from_file()
response_data = {
'model_name': config.get('model_name', LLM_CONFIG['model_name']),
'system_prompt': config.get('system_prompt', LLM_CONFIG['system_prompt']),
'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),
'MAX_IMAGE_SIZE': config.get('MAX_IMAGE_SIZE', MAX_IMAGE_SIZE),
'TIMELINE_PAGESIZE': config.get('TIMELINE_PAGESIZE', TIMELINE_PAGESIZE),
}
return create_api_response(code="200", message="配置获取成功", data=response_data)
except Exception as e:
@ -78,10 +78,10 @@ async def update_system_config(
try:
config_data = {
'model_name': config.model_name,
'system_prompt': config.system_prompt,
'template_text': config.template_text,
'DEFAULT_RESET_PASSWORD': config.DEFAULT_RESET_PASSWORD,
'MAX_FILE_SIZE': config.MAX_FILE_SIZE,
'MAX_IMAGE_SIZE': config.MAX_IMAGE_SIZE
'TIMELINE_PAGESIZE': config.TIMELINE_PAGESIZE
}
if not save_config_to_file(config_data):
@ -89,11 +89,11 @@ async def update_system_config(
# 更新运行时配置
LLM_CONFIG['model_name'] = config.model_name
LLM_CONFIG['system_prompt'] = config.system_prompt
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.MAX_IMAGE_SIZE = config.MAX_IMAGE_SIZE
config_module.TIMELINE_PAGESIZE = config.TIMELINE_PAGESIZE
return create_api_response(
code="200",
@ -109,11 +109,11 @@ def load_system_config():
try:
config = load_config_from_file()
LLM_CONFIG['model_name'] = config.get('model_name', LLM_CONFIG['model_name'])
LLM_CONFIG['system_prompt'] = config.get('system_prompt', LLM_CONFIG['system_prompt'])
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.MAX_IMAGE_SIZE = config.get('MAX_IMAGE_SIZE', MAX_IMAGE_SIZE)
print(f"系统配置加载成功: model={config.get('model_name')}")
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}")

View File

@ -45,34 +45,180 @@ def _process_tags(cursor, tag_string: Optional[str], creator_id: Optional[int] =
return [Tag(**tag) for tag in tags_data]
@router.get("/meetings")
def get_meetings(current_user: dict = Depends(get_current_user), user_id: Optional[int] = None):
def get_meetings(
current_user: dict = Depends(get_current_user),
user_id: Optional[int] = None,
page: int = 1,
page_size: Optional[int] = None,
search: Optional[str] = None,
tags: Optional[str] = None,
filter_type: str = "all"
):
from app.core.config import TIMELINE_PAGESIZE
# 使用配置的默认页面大小
if page_size is None:
page_size = TIMELINE_PAGESIZE
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# 构建WHERE子句
where_conditions = []
params = []
# 用户过滤
if user_id:
# 需要联表查询参与者
has_attendees_join = True
else:
has_attendees_join = False
# 按类型过滤 (created/attended/all)
if user_id:
if filter_type == "created":
where_conditions.append("m.user_id = %s")
params.append(user_id)
elif filter_type == "attended":
where_conditions.append("m.user_id != %s AND a.user_id = %s")
params.extend([user_id, user_id])
has_attendees_join = True
else: # all
where_conditions.append("(m.user_id = %s OR a.user_id = %s)")
params.extend([user_id, user_id])
has_attendees_join = True
# 搜索关键词过滤
if search and search.strip():
search_pattern = f"%{search.strip()}%"
where_conditions.append("(m.title LIKE %s OR u.caption LIKE %s)")
params.extend([search_pattern, search_pattern])
# 标签过滤
if tags and tags.strip():
tag_list = [t.strip() for t in tags.split(',') if t.strip()]
if tag_list:
# 使用JSON_CONTAINS或LIKE查询
tag_conditions = []
for tag in tag_list:
tag_conditions.append("m.tags LIKE %s")
params.append(f"%{tag}%")
where_conditions.append(f"({' OR '.join(tag_conditions)})")
# 构建基础查询
base_query = '''
SELECT m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at, m.tags,
m.user_id as creator_id, u.caption as creator_username, af.file_path as audio_file_path
FROM meetings m JOIN users u ON m.user_id = u.user_id LEFT JOIN audio_files af ON m.meeting_id = af.meeting_id
FROM meetings m
JOIN users u ON m.user_id = u.user_id
LEFT JOIN audio_files af ON m.meeting_id = af.meeting_id
'''
if user_id:
query = f'{base_query} LEFT JOIN attendees a ON m.meeting_id = a.meeting_id WHERE m.user_id = %s OR a.user_id = %s GROUP BY m.meeting_id ORDER BY m.meeting_time DESC, m.created_at DESC'
cursor.execute(query, (user_id, user_id))
if has_attendees_join:
base_query += " LEFT JOIN attendees a ON m.meeting_id = a.meeting_id"
# 添加WHERE子句
if where_conditions:
base_query += f" WHERE {' AND '.join(where_conditions)}"
# 获取总数 - 需要在添加 GROUP BY 之前
count_base = base_query # 保存一份不含GROUP BY的查询
if has_attendees_join:
# 如果有联表,使用子查询计数
count_query = f"SELECT COUNT(DISTINCT m.meeting_id) as total {count_base[count_base.find('FROM'):]}"
else:
query = f" {base_query} ORDER BY m.meeting_time DESC, m.created_at DESC"
cursor.execute(query)
# 没有联表,直接计数
count_query = f"SELECT COUNT(*) as total {count_base[count_base.find('FROM'):]}"
cursor.execute(count_query, params)
total = cursor.fetchone()['total']
# 添加GROUP BY如果联表了attendees
if has_attendees_join:
base_query += " GROUP BY m.meeting_id"
# 计算分页
total_pages = (total + page_size - 1) // page_size
has_more = page < total_pages
offset = (page - 1) * page_size
# 添加排序和分页
query = f"{base_query} ORDER BY m.meeting_time DESC, m.created_at DESC LIMIT %s OFFSET %s"
params.extend([page_size, offset])
cursor.execute(query, params)
meetings = cursor.fetchall()
meeting_list = []
for meeting in meetings:
attendees_query = 'SELECT u.user_id, u.caption FROM attendees a JOIN users u ON a.user_id = u.user_id WHERE a.meeting_id = %s'
cursor.execute(attendees_query, (meeting['meeting_id'],))
attendees_data = cursor.fetchall()
attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data]
tags = _process_tags(cursor, meeting.get('tags'))
tags_list = _process_tags(cursor, meeting.get('tags'))
meeting_list.append(Meeting(
meeting_id=meeting['meeting_id'], title=meeting['title'], meeting_time=meeting['meeting_time'],
summary=meeting['summary'], created_at=meeting['created_at'], audio_file_path=meeting['audio_file_path'],
attendees=attendees, creator_id=meeting['creator_id'], creator_username=meeting['creator_username'], tags=tags
attendees=attendees, creator_id=meeting['creator_id'], creator_username=meeting['creator_username'], tags=tags_list
))
return create_api_response(code="200", message="获取会议列表成功", data=meeting_list)
return create_api_response(code="200", message="获取会议列表成功", data={
"meetings": meeting_list,
"total": total,
"page": page,
"page_size": page_size,
"total_pages": total_pages,
"has_more": has_more
})
@router.get("/meetings/stats")
def get_meetings_stats(
current_user: dict = Depends(get_current_user),
user_id: Optional[int] = None
):
"""
获取会议统计数据全部会议我创建的会议我参加的会议数量
"""
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
if not user_id:
return create_api_response(code="400", message="user_id is required")
# 获取全部会议数量(创建的 + 参加的)
all_query = '''
SELECT COUNT(DISTINCT m.meeting_id) as count
FROM meetings m
LEFT JOIN attendees a ON m.meeting_id = a.meeting_id
WHERE m.user_id = %s OR a.user_id = %s
'''
cursor.execute(all_query, (user_id, user_id))
all_count = cursor.fetchone()['count']
# 获取我创建的会议数量
created_query = '''
SELECT COUNT(*) as count
FROM meetings m
WHERE m.user_id = %s
'''
cursor.execute(created_query, (user_id,))
created_count = cursor.fetchone()['count']
# 获取我参加的会议数量(不包括我创建的)
attended_query = '''
SELECT COUNT(DISTINCT a.meeting_id) as count
FROM attendees a
JOIN meetings m ON a.meeting_id = m.meeting_id
WHERE a.user_id = %s AND m.user_id != %s
'''
cursor.execute(attended_query, (user_id, user_id))
attended_count = cursor.fetchone()['count']
return create_api_response(code="200", message="获取会议统计成功", data={
"all_meetings": all_count,
"created_meetings": created_count,
"attended_meetings": attended_count
})
@router.get("/meetings/{meeting_id}")
def get_meeting_details(meeting_id: int, current_user: dict = Depends(get_current_user)):
@ -477,3 +623,53 @@ def get_meeting_llm_tasks(meeting_id: int, current_user: dict = Depends(get_curr
})
except Exception as e:
return create_api_response(code="500", message=f"Failed to get LLM tasks: {str(e)}")
@router.get("/meetings/{meeting_id}/preview-data")
def get_meeting_preview_data(meeting_id: int):
"""
获取会议预览数据无需登录认证
用于二维码扫描后的预览页面
"""
try:
with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True)
# 检查会议是否存在
query = '''
SELECT m.meeting_id, m.title, m.meeting_time, m.summary, m.updated_at,
m.user_id as creator_id, u.caption as creator_username
FROM meetings m
JOIN users u ON m.user_id = u.user_id
WHERE m.meeting_id = %s
'''
cursor.execute(query, (meeting_id,))
meeting = cursor.fetchone()
if not meeting:
return create_api_response(code="404", message="会议不存在")
# 检查是否已生成会议总结
if not meeting['summary'] or not meeting['updated_at']:
return create_api_response(code="400", message="该会议总结尚未生成")
# 获取参会人员信息
attendees_query = 'SELECT u.user_id, u.caption FROM attendees a JOIN users u ON a.user_id = u.user_id WHERE a.meeting_id = %s'
cursor.execute(attendees_query, (meeting_id,))
attendees_data = cursor.fetchall()
attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data]
# 组装返回数据
preview_data = {
"meeting_id": meeting['meeting_id'],
"title": meeting['title'],
"meeting_time": meeting['meeting_time'],
"summary": meeting['summary'],
"creator_username": meeting['creator_username'],
"attendees": attendees,
"attendees_count": len(attendees)
}
return create_api_response(code="200", message="获取会议预览数据成功", data=preview_data)
except Exception as e:
return create_api_response(code="500", message=f"Failed to get meeting preview data: {str(e)}")

View File

@ -95,3 +95,6 @@ VOICEPRINT_CONFIG = {
"sample_rate": 16000,
"channels": 1
}
#首页TimeLine每页数量
TIMELINE_PAGESIZE=10

View File

@ -1,7 +1,7 @@
{
"model_name": "qwen-plus",
"system_prompt": "你是一个专业的会议记录分析助手。请根据提供的会议转录内容,生成简洁明了的会议总结。\n\n总结包括五个部分名称严格一致生成为MD二级目录\n1. 会议概述 - 简要说明会议的主要目的和背景(生成MD引用)\n2. 主要讨论点 - 列出会议中讨论的重要话题和内容\n3. 决策事项 - 明确记录会议中做出的决定和结论\n4. 待办事项 - 列出需要后续跟进的任务和责任人\n5. 关键信息 - 其他重要的信息点\n\n输出要求\n- 保持客观中性,不添加个人观点\n- 使用简洁、准确的中文表达\n- 按重要性排序各项内容\n- 如果某个部分没有相关内容,可以说明\"无相关内容\"\n- 总字数控制在500字以内",
"template_text": "我正在进行声纹采集,这段语音将用于身份识别和验证。\n声纹技术能够识别每个人独特的声音特征用于人声识别应用。",
"DEFAULT_RESET_PASSWORD": "123456",
"MAX_FILE_SIZE": 208666624,
"MAX_IMAGE_SIZE": 10485760
"TIMELINE_PAGESIZE": 10
}