import asyncio import base64 import json import logging import os import re import shutil import tempfile import time import uuid from datetime import datetime from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, List, Optional from ...api.models import ( PPTGenerationRequest, PPTOutline, EnhancedPPTOutline, SlideContent, PPTProject, TodoBoard, FileOutlineGenerationResponse, ) from ...ai import get_ai_provider, get_role_provider, AIMessage, MessageRole from ...ai.base import TextContent, ImageContent from ...core.config import ai_config, app_config from .ai_execution import ExecutionContext from ..prompts import prompts_manager from ..research.enhanced_research_service import EnhancedResearchService from ..research.enhanced_report_generator import EnhancedReportGenerator from ..pyppeteer_pdf_converter import get_pdf_converter from ..image.image_service import ImageService from ..image.adapters.ppt_prompt_adapter import PPTSlideContext from ...utils.thread_pool import run_blocking_io, to_thread logger = logging.getLogger(__name__) from .runtime_ai_service import RuntimeAIService from .runtime_image_service import RuntimeImageService from .runtime_research_service import RuntimeResearchService if TYPE_CHECKING: from ..enhanced_ppt_service import EnhancedPPTService class RuntimeSupportService: """Facade over runtime research, AI config, and image support services.""" def __init__(self, service: "EnhancedPPTService"): self._service = service self._research_runtime = RuntimeResearchService(self) self._ai_runtime = RuntimeAIService(self) self._image_runtime = RuntimeImageService(self) def __getattr__(self, name: str): return getattr(self._service, name) @property def ai_provider(self): return self._ai_runtime.ai_provider @staticmethod def _trim_research_preview_text(text: str, limit: int = 16000) -> str: return RuntimeResearchService._trim_research_preview_text(text, limit) @staticmethod def _chunk_research_preview_text(text: str, chunk_size: int = 900) -> List[str]: return RuntimeResearchService._chunk_research_preview_text(text, chunk_size) @staticmethod def _excerpt_research_preview_text(text: str, limit: int = 320) -> str: return RuntimeResearchService._excerpt_research_preview_text(text, limit) def _initialize_research_services(self): return self._research_runtime._initialize_research_services() def _get_preferred_outline_research_runtime(self) -> Dict[str, Any]: return self._research_runtime._get_preferred_outline_research_runtime() def _create_research_context(self, research_report: Any) -> str: return self._research_runtime._create_research_context(research_report) def _build_research_preview_payloads(self, header: str, body: str, *, status: Optional[Dict[str, Any]]=None) -> List[Dict[str, Any]]: return self._research_runtime._build_research_preview_payloads(header, body, status=status) def iter_research_stream_payloads(self, event: Dict[str, Any]) -> List[Dict[str, Any]]: return self._research_runtime.iter_research_stream_payloads(event) def reload_research_config(self): return self._research_runtime.reload_research_config() def _initialize_image_service(self): return self._image_runtime._initialize_image_service() async def _async_initialize_image_service(self): return await self._image_runtime._async_initialize_image_service() def _get_current_ai_config(self, role: str='default'): return self._ai_runtime._get_current_ai_config(role) def _get_current_ai_config_sync_impl(self, role: str='default'): return self._ai_runtime._get_current_ai_config_sync_impl(role) async def _get_current_ai_config_async(self, role: str='default'): return await self._ai_runtime._get_current_ai_config_async(role) async def _get_current_mineru_config_async(self) -> Dict[str, Optional[str]]: return await self._ai_runtime._get_current_mineru_config_async() def _extract_ai_config_from_user_config(self, user_config: dict, role: str='default'): return self._ai_runtime._extract_ai_config_from_user_config(user_config, role) def _get_fallback_ai_config(self, role: str='default'): return self._ai_runtime._get_fallback_ai_config(role) def _get_role_provider(self, role: str): return self._ai_runtime._get_role_provider(role) async def _get_role_provider_async(self, role: str): return await self._ai_runtime._get_role_provider_async(role) def get_role_provider(self, role: str): return self._ai_runtime.get_role_provider(role) async def get_role_provider_async(self, role: str): return await self._ai_runtime.get_role_provider_async(role) async def _text_completion_for_role(self, role: str, *, prompt: str, **kwargs): return await self._ai_runtime._text_completion_for_role(role, prompt=prompt, **kwargs) async def _stream_text_completion_for_role(self, role: str, *, prompt: str, **kwargs): async for item in self._ai_runtime._stream_text_completion_for_role(role, prompt=prompt, **kwargs): yield item async def _chat_completion_for_role(self, role: str, *, messages: List[AIMessage], **kwargs): return await self._ai_runtime._chat_completion_for_role(role, messages=messages, **kwargs) async def _get_user_generation_config(self) -> Dict[str, Any]: return await self._ai_runtime._get_user_generation_config() def update_ai_config(self): return self._ai_runtime.update_ai_config() def _configure_summeryfile_api(self, generator, role: str='default'): return self._ai_runtime._configure_summeryfile_api(generator, role) def _build_execution_context(self, role: str, current_ai_config: Optional[Dict[str, Any]]=None) -> ExecutionContext: return self._ai_runtime._build_execution_context(role, current_ai_config) def _build_summeryanyfile_processing_config(self, *, processing_config_cls, execution_context: ExecutionContext, target_language: str, min_slides: int, max_slides: int, chunk_size: int, chunk_strategy: Any): return self._ai_runtime._build_summeryanyfile_processing_config(processing_config_cls=processing_config_cls, execution_context=execution_context, target_language=target_language, min_slides=min_slides, max_slides=max_slides, chunk_size=chunk_size, chunk_strategy=chunk_strategy) def get_cache_stats(self) -> Dict[str, Any]: return self._ai_runtime.get_cache_stats() def cleanup_cache(self): return self._ai_runtime.cleanup_cache() def _cleanup_style_genes_cache(self, max_age_days: int=7): return self._ai_runtime._cleanup_style_genes_cache(max_age_days)