From 809578a9d68b7e0a47859b9f9203e3967b78fcbd Mon Sep 17 00:00:00 2001 From: Marcos Date: Sun, 22 Mar 2026 15:07:34 -0300 Subject: [PATCH] feat: Integrate orchestrator with Telegram bot - Add orchestrator imports and handlers - Detect orchestrator tasks via keywords (deploy, docker, git, etc.) - Add commands: /status, /tools, /sync, /orchestrate - Add confirmation flow (sim/nao) - Keep fallback to ai_agent for non-orchestrator tasks --- bot_logic.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/bot_logic.py b/bot_logic.py index bc34c78..c6a3323 100644 --- a/bot_logic.py +++ b/bot_logic.py @@ -8,6 +8,7 @@ from ai_agent import query_agent import speech_recognition as sr from pydub import AudioSegment from gtts import gTTS +from orchestrator import handle_message, orchestrate, format_confirmation_message, format_completion_message load_dotenv() @@ -51,17 +52,92 @@ async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE): user_msg = update.message.text await update.message.reply_chat_action(action="typing") - # Busca histórico anterior - history = chat_histories.get(chat_id, []) + # ===================================================== + # COMANDOS DO ORCHESTRATOR + # ===================================================== + if user_msg.startswith('/orchestrate'): + # Força uso do orchestrator + task = user_msg.replace('/orchestrate', '').strip() + result = orchestrate(task, user_confirmed=False) + if result["status"] == "needs_confirmation": + reply = format_confirmation_message(result) + else: + reply = format_completion_message(result) + await update.message.reply_text(reply) + return - # Aciona o Agente de IA para processar o prompt e executar Tools se precisar + if user_msg.startswith('/status') and not user_msg.startswith('/statusall'): + # Status do orchestrator + reply = handle_message('/status') + await update.message.reply_text(reply) + return + + if user_msg.startswith('/tools'): + # Lista de ferramentas + reply = handle_message('/tools') + await update.message.reply_text(reply) + return + + if user_msg.startswith('/sync'): + # Sync de credenciais + reply = handle_message('/sync') + await update.message.reply_text(reply) + return + + if user_msg.lower() in ['sim', 'confirmar', 'confirma', 'sim!', 'confirma!', 's']: + # Verifica se há confirmação pendente + if 'pending_confirmation' in context.bot_data: + task = context.bot_data['pending_confirmation'] + result = orchestrate(task, user_confirmed=True) + reply = format_completion_message(result) + del context.bot_data['pending_confirmation'] + await update.message.reply_text(reply) + return + + if user_msg.lower() in ['nao', 'não', 'cancelar', 'cancela', 'n', 'n!']: + # Cancela confirmação pendente + if 'pending_confirmation' in context.bot_data: + del context.bot_data['pending_confirmation'] + await update.message.reply_text("Operacao cancelada.") + return + await update.message.reply_text("Nada pendente para cancelar.") + return + + # ===================================================== + # ORCHESTRATOR: Detecta tarefas de orquestração + # ===================================================== + orchestrator_keywords = ['deploy', 'restart', 'restartar', 'reiniciar', + 'git pull', 'git push', 'docker', 'container', + 'mostra status', 'status dos', 'verificar', + 'faz um', 'executa', 'roda', 'rodar', + 'atualiza', 'atualizar', 'backup'] + + is_orchestrator_task = any(kw in user_msg.lower() for kw in orchestrator_keywords) + + if is_orchestrator_task: + result = orchestrate(user_msg, user_confirmed=False) + if result["status"] == "needs_confirmation": + # Salva tarefa pendente + context.bot_data['pending_confirmation'] = user_msg + reply = format_confirmation_message(result) + reply += "\n\nResponda *sim* para confirmar ou *nao* para cancelar." + else: + reply = format_completion_message(result) + + await update.message.reply_text(reply) + return + + # ===================================================== + # FALLBACK: Usa o agente normal (ai_agent.py) + # ===================================================== + history = chat_histories.get(chat_id, []) from config import get_config cfg = get_config() reply = query_agent(prompt=user_msg, override_provider=cfg.get("active_provider"), chat_history=history) # Atualiza histórico history.append({"user": user_msg, "bot": reply}) - chat_histories[chat_id] = history[-10:] # Mantém apenas as últimas 10 + chat_histories[chat_id] = history[-10:] # Se o usuário pedir ativamente por áudio no texto if "áudio" in user_msg.lower() or "audio" in user_msg.lower() or "voz" in user_msg.lower(): @@ -73,15 +149,12 @@ async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE): # --- NOVO: Lógica para enviar IMAGENS se a IA localizou um arquivo --- import re - # Procura por padrões de imagem Markdown ou caminhos absolutos de imagem - img_matches = re.findall(r'!\[.*?\]\((/.*?)\)', reply) # ![alt](/caminho/img.jpg) + img_matches = re.findall(r'!\[.*?\]\((/.*?)\)', reply) if not img_matches: - # Tenta achar caminhos absolutos que terminam em extensões de imagem img_matches = re.findall(r'(/[^\s]+?\.(?:jpg|jpeg|png|gif|webp))', reply, re.IGNORECASE) if img_matches: for img_path in img_matches: - # Garante que o caminho use /host_root se for um arquivo da VPS real_path = img_path if not real_path.startswith("/host_root") and real_path.startswith("/root"): real_path = f"/host_root{real_path}" @@ -89,11 +162,10 @@ async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE): if os.path.exists(real_path): try: await update.message.reply_chat_action(action="upload_photo") - await update.message.reply_photo(photo=open(real_path, 'rb'), caption="🖼️ Imagem localizada na VPS") + await update.message.reply_photo(photo=open(real_path, 'rb'), caption="Imagem localizada na VPS") except Exception as e: print(f"Erro ao enviar imagem {real_path}: {e}") - # Responde no chat normalmente await update.message.reply_text(reply) async def handle_voice(update: Update, context: ContextTypes.DEFAULT_TYPE): @@ -184,6 +256,10 @@ def get_telegram_app(): app.add_handler(CommandHandler("start", start)) app.add_handler(CommandHandler("llm", llm_command)) app.add_handler(CommandHandler("limpar", clear_history)) + app.add_handler(CommandHandler("status", lambda u, c: handle_text(u, c))) # Alias para status + app.add_handler(CommandHandler("tools", lambda u, c: handle_text(u, c))) # Alias para tools + app.add_handler(CommandHandler("sync", lambda u, c: handle_text(u, c))) # Alias para sync + app.add_handler(CommandHandler("orchestrate", lambda u, c: handle_text(u, c))) # Orchestrate app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text)) app.add_handler(MessageHandler(filters.VOICE, handle_voice)) return app