# aduc_framework/engineers/deformes2D_thinker.py # # Copyright (C) 4 de Agosto de 2025 Carlos Rodrigues dos Santos # # Versão 4.0.0 (Dual-Role Autonomous Director & Cinematographer) # # Esta classe é o cérebro criativo do framework, o Maestro (Γ). Ela utiliza um LLM # para desempenhar múltiplos papéis: # 1. Roteirista: Cria a lista inicial de atos (storyboard). # 2. Diretor Autônomo: Gerencia a produção de KEYFRAMES, com poder de # avaliar o trabalho feito, corrigir falhas e improvisar o roteiro. # 3. Cineasta: Cria os prompts de MOVIMENTO que conectam os keyframes. import logging from pathlib import Path from PIL import Image import gradio as gr from typing import List, Dict, Any from ..managers.gemini_manager import gemini_manager_singleton logger = logging.getLogger(__name__) class Deformes2DThinker: """ O especialista cognitivo (Maestro Γ) que atua como a inteligência criativa central, desempenhando os papéis de Roteirista, Diretor Autônomo e Cineasta. """ def _read_prompt_template(self, filename: str) -> str: """Lê um arquivo de template de prompt do diretório 'prompts'.""" try: prompts_dir = Path(__file__).resolve().parent.parent / "prompts" with open(prompts_dir / filename, "r", encoding="utf-8") as f: return f.read() except FileNotFoundError: raise gr.Error(f"Arquivo de template de prompt não encontrado: {filename}") # --- PAPEL 1: DIRETOR AUTÔNOMO (Para Deformes3D, geração de KEYFRAMES) --- def get_directorial_decision(self, context: Dict[str, Any]) -> Dict[str, Any]: """ Função principal do Diretor. Analisa o estado da produção e retorna a próxima ação (avançar, corrigir, improvisar) para a criação de KEYFRAMES. Esta é a implementação da lógica de planejamento de Γ para os pontos de contorno (K). """ try: template = self._read_prompt_template("autonomous_director_prompt.txt") # Formata o estado atual em texto legível para o LLM keyframes_str = "\n".join([f"- ID {kf['id']}: {kf['prompt_keyframe']}" for kf in context.get('keyframes_gerados', [])]) script_str = "\n".join([f"- Ato {i+1}: {ato}" for i, ato in enumerate(context.get('roteiro_completo', []))]) formatted_prompt = template.format( prompt_geral=context.get('prompt_geral', ''), midias_usuario=", ".join(context.get('midias_usuario', [])), roteiro_completo=script_str, indice_ato_atual=context.get('indice_ato_atual', 0), keyframes_gerados=keyframes_str if keyframes_str else "Nenhuma cena filmada ainda." ) # Monta o payload para o Gemini, incluindo a última imagem gerada para avaliação prompt_parts = [formatted_prompt] keyframes_gerados = context.get('keyframes_gerados', []) if keyframes_gerados: last_keyframe_path = keyframes_gerados[-1]['caminho_pixel'] prompt_parts.append("\nÚltimo Keyframe Gerado (para sua avaliação crítica):") try: prompt_parts.append(Image.open(last_keyframe_path)) except FileNotFoundError: logger.warning(f"Não foi possível encontrar a imagem do último keyframe para avaliação: {last_keyframe_path}") # Chama a camada de comunicação para obter a decisão em formato JSON decision_json = gemini_manager_singleton.get_json_object(prompt_parts) return decision_json except Exception as e: logger.error(f"O Diretor Autônomo (Gemini) falhou: {e}. Acionando fallback.", exc_info=True) return self._get_fallback_decision(context) def _get_fallback_decision(self, context: Dict[str, Any]) -> Dict[str, Any]: """Gera uma decisão de 'avançar' segura em caso de falha do LLM.""" script = context.get('roteiro_completo', []) current_index = context.get('indice_ato_atual', 0) current_act_text = script[current_index] if current_index < len(script) else "cena final" return { "acao": "avancar", "justificativa": "Fallback acionado devido a erro na decisão principal da IA.", "prompt_proximo_keyframe": f"Uma cena cinematográfica para o ato: {current_act_text}", "midia_base_escolhida": context.get('midias_usuario', [''])[0], "midias_contexto_escolhidas": [], "is_cut": False } # --- PAPEL 2: CINEASTA (Para Deformes4D, geração de MOVIMENTO) --- def get_motion_decision(self, start_kf: Dict, end_kf: Dict, motion_history: str) -> str: """ Atua como Cineasta para decidir o melhor `motion_prompt` que conecta dois keyframes existentes, criando o MOVIMENTO (Pᵢ). """ try: template = self._read_prompt_template("motion_director_prompt.txt") formatted_prompt = template.format( cena_atual_desc=start_kf.get('prompt_keyframe', 'início da cena'), cena_futura_desc=end_kf.get('prompt_keyframe', 'fim da cena'), historico_movimento=motion_history ) prompt_parts = [ formatted_prompt, "IMAGEM ATUAL:", Image.open(start_kf['caminho_pixel']), "IMAGEM FUTURA:", Image.open(end_kf['caminho_pixel']) ] motion_prompt = gemini_manager_singleton.get_raw_text(prompt_parts) # Limpa a resposta para garantir que é uma string pura return motion_prompt.strip().replace("`", "").replace("\"", "") except Exception as e: logger.error(f"O Cineasta (Gemini) falhou ao criar motion_prompt: {e}. Usando fallback.", exc_info=True) return f"Uma transição cinematográfica suave de '{start_kf.get('prompt_keyframe', 'cena anterior')}' para '{end_kf.get('prompt_keyframe', 'próxima cena')}'." # --- PAPEL 3: ROTEIRISTA (Para Orquestrador, criação do storyboard) --- def generate_storyboard(self, prompt: str, num_keyframes: int, ref_image_paths: List[str]) -> List[str]: """ Atua como Roteirista para criar o roteiro inicial (lista de atos) que servirá de base para o Diretor. """ try: template = self._read_prompt_template("unified_storyboard_prompt.txt") storyboard_prompt = template.format(user_prompt=prompt, num_fragments=num_keyframes) images = [Image.open(p) for p in ref_image_paths] prompt_parts = [storyboard_prompt] + images storyboard_data = gemini_manager_singleton.get_json_object(prompt_parts) storyboard = storyboard_data.get("scene_storyboard", []) if not storyboard or len(storyboard) != num_keyframes: logger.warning(f"Número de cenas gerado pelo Roteirista ({len(storyboard)}) é diferente do solicitado ({num_keyframes}). Ajustando para garantir o comprimento correto.") # Garante que a lista tenha o tamanho exato solicitado, repetindo a última cena se necessário. default_scene = storyboard[-1] if storyboard else "Cena de continuação." storyboard = (storyboard + [default_scene] * num_keyframes)[:num_keyframes] return storyboard except Exception as e: raise gr.Error(f"O Roteirista (Deformes2D Thinker) falhou ao criar o storyboard: {e}") # --- Instância Singleton --- # Cria uma única instância global do Maestro para ser usada em todo o framework. deformes2d_thinker_singleton = Deformes2DThinker()