马一丁

Fix the Error "'ReportTask' object has no attribute 'ir_file_path'" When Exporti…

…ng PDF and Ensure that Pango Dependencies are not Missing
@@ -9,7 +9,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \ @@ -9,7 +9,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
9 PATH="/root/.local/bin:${PATH}" \ 9 PATH="/root/.local/bin:${PATH}" \
10 PLAYWRIGHT_BROWSERS_PATH=/ms-playwright 10 PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
11 11
12 -# Install system dependencies required by scientific Python stack, Playwright, and Streamlit 12 +# Install system dependencies required by scientific Python stack, Playwright, Streamlit, and WeasyPrint PDF
13 RUN apt-get update && apt-get install -y --no-install-recommends \ 13 RUN apt-get update && apt-get install -y --no-install-recommends \
14 build-essential \ 14 build-essential \
15 curl \ 15 curl \
@@ -19,6 +19,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -19,6 +19,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
19 libgtk-3-0 \ 19 libgtk-3-0 \
20 libpango-1.0-0 \ 20 libpango-1.0-0 \
21 libpangocairo-1.0-0 \ 21 libpangocairo-1.0-0 \
  22 + libpangoft2-1.0-0 \
  23 + libgdk-pixbuf2.0-0 \
  24 + libffi-dev \
  25 + libcairo2 \
22 libatk1.0-0 \ 26 libatk1.0-0 \
23 libatk-bridge2.0-0 \ 27 libatk-bridge2.0-0 \
24 libxcb1 \ 28 libxcb1 \
@@ -160,6 +160,14 @@ def initialize_report_engine(): @@ -160,6 +160,14 @@ def initialize_report_engine():
160 try: 160 try:
161 report_agent = create_agent() 161 report_agent = create_agent()
162 logger.info("Report Engine初始化成功") 162 logger.info("Report Engine初始化成功")
  163 +
  164 + # 检测 PDF 生成依赖(Pango)
  165 + try:
  166 + from .utils.dependency_check import log_dependency_status
  167 + log_dependency_status()
  168 + except Exception as dep_err:
  169 + logger.warning(f"依赖检测失败: {dep_err}")
  170 +
163 return True 171 return True
164 except Exception as e: 172 except Exception as e:
165 logger.exception(f"Report Engine初始化失败: {str(e)}") 173 logger.exception(f"Report Engine初始化失败: {str(e)}")
@@ -198,6 +206,8 @@ class ReportTask: @@ -198,6 +206,8 @@ class ReportTask:
198 self.report_file_name = "" 206 self.report_file_name = ""
199 self.state_file_path = "" 207 self.state_file_path = ""
200 self.state_file_relative_path = "" 208 self.state_file_relative_path = ""
  209 + self.ir_file_path = ""
  210 + self.ir_file_relative_path = ""
201 # ====== 流式事件缓存与并发保护 ====== 211 # ====== 流式事件缓存与并发保护 ======
202 # 使用deque保存最近的事件,结合锁保证多线程下的安全访问 212 # 使用deque保存最近的事件,结合锁保证多线程下的安全访问
203 self.event_history: deque = deque(maxlen=1000) 213 self.event_history: deque = deque(maxlen=1000)
@@ -248,7 +258,9 @@ class ReportTask: @@ -248,7 +258,9 @@ class ReportTask:
248 'report_file_name': self.report_file_name, 258 'report_file_name': self.report_file_name,
249 'report_file_path': self.report_file_relative_path or self.report_file_path, 259 'report_file_path': self.report_file_relative_path or self.report_file_path,
250 'state_file_ready': bool(self.state_file_path), 260 'state_file_ready': bool(self.state_file_path),
251 - 'state_file_path': self.state_file_relative_path or self.state_file_path 261 + 'state_file_path': self.state_file_relative_path or self.state_file_path,
  262 + 'ir_file_ready': bool(self.ir_file_path),
  263 + 'ir_file_path': self.ir_file_relative_path or self.ir_file_path
252 } 264 }
253 265
254 def publish_event(self, event_type: str, payload: Dict[str, Any]) -> None: 266 def publish_event(self, event_type: str, payload: Dict[str, Any]) -> None:
@@ -431,6 +443,8 @@ def run_report_generation(task: ReportTask, query: str, custom_template: str = " @@ -431,6 +443,8 @@ def run_report_generation(task: ReportTask, query: str, custom_template: str = "
431 task.report_file_name = generation_result.get('report_filename', '') 443 task.report_file_name = generation_result.get('report_filename', '')
432 task.state_file_path = generation_result.get('state_filepath', '') 444 task.state_file_path = generation_result.get('state_filepath', '')
433 task.state_file_relative_path = generation_result.get('state_relative_path', '') 445 task.state_file_relative_path = generation_result.get('state_relative_path', '')
  446 + task.ir_file_path = generation_result.get('ir_filepath', '')
  447 + task.ir_file_relative_path = generation_result.get('ir_relative_path', '')
434 task.publish_event('html_ready', { 448 task.publish_event('html_ready', {
435 'message': 'HTML渲染完成,可刷新预览', 449 'message': 'HTML渲染完成,可刷新预览',
436 'report_file': task.report_file_relative_path or task.report_file_path, 450 'report_file': task.report_file_relative_path or task.report_file_path,
@@ -1027,6 +1041,17 @@ def export_pdf(task_id: str): @@ -1027,6 +1041,17 @@ def export_pdf(task_id: str):
1027 Response: PDF文件流或错误信息 1041 Response: PDF文件流或错误信息
1028 """ 1042 """
1029 try: 1043 try:
  1044 + # 检测 Pango 依赖
  1045 + from .utils.dependency_check import check_pango_available
  1046 + pango_available, pango_message = check_pango_available()
  1047 + if not pango_available:
  1048 + return jsonify({
  1049 + 'success': False,
  1050 + 'error': 'PDF 导出功能不可用:缺少 Pango 系统依赖',
  1051 + 'details': '请查看 requirements.txt 文件中的 "===== PDF生成 =====" 部分了解如何安装 Pango',
  1052 + 'system_message': pango_message
  1053 + }), 503
  1054 +
1030 # 获取任务信息 1055 # 获取任务信息
1031 task = tasks_registry.get(task_id) 1056 task = tasks_registry.get(task_id)
1032 if not task: 1057 if not task:
@@ -1104,6 +1129,17 @@ def export_pdf_from_ir(): @@ -1104,6 +1129,17 @@ def export_pdf_from_ir():
1104 Response: PDF文件流或错误信息 1129 Response: PDF文件流或错误信息
1105 """ 1130 """
1106 try: 1131 try:
  1132 + # 检测 Pango 依赖
  1133 + from .utils.dependency_check import check_pango_available
  1134 + pango_available, pango_message = check_pango_available()
  1135 + if not pango_available:
  1136 + return jsonify({
  1137 + 'success': False,
  1138 + 'error': 'PDF 导出功能不可用:缺少 Pango 系统依赖',
  1139 + 'details': '请查看 requirements.txt 文件中的 "===== PDF生成 =====" 部分了解如何安装 Pango',
  1140 + 'system_message': pango_message
  1141 + }), 503
  1142 +
1107 data = request.get_json() 1143 data = request.get_json()
1108 1144
1109 if not data or 'document_ir' not in data: 1145 if not data or 'document_ir' not in data:
  1 +"""
  2 +检测系统依赖工具
  3 +用于检测 PDF 生成所需的系统依赖
  4 +"""
  5 +import sys
  6 +from loguru import logger
  7 +
  8 +
  9 +def check_pango_available():
  10 + """
  11 + 检测 Pango 库是否可用
  12 +
  13 + Returns:
  14 + tuple: (is_available: bool, message: str)
  15 + """
  16 + try:
  17 + # 尝试导入 weasyprint 并初始化 Pango
  18 + from weasyprint import HTML
  19 + from weasyprint.text.ffi import ffi, pango
  20 +
  21 + # 尝试调用 Pango 函数来确认库可用
  22 + pango.pango_version()
  23 +
  24 + return True, "✓ Pango 依赖检测通过,PDF 导出功能可用"
  25 + except OSError as e:
  26 + # Pango 库未安装或无法加载
  27 + error_msg = str(e)
  28 + if 'pango' in error_msg.lower():
  29 + return False, (
  30 + "⚠ Pango 依赖未安装或无法加载,PDF 导出功能将不可用(其他功能不受影响)\n"
  31 + " 请查看 requirements.txt 文件中的 PDF 生成部分,了解如何安装 Pango 依赖"
  32 + )
  33 + return False, f"⚠ PDF 依赖加载失败: {error_msg}"
  34 + except ImportError as e:
  35 + # weasyprint 未安装
  36 + return False, f"⚠ WeasyPrint 未安装: {e}"
  37 + except Exception as e:
  38 + # 其他未知错误
  39 + return False, f"⚠ PDF 依赖检测失败: {e}"
  40 +
  41 +
  42 +def log_dependency_status():
  43 + """
  44 + 记录系统依赖状态到日志
  45 + """
  46 + is_available, message = check_pango_available()
  47 +
  48 + if is_available:
  49 + logger.success(message)
  50 + else:
  51 + logger.warning(message)
  52 + logger.info("提示:PDF 导出功能需要 Pango 库支持,但不影响系统其他功能的正常使用")
  53 + logger.info("安装说明请参考:requirements.txt 文件中的 '===== PDF生成 =====' 部分")
  54 +
  55 + return is_available
  56 +
  57 +
  58 +if __name__ == "__main__":
  59 + # 用于独立测试
  60 + is_available, message = check_pango_available()
  61 + print(message)
  62 + sys.exit(0 if is_available else 1)