From cb0446e85ffb109cf0f15020e36cecbd3121346f Mon Sep 17 00:00:00 2001 From: OoO Date: Tue, 19 May 2026 09:48:00 +0800 Subject: [PATCH] =?UTF-8?q?=E9=99=90=E5=88=B6=20PPT=20=E8=A6=96=E8=A6=BA?= =?UTF-8?q?=20QA=20=E6=8A=95=E5=BD=B1=E7=89=87=E6=8A=BD=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 1 + config.py | 2 +- services/ppt_vision_service.py | 21 +++++++++++++++++++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 6af6881..8776405 100644 --- a/.env.example +++ b/.env.example @@ -315,6 +315,7 @@ RAG_EMBED_NORMALIZE=true PPT_VISION_ENABLED=true PPT_VISION_MODEL=minicpm-v:latest PPT_VISION_TIMEOUT=120 +PPT_VISION_MAX_SLIDES=1 PPT_AUTO_GENERATION_ENABLED=true PPT_AUTO_REPORT_TYPES=all PPT_AUTO_DEFAULT_CATEGORY=美妝保養 diff --git a/config.py b/config.py index 62c63e1..2e14a4e 100644 --- a/config.py +++ b/config.py @@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '') # ========================================== # 系統版本與路徑 # ========================================== -SYSTEM_VERSION = "V10.232" +SYSTEM_VERSION = "V10.233" LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log') public_url = PUBLIC_URL # 用於模板顯示 diff --git a/services/ppt_vision_service.py b/services/ppt_vision_service.py index a3a46f3..4caea3e 100644 --- a/services/ppt_vision_service.py +++ b/services/ppt_vision_service.py @@ -36,6 +36,7 @@ logger = logging.getLogger(__name__) # ───────────────────────────────────────────────────────────────────────────── PPT_VISION_MODEL = os.getenv('PPT_VISION_MODEL', 'minicpm-v:latest') PPT_VISION_TIMEOUT = int(os.getenv('PPT_VISION_TIMEOUT', '120')) +PPT_VISION_MAX_SLIDES = int(os.getenv('PPT_VISION_MAX_SLIDES', '1')) PPT_VISION_IMAGE_MAX_EDGE = int(os.getenv('PPT_VISION_IMAGE_MAX_EDGE', '1280')) PPT_VISION_IMAGE_QUALITY = int(os.getenv('PPT_VISION_IMAGE_QUALITY', '82')) _AUDIT_LOCK = threading.Lock() @@ -182,6 +183,16 @@ def _is_recent_active_audit_run(run: Dict[str, Any] | None) -> bool: return age is None or age < _ACTIVE_AUDIT_TTL_SECONDS +def _is_vision_infra_error(error: str | None) -> bool: + text = (error or '').lower() + return any(marker in text for marker in ( + 'all 3 hosts failed', + 'connection', + 'ollama vision failed', + 'timeout', + )) + + def _public_audit_run_payload(run: Dict[str, Any] | None) -> Dict[str, Any] | None: if not run: return None @@ -316,7 +327,7 @@ class PPTVisionService: with open(image_path, 'rb') as f: return base64.b64encode(f.read()).decode('ascii') - def check_ppt_file(self, pptx_path: str, max_slides: int = 5) -> Dict[str, Any]: + def check_ppt_file(self, pptx_path: str, max_slides: int | None = None) -> Dict[str, Any]: """檢查整份 .pptx — Phase 26 整合到 PPT 生成流程。 流程: @@ -338,6 +349,7 @@ class PPTVisionService: import subprocess import tempfile + max_slides = max(1, int(max_slides or PPT_VISION_MAX_SLIDES)) result = { 'success': False, 'slides_checked': 0, 'total_issues': 0, 'issues_by_slide': [], 'error': None, @@ -424,11 +436,16 @@ class PPTVisionService: result['total_issues'] += len(vr.issues_found) result['issues_by_slide'].append((idx + 1, vr.issues_found)) else: - slide_errors.append(f"slide {idx + 1}: {vr.error or 'vision model failed'}") + message = f"slide {idx + 1}: {vr.error or 'vision model failed'}" + slide_errors.append(message) + if _is_vision_infra_error(vr.error): + break except Exception as exc: message = f"slide {idx + 1}: {type(exc).__name__}: {str(exc)[:160]}" slide_errors.append(message) logger.warning(f"[PPTVision] slide {idx+1} check failed: {exc}") + if _is_vision_infra_error(message): + break result['success'] = result['slides_checked'] > 0 if not result['success'] and slide_errors: