feat: upgrade interface web e suporte a áudio completo

This commit is contained in:
2026-03-22 01:05:27 +00:00
parent 2d3da03ee6
commit 3e2e81bd64
7 changed files with 435 additions and 131 deletions

View File

@@ -1,120 +1,90 @@
import os
import re
import requests
from tools import run_bash_command, get_system_health
import json
from tools import AVAILABLE_TOOLS
from config import get_config
def get_llm_response(prompt: str, provider: str, cfg: dict) -> str:
"""Invoca o provedor de LLM configurado."""
if provider == "gemini":
api_key = cfg.get("gemini_api_key")
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={api_key}"
payload = {"contents": [{"parts": [{"text": prompt}]}]}
res = requests.post(url, json=payload)
if res.status_code == 200:
return res.json()["candidates"][0]["content"]["parts"][0]["text"]
return f"Erro Gemini: {res.text}"
elif provider == "ollama":
ollama_host = os.getenv("OLLAMA_HOST", "http://ollama-lw4s8g4gc8gss4gkc4gg0wk4:11434")
res = requests.post(f"{ollama_host}/api/generate", json={
"model": os.getenv("OLLAMA_MODEL", "qwen2.5-coder:1.5b"),
"prompt": prompt,
"stream": False
})
if res.status_code == 200:
return res.json().get("response", "")
return f"Erro Ollama: {res.text}"
return "Provedor desconhecido."
def query_agent(prompt: str, override_provider: str = None) -> str:
"""
Função principal que roteia pro LLM desejado, detecta intents e aciona as Tools.
Motor Agente em Loop (ReAct): Pensamento -> Ação -> Observação -> Resposta Final.
"""
# SYSTEM PROMPT AVANÇADO: O "Ensinamento" da Inteligência Artificial
system_prompt = """Você é o [Antigravity VPS Agent], uma Inteligência Artificial autônoma de Dev/SysAdmin operando diretamente em uma máquina Ubuntu Linux do usuário (Marcos).
Sua missão é ajudar o Marcos a gerenciar o servidor, criar aplicações, analisar código e orquestrar o Docker. VOCÊ DEVE RESPONDER SEMPRE EM PORTUGUÊS FLUENTE DO BRASIL.
### SEUS PODERES E ARQUITETURA
1. **Nível Root**: Você roda em um Bot Python empacotado via Docker. Este container mapeia o `/var/run/docker.sock`, o que te dá o poder DIVINO sobre tudo. Você pode listar, deletar, parar e recriar qualquer container na máquina hospedeira inteira.
2. **Sistema de Arquivos**: O disco principal do servidor está montado de forma segura, o que permite que você leia logs nativos.
3. **Mecanismo de Ação (A Interface de Ferramentas)**: Você **não executa os comandos diretamente pela web**. Você possui uma interface conectada ao bot no Telegram e Web. Para interagir com o servidor, sempre que decidir que algo precisa ser lido (ex: `ls -la`, `docker network ls`, `cat arquivo.txt`) ou modificado, responda com a notação: [CMD] o_comando_aqui [/CMD].
4. O bot Python varrerá sua resposta via Regex, extrairá o comando que você colocou entre os blocos, abrirá um shell Bash real, e te devolverá na conversa o resultado do seu comando.
### SEUS LIMITES E REGRAS
- **Sem Comandos Interativos**: Jamais use comandos que exijam resposta humana travando o terminal (ex: `nano`, `top -d 1`, `apt-get install` sem `-y`). Se usar, o bash vai dar timeout (60s limit).
- **Nunca use systemctl**: Você roda dentro de um container Docker. Logo, o comando `systemctl` NÃO FUNCIONA. Para ver serviços, use invariavelmente o comando `docker ps -a`.
- **Aja, Não Explique Demais**: Se o usuário te der uma tarefa como "Veja meus containers", não explique o que é Docker. Apenas devolva [CMD] docker ps -a [/CMD] e diga "Estou olhando agora mesmo."
- **Erros**: Se o comando retornar um código 127, 1 ou falha de acesso, aceite o erro e sugira tentar um comando de diagnóstico em seguida.
Acorde, a VPS é sua para cuidar!"""
if "logs do nginx" in prompt.lower() or "nginx" in prompt.lower():
output = run_bash_command("systemctl status nginx || echo 'Nginx não parece ser gerenciado por systemctl aqui'")
return f"Executei a checagem no Nginx. Olha o resultado: \n\n{output}"
if any(palavra in prompt.lower() for palavra in ["status", "cpu", "saude", "saúde", "sauda", "memória", "disco", "hd"]):
health = get_system_health()
return f"A saúde atual pontual da sua máquina direta do Python está assim:\n{health}"
cfg = get_config()
provider = override_provider or cfg.get("active_provider", "ollama")
provider = override_provider or cfg.get("active_provider", "gemini")
if provider == "gemini":
gemini_api_key = cfg.get("gemini_api_key", "")
if not gemini_api_key: return "Chave API do Gemini não configurada."
# Contexto de Ferramentas para a IA
tools_desc = "\n".join([f"- {k}: {v['description']}" for k,v in AVAILABLE_TOOLS.items()])
# Prompt especializado (sem chaves complexas)
system_prompt_base = """Você é o [Antigravity VPS Agent].
Sua missão é ser o SysAdmin de elite do Marcos. Você tem acesso root.
### REGRAS:
1. Responda em PORTUGUÊS (Brasil).
2. Para agir, use: [CMD] comando [/CMD]. Rode UM comando por vez.
3. Seus comandos devem ser diretos (docker, git, ls, rm, mkdir).
4. Após o comando, você receberá a saída. O seu objetivo é resolver a solicitação do usuário.
5. Quando terminar, sua resposta FINAL deve ter:
- Um resumo técnico rápido.
- Uma seção entre tags <REFINED> ... </REFINED> com uma tabela Markdown limpa ou resumo em tópicos (Nome: Valor) para o usuário leigo.
### FERRAMENTAS DISPONÍVEIS:
{TOOLS_LIST}
### EXEMPLO DE REFINAMENTO VISUAL:
Relatório: Coletei os dados solicitados.
<REFINED>
### 📊 Status Global
- **CPU**: 10%
- **RAM**: 500MB livre
</REFINED>
"""
system_prompt = system_prompt_base.replace("{TOOLS_LIST}", tools_desc)
history = f"\nUsuário: {prompt}\n"
max_loops = 10
for _ in range(max_loops):
full_prompt = system_prompt + history
response = get_llm_response(full_prompt, provider, cfg)
try:
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={gemini_api_key}"
payload = {
"contents": [{"parts": [{"text": system_prompt + "\n\nUsuário: " + prompt}]}]
}
res = requests.post(url, json=payload)
if res.status_code == 200:
raw_response = res.json()["candidates"][0]["content"]["parts"][0]["text"]
else:
return f"Erro na API do Gemini: {res.text}"
# Regex Universal para processar Comandos
match = re.search(r"\[.*?CMD\](.*?)\[/.*?CMD\]", raw_response, re.IGNORECASE | re.DOTALL)
if match:
comando_bash = match.group(1).strip()
resultado = run_bash_command(comando_bash)
# Tradução via Gemini
traducao_prompt = f"O comando `{comando_bash}` retornou o seguinte dado: {resultado[:1500]}\nTraduza gentilmente para um formato leigo sem códigos de erro difíceis."
t_payload = {"contents": [{"parts": [{"text": system_prompt + "\n\n" + traducao_prompt}]}]}
t_res = requests.post(url, json=t_payload)
if t_res.status_code == 200:
texto_leigo = t_res.json()["candidates"][0]["content"]["parts"][0]["text"]
else:
texto_leigo = "Falha ao gerar resumo no Gemini."
return f"🤖 **Comando Técnico (Gemini):** `{comando_bash}`\n\n**🖥️ Log Nativo:**\n```\n{resultado[:1000]}\n```\n\n🧑‍🏫 **Tradução:**\n{texto_leigo}"
# Procura por comandos na resposta
match = re.search(r"\[CMD\](.*?)\[/CMD\]", response, re.IGNORECASE | re.DOTALL)
if match:
cmd = match.group(1).strip()
# Executa a tool (por enquanto focada em bash que é a mais poderosa)
print(f"Agente executando: {cmd}")
observation = AVAILABLE_TOOLS["run_bash_command"]["func"](cmd)
return raw_response
except Exception as e:
return f"Falha de Conexão com Gemini Pro: {e}"
elif provider == "ollama":
try:
ollama_host = os.getenv("OLLAMA_HOST", "http://ollama-lw4s8g4gc8gss4gkc4gg0wk4:11434")
res = requests.post(f"{ollama_host}/api/generate", json={
"model": os.getenv("OLLAMA_MODEL", "qwen2.5-coder:1.5b"),
"prompt": system_prompt + "\nUsuário: " + prompt,
"stream": False
})
if res.status_code == 200:
raw_response = res.json().get("response", "Erro vazio do Ollama")
# Motor de Tool Calling Tolerante: Detecta [CMD] ou [VCMD] ou variações que LLMs minúsculas inventam
match = re.search(r"\[.*?CMD\](.*?)\[/.*?CMD\]", raw_response, re.IGNORECASE | re.DOTALL)
if match:
comando_bash = match.group(1).strip()
resultado = run_bash_command(comando_bash)
# ----------------------------------------------------
# NOVA LÓGICA: Tradução Leiga (Segundo Prompt para a IA)
# ----------------------------------------------------
traducao_prompt = f"O comando `{comando_bash}` retornou a seguinte saída do servidor:\n\n{resultado[:1500]}\n\nTraduza GENTILMENTE essa saída técnica explicando de forma amigável, gentil e em português muito simples (para um não-técnico) o que isso indica. SE a saída for um 'ERRO', acalme o usuário, resuma que um comando técnico falhou e sugira verbalmente o que de forma segura investigar a seguir (não mande blocos confusos, apenas explique em português fluente)."
try:
res_traducao = requests.post(f"{ollama_host}/api/generate", json={
"model": os.getenv("OLLAMA_MODEL", "qwen2.5-coder:1.5b"),
"prompt": system_prompt + "\n\n" + traducao_prompt,
"stream": False
})
if res_traducao.status_code == 200:
texto_leigo = res_traducao.json().get("response", "Erro ao processar resumo.")
else:
texto_leigo = "Falha ao gerar resumo na LLM."
except Exception as e_traducao:
texto_leigo = "Ocorreu uma falha ao tentar traduzir o log."
# Retorna na tela a versão técnica seguida da versão explicada
return f"🤖 **Comando Técnico:** `{comando_bash}`\n\n**🖥️ Log Nativo (Terminal):**\n```\n{resultado[:1000]}\n```\n\n🧑‍🏫 **Tradução:**\n{texto_leigo}"
return raw_response
except Exception as e:
return f"Falha ao conectar no Ollama local: {e}"
return "Ação não reconhecida pelo Agente no momento."
# Adiciona ao histórico para a IA ler na próxima rodada
history += f"\nAgente (Pensamento/Ação): {response}\nSISTEMA (Saída do Terminal): {observation}\n"
else:
# Se não tem comando, é a resposta final
return response
return "O agente atingiu o limite de tentativas para esta tarefa."