Add final report download button (#329)

* fix(app): 改进应用健康检查机制并更新默认配置

添加专用的健康检查路径和代理配置,重构健康检查URL构建逻辑
增加健康检查失败时的日志记录
延长应用启动等待时间至90秒

* style(templates): 统一CSS选择器缩进格式并修复空格问题

* feat(报告下载): 实现报告文件下载功能并增强任务状态管理

- 在ReportAgent中修改generate_report返回包含文件路径的字典
- 在ReportTask中添加文件路径相关字段
- 新增/download接口用于下载报告文件
- 在前端添加下载按钮及相关控制逻辑
- 完善任务状态显示,增加文件路径信息

* feat(report): 添加报告下载功能并优化状态管理

- 在ReportAgent中返回报告文件保存路径信息
- 新增Flask接口/download/<task_id>用于下载报告文件
- 在前端添加下载按钮及相关控制逻辑
- 修复报告生成状态重置问题
- 优化健康检查URL构建和代理设置
- 统一CSS样式中的空格和缩进

---------

Co-authored-by: HKLHaoBin <we3q@qq.com>
Co-authored-by: Zhang Yuxiang <51037789+NTFago@users.noreply.github.com>
This commit is contained in:
BaiFu
2025-11-13 00:48:52 +08:00
committed by GitHub
parent 516f37bd25
commit f004407f4b
5 changed files with 358 additions and 69 deletions
+29 -6
View File
@@ -190,10 +190,16 @@ class ReportAgent:
save_report: 是否保存报告到文件
Returns:
最终HTML报告内容
dict: 包含HTML内容与保存文件信息
"""
start_time = datetime.now()
# 为新的查询重置状态,确保文件命名信息完整
self.state = ReportState(query=query)
self.state.metadata.query = query
self.state.query = query
self.state.mark_processing()
logger.info(f"开始生成报告: {query}")
logger.info(f"输入数据 - 报告数量: {len(reports)}, 论坛日志长度: {len(forum_logs)}")
@@ -205,8 +211,9 @@ class ReportAgent:
html_report = self._generate_html_report(query, reports, forum_logs, template_result)
# Step 3: 保存报告
saved_files = {}
if save_report:
self._save_report(html_report)
saved_files = self._save_report(html_report)
# 更新生成时间
end_time = datetime.now()
@@ -215,7 +222,10 @@ class ReportAgent:
logger.info(f"报告生成完成,耗时: {generation_time:.2f}")
return html_report
return {
'html_content': html_report,
**saved_files
}
except Exception as e:
logger.exception(f"报告生成过程中发生错误: {str(e)}")
@@ -357,13 +367,26 @@ class ReportAgent:
with open(filepath, 'w', encoding='utf-8') as f:
f.write(html_content)
logger.info(f"报告已保存到: {filepath}")
abs_report_path = os.path.abspath(filepath)
rel_report_path = os.path.relpath(abs_report_path, os.getcwd())
logger.info(f"报告已保存到: {abs_report_path}")
# 保存状态
state_filename = f"report_state_{query_safe}_{timestamp}.json"
state_filepath = os.path.join(self.config.OUTPUT_DIR, state_filename)
self.state.save_to_file(state_filepath)
logger.info(f"状态已保存到: {state_filepath}")
abs_state_path = os.path.abspath(state_filepath)
rel_state_path = os.path.relpath(abs_state_path, os.getcwd())
logger.info(f"状态已保存到: {abs_state_path}")
return {
'report_filename': filename,
'report_filepath': abs_report_path,
'report_relative_path': rel_report_path,
'state_filename': state_filename,
'state_filepath': abs_state_path,
'state_relative_path': rel_state_path
}
def get_progress_summary(self) -> Dict[str, Any]:
"""获取进度摘要"""
@@ -492,4 +515,4 @@ def create_agent(config_file: Optional[str] = None) -> ReportAgent:
"""
config = Settings() # 以空配置初始化,而从从环境变量初始化
return ReportAgent(config)
return ReportAgent(config)
+61 -5
View File
@@ -8,7 +8,7 @@ import json
import threading
import time
from datetime import datetime
from flask import Blueprint, request, jsonify, Response
from flask import Blueprint, request, jsonify, Response, send_file
from typing import Dict, Any
from loguru import logger
from .agent import ReportAgent, create_agent
@@ -50,6 +50,11 @@ class ReportTask:
self.created_at = datetime.now()
self.updated_at = datetime.now()
self.html_content = ""
self.report_file_path = ""
self.report_file_relative_path = ""
self.report_file_name = ""
self.state_file_path = ""
self.state_file_relative_path = ""
def update_status(self, status: str, progress: int = None, error_message: str = ""):
"""更新任务状态"""
@@ -70,7 +75,10 @@ class ReportTask:
'error_message': self.error_message,
'created_at': self.created_at.isoformat(),
'updated_at': self.updated_at.isoformat(),
'has_result': bool(self.html_content)
'has_result': bool(self.html_content),
'report_file_ready': bool(self.report_file_path),
'report_file_name': self.report_file_name,
'report_file_path': self.report_file_relative_path
}
@@ -119,7 +127,7 @@ def run_report_generation(task: ReportTask, query: str, custom_template: str = "
task.update_status("running", 50)
# 生成报告
html_report = report_agent.generate_report(
generation_result = report_agent.generate_report(
query=query,
reports=content['reports'],
forum_logs=content['forum_logs'],
@@ -127,10 +135,17 @@ def run_report_generation(task: ReportTask, query: str, custom_template: str = "
save_report=True
)
html_report = generation_result.get('html_content', '')
task.update_status("running", 90)
# 保存结果
task.html_content = html_report
task.report_file_path = generation_result.get('report_filepath', '')
task.report_file_relative_path = generation_result.get('report_relative_path', '')
task.report_file_name = generation_result.get('report_filename', '')
task.state_file_path = generation_result.get('state_filepath', '')
task.state_file_relative_path = generation_result.get('state_relative_path', '')
task.update_status("completed", 100)
except Exception as e:
@@ -251,7 +266,10 @@ def get_progress(task_id: str):
'status': 'completed',
'progress': 100,
'error_message': '',
'has_result': True
'has_result': True,
'report_file_ready': False,
'report_file_name': '',
'report_file_path': ''
}
})
@@ -329,6 +347,44 @@ def get_result_json(task_id: str):
}), 500
@report_bp.route('/download/<task_id>', methods=['GET'])
def download_report(task_id: str):
"""下载已生成的报告HTML文件"""
try:
if not current_task or current_task.task_id != task_id:
return jsonify({
'success': False,
'error': '任务不存在'
}), 404
if current_task.status != "completed" or not current_task.report_file_path:
return jsonify({
'success': False,
'error': '报告尚未完成或尚未保存'
}), 400
if not os.path.exists(current_task.report_file_path):
return jsonify({
'success': False,
'error': '报告文件不存在或已被删除'
}), 404
download_name = current_task.report_file_name or os.path.basename(current_task.report_file_path)
return send_file(
current_task.report_file_path,
mimetype='text/html',
as_attachment=True,
download_name=download_name
)
except Exception as e:
logger.exception(f"下载报告失败: {str(e)}")
return jsonify({
'success': False,
'error': str(e)
}), 500
@report_bp.route('/cancel/<task_id>', methods=['POST'])
def cancel_task(task_id: str):
"""取消报告生成任务"""
@@ -478,4 +534,4 @@ def clear_log():
return jsonify({
'success': False,
'error': f'清空日志失败: {str(e)}'
}), 500
}), 500