# 文件位置: app/routes/api.py # 文件名: api.py """ API路由 处理研究相关的API请求 """ from flask import Blueprint, request, jsonify, current_app, send_file from app.services.research_manager import ResearchManager from app.services.task_manager import task_manager from app.utils.validators import validate_question, validate_outline_feedback import os api_bp = Blueprint('api', __name__) research_manager = ResearchManager() @api_bp.route('/research', methods=['POST']) def create_research(): """创建新的研究任务""" try: data = request.get_json() # 验证输入 question = data.get('question', '').strip() error = validate_question(question) if error: return jsonify({"error": error}), 400 # 创建研究会话 session = research_manager.create_session(question) # 自动开始研究(可选) auto_start = data.get('auto_start', True) if auto_start: result = research_manager.start_research(session.id) return jsonify({ "session_id": session.id, "status": "started", "message": "研究已开始", "created_at": session.created_at.isoformat() }) else: return jsonify({ "session_id": session.id, "status": "created", "message": "研究会话已创建,等待开始", "created_at": session.created_at.isoformat() }) except Exception as e: current_app.logger.error(f"创建研究失败: {e}") return jsonify({"error": str(e)}), 500 @api_bp.route('/research//start', methods=['POST']) def start_research(session_id): """手动开始研究""" try: result = research_manager.start_research(session_id) return jsonify(result) except ValueError as e: return jsonify({"error": str(e)}), 404 except Exception as e: current_app.logger.error(f"开始研究失败: {e}") return jsonify({"error": str(e)}), 500 @api_bp.route('/research//status', methods=['GET']) def get_research_status(session_id): """获取研究状态""" try: status = research_manager.get_session_status(session_id) if "error" in status: return jsonify(status), 404 # 添加任务信息 tasks = task_manager.get_session_tasks(session_id) status['tasks'] = tasks return jsonify(status) except Exception as e: current_app.logger.error(f"获取状态失败: {e}") return jsonify({"error": str(e)}), 500 @api_bp.route('/research//outline', methods=['GET']) def get_research_outline(session_id): """获取研究大纲""" try: session = research_manager.get_session(session_id) if not session: return jsonify({"error": "Session not found"}), 404 if not session.outline: return jsonify({"error": "Outline not yet created"}), 400 return jsonify({ "main_topic": session.outline.main_topic, "research_questions": session.outline.research_questions, "sub_topics": [ { "id": st.id, "topic": st.topic, "explain": st.explain, "priority": st.priority, "status": st.status } for st in session.outline.sub_topics ], "version": session.outline.version }) except Exception as e: current_app.logger.error(f"获取大纲失败: {e}") return jsonify({"error": str(e)}), 500 @api_bp.route('/research//outline', methods=['PUT']) def update_research_outline(session_id): """更新研究大纲(用户反馈)""" try: data = request.get_json() feedback = data.get('feedback', '').strip() error = validate_outline_feedback(feedback) if error: return jsonify({"error": error}), 400 # TODO: 实现大纲更新逻辑 # 这需要调用AI服务来修改大纲 return jsonify({ "message": "大纲更新请求已接收", "status": "processing" }) except Exception as e: current_app.logger.error(f"更新大纲失败: {e}") return jsonify({"error": str(e)}), 500 @api_bp.route('/research//cancel', methods=['POST']) def cancel_research(session_id): """取消研究""" try: # 取消任务 cancelled_count = task_manager.cancel_session_tasks(session_id) # 更新会话状态 result = research_manager.cancel_research(session_id) if "error" in result: return jsonify(result), 404 result['cancelled_tasks'] = cancelled_count return jsonify(result) except Exception as e: current_app.logger.error(f"取消研究失败: {e}") return jsonify({"error": str(e)}), 500 @api_bp.route('/research//report', methods=['GET']) def get_research_report(session_id): """获取研究报告""" try: format = request.args.get('format', 'json') report_content = research_manager.get_research_report(session_id) if not report_content: return jsonify({"error": "Report not available"}), 404 if format == 'markdown': # 返回Markdown文件 report_path = os.path.join( current_app.config['REPORTS_DIR'], f"{session_id}.md" ) if os.path.exists(report_path): return send_file( report_path, mimetype='text/markdown', as_attachment=True, download_name=f"research_report_{session_id}.md" ) else: # 临时创建文件 with open(report_path, 'w', encoding='utf-8') as f: f.write(report_content) return send_file( report_path, mimetype='text/markdown', as_attachment=True, download_name=f"research_report_{session_id}.md" ) else: # 返回JSON格式 return jsonify({ "session_id": session_id, "report": report_content, "format": "markdown" }) except Exception as e: current_app.logger.error(f"获取报告失败: {e}") return jsonify({"error": str(e)}), 500 @api_bp.route('/research//subtopic/', methods=['GET']) def get_subtopic_detail(session_id, subtopic_id): """获取子主题详情""" try: session = research_manager.get_session(session_id) if not session: return jsonify({"error": "Session not found"}), 404 if not session.outline: return jsonify({"error": "Outline not created"}), 400 # 找到对应的子主题 subtopic = None for st in session.outline.sub_topics: if st.id == subtopic_id: subtopic = st break if not subtopic: return jsonify({"error": "Subtopic not found"}), 404 return jsonify({ "id": subtopic.id, "topic": subtopic.topic, "explain": subtopic.explain, "priority": subtopic.priority, "status": subtopic.status, "search_count": subtopic.search_count, "max_searches": subtopic.max_searches, "progress": subtopic.get_total_searches() / subtopic.max_searches * 100, "has_report": subtopic.report is not None }) except Exception as e: current_app.logger.error(f"获取子主题详情失败: {e}") return jsonify({"error": str(e)}), 500 @api_bp.route('/research/sessions', methods=['GET']) def list_research_sessions(): """列出所有研究会话""" try: limit = request.args.get('limit', 20, type=int) offset = request.args.get('offset', 0, type=int) sessions = research_manager.list_sessions(limit=limit, offset=offset) return jsonify({ "sessions": sessions, "total": len(sessions), "limit": limit, "offset": offset }) except Exception as e: current_app.logger.error(f"列出会话失败: {e}") return jsonify({"error": str(e)}), 500 @api_bp.route('/tasks/status', methods=['GET']) def get_tasks_status(): """获取任务管理器状态""" try: tasks = task_manager.tasks status_counts = { 'pending': 0, 'running': 0, 'completed': 0, 'failed': 0, 'cancelled': 0 } for task in tasks.values(): status_counts[task.status.value] += 1 return jsonify({ "total_tasks": len(tasks), "status_counts": status_counts, "sessions_count": len(task_manager.session_tasks) }) except Exception as e: current_app.logger.error(f"获取任务状态失败: {e}") return jsonify({"error": str(e)}), 500 @api_bp.route('/test/connections', methods=['GET']) def test_connections(): """测试API连接(仅开发环境)""" if not current_app.debug: return jsonify({"error": "Not available in production"}), 403 from app.services.search_service import SearchService from app.services.ai_service import AIService results = { "deepseek_api": False, "tavily_api": False, "task_manager": False } try: # 测试DeepSeek API ai_service = AIService() test_result = ai_service.analyze_question_type("test question") results["deepseek_api"] = bool(test_result) except Exception as e: current_app.logger.error(f"DeepSeek API测试失败: {e}") try: # 测试Tavily API search_service = SearchService() results["tavily_api"] = search_service.test_connection() except Exception as e: current_app.logger.error(f"Tavily API测试失败: {e}") # 测试任务管理器 try: # 提交一个测试任务 def test_task(): return "test" task_id = task_manager.submit_task(test_task) status = task_manager.get_task_status(task_id) results["task_manager"] = status is not None except Exception as e: current_app.logger.error(f"任务管理器测试失败: {e}") return jsonify({ "connections": results, "all_connected": all(results.values()) })