diff --git a/.DS_Store b/.DS_Store index c8ee9a3..20a21f1 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/app.zip b/app.zip new file mode 100644 index 0000000..92ba9aa Binary files /dev/null and b/app.zip differ diff --git a/app/api/endpoints/meetings.py b/app/api/endpoints/meetings.py index fdaa93d..0ddff02 100644 --- a/app/api/endpoints/meetings.py +++ b/app/api/endpoints/meetings.py @@ -50,6 +50,88 @@ def _process_tags(cursor, tag_string: Optional[str], creator_id: Optional[int] = tags_data = cursor.fetchall() return [Tag(**tag) for tag in tags_data] +def _get_meeting_overall_status(meeting_id: int) -> dict: + """ + 获取会议的整体进度状态(包含转译和LLM两个阶段) + + Returns: + dict: { + "overall_status": "pending" | "transcribing" | "summarizing" | "completed" | "failed", + "overall_progress": 0-100, + "current_stage": "transcription" | "llm" | "completed", + "transcription": {status, progress, task_id, error_message, created_at}, + "llm": {status, progress, task_id, error_message, created_at} + } + """ + # 获取转译状态 + transcription_status = transcription_service.get_meeting_transcription_status(meeting_id) + trans_data = { + "status": transcription_status.get('status', 'pending') if transcription_status else 'pending', + "progress": transcription_status.get('progress', 0) if transcription_status else 0, + "task_id": transcription_status.get('task_id') if transcription_status else None, + "error_message": transcription_status.get('error_message') if transcription_status else None, + "created_at": transcription_status.get('created_at') if transcription_status else None + } + + # 获取LLM状态 + llm_status = async_meeting_service.get_meeting_llm_status(meeting_id) + llm_data = { + "status": llm_status.get('status', 'pending') if llm_status else 'pending', + "progress": llm_status.get('progress', 0) if llm_status else 0, + "task_id": llm_status.get('task_id') if llm_status else None, + "error_message": llm_status.get('error_message') if llm_status else None, + "created_at": llm_status.get('created_at') if llm_status else None + } + + # 计算整体状态和进度 + trans_status_val = trans_data["status"] + llm_status_val = llm_data["status"] + + # 判断是否有失败 + if trans_status_val == 'failed': + overall_status = "failed" + current_stage = "transcription" + overall_progress = 0 + elif llm_status_val == 'failed': + overall_status = "failed" + current_stage = "llm" + overall_progress = 50 # 转译已完成 + # 判断当前阶段 + elif trans_status_val == 'completed' and llm_status_val == 'completed': + overall_status = "completed" + current_stage = "completed" + overall_progress = 100 + elif trans_status_val == 'completed': + # 转译完成,进入LLM阶段 + if llm_status_val in ['pending', 'processing']: + overall_status = "summarizing" + current_stage = "llm" + overall_progress = 50 + int(llm_data["progress"] * 0.5) + else: + # llm还未开始 + overall_status = "summarizing" + current_stage = "llm" + overall_progress = 50 + else: + # 还在转译阶段 + if trans_status_val in ['pending', 'processing']: + overall_status = "transcribing" + current_stage = "transcription" + overall_progress = int(trans_data["progress"] * 0.5) + else: + # 转译还未开始 + overall_status = "pending" + current_stage = "transcription" + overall_progress = 0 + + return { + "overall_status": overall_status, + "overall_progress": overall_progress, + "current_stage": current_stage, + "transcription": trans_data, + "llm": llm_data + } + @router.get("/meetings") def get_meetings( current_user: dict = Depends(get_current_user), @@ -164,7 +246,8 @@ def get_meetings( 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_list + attendees=attendees, creator_id=meeting['creator_id'], creator_username=meeting['creator_username'], tags=tags_list, + access_password=meeting.get('access_password') )) return create_api_response(code="200", message="获取会议列表成功", data={ @@ -882,12 +965,18 @@ def get_meeting_preview_data(meeting_id: int): """ 获取会议预览数据(无需登录认证) 用于二维码扫描后的预览页面 + + 返回状态码说明: + - 200: 会议已完成(summary已生成) + - 400: 会议处理中(转译或总结阶段) + - 503: 处理失败(转译或总结失败) + - 404: 会议不存在 """ 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.prompt_id, m.user_id as creator_id, u.caption as creator_username, @@ -903,15 +992,55 @@ def get_meeting_preview_data(meeting_id: int): 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="该会议总结尚未生成") + # 获取整体进度状态(两阶段) + progress_info = _get_meeting_overall_status(meeting_id) + overall_status = progress_info["overall_status"] + # 根据整体状态返回不同响应 + + # 情况1: 任一阶段失败 → 返回503 + if overall_status == "failed": + failed_stage = progress_info["current_stage"] + error_info = progress_info["transcription"] if failed_stage == "transcription" else progress_info["llm"] + error_message = error_info["error_message"] or "处理失败" + + stage_name = "转译" if failed_stage == "transcription" else "总结" + return create_api_response( + code="503", + message=f"会议{stage_name}失败: {error_message}", + data={ + "meeting_id": meeting_id, + "title": meeting['title'], + "processing_status": progress_info + } + ) + + # 情况2: 处理中(转译或总结阶段)→ 返回400 + if overall_status in ["pending", "transcribing", "summarizing"]: + stage_descriptions = { + "pending": "等待开始", + "transcribing": "正在转译音频", + "summarizing": "正在生成总结" + } + return create_api_response( + code="400", + message=f"会议正在处理中: {stage_descriptions[overall_status]}", + data={ + "meeting_id": meeting_id, + "title": meeting['title'], + "processing_status": progress_info + } + ) + + # 情况3: 全部完成 → 返回200,提供完整预览数据 + if overall_status == "completed" and meeting['summary']: # 获取参会人员信息 - 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] + with get_db_connection() as connection: + cursor = connection.cursor(dictionary=True) + 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 = { @@ -924,7 +1053,8 @@ def get_meeting_preview_data(meeting_id: int): "prompt_name": meeting['prompt_name'], "attendees": attendees, "attendees_count": len(attendees), - "has_password": bool(meeting.get('access_password')) # 新增:是否设置了访问密码 + "has_password": bool(meeting.get('access_password')), + "processing_status": progress_info # 附带进度信息供调试 } return create_api_response(code="200", message="获取会议预览数据成功", data=preview_data) diff --git a/app/services/async_meeting_service.py b/app/services/async_meeting_service.py index 808d840..9ec1f9b 100644 --- a/app/services/async_meeting_service.py +++ b/app/services/async_meeting_service.py @@ -321,6 +321,57 @@ class AsyncMeetingService: print(f"Error getting meeting LLM tasks: {e}") return [] + def get_meeting_llm_status(self, meeting_id: int) -> Optional[Dict[str, Any]]: + """ + 获取会议的最新LLM任务状态(与transcription对齐) + + Args: + meeting_id: 会议ID + + Returns: + Optional[Dict]: 任务状态信息,如果没有任务返回None + """ + try: + with get_db_connection() as connection: + cursor = connection.cursor(dictionary=True) + + # 查询最新的LLM任务 + query = """ + SELECT task_id, status, progress, created_at, completed_at, error_message + FROM llm_tasks + WHERE meeting_id = %s + ORDER BY created_at DESC + LIMIT 1 + """ + cursor.execute(query, (meeting_id,)) + task_record = cursor.fetchone() + + cursor.close() + + if not task_record: + return None + + # 如果任务还在进行中,获取最新状态 + if task_record['status'] in ['pending', 'processing']: + try: + return self.get_task_status(task_record['task_id']) + except Exception as e: + print(f"Failed to get latest LLM task status for meeting {meeting_id}, returning DB status. Error: {e}") + + return { + 'task_id': task_record['task_id'], + 'status': task_record['status'], + 'progress': task_record['progress'] or 0, + 'meeting_id': meeting_id, + 'created_at': task_record['created_at'].isoformat() if task_record['created_at'] else None, + 'completed_at': task_record['completed_at'].isoformat() if task_record['completed_at'] else None, + 'error_message': task_record['error_message'] + } + + except Exception as e: + print(f"Error getting meeting LLM status: {e}") + return None + def _update_task_status_in_redis(self, task_id: str, status: str, progress: int, message: str = None, result: str = None, error_message: str = None): """更新Redis中的任务状态""" try: diff --git a/app/utils/apk_parser.py b/app/utils/apk_parser.py index 226fd0c..dc1e157 100644 --- a/app/utils/apk_parser.py +++ b/app/utils/apk_parser.py @@ -1,24 +1,20 @@ """ APK解析工具 -用于从APK文件中提取版本信息 +用于从APK文件中提取版本信息(使用 pyaxmlparser) """ -import zipfile -import xml.etree.ElementTree as ET -import struct -# 如果安装了 androguard,使用更可靠的解析方法 def parse_apk_with_androguard(apk_path): """ - 使用 androguard 库解析 APK - 需要先安装: pip install androguard + 解析APK文件,提取版本信息 + 使用 pyaxmlparser 库(轻量级,~1MB) """ try: - from androguard.core.apk import APK + from pyaxmlparser import APK apk = APK(apk_path) - version_code = apk.get_androidversion_code() - version_name = apk.get_androidversion_name() + version_code = apk.version_code + version_name = apk.version_name print(f"APK解析成功: version_code={version_code}, version_name={version_name}") @@ -26,11 +22,12 @@ def parse_apk_with_androguard(apk_path): 'version_code': int(version_code) if version_code else None, 'version_name': version_name } - except ImportError as ie: - print(f"androguard 导入失败: {str(ie)}") - return parse_apk(apk_path) + except ImportError: + print("错误: pyaxmlparser 未安装") + print("请运行: pip install pyaxmlparser") + return None except Exception as e: - print(f"使用androguard解析APK失败: {str(e)}") + print(f"APK解析失败: {str(e)}") import traceback traceback.print_exc() return None diff --git a/requirements-prod.txt b/requirements-prod.txt index 840a5d4..475ac80 100644 --- a/requirements-prod.txt +++ b/requirements-prod.txt @@ -20,4 +20,4 @@ python-multipart psutil # APK Parsing -androguard +pyaxmlparser diff --git a/requirements.txt b/requirements.txt index 7573184..cc8c91b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,4 @@ dashscope PyJWT>=2.8.0 python-jose[cryptography]>=3.3.0 psutil -androguard +pyaxmlparser