diff --git a/.DS_Store b/.DS_Store index 932e278..248e563 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/app.zip b/app.zip index 62f0b2c..c146afb 100644 Binary files a/app.zip and b/app.zip differ diff --git a/app/api/endpoints/admin.py b/app/api/endpoints/admin.py index 287df3a..6f8726b 100644 --- a/app/api/endpoints/admin.py +++ b/app/api/endpoints/admin.py @@ -1,8 +1,8 @@ -from fastapi import APIRouter, HTTPException, Depends -from app.core.auth import get_current_admin_user +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.response import create_api_response from pydantic import BaseModel -import os import json from pathlib import Path @@ -18,14 +18,6 @@ class SystemConfigModel(BaseModel): MAX_FILE_SIZE: int # 字节为单位 MAX_IMAGE_SIZE: int # 字节为单位 -class SystemConfigResponse(BaseModel): - model_name: str - system_prompt: str - DEFAULT_RESET_PASSWORD: str - MAX_FILE_SIZE: int - MAX_IMAGE_SIZE: int - message: str = "" - def load_config_from_file(): """从文件加载配置,如果文件不存在则返回默认配置""" try: @@ -47,9 +39,7 @@ def load_config_from_file(): 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 @@ -57,28 +47,26 @@ def save_config_to_file(config_data): print(f"保存配置文件失败: {e}") return False -@router.get("/admin/system-config", response_model=SystemConfigResponse) -async def get_system_config(current_user=Depends(get_current_admin_user)): +@router.get("/admin/system-config") +async def get_system_config(current_user=Depends(get_current_user)): """ 获取系统配置 - 只有管理员才能访问 + 普通用户也可以获取 """ try: - # 优先从文件加载配置,然后从内存配置补充 config = load_config_from_file() - - return SystemConfigResponse( - model_name=config.get('model_name', LLM_CONFIG['model_name']), - system_prompt=config.get('system_prompt', LLM_CONFIG['system_prompt']), - 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), - message="配置获取成功" - ) + response_data = { + 'model_name': config.get('model_name', LLM_CONFIG['model_name']), + 'system_prompt': config.get('system_prompt', LLM_CONFIG['system_prompt']), + '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), + } + return create_api_response(code="200", message="配置获取成功", data=response_data) except Exception as e: - raise HTTPException(status_code=500, detail=f"获取配置失败: {str(e)}") + return create_api_response(code="500", message=f"获取配置失败: {str(e)}") -@router.put("/admin/system-config", response_model=SystemConfigResponse) +@router.put("/admin/system-config") async def update_system_config( config: SystemConfigModel, current_user=Depends(get_current_admin_user) @@ -88,7 +76,6 @@ async def update_system_config( 只有管理员才能访问 """ try: - # 准备要保存的配置数据 config_data = { 'model_name': config.model_name, 'system_prompt': config.system_prompt, @@ -97,49 +84,36 @@ async def update_system_config( 'MAX_IMAGE_SIZE': config.MAX_IMAGE_SIZE } - # 保存到文件 if not save_config_to_file(config_data): - raise HTTPException(status_code=500, detail="配置保存到文件失败") + return create_api_response(code="500", message="配置保存到文件失败") # 更新运行时配置 LLM_CONFIG['model_name'] = config.model_name LLM_CONFIG['system_prompt'] = config.system_prompt - - # 更新模块级别的配置 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 - return SystemConfigResponse( - model_name=config.model_name, - system_prompt=config.system_prompt, - DEFAULT_RESET_PASSWORD=config.DEFAULT_RESET_PASSWORD, - MAX_FILE_SIZE=config.MAX_FILE_SIZE, - MAX_IMAGE_SIZE=config.MAX_IMAGE_SIZE, - message="配置更新成功,重启服务后完全生效" + return create_api_response( + code="200", + message="配置更新成功,重启服务后完全生效", + data=config_data ) - except HTTPException: - raise except Exception as e: - raise HTTPException(status_code=500, detail=f"更新配置失败: {str(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']) LLM_CONFIG['system_prompt'] = config.get('system_prompt', LLM_CONFIG['system_prompt']) - - # 更新其他配置 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')}") except Exception as e: - print(f"加载系统配置失败,使用默认配置: {e}") \ No newline at end of file + print(f"加载系统配置失败,使用默认配置: {e}") diff --git a/app/api/endpoints/auth.py b/app/api/endpoints/auth.py index 38953fa..710d433 100644 --- a/app/api/endpoints/auth.py +++ b/app/api/endpoints/auth.py @@ -1,11 +1,16 @@ -from fastapi import APIRouter, HTTPException, Depends -from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials -from app.models.models import LoginRequest, LoginResponse -from app.core.database import get_db_connection -from app.services.jwt_service import jwt_service -from app.core.auth import get_current_user import hashlib +from typing import Union + +from fastapi import APIRouter, Depends, HTTPException +from fastapi.responses import JSONResponse +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials + +from app.core.auth import get_current_user +from app.core.database import get_db_connection +from app.models.models import LoginRequest, LoginResponse +from app.services.jwt_service import jwt_service +from app.core.response import create_api_response security = HTTPBearer() @@ -14,7 +19,7 @@ router = APIRouter() def hash_password(password: str) -> str: return hashlib.sha256(password.encode()).hexdigest() -@router.post("/auth/login", response_model=LoginResponse) +@router.post("/auth/login") def login(request: LoginRequest): with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) @@ -24,11 +29,11 @@ def login(request: LoginRequest): user = cursor.fetchone() if not user: - raise HTTPException(status_code=401, detail="用户名或密码错误") + return create_api_response(code="401", message="用户名或密码错误") hashed_input = hash_password(request.password) if user['password_hash'] != hashed_input: - raise HTTPException(status_code=401, detail="用户名或密码错误") + return create_api_response(code="401", message="用户名或密码错误") # 创建JWT token token_data = { @@ -39,7 +44,7 @@ def login(request: LoginRequest): } token = jwt_service.create_access_token(token_data) - return LoginResponse( + login_response_data = LoginResponse( user_id=user['user_id'], username=user['username'], caption=user['caption'], @@ -47,71 +52,83 @@ def login(request: LoginRequest): token=token, role_id=user['role_id'] ) + + return create_api_response( + code="200", + message="登录成功", + data=login_response_data.dict() + ) @router.post("/auth/logout") def logout(credentials: HTTPAuthorizationCredentials = Depends(security)): """登出接口,撤销当前token""" token = credentials.credentials - - # 验证token并获取用户信息(不查询数据库) + payload = jwt_service.verify_token(token) if not payload: - raise HTTPException(status_code=401, detail="Invalid or expired token") - + return create_api_response(code="401", message="无效或过期的token") + user_id = payload.get("user_id") if not user_id: - raise HTTPException(status_code=401, detail="Invalid token payload") - - # 撤销当前token + return create_api_response(code="401", message="无效的token payload") + revoked = jwt_service.revoke_token(token, user_id) - + if revoked: - return {"message": "Logged out successfully"} + return create_api_response(code="200", message="登出成功") else: - return {"message": "Already logged out or token not found"} + return create_api_response(code="400", message="已经登出或token未找到") + @router.post("/auth/logout-all") def logout_all(current_user: dict = Depends(get_current_user)): """登出所有设备""" - user_id = current_user['user_id'] + user_id = current_user["user_id"] revoked_count = jwt_service.revoke_all_user_tokens(user_id) - return {"message": f"Logged out from {revoked_count} devices"} + return create_api_response(code="200", message=f"从 {revoked_count} 个设备登出") + @router.post("/auth/admin/revoke-user-tokens/{user_id}") -def admin_revoke_user_tokens(user_id: int, credentials: HTTPAuthorizationCredentials = Depends(security)): +def admin_revoke_user_tokens( + user_id: int, credentials: HTTPAuthorizationCredentials = Depends(security) +): """管理员功能:撤销指定用户的所有token""" token = credentials.credentials - - # 验证管理员token(不查询数据库) + payload = jwt_service.verify_token(token) if not payload: - raise HTTPException(status_code=401, detail="Invalid or expired token") - + return create_api_response(code="401", message="无效或过期的token") + admin_user_id = payload.get("user_id") if not admin_user_id: - raise HTTPException(status_code=401, detail="Invalid token payload") - + return create_api_response(code="401", message="无效的token payload") + # 这里可以添加管理员权限检查,目前暂时允许任何登录用户操作 - # if not payload.get('is_admin'): - # raise HTTPException(status_code=403, detail="需要管理员权限") - + # if payload.get('role_id') != ADMIN_ROLE_ID: + # return create_api_response(code="403", message="需要管理员权限") + revoked_count = jwt_service.revoke_all_user_tokens(user_id) - return {"message": f"Revoked {revoked_count} tokens for user {user_id}"} + return create_api_response( + code="200", message=f"为用户 {user_id} 撤销了 {revoked_count} 个token" + ) + @router.get("/auth/me") def get_me(current_user: dict = Depends(get_current_user)): """获取当前用户信息""" - return current_user + return create_api_response(code="200", message="获取用户信息成功", data=current_user) + @router.post("/auth/refresh") def refresh_token(current_user: dict = Depends(get_current_user)): """刷新token""" - # 这里需要从请求中获取当前token,为简化先返回新token token_data = { - "user_id": current_user['user_id'], - "username": current_user['username'], - "caption": current_user['caption'], - "role_id": current_user['role_id'] + "user_id": current_user["user_id"], + "username": current_user["username"], + "caption": current_user["caption"], + "role_id": current_user["role_id"], } new_token = jwt_service.create_access_token(token_data) - return {"token": new_token} + return create_api_response( + code="200", message="Token刷新成功", data={"token": new_token} + ) diff --git a/app/api/endpoints/meetings.py b/app/api/endpoints/meetings.py index ad2d378..0206276 100644 --- a/app/api/endpoints/meetings.py +++ b/app/api/endpoints/meetings.py @@ -1,4 +1,3 @@ - from fastapi import APIRouter, HTTPException, UploadFile, File, Form, Depends, BackgroundTasks from fastapi.responses import StreamingResponse from app.models.models import Meeting, TranscriptSegment, TranscriptionTaskStatus, CreateMeetingRequest, UpdateMeetingRequest, SpeakerTagUpdateRequest, BatchSpeakerTagUpdateRequest, TranscriptUpdateRequest, BatchTranscriptUpdateRequest, Tag @@ -9,6 +8,7 @@ from app.services.llm_service import LLMService from app.services.async_transcription_service import AsyncTranscriptionService from app.services.async_llm_service import async_llm_service from app.core.auth import get_current_user +from app.core.response import create_api_response from typing import List, Optional from datetime import datetime from pydantic import BaseModel @@ -18,483 +18,254 @@ import shutil router = APIRouter() -# 实例化服务 llm_service = LLMService() transcription_service = AsyncTranscriptionService() -# 注意:异步LLM服务需要单独启动worker进程 -# 运行命令:python llm_worker.py - -# 请求模型 class GenerateSummaryRequest(BaseModel): user_prompt: Optional[str] = "" def _process_tags(cursor, tag_string: Optional[str]) -> List[Tag]: if not tag_string: return [] - tag_names = [name.strip() for name in tag_string.split(',') if name.strip()] if not tag_names: return [] - - # Ensure all tags exist in the 'tags' table insert_ignore_query = "INSERT IGNORE INTO tags (name) VALUES (%s)" cursor.executemany(insert_ignore_query, [(name,) for name in tag_names]) - - # Fetch the full tag objects format_strings = ', '.join(['%s'] * len(tag_names)) cursor.execute(f"SELECT id, name, color FROM tags WHERE name IN ({format_strings})", tuple(tag_names)) tags_data = cursor.fetchall() return [Tag(**tag) for tag in tags_data] -@router.get("/meetings", response_model=list[Meeting]) +@router.get("/meetings") def get_meetings(current_user: dict = Depends(get_current_user), user_id: Optional[int] = None): with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - 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 - FROM meetings m - JOIN users u ON m.user_id = u.user_id + 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 + FROM meetings m JOIN users u ON m.user_id = u.user_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 - ''' + 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)) else: query = f" {base_query} ORDER BY m.meeting_time DESC, m.created_at DESC" cursor.execute(query) - 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 - ''' + 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')) - 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'], - attendees=attendees, - creator_id=meeting['creator_id'], - creator_username=meeting['creator_username'], - tags=tags + meeting_id=meeting['meeting_id'], title=meeting['title'], meeting_time=meeting['meeting_time'], + summary=meeting['summary'], created_at=meeting['created_at'], attendees=attendees, + creator_id=meeting['creator_id'], creator_username=meeting['creator_username'], tags=tags )) - - return meeting_list + return create_api_response(code="200", message="获取会议列表成功", data=meeting_list) -@router.get("/meetings/{meeting_id}", response_model=Meeting) +@router.get("/meetings/{meeting_id}") def get_meeting_details(meeting_id: int, current_user: dict = Depends(get_current_user)): with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - 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 + 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 WHERE m.meeting_id = %s ''' cursor.execute(query, (meeting_id,)) meeting = cursor.fetchone() - if not meeting: - cursor.close() # 明确关闭游标 - raise HTTPException(status_code=404, detail="Meeting not found") - - 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 - ''' + return create_api_response(code="404", message="Meeting not found") + 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')) - - # 关闭游标,避免与转录服务的数据库连接冲突 cursor.close() - meeting_data = Meeting( - meeting_id=meeting['meeting_id'], - title=meeting['title'], - meeting_time=meeting['meeting_time'], - summary=meeting['summary'], - created_at=meeting['created_at'], - attendees=attendees, - creator_id=meeting['creator_id'], - creator_username=meeting['creator_username'], - tags=tags + meeting_id=meeting['meeting_id'], title=meeting['title'], meeting_time=meeting['meeting_time'], + summary=meeting['summary'], created_at=meeting['created_at'], attendees=attendees, + creator_id=meeting['creator_id'], creator_username=meeting['creator_username'], tags=tags ) - - # Add audio file path if exists if meeting['audio_file_path']: meeting_data.audio_file_path = meeting['audio_file_path'] - - # 在连接外部获取转录状态,避免游标冲突 try: transcription_status_data = transcription_service.get_meeting_transcription_status(meeting_id) if transcription_status_data: meeting_data.transcription_status = TranscriptionTaskStatus(**transcription_status_data) except Exception as e: print(f"Warning: Failed to get transcription status for meeting {meeting_id}: {e}") - # Don't fail the entire request if transcription status can't be fetched - - return meeting_data + return create_api_response(code="200", message="获取会议详情成功", data=meeting_data) -@router.get("/meetings/{meeting_id}/transcript", response_model=list[TranscriptSegment]) +@router.get("/meetings/{meeting_id}/transcript") def get_meeting_transcript(meeting_id: int, current_user: dict = Depends(get_current_user)): - """获取会议的转录内容""" with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - - # First check if meeting exists - meeting_query = "SELECT meeting_id FROM meetings WHERE meeting_id = %s" - cursor.execute(meeting_query, (meeting_id,)) + cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,)) if not cursor.fetchone(): - raise HTTPException(status_code=404, detail="Meeting not found") - - # Get transcript segments + return create_api_response(code="404", message="Meeting not found") transcript_query = ''' SELECT segment_id, meeting_id, speaker_id, speaker_tag, start_time_ms, end_time_ms, text_content - FROM transcript_segments - WHERE meeting_id = %s - ORDER BY start_time_ms ASC + FROM transcript_segments WHERE meeting_id = %s ORDER BY start_time_ms ASC ''' cursor.execute(transcript_query, (meeting_id,)) segments = cursor.fetchall() - - transcript_segments = [] - for segment in segments: - transcript_segments.append(TranscriptSegment( - segment_id=segment['segment_id'], - meeting_id=segment['meeting_id'], - speaker_id=segment['speaker_id'], - speaker_tag=segment['speaker_tag'] if segment['speaker_tag'] else f"发言人 {segment['speaker_id']}", - start_time_ms=segment['start_time_ms'], - end_time_ms=segment['end_time_ms'], - text_content=segment['text_content'] - )) - - return transcript_segments + transcript_segments = [TranscriptSegment( + segment_id=s['segment_id'], meeting_id=s['meeting_id'], speaker_id=s['speaker_id'], + speaker_tag=s['speaker_tag'] if s['speaker_tag'] else f"发言人 {s['speaker_id']}", + start_time_ms=s['start_time_ms'], end_time_ms=s['end_time_ms'], text_content=s['text_content'] + ) for s in segments] + return create_api_response(code="200", message="获取转录内容成功", data=transcript_segments) @router.post("/meetings") def create_meeting(meeting_request: CreateMeetingRequest, current_user: dict = Depends(get_current_user)): with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - - # Process tags if meeting_request.tags: tag_names = [name.strip() for name in meeting_request.tags.split(',') if name.strip()] if tag_names: - insert_ignore_query = "INSERT IGNORE INTO tags (name) VALUES (%s)" - cursor.executemany(insert_ignore_query, [(name,) for name in tag_names]) - - # Create meeting - meeting_query = ''' - INSERT INTO meetings (user_id, title, meeting_time, summary, tags, created_at) - VALUES (%s, %s, %s, %s, %s, %s) - ''' - cursor.execute(meeting_query, ( - meeting_request.user_id, - meeting_request.title, - meeting_request.meeting_time, - None, - meeting_request.tags, - datetime.now().isoformat() - )) - + cursor.executemany("INSERT IGNORE INTO tags (name) VALUES (%s)", [(name,) for name in tag_names]) + meeting_query = 'INSERT INTO meetings (user_id, title, meeting_time, summary, tags, created_at) VALUES (%s, %s, %s, %s, %s, %s)' + cursor.execute(meeting_query, (meeting_request.user_id, meeting_request.title, meeting_request.meeting_time, None, meeting_request.tags, datetime.now().isoformat())) meeting_id = cursor.lastrowid - - # Add attendees for attendee_id in meeting_request.attendee_ids: - attendee_query = ''' - INSERT IGNORE INTO attendees (meeting_id, user_id) - VALUES (%s, %s) - ''' - cursor.execute(attendee_query, (meeting_id, attendee_id)) - + cursor.execute('INSERT IGNORE INTO attendees (meeting_id, user_id) VALUES (%s, %s)', (meeting_id, attendee_id)) connection.commit() - return {"message": "Meeting created successfully", "meeting_id": meeting_id} + return create_api_response(code="200", message="Meeting created successfully", data={"meeting_id": meeting_id}) @router.put("/meetings/{meeting_id}") def update_meeting(meeting_id: int, meeting_request: UpdateMeetingRequest, current_user: dict = Depends(get_current_user)): with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - - # Check if meeting exists and user has permission cursor.execute("SELECT user_id FROM meetings WHERE meeting_id = %s", (meeting_id,)) meeting = cursor.fetchone() if not meeting: - raise HTTPException(status_code=404, detail="Meeting not found") - + return create_api_response(code="404", message="Meeting not found") if meeting['user_id'] != current_user['user_id']: - raise HTTPException(status_code=403, detail="Permission denied") - - # Process tags + return create_api_response(code="403", message="Permission denied") if meeting_request.tags: tag_names = [name.strip() for name in meeting_request.tags.split(',') if name.strip()] if tag_names: - insert_ignore_query = "INSERT IGNORE INTO tags (name) VALUES (%s)" - cursor.executemany(insert_ignore_query, [(name,) for name in tag_names]) - - # Update meeting - update_query = ''' - UPDATE meetings - SET title = %s, meeting_time = %s, summary = %s, tags = %s - WHERE meeting_id = %s - ''' - cursor.execute(update_query, ( - meeting_request.title, - meeting_request.meeting_time, - meeting_request.summary, - meeting_request.tags, - meeting_id - )) - - # Update attendees - remove existing and add new ones + cursor.executemany("INSERT IGNORE INTO tags (name) VALUES (%s)", [(name,) for name in tag_names]) + update_query = 'UPDATE meetings SET title = %s, meeting_time = %s, summary = %s, tags = %s WHERE meeting_id = %s' + cursor.execute(update_query, (meeting_request.title, meeting_request.meeting_time, meeting_request.summary, meeting_request.tags, meeting_id)) cursor.execute("DELETE FROM attendees WHERE meeting_id = %s", (meeting_id,)) - for attendee_id in meeting_request.attendee_ids: - attendee_query = ''' - INSERT INTO attendees (meeting_id, user_id) - VALUES (%s, %s) - ''' - cursor.execute(attendee_query, (meeting_id, attendee_id)) - + cursor.execute('INSERT INTO attendees (meeting_id, user_id) VALUES (%s, %s)', (meeting_id, attendee_id)) connection.commit() - return {"message": "Meeting updated successfully"} + return create_api_response(code="200", message="Meeting updated successfully") @router.delete("/meetings/{meeting_id}") def delete_meeting(meeting_id: int, current_user: dict = Depends(get_current_user)): with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - - # Check if meeting exists and user has permission cursor.execute("SELECT user_id FROM meetings WHERE meeting_id = %s", (meeting_id,)) meeting = cursor.fetchone() if not meeting: - raise HTTPException(status_code=404, detail="Meeting not found") - + return create_api_response(code="404", message="Meeting not found") if meeting['user_id'] != current_user['user_id']: - raise HTTPException(status_code=403, detail="Permission denied") - - # Delete related records first (foreign key constraints) + return create_api_response(code="403", message="Permission denied") cursor.execute("DELETE FROM transcript_segments WHERE meeting_id = %s", (meeting_id,)) cursor.execute("DELETE FROM audio_files WHERE meeting_id = %s", (meeting_id,)) cursor.execute("DELETE FROM attachments WHERE meeting_id = %s", (meeting_id,)) cursor.execute("DELETE FROM attendees WHERE meeting_id = %s", (meeting_id,)) - - # Delete meeting cursor.execute("DELETE FROM meetings WHERE meeting_id = %s", (meeting_id,)) - connection.commit() - return {"message": "Meeting deleted successfully"} + return create_api_response(code="200", message="Meeting deleted successfully") -@router.get("/meetings/{meeting_id}/edit", response_model=Meeting) +@router.get("/meetings/{meeting_id}/edit") def get_meeting_for_edit(meeting_id: int, current_user: dict = Depends(get_current_user)): - """获取会议信息用于编辑""" with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - 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 + 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 WHERE m.meeting_id = %s ''' cursor.execute(query, (meeting_id,)) meeting = cursor.fetchone() - if not meeting: - cursor.close() - raise HTTPException(status_code=404, detail="Meeting not found") - - # Get attendees - 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,)) + return create_api_response(code="404", message="Meeting not found") + 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')) - - # 关闭游标,避免与转录服务的数据库连接冲突 cursor.close() - meeting_data = Meeting( - meeting_id=meeting['meeting_id'], - title=meeting['title'], - meeting_time=meeting['meeting_time'], - summary=meeting['summary'], - created_at=meeting['created_at'], - attendees=attendees, - creator_id=meeting['creator_id'], - creator_username=meeting['creator_username'], - tags=tags + meeting_id=meeting['meeting_id'], title=meeting['title'], meeting_time=meeting['meeting_time'], + summary=meeting['summary'], created_at=meeting['created_at'], attendees=attendees, + creator_id=meeting['creator_id'], creator_username=meeting['creator_username'], tags=tags ) - - # Add audio file path if exists if meeting.get('audio_file_path'): meeting_data.audio_file_path = meeting['audio_file_path'] - - # 在连接外部获取转录状态,避免游标冲突 try: transcription_status_data = transcription_service.get_meeting_transcription_status(meeting_id) if transcription_status_data: meeting_data.transcription_status = TranscriptionTaskStatus(**transcription_status_data) except Exception as e: print(f"Warning: Failed to get transcription status for meeting {meeting_id}: {e}") - - return meeting_data + return create_api_response(code="200", message="获取会议编辑信息成功", data=meeting_data) @router.post("/meetings/upload-audio") -async def upload_audio( - audio_file: UploadFile = File(...), - meeting_id: int = Form(...), - force_replace: str = Form("false"), # 接收字符串,然后手动转换 - current_user: dict = Depends(get_current_user) -): - # Convert string to boolean +async def upload_audio(audio_file: UploadFile = File(...), meeting_id: int = Form(...), force_replace: str = Form("false"), current_user: dict = Depends(get_current_user)): force_replace_bool = force_replace.lower() in ("true", "1", "yes") - - # Validate file extension file_extension = os.path.splitext(audio_file.filename)[1].lower() if file_extension not in ALLOWED_EXTENSIONS: - raise HTTPException( - status_code=400, - detail=f"Unsupported file type. Allowed types: {', '.join(ALLOWED_EXTENSIONS)}" - ) - - # Check file size using dynamic config + return create_api_response(code="400", message=f"Unsupported file type. Allowed types: {', '.join(ALLOWED_EXTENSIONS)}") max_file_size = getattr(config_module, 'MAX_FILE_SIZE', 100 * 1024 * 1024) if audio_file.size > max_file_size: - max_size_mb = max_file_size // (1024 * 1024) - raise HTTPException( - status_code=400, - detail=f"File size exceeds {max_size_mb}MB limit" - ) - - # 检查是否已有音频文件和转录记录 - existing_info = None - has_transcription = False - + return create_api_response(code="400", message=f"File size exceeds {max_file_size // (1024 * 1024)}MB limit") try: with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - - # Check if meeting exists and user has permission cursor.execute("SELECT user_id FROM meetings WHERE meeting_id = %s", (meeting_id,)) meeting = cursor.fetchone() if not meeting: - cursor.close() - raise HTTPException(status_code=404, detail="Meeting not found") - + return create_api_response(code="404", message="Meeting not found") if meeting['user_id'] != current_user['user_id']: - cursor.close() - raise HTTPException(status_code=403, detail="Permission denied") - - # Check existing audio file - cursor.execute(""" - SELECT file_name, file_path, upload_time - FROM audio_files - WHERE meeting_id = %s - """, (meeting_id,)) + return create_api_response(code="403", message="Permission denied") + cursor.execute("SELECT file_name, file_path, upload_time FROM audio_files WHERE meeting_id = %s", (meeting_id,)) existing_info = cursor.fetchone() - - # Check existing transcription segments + has_transcription = False if existing_info: - cursor.execute(""" - SELECT COUNT(*) as segment_count - FROM transcript_segments - WHERE meeting_id = %s - """, (meeting_id,)) - result = cursor.fetchone() - has_transcription = result['segment_count'] > 0 - + cursor.execute("SELECT COUNT(*) as segment_count FROM transcript_segments WHERE meeting_id = %s", (meeting_id,)) + has_transcription = cursor.fetchone()['segment_count'] > 0 cursor.close() except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to check existing files: {str(e)}") - - # 如果存在音频文件且有转录记录,需要用户确认 + return create_api_response(code="500", message=f"Failed to check existing files: {str(e)}") if existing_info and has_transcription and not force_replace_bool: - return { + return create_api_response(code="300", message="该会议已有音频文件和转录记录,重新上传将删除现有的转录内容", data={ "requires_confirmation": True, - "message": "该会议已有音频文件和转录记录,重新上传将删除现有的转录内容", "existing_file": { "file_name": existing_info['file_name'], "upload_time": existing_info['upload_time'].isoformat() if existing_info['upload_time'] else None - }, - "transcription_segments": has_transcription, - "suggestion": "请确认是否要替换现有音频文件并重新进行转录" - } - - # Create meeting-specific directory + } + }) meeting_dir = AUDIO_DIR / str(meeting_id) meeting_dir.mkdir(exist_ok=True) - - # Generate unique filename unique_filename = f"{uuid.uuid4()}{file_extension}" absolute_path = meeting_dir / unique_filename relative_path = absolute_path.relative_to(BASE_DIR) - - # Save file try: with open(absolute_path, "wb") as buffer: shutil.copyfileobj(audio_file.file, buffer) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to save file: {str(e)}") - - # Save file info to database and start transcription + return create_api_response(code="500", message=f"Failed to save file: {str(e)}") task_id = None replaced_existing = existing_info is not None - try: with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - - # 如果是替换操作,清理旧的转录数据 if replaced_existing and force_replace_bool: - # Delete existing transcription segments cursor.execute("DELETE FROM transcript_segments WHERE meeting_id = %s", (meeting_id,)) - - # Delete existing transcription tasks cursor.execute("DELETE FROM transcript_tasks WHERE meeting_id = %s", (meeting_id,)) - - # Clean up old audio file if existing_info and existing_info['file_path']: old_file_path = BASE_DIR / existing_info['file_path'].lstrip('/') if old_file_path.exists(): @@ -502,312 +273,177 @@ async def upload_audio( os.remove(old_file_path) except Exception as e: print(f"Warning: Failed to delete old file {old_file_path}: {e}") - - # Insert or update audio file record if replaced_existing: - update_query = ''' - UPDATE audio_files - SET file_name = %s, file_path = %s, file_size = %s, upload_time = NOW(), task_id = NULL - WHERE meeting_id = %s - ''' - cursor.execute(update_query, (audio_file.filename, '/'+str(relative_path), audio_file.size, meeting_id)) + cursor.execute('UPDATE audio_files SET file_name = %s, file_path = %s, file_size = %s, upload_time = NOW(), task_id = NULL WHERE meeting_id = %s', (audio_file.filename, '/'+str(relative_path), audio_file.size, meeting_id)) else: - insert_query = ''' - INSERT INTO audio_files (meeting_id, file_name, file_path, file_size, upload_time) - VALUES (%s, %s, %s, %s, NOW()) - ''' - cursor.execute(insert_query, (meeting_id, audio_file.filename, '/'+str(relative_path), audio_file.size)) - + cursor.execute('INSERT INTO audio_files (meeting_id, file_name, file_path, file_size, upload_time) VALUES (%s, %s, %s, %s, NOW())', (meeting_id, audio_file.filename, '/'+str(relative_path), audio_file.size)) connection.commit() cursor.close() - - # Start transcription task try: task_id = transcription_service.start_transcription(meeting_id, '/'+str(relative_path)) print(f"Transcription task started with ID: {task_id}") except Exception as e: print(f"Failed to start transcription task: {e}") - # 不抛出异常,允许文件上传成功但转录失败 - except Exception as e: - # Clean up uploaded file on database error if os.path.exists(absolute_path): os.remove(absolute_path) - raise HTTPException(status_code=500, detail=f"Failed to save file info: {str(e)}") - - return { - "message": "Audio file uploaded successfully" + (" and replaced existing file" if replaced_existing else ""), - "file_name": audio_file.filename, - "file_path": '/'+str(relative_path), - "task_id": task_id, - "transcription_started": task_id is not None, - "replaced_existing": replaced_existing, + return create_api_response(code="500", message=f"Failed to save file info: {str(e)}") + return create_api_response(code="200", message="Audio file uploaded successfully" + (" and replaced existing file" if replaced_existing else ""), data={ + "file_name": audio_file.filename, "file_path": '/'+str(relative_path), "task_id": task_id, + "transcription_started": task_id is not None, "replaced_existing": replaced_existing, "previous_transcription_cleared": replaced_existing and has_transcription - } + }) @router.get("/meetings/{meeting_id}/audio") def get_audio_file(meeting_id: int, current_user: dict = Depends(get_current_user)): with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - - query = ''' - SELECT file_name, file_path, file_size, upload_time - FROM audio_files - WHERE meeting_id = %s - ''' - cursor.execute(query, (meeting_id,)) + cursor.execute("SELECT file_name, file_path, file_size, upload_time FROM audio_files WHERE meeting_id = %s", (meeting_id,)) audio_file = cursor.fetchone() - if not audio_file: - raise HTTPException(status_code=404, detail="Audio file not found for this meeting") - - return { - "file_name": audio_file['file_name'], - "file_path": audio_file['file_path'], - "file_size": audio_file['file_size'], - "upload_time": audio_file['upload_time'] - } + return create_api_response(code="404", message="Audio file not found for this meeting") + return create_api_response(code="200", message="Audio file found", data=audio_file) -# 转录任务相关接口 @router.get("/transcription/tasks/{task_id}/status") def get_transcription_task_status(task_id: str, current_user: dict = Depends(get_current_user)): - """获取转录任务状态""" try: status_info = transcription_service.get_task_status(task_id) - return status_info + return create_api_response(code="200", message="Task status retrieved", data=status_info) except Exception as e: if "Task not found" in str(e): - raise HTTPException(status_code=404, detail="Transcription task not found") + return create_api_response(code="404", message="Transcription task not found") else: - raise HTTPException(status_code=500, detail=f"Failed to get task status: {str(e)}") + return create_api_response(code="500", message=f"Failed to get task status: {str(e)}") @router.get("/meetings/{meeting_id}/transcription/status") def get_meeting_transcription_status(meeting_id: int, current_user: dict = Depends(get_current_user)): - """获取会议的转录任务状态""" try: status_info = transcription_service.get_meeting_transcription_status(meeting_id) if not status_info: - return {"message": "No transcription task found for this meeting"} - return status_info + return create_api_response(code="404", message="No transcription task found for this meeting") + return create_api_response(code="200", message="Transcription status retrieved", data=status_info) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to get meeting transcription status: {str(e)}") + return create_api_response(code="500", message=f"Failed to get meeting transcription status: {str(e)}") @router.post("/meetings/{meeting_id}/transcription/start") def start_meeting_transcription(meeting_id: int, current_user: dict = Depends(get_current_user)): - """手动启动会议转录任务(如果有音频文件的话)""" try: with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - - # 检查会议是否存在 cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,)) if not cursor.fetchone(): - raise HTTPException(status_code=404, detail="Meeting not found") - - # 查询音频文件 - audio_query = "SELECT file_path FROM audio_files WHERE meeting_id = %s LIMIT 1" - cursor.execute(audio_query, (meeting_id,)) + return create_api_response(code="404", message="Meeting not found") + cursor.execute("SELECT file_path FROM audio_files WHERE meeting_id = %s LIMIT 1", (meeting_id,)) audio_file = cursor.fetchone() - if not audio_file: - raise HTTPException(status_code=400, detail="No audio file found for this meeting") - - # 检查是否已有进行中的任务 + return create_api_response(code="400", message="No audio file found for this meeting") existing_status = transcription_service.get_meeting_transcription_status(meeting_id) if existing_status and existing_status['status'] in ['pending', 'processing']: - return { - "message": "Transcription task already exists", - "task_id": existing_status['task_id'], - "status": existing_status['status'] - } - - # 启动转录任务 + return create_api_response(code="409", message="Transcription task already exists", data={ + "task_id": existing_status['task_id'], "status": existing_status['status'] + }) task_id = transcription_service.start_transcription(meeting_id, audio_file['file_path']) - - return { - "message": "Transcription task started successfully", - "task_id": task_id, - "meeting_id": meeting_id - } - - except HTTPException: - raise + return create_api_response(code="200", message="Transcription task started successfully", data={ + "task_id": task_id, "meeting_id": meeting_id + }) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to start transcription: {str(e)}") + return create_api_response(code="500", message=f"Failed to start transcription: {str(e)}") @router.post("/meetings/{meeting_id}/upload-image") -async def upload_image( - meeting_id: int, - image_file: UploadFile = File(...), - current_user: dict = Depends(get_current_user) -): - # Validate file extension +async def upload_image(meeting_id: int, image_file: UploadFile = File(...), current_user: dict = Depends(get_current_user)): file_extension = os.path.splitext(image_file.filename)[1].lower() if file_extension not in ALLOWED_IMAGE_EXTENSIONS: - raise HTTPException( - status_code=400, - detail=f"Unsupported image type. Allowed types: {', '.join(ALLOWED_IMAGE_EXTENSIONS)}" - ) - - # Check file size using dynamic config + return create_api_response(code="400", message=f"Unsupported image type. Allowed types: {', '.join(ALLOWED_IMAGE_EXTENSIONS)}") max_image_size = getattr(config_module, 'MAX_IMAGE_SIZE', 10 * 1024 * 1024) if image_file.size > max_image_size: - max_size_mb = max_image_size // (1024 * 1024) - raise HTTPException( - status_code=400, - detail=f"Image size exceeds {max_size_mb}MB limit" - ) - - # Check if meeting exists and user has permission + return create_api_response(code="400", message=f"Image size exceeds {max_image_size // (1024 * 1024)}MB limit") with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) cursor.execute("SELECT user_id FROM meetings WHERE meeting_id = %s", (meeting_id,)) meeting = cursor.fetchone() if not meeting: - raise HTTPException(status_code=404, detail="Meeting not found") - + return create_api_response(code="404", message="Meeting not found") if meeting['user_id'] != current_user['user_id']: - raise HTTPException(status_code=403, detail="Permission denied") - - # Create meeting-specific directory + return create_api_response(code="403", message="Permission denied") meeting_dir = MARKDOWN_DIR / str(meeting_id) meeting_dir.mkdir(exist_ok=True) - - # Generate unique filename unique_filename = f"{uuid.uuid4()}{file_extension}" absolute_path = meeting_dir / unique_filename relative_path = absolute_path.relative_to(BASE_DIR) - - # Save file try: with open(absolute_path, "wb") as buffer: shutil.copyfileobj(image_file.file, buffer) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to save image: {str(e)}") - - return { - "message": "Image uploaded successfully", - "file_name": image_file.filename, - "file_path": '/'+ str(relative_path) - } + return create_api_response(code="500", message=f"Failed to save image: {str(e)}") + return create_api_response(code="200", message="Image uploaded successfully", data= { + "file_name": image_file.filename, "file_path": '/'+ str(relative_path) + }) -# 发言人标签更新接口 @router.put("/meetings/{meeting_id}/speaker-tags") def update_speaker_tag(meeting_id: int, request: SpeakerTagUpdateRequest, current_user: dict = Depends(get_current_user)): - """更新单个发言人标签(基于原始的speaker_id值)""" try: with get_db_connection() as connection: cursor = connection.cursor() - - # 只修改speaker_tag,保留speaker_id的原始值 - update_query = """ - UPDATE transcript_segments - SET speaker_tag = %s - WHERE meeting_id = %s AND speaker_id = %s - """ + update_query = 'UPDATE transcript_segments SET speaker_tag = %s WHERE meeting_id = %s AND speaker_id = %s' cursor.execute(update_query, (request.new_tag, meeting_id, request.speaker_id)) - if cursor.rowcount == 0: - raise HTTPException(status_code=404, detail="No segments found for this speaker") - + return create_api_response(code="404", message="No segments found for this speaker") connection.commit() - return {'message': 'Speaker tag updated successfully', 'updated_count': cursor.rowcount} - + return create_api_response(code="200", message="Speaker tag updated successfully", data={'updated_count': cursor.rowcount}) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to update speaker tag: {str(e)}") + return create_api_response(code="500", message=f"Failed to update speaker tag: {str(e)}") @router.put("/meetings/{meeting_id}/speaker-tags/batch") def batch_update_speaker_tags(meeting_id: int, request: BatchSpeakerTagUpdateRequest, current_user: dict = Depends(get_current_user)): - """批量更新发言人标签(基于原始的speaker_id值)""" try: with get_db_connection() as connection: cursor = connection.cursor() - total_updated = 0 for update_item in request.updates: - # 只修改speaker_tag,保留speaker_id的原始值 - update_query = """ - UPDATE transcript_segments - SET speaker_tag = %s - WHERE meeting_id = %s AND speaker_id = %s - """ + update_query = 'UPDATE transcript_segments SET speaker_tag = %s WHERE meeting_id = %s AND speaker_id = %s' cursor.execute(update_query, (update_item.new_tag, meeting_id, update_item.speaker_id)) total_updated += cursor.rowcount - connection.commit() - return {'message': 'Speaker tags updated successfully', 'total_updated': total_updated} - + return create_api_response(code="200", message="Speaker tags updated successfully", data={'total_updated': total_updated}) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to batch update speaker tags: {str(e)}") + return create_api_response(code="500", message=f"Failed to batch update speaker tags: {str(e)}") -# 转录内容更新接口 @router.put("/meetings/{meeting_id}/transcript/batch") def batch_update_transcript(meeting_id: int, request: BatchTranscriptUpdateRequest, current_user: dict = Depends(get_current_user)): - """批量更新转录内容""" try: with get_db_connection() as connection: cursor = connection.cursor() - total_updated = 0 for update_item in request.updates: - # 验证segment_id是否属于指定会议 - verify_query = "SELECT segment_id FROM transcript_segments WHERE segment_id = %s AND meeting_id = %s" - cursor.execute(verify_query, (update_item.segment_id, meeting_id)) + cursor.execute("SELECT segment_id FROM transcript_segments WHERE segment_id = %s AND meeting_id = %s", (update_item.segment_id, meeting_id)) if not cursor.fetchone(): - continue # 跳过不属于该会议的转录条目 - - # 更新转录内容 - update_query = """ - UPDATE transcript_segments - SET text_content = %s - WHERE segment_id = %s AND meeting_id = %s - """ + continue + update_query = 'UPDATE transcript_segments SET text_content = %s WHERE segment_id = %s AND meeting_id = %s' cursor.execute(update_query, (update_item.text_content, update_item.segment_id, meeting_id)) total_updated += cursor.rowcount - connection.commit() - return {'message': 'Transcript updated successfully', 'total_updated': total_updated} - + return create_api_response(code="200", message="Transcript updated successfully", data={'total_updated': total_updated}) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to update transcript: {str(e)}") + return create_api_response(code="500", message=f"Failed to update transcript: {str(e)}") -# AI总结相关接口 @router.post("/meetings/{meeting_id}/generate-summary-stream") def generate_meeting_summary_stream(meeting_id: int, request: GenerateSummaryRequest, current_user: dict = Depends(get_current_user)): - """生成会议AI总结(流式输出)""" try: - # 检查会议是否存在 with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,)) if not cursor.fetchone(): raise HTTPException(status_code=404, detail="Meeting not found") - - # 创建流式生成器 def generate_stream(): for chunk in llm_service.generate_meeting_summary_stream(meeting_id, request.user_prompt): if chunk.startswith("error:"): - # 如果遇到错误,发送错误信息并结束 - yield f"data: {{\"error\": \"{chunk[6:]}\"}}\n\n" + yield f"data: {{'error': '{chunk[6:]}'}}\n\n" break else: - # 发送正常的内容块 import json - yield f"data: {{\"content\": {json.dumps(chunk, ensure_ascii=False)}}}\n\n" - - # 发送结束标记 - yield "data: {\"done\": true}\n\n" - - return StreamingResponse( - generate_stream(), - media_type="text/plain", - headers={ - "Cache-Control": "no-cache", - "Connection": "keep-alive", - "Content-Type": "text/plain; charset=utf-8" - } - ) - + yield f"data: {{'content': {json.dumps(chunk, ensure_ascii=False)}}}\\n\n" + yield "data: {\'done\': true}\\n\n" + return StreamingResponse(generate_stream(), media_type="text/plain", headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "Content-Type": "text/plain; charset=utf-8"}) except HTTPException: raise except Exception as e: @@ -815,185 +451,83 @@ def generate_meeting_summary_stream(meeting_id: int, request: GenerateSummaryReq @router.post("/meetings/{meeting_id}/generate-summary") def generate_meeting_summary(meeting_id: int, request: GenerateSummaryRequest, current_user: dict = Depends(get_current_user)): - """生成会议AI总结""" try: - # 检查会议是否存在 with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,)) if not cursor.fetchone(): - raise HTTPException(status_code=404, detail="Meeting not found") - - # 调用LLM服务生成总结 + return create_api_response(code="404", message="Meeting not found") result = llm_service.generate_meeting_summary(meeting_id, request.user_prompt) - if result.get("error"): - raise HTTPException(status_code=500, detail=result["error"]) - - return { - "message": "Summary generated successfully", - "summary_id": result["summary_id"], - "content": result["content"], - "meeting_id": meeting_id - } - - except HTTPException: - raise + return create_api_response(code="500", message=result["error"]) + return create_api_response(code="200", message="Summary generated successfully", data=result) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to generate summary: {str(e)}") + return create_api_response(code="500", message=f"Failed to generate summary: {str(e)}") @router.get("/meetings/{meeting_id}/summaries") def get_meeting_summaries(meeting_id: int, current_user: dict = Depends(get_current_user)): - """获取会议的所有AI总结历史""" try: - # 检查会议是否存在 with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,)) if not cursor.fetchone(): - raise HTTPException(status_code=404, detail="Meeting not found") - - # 获取总结列表 + return create_api_response(code="404", message="Meeting not found") summaries = llm_service.get_meeting_summaries(meeting_id) - - return { - "meeting_id": meeting_id, - "summaries": summaries - } - - except HTTPException: - raise + return create_api_response(code="200", message="Summaries retrieved successfully", data={"summaries": summaries}) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to get summaries: {str(e)}") + return create_api_response(code="500", message=f"Failed to get summaries: {str(e)}") @router.get("/meetings/{meeting_id}/summaries/{summary_id}") def get_summary_detail(meeting_id: int, summary_id: int, current_user: dict = Depends(get_current_user)): - """获取特定总结的详细内容""" try: with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - query = """ - SELECT id, summary_content, user_prompt, created_at - FROM meeting_summaries - WHERE id = %s AND meeting_id = %s - """ + query = "SELECT id, summary_content, user_prompt, created_at FROM meeting_summaries WHERE id = %s AND meeting_id = %s" cursor.execute(query, (summary_id, meeting_id)) summary = cursor.fetchone() - if not summary: - raise HTTPException(status_code=404, detail="Summary not found") - - return { - "id": summary["id"], - "meeting_id": meeting_id, - "content": summary["summary_content"], - "user_prompt": summary["user_prompt"], - "created_at": summary["created_at"].isoformat() if summary["created_at"] else None - } - - except HTTPException: - raise + return create_api_response(code="404", message="Summary not found") + return create_api_response(code="200", message="Summary detail retrieved", data=summary) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to get summary detail: {str(e)}") - -# ==================== 异步LLM总结相关接口 ==================== + return create_api_response(code="500", message=f"Failed to get summary detail: {str(e)}") @router.post("/meetings/{meeting_id}/generate-summary-async") def generate_meeting_summary_async(meeting_id: int, request: GenerateSummaryRequest, background_tasks: BackgroundTasks, current_user: dict = Depends(get_current_user)): - """生成会议AI总结(异步版本)""" try: - - # 1. 检查会议是否存在 with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,)) if not cursor.fetchone(): - raise HTTPException(status_code=404, detail="Meeting not found") - - # 2. 启动异步任务 + return create_api_response(code="404", message="Meeting not found") task_id = async_llm_service.start_summary_generation(meeting_id, request.user_prompt) - - # 3. 将真正的处理函数作为后台任务添加 background_tasks.add_task(async_llm_service._process_task, task_id) - - return { - "message": "Summary generation task has been accepted.", - "task_id": task_id, - "status": "pending", - "meeting_id": meeting_id - } - - except HTTPException: - raise + return create_api_response(code="200", message="Summary generation task has been accepted.", data= { + "task_id": task_id, "status": "pending", "meeting_id": meeting_id + }) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to start summary generation: {str(e)}") + return create_api_response(code="500", message=f"Failed to start summary generation: {str(e)}") @router.get("/llm-tasks/{task_id}/status") def get_llm_task_status(task_id: str, current_user: dict = Depends(get_current_user)): - """获取LLM任务状态(包括进度)""" try: status = async_llm_service.get_task_status(task_id) - if status.get('status') == 'not_found': - raise HTTPException(status_code=404, detail="Task not found") - - return status - - except HTTPException: - raise + return create_api_response(code="404", message="Task not found") + return create_api_response(code="200", message="Task status retrieved", data=status) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to get task status: {str(e)}") + return create_api_response(code="500", message=f"Failed to get task status: {str(e)}") @router.get("/meetings/{meeting_id}/llm-tasks") def get_meeting_llm_tasks(meeting_id: int, current_user: dict = Depends(get_current_user)): - """获取会议的所有LLM任务历史""" try: - # 检查会议是否存在 with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) cursor.execute("SELECT meeting_id FROM meetings WHERE meeting_id = %s", (meeting_id,)) if not cursor.fetchone(): - raise HTTPException(status_code=404, detail="Meeting not found") - - # 获取任务列表 + return create_api_response(code="404", message="Meeting not found") tasks = async_llm_service.get_meeting_llm_tasks(meeting_id) - - return { - "meeting_id": meeting_id, - "tasks": tasks, - "total": len(tasks) - } - - except HTTPException: - raise + return create_api_response(code="200", message="LLM tasks retrieved successfully", data= { + "tasks": tasks, "total": len(tasks) + }) except Exception as e: - raise HTTPException(status_code=500, detail=f"Failed to get LLM tasks: {str(e)}") - -# @router.get("/meetings/{meeting_id}/latest-llm-task") #此方法被替代 -# def get_latest_llm_task(meeting_id: int, current_user: dict = Depends(get_current_user)): -# """获取会议最新的LLM任务状态""" -# try: -# tasks = async_llm_service.get_meeting_llm_tasks(meeting_id) - -# if not tasks: -# return { -# "meeting_id": meeting_id, -# "task": None, -# "message": "No LLM tasks found for this meeting" -# } - -# # 返回最新的任务 -# latest_task = tasks[0] - -# # 如果任务还在进行中,获取实时状态 -# if latest_task['status'] in ['pending', 'processing']: -# latest_status = async_llm_service.get_task_status(latest_task['task_id']) -# latest_task.update(latest_status) - -# return { -# "meeting_id": meeting_id, -# "task": latest_task -# } - -# except Exception as e: -# raise HTTPException(status_code=500, detail=f"Failed to get latest LLM task: {str(e)}") + return create_api_response(code="500", message=f"Failed to get LLM tasks: {str(e)}") \ No newline at end of file diff --git a/app/api/endpoints/tags.py b/app/api/endpoints/tags.py index fffd419..0964963 100644 --- a/app/api/endpoints/tags.py +++ b/app/api/endpoints/tags.py @@ -1,13 +1,13 @@ - -from fastapi import APIRouter, HTTPException, Depends +from fastapi import APIRouter, Depends from app.core.database import get_db_connection +from app.core.response import create_api_response from app.models.models import Tag from typing import List import mysql.connector router = APIRouter() -@router.get("/tags/", response_model=List[Tag]) +@router.get("/tags/") def get_all_tags(): """_summary_ 获取所有标签 @@ -18,12 +18,12 @@ def get_all_tags(): with connection.cursor(dictionary=True) as cursor: cursor.execute(query) tags = cursor.fetchall() - return tags + return create_api_response(code="200", message="获取标签列表成功", data=tags) except mysql.connector.Error as err: print(f"Error: {err}") - raise HTTPException(status_code=500, detail="Failed to retrieve tags from database.") + return create_api_response(code="500", message="获取标签失败") -@router.post("/tags/", response_model=Tag) +@router.post("/tags/") def create_tag(tag_in: Tag): """_summary_ 创建一个新标签 @@ -36,10 +36,11 @@ def create_tag(tag_in: Tag): cursor.execute(query, (tag_in.name, tag_in.color)) connection.commit() tag_id = cursor.lastrowid - return {"id": tag_id, "name": tag_in.name, "color": tag_in.color} + new_tag = {"id": tag_id, "name": tag_in.name, "color": tag_in.color} + return create_api_response(code="200", message="标签创建成功", data=new_tag) except mysql.connector.IntegrityError: connection.rollback() - raise HTTPException(status_code=400, detail=f"Tag '{tag_in.name}' already exists.") + return create_api_response(code="400", message=f"标签 '{tag_in.name}' 已存在") except mysql.connector.Error as err: print(f"Error: {err}") - raise HTTPException(status_code=500, detail="Failed to create tag in database.") + return create_api_response(code="500", message="创建标签失败") \ No newline at end of file diff --git a/app/api/endpoints/users.py b/app/api/endpoints/users.py index c752036..a6e1fe6 100644 --- a/app/api/endpoints/users.py +++ b/app/api/endpoints/users.py @@ -1,8 +1,8 @@ - -from fastapi import APIRouter, HTTPException, Depends +from fastapi import APIRouter, Depends from app.models.models import UserInfo, PasswordChangeRequest, UserListResponse, CreateUserRequest, UpdateUserRequest, RoleInfo from app.core.database import get_db_connection from app.core.auth import get_current_user +from app.core.response import create_api_response import app.core.config as config_module import hashlib import datetime @@ -22,68 +22,60 @@ def hash_password(password: str) -> str: def get_all_roles(current_user: dict = Depends(get_current_user)): """获取所有角色列表""" if current_user['role_id'] != 1: # 1 is admin - raise HTTPException(status_code=403, detail="仅管理员有权限查看角色列表") + return create_api_response(code="403", message="仅管理员有权限查看角色列表") with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) cursor.execute("SELECT role_id, role_name FROM roles ORDER BY role_id") roles = cursor.fetchall() - return [RoleInfo(**role) for role in roles] + return create_api_response(code="200", message="获取角色列表成功", data=[RoleInfo(**role).dict() for role in roles]) -@router.post("/users", status_code=201) +@router.post("/users") def create_user(request: CreateUserRequest, current_user: dict = Depends(get_current_user)): if current_user['role_id'] != 1: # 1 is admin - raise HTTPException(status_code=403, detail="仅管理员有权限创建用户") + return create_api_response(code="403", message="仅管理员有权限创建用户") - # Validate email format if not validate_email(request.email): - raise HTTPException(status_code=400, detail="邮箱格式不正确") + return create_api_response(code="400", message="邮箱格式不正确") with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - # Check if username exists cursor.execute("SELECT user_id FROM users WHERE username = %s", (request.username,)) if cursor.fetchone(): - raise HTTPException(status_code=400, detail="用户名已存在") + return create_api_response(code="400", message="用户名已存在") - # Use provided password or default password password = request.password if request.password else config_module.DEFAULT_RESET_PASSWORD hashed_password = hash_password(password) - # Insert new user query = "INSERT INTO users (username, password_hash, caption, email, role_id, created_at) VALUES (%s, %s, %s, %s, %s, %s)" created_at = datetime.datetime.utcnow() cursor.execute(query, (request.username, hashed_password, request.caption, request.email, request.role_id, created_at)) connection.commit() - return {"message": "用户创建成功"} + return create_api_response(code="200", message="用户创建成功") -@router.put("/users/{user_id}", response_model=UserInfo) +@router.put("/users/{user_id}") def update_user(user_id: int, request: UpdateUserRequest, current_user: dict = Depends(get_current_user)): if current_user['role_id'] != 1: # 1 is admin - raise HTTPException(status_code=403, detail="仅管理员有权限修改用户信息") + return create_api_response(code="403", message="仅管理员有权限修改用户信息") - # Validate email format if provided if request.email and not validate_email(request.email): - raise HTTPException(status_code=400, detail="邮箱格式不正确") + return create_api_response(code="400", message="邮箱格式不正确") with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - # Check if user exists cursor.execute("SELECT user_id, username, caption, email, role_id FROM users WHERE user_id = %s", (user_id,)) existing_user = cursor.fetchone() if not existing_user: - raise HTTPException(status_code=404, detail="用户不存在") + return create_api_response(code="404", message="用户不存在") - # Check if username is being changed and if it already exists if request.username and request.username != existing_user['username']: cursor.execute("SELECT user_id FROM users WHERE username = %s AND user_id != %s", (request.username, user_id)) if cursor.fetchone(): - raise HTTPException(status_code=400, detail="用户名已存在") + return create_api_response(code="400", message="用户名已存在") - # Prepare update data, using existing values if not provided update_data = { 'username': request.username if request.username else existing_user['username'], 'caption': request.caption if request.caption else existing_user['caption'], @@ -91,12 +83,10 @@ def update_user(user_id: int, request: UpdateUserRequest, current_user: dict = D 'role_id': request.role_id if request.role_id is not None else existing_user['role_id'] } - # Update user query = "UPDATE users SET username = %s, caption = %s, email = %s, role_id = %s WHERE user_id = %s" cursor.execute(query, (update_data['username'], update_data['caption'], update_data['email'], update_data['role_id'], user_id)) connection.commit() - # Return updated user info cursor.execute(''' SELECT u.user_id, u.username, u.caption, u.email, u.created_at, u.role_id, r.role_name FROM users u @@ -105,7 +95,7 @@ def update_user(user_id: int, request: UpdateUserRequest, current_user: dict = D ''', (user_id,)) updated_user = cursor.fetchone() - return UserInfo( + user_info = UserInfo( user_id=updated_user['user_id'], username=updated_user['username'], caption=updated_user['caption'], @@ -113,62 +103,56 @@ def update_user(user_id: int, request: UpdateUserRequest, current_user: dict = D created_at=updated_user['created_at'], role_id=updated_user['role_id'], role_name=updated_user['role_name'], - meetings_created=0, # This is not accurate, but it is not displayed in the list + meetings_created=0, meetings_attended=0 ) + return create_api_response(code="200", message="用户信息更新成功", data=user_info.dict()) @router.delete("/users/{user_id}") def delete_user(user_id: int, current_user: dict = Depends(get_current_user)): if current_user['role_id'] != 1: # 1 is admin - raise HTTPException(status_code=403, detail="仅管理员有权限删除用户") + return create_api_response(code="403", message="仅管理员有权限删除用户") with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - # Check if user exists cursor.execute("SELECT user_id FROM users WHERE user_id = %s", (user_id,)) if not cursor.fetchone(): - raise HTTPException(status_code=404, detail="用户不存在") + return create_api_response(code="404", message="用户不存在") - # Delete user cursor.execute("DELETE FROM users WHERE user_id = %s", (user_id,)) connection.commit() - return {"message": "用户删除成功"} + return create_api_response(code="200", message="用户删除成功") @router.post("/users/{user_id}/reset-password") def reset_password(user_id: int, current_user: dict = Depends(get_current_user)): if current_user['role_id'] != 1: # 1 is admin - raise HTTPException(status_code=403, detail="仅管理员有权限重置密码") + return create_api_response(code="403", message="仅管理员有权限重置密码") with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - # Check if user exists cursor.execute("SELECT user_id FROM users WHERE user_id = %s", (user_id,)) if not cursor.fetchone(): - raise HTTPException(status_code=404, detail="用户不存在") + return create_api_response(code="404", message="用户不存在") - # Hash password hashed_password = hash_password(config_module.DEFAULT_RESET_PASSWORD) - # Update user password query = "UPDATE users SET password_hash = %s WHERE user_id = %s" cursor.execute(query, (hashed_password, user_id)) connection.commit() - return {"message": f"用户 {user_id} 的密码已重置"} + return create_api_response(code="200", message=f"用户 {user_id} 的密码已重置") -@router.get("/users", response_model=UserListResponse) +@router.get("/users") def get_all_users(page: int = 1, size: int = 10, current_user: dict = Depends(get_current_user)): with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) - # Get total count cursor.execute("SELECT COUNT(*) as total FROM users") total = cursor.fetchone()['total'] - # Get paginated users with role names offset = (page - 1) * size query = ''' SELECT @@ -186,9 +170,10 @@ def get_all_users(page: int = 1, size: int = 10, current_user: dict = Depends(ge user_list = [UserInfo(**user) for user in users] - return UserListResponse(users=user_list, total=total) + response_data = UserListResponse(users=user_list, total=total) + return create_api_response(code="200", message="获取用户列表成功", data=response_data.dict()) -@router.get("/users/{user_id}", response_model=UserInfo) +@router.get("/users/{user_id}") def get_user_info(user_id: int, current_user: dict = Depends(get_current_user)): with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) @@ -203,7 +188,7 @@ def get_user_info(user_id: int, current_user: dict = Depends(get_current_user)): user = cursor.fetchone() if not user: - raise HTTPException(status_code=404, detail="用户不存在") + return create_api_response(code="404", message="用户不存在") created_query = "SELECT COUNT(*) as count FROM meetings WHERE user_id = %s" cursor.execute(created_query, (user_id,)) @@ -213,7 +198,7 @@ def get_user_info(user_id: int, current_user: dict = Depends(get_current_user)): cursor.execute(attended_query, (user_id,)) meetings_attended = cursor.fetchone()['count'] - return UserInfo( + user_info = UserInfo( user_id=user['user_id'], username=user['username'], caption=user['caption'], @@ -224,11 +209,12 @@ def get_user_info(user_id: int, current_user: dict = Depends(get_current_user)): meetings_created=meetings_created, meetings_attended=meetings_attended ) + return create_api_response(code="200", message="获取用户信息成功", data=user_info.dict()) @router.put("/users/{user_id}/password") def update_password(user_id: int, request: PasswordChangeRequest, current_user: dict = Depends(get_current_user)): if user_id != current_user['user_id'] and current_user['role_id'] != 1: - raise HTTPException(status_code=403, detail="没有权限修改其他用户的密码") + return create_api_response(code="403", message="没有权限修改其他用户的密码") with get_db_connection() as connection: cursor = connection.cursor(dictionary=True) @@ -237,15 +223,14 @@ def update_password(user_id: int, request: PasswordChangeRequest, current_user: user = cursor.fetchone() if not user: - raise HTTPException(status_code=404, detail="用户不存在") + return create_api_response(code="404", message="用户不存在") - # If not admin, verify old password if current_user['role_id'] != 1: if user['password_hash'] != hash_password(request.old_password): - raise HTTPException(status_code=400, detail="旧密码错误") + return create_api_response(code="400", message="旧密码错误") new_password_hash = hash_password(request.new_password) cursor.execute("UPDATE users SET password_hash = %s WHERE user_id = %s", (new_password_hash, user_id)) connection.commit() - return {"message": "密码修改成功"} + return create_api_response(code="200", message="密码修改成功") \ No newline at end of file diff --git a/app/core/config.py b/app/core/config.py index ffff929..1a9f5fb 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -23,7 +23,7 @@ DATABASE_CONFIG = { 'host': os.getenv('DB_HOST', '10.100.51.161'), 'user': os.getenv('DB_USER', 'root'), 'password': os.getenv('DB_PASSWORD', 'sagacity'), - 'database': os.getenv('DB_NAME', 'imeeting'), + 'database': os.getenv('DB_NAME', 'imeeting_dev'), 'port': int(os.getenv('DB_PORT', '3306')), 'charset': 'utf8mb4' } @@ -42,7 +42,7 @@ QINIU_DOMAIN = os.getenv('QINIU_DOMAIN', 't0vogyxkz.hn-bkt.clouddn.com') # 应用配置 APP_CONFIG = { - 'base_url': os.getenv('BASE_URL', 'http://imeeting.unisspace.com') + 'base_url': os.getenv('BASE_URL', 'http://dev.imeeting.unisspace.com') } # Redis配置 diff --git a/app/core/response.py b/app/core/response.py new file mode 100644 index 0000000..3561d4d --- /dev/null +++ b/app/core/response.py @@ -0,0 +1,14 @@ +from typing import Union +from fastapi.responses import JSONResponse +from fastapi.encoders import jsonable_encoder + +def create_api_response(code: str, message: str, data: Union[dict, list, None] = None) -> JSONResponse: + """Creates a standardized API JSON response.""" + return JSONResponse( + status_code=200, + content={ + "code": str(code), + "message": message, + "data": jsonable_encoder(data) if data is not None else {}, + }, + ) \ No newline at end of file diff --git a/config/.DS_Store b/config/.DS_Store new file mode 100644 index 0000000..e129a5b Binary files /dev/null and b/config/.DS_Store differ diff --git a/config/system_config.json b/config/system_config.json index ccfc771..9cec9a3 100644 --- a/config/system_config.json +++ b/config/system_config.json @@ -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字以内", - "DEFAULT_RESET_PASSWORD": "123456", + "system_prompt": "你是一个专业的会议记录分析助手。请根据提供的会议转录内容,生成简洁明了的会议总结。\n\n总结包括五个部分(名称严格一致,生成为MD二级目录):\n1. 会议概述 - 简要说明会议的主要目的和背景(生成MD引用)\n2. 主要讨论点 - 列出会议中讨论的重要话题和内容\n3. 决策事项 - 明确记录会议中做出的决定和结论\n4. 待办事项 - 列出需要后续跟进的任务和责任人\n5. 关键信息 - 其他重要的信息点\n\n输出要求:\n- 保持客观中性,不添加个人观点\n- 使用简洁、准确的中文表达\n- 按重要性排序各项内容\n- 如果某个部分没有相关内容,可以说明\"无相关内容\"\n- 总字数控制在500字以内", + "DEFAULT_RESET_PASSWORD": "111111", "MAX_FILE_SIZE": 209715200, "MAX_IMAGE_SIZE": 10485760 } \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index f76e7f7..cc19d27 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -46,6 +46,7 @@ services: volumes: # 挂载上传目录保持数据持久化 - ./uploads:/app/uploads + - ./config:/app/config restart: unless-stopped container_name: imeeting-backend extra_hosts: diff --git a/main.py b/main.py index e873a5e..4e8d1d6 100644 --- a/main.py +++ b/main.py @@ -3,8 +3,7 @@ from fastapi import FastAPI, Request, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from app.api.endpoints import auth, users, meetings, tags, admin -from app.core.config import UPLOAD_DIR, API_CONFIG, MAX_FILE_SIZE -from app.services.async_llm_service import async_llm_service +from app.core.config import UPLOAD_DIR, API_CONFIG from app.api.endpoints.admin import load_system_config import os @@ -47,7 +46,7 @@ def health_check(): return { "status": "healthy", "service": "iMeeting API", - "version": "1.0.0" + "version": "1.0.2" } if __name__ == "__main__":