Aplicativo Web de IA Local no Termux
Construa e execute um aplicativo web local em um dispositivo Android rodando Termux, integrando APIs de IA (especialmente NaraRouter) e, opcionalmente, o Hermes Agent como backend de raciocínio.
Quando usar
- O usuário quer uma interface de chat/assistente de IA local rodando no celular
- Construindo um aplicativo web no Termux com Python/Flask
- Integrando múltiplas APIs de IA com roteamento baseado em descrição
- Adicionando modo de voz via Web Speech API do navegador
- Gerenciando sessões e memória entre conversas
- O usuário cola instruções de instalação para uma ferramenta de desenvolvimento (binário, Rust/cargo, npm/npx, pip) e pede para instalar e usar — avalie primeiro a viabilidade para o ambiente do celular/Termux, depois instale com permissão
Padrão de arquitetura
Navegador (Chrome no Android) → Backend Flask (Termux) → API(s) de IA
↓
Hermes Agent (opcional, como cérebro)
Princípio chave: O aplicativo web é a camada de interface. O Hermes Agent (se usado) é o cérebro. O backend Flask faz a ponte entre eles.
Início rápido
# No Termux
mkdir ~/sayip && cd ~/sayip
mkdir -p templates static/css static/js storage/sessions
# app.py — Flask mínimo para começar
from flask import Flask, render_template, request, jsonify
from pathlib import Path
import json, time, uuid, os
app = Flask(__name__, template_folder="templates")
HERMES_HOME = Path(os.path.expanduser("~/.hermes"))
SAYIP_STORAGE = Path(os.path.expanduser("~/sayip/storage"))
@app.route("/")
def index():
return render_template("chat.html")
@app.route("/api/chat", methods=["POST"])
def chat():
data = request.get_json()
# ... processa mensagem, chama API, retorna resposta
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000)
# Executar
python3 app.py
# Abrir no Chrome: http://127.0.0.1:5000
Padrões de integração de API
NaraRouter (recomendação principal)
O NaraRouter é um gateway unificado de IA com 53+ modelos, com plano gratuito disponível.
Formato da chave: sk-nry-... (começa com sk-nry-)
Endpoints (todos sob a mesma URL base + chave Bearer):
| Endpoint | Tipo | Uso |
|---|---|---|
https://router.bynara.id/v1/chat/completions | Compatível com OpenAI | Chat, tarefas gerais |
https://router.bynara.id/v1/messages | Compatível com Anthropic | Requisições no formato Anthropic |
https://router.bynara.id/v1/responses | OpenAI Responses | API de respostas com estado |
https://router.bynara.id/v1/embeddings | OpenAI Embeddings | Embeddings vetoriais |
https://router.bynara.id/v1/models | LISTA | Obter modelos disponíveis para seu plano |
https://api-images.bynara.id/v1/images/generations | Geração de imagem | Texto-para-imagem |
https://api-images.bynara.id/v1/images/edits | Edição de imagem | Edição de imagens |
Teste uma chave antes de usar:
curl -s -X POST https://router.bynara.id/v1/chat/completions \
-H "Authorization: Bearer sk-nry-SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"model":"agnes-2.5-flash","messages":[{"role":"user","content":"oi"}],"max_tokens":10}'
Modelos gratuitos conhecidos que funcionam: agnes-2.5-flash, agnes-2.0-flash
Verifique os modelos do seu plano:
curl -s https://router.bynara.id/v1/models \
-H "Authorization: Bearer sk-nry-SUA_CHAVE" | python3 -c "import sys,json; [print(m['id']) for m in json.load(sys.stdin).get('data',[])]"
IMPORTANTE: Nem todos os 53 modelos estão em todos os planos. deepseek-v4-pro-0813-bynara requer créditos. deepseek-v4-flash-free pode não estar incluído. Sempre teste antes de configurar.
API NVIDIA NIM (integrate.api.nvidia.com/v1)
O NVIDIA NIM oferece 82+ modelos através de um único endpoint compatível com OpenAI.
URL base: https://integrate.api.nvidia.com/v1
Endpoint: https://integrate.api.nvidia.com/v1/chat/completions
Formato da chave: nvapi- (ex.: nvapi-_qJeGmEP...)
Teste uma chave antes de usar:
curl -s -X POST https://integrate.api.nvidia.com/v1/chat/completions \
-H "Authorization: Bearer nvapi-..." \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"model":"nvidia/nemotron-3.5-lightning-30b-a3b","messages":[{"role":"user","content":"oi"}],"max_tokens":10,"stream":true}'
Modelos gratuitos conhecidos que funcionam (testados na rede móvel do Termux):
| ID do Modelo | Chave | Status | Notas |
|---|---|---|---|
nvidia/nemotron-3.5-lightning-30b-a3b | nvapi-... | Funciona | Rápido (~5-7s para simples), raciocínio forte, suporta modo thinking. Mais confiável no móvel. |
gpt-oss-20b | nvapi-... | Funciona | Compatível com OpenAI |
deepseek-ai/deepseek-v4-pro-0813 | nvapi-... | Risco de timeout | Funciona no desktop, frequentemente timeout na rede móvel do Termux |
moonshotai/kimi-k3 | nvapi-... | Risco de timeout | Capaz de visão, mas timeout na rede móvel |
Aviso de assimetria de chaves: Nem todos os modelos estão disponíveis em todas as chaves. Chave 1 (nvapi-_qJeGmEP...) tem acesso mais amplo. Chave 2 (nvapi-WDb7Rr...) é mais restrita — alguns modelos retornam 404. Sempre teste um modelo com uma chave específica antes de configurar.
Indicadores de chave morta:
401 Unauthorized— chave inválida/expirada404 Not Foundem um modelo — modelo não está nesta conta/chave- Timeout de conexão (30s+) — API lenta/inacessível desta rede
- Chaves Teamorouter
sk-teamo-...→insufficient_balanceno endpoint OpenAI (https://api.teamorouter.com/v1); modelo não suportado no endpoint Anthropic (https://api.teamorouter.com). Morta. Oferece modelodeepseek-v4-pro-freemas inutilizável sem saldo.
Consulte references/nvidia-api.md para detalhes completos: padrões Python específicos de modelos (modo thinking do Nemotron, DeepSeek não-streaming, multimodal Kimi vision), extração de reasoning_content em streaming, padrão de timeout rígido baseado em thread para urllib e formato de payload de visão.
Motor de despacho de API
Quando você tem múltiplas APIs configuradas, construa um despachante que:
- Classifica a tarefa (matemática, código, ciência, geral)
- Escolhe a melhor API com base no texto da descrição
- Chama a API e retorna o resultado
- Cai para outra API se a primeira falhar
Consulte references/nararouter-api.md para detalhes de integração.
Use a stdlib urllib, não requests
No Termux, pip install requests pode timeout em downloads grandes. Use urllib da stdlib do Python — está sempre disponível e lida bem com APIs JSON:
import urllib.request, json
def call_api(url, payload, api_key):
data = json.dumps(payload).encode()
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
req = urllib.request.Request(url, data=data, headers=headers, method="POST")
with urllib.request.urlopen(req, timeout=30) as resp:
return json.loads(resp.read())
Preferências de design (específicas do usuário)
Aparência minimalista, profissional e não gerada por IA:
- Tema escuro, espaçamento justo, sem gradientes, sem sombras decorativas
- Fontes monoespaçadas quando apropriado (código, chaves de API, timestamps)
- Retângulos simples para balões de chat — sem bordas arredondadas a menos que funcionais
- Barra lateral = lista simples, sem ícones a menos que significativos
- Busque implementações de referência em vez de gerar designs do zero
- Quando em dúvida, remova recursos em vez de adicioná-los
Antipadrões concretos — estes fazem um webapp parecer gerado por IA, remova-os:
- Emojis em todo cabeçalho/bloco/botão (
🤖,⚙,🎭,🔌,🧠,💬,🎤) — remova. Apenas rótulos de texto simples. - Brilho de acento / sombra de caixa ao passar o mouse (
box-shadow: 0 0 12px var(--accent-glow)) — remova. Apenas cores chapadas. - Animações keyframe de bounce / pulse / slideDown (
@keyframes bounce,@keyframes pulse,@keyframes slideDown) — remova. Sem design de movimento. - Cartão de boas-vindas com ícones (
.welcome-screencomwelcome-icon, gradewelcome-hints) — substitua por um placeholder de uma linha na área de entrada. - Ícone de marca decorativo ao lado do logotipo (
.brand-iconcom emoji) — remova. - Fitas, badges, pontos de status decorando o cabeçalho (
.health-badge,.health-dot) — remova ou torne puramente funcional. - Fundos gradientes, glassmorfismo, sobreposições translúcidas — nunca.
Regra de ouro: abra a página no navegador e pergunte "um desenvolvedor humano escreveria este CSS manualmente para uma ferramenta utilitária?" Se a resposta for não (porque tem movimento, brilho ou decoração com emoji), remova. Uma ferramenta normal parece um terminal, um aplicativo de notas ou um chat mínimo — não uma página de destino.
Modo de voz:
- Use Web Speech API (
SpeechRecognition+SpeechSynthesis) — gratuita, integrada ao navegador - Botão de alternância para mudar modo texto/voz
- No modo voz: microfone escuta continuamente, fala→texto→envia, resposta→fala
- Qualidade de voz é limitada ao que o Chrome no Android oferece — não prometa "voz do Ultron"
Preferências de fluxo de trabalho (específicas do usuário)
Pergunte antes de instalar pacotes: Antes de executar pip install ou apt install, pergunte ao usuário primeiro. Isso é inegociável.
Mostre progresso continuamente: Se uma tarefa levar mais de alguns minutos, mostre resultados intermediários. Não desapareça por longos períodos sem saída. Se estiver travado em algo, diga isso e o que está tentando.
Sem palestras de segurança: O usuário entende os riscos das chaves de API. Não dê palestras sobre segurança de chaves, exposição ou melhores práticas a menos que seja explicitamente perguntado. Eles forneceram sua própria explicação quando relevante.
Prefira soluções funcionais a arquitetura perfeita: Faça algo funcionar primeiro. O teste da chave NaraRouter mostrou isso — teste a chave, encontre um modelo funcional, configure-o. Não planeje demais.
Armazenamento é volátil — faça backup no GitHub cedo: No Termux/Android, o armazenamento do app (~/sayip/storage/) pode ser apagado na reinicialização do servidor, reinicialização do Termux ou reinicialização do dispositivo. Após configurar chaves de API e configuração, envie o projeto para um repositório GitHub via SSH imediatamente para que a configuração possa ser recuperada. Durante a sessão, não presuma que storage/ sobrevive a uma reinicialização — verifique novamente após qualquer reinicialização. O truque SSH na porta 443 (veja references/termux-git-ssh.md) é frequentemente necessário porque redes móveis bloqueiam a porta 22.
O modo de voz deve ser testado de ponta a ponta antes de declarar concluído: A fiação da Web Speech API em chat.js (setupVoice, startListening, speakText) não é o mesmo que o modo de voz funcionando. Teste falando de fato no microfone do celular e ouvindo a resposta falada de volta. O Chrome no Android é o navegador alvo — é o que importa, não o Chrome no desktop.
Depuração
Porta 5000 já em uso:
fuser -k 5000/tcp # Mata o que está na porta 5000
# Ou use ss para verificar:
ss -tlnp | grep 5000
Aplicativo Flask não inicia / trava:
- Verifique a sintaxe:
python3 -c "import py_compile; py_compile.compile('app.py', doraise=True)" - Verifique se a porta está livre antes de iniciar
- Execute com
timeout 15 python3 app.pypara ver a saída de inicialização sem bloquear
API retorna 401/403/payment_required:
- A chave pode ser inválida, expirada ou o modelo não está no seu plano
- Teste a chave diretamente com curl antes de configurá-la no aplicativo
- Verifique
/v1/modelspara ver quais modelos seu plano realmente inclui
Timeouts no pip install:
- O Termux no celular pode ter rede lenta/não confiável para pacotes grandes
- Use alternativas da stdlib quando possível (urllib em vez de requests)
- Se precisar instalar, use
pip3 install --timeout 30 <pacote>para falhar rapidamente
Processos Flask em segundo plano mortos à força mantêm a porta ocupada brevemente:
- Após
pkill -9 -f "[p]ython3 app.py", aguarde 2-3s antes de reiniciar - Se obtiver "Address already in use", o processo antigo não liberou o socket completamente — aguarde mais
Antes de reiniciar o servidor Flask para aplicar mudanças no código, mate o processo antigo primeiro:
- Padrão:
pkill -9 -f "[p]ython3 app.py" && sleep 3 && cd ~/sayip && python3 app.py & - Pular a morte causa "Address already in use" e a nova instância não inicia
- Sempre verifique com
curl -s --max-time 3 http://127.0.0.1:5000/api/healthapós reiniciar
Quando o usuário relatar que o webapp está quebrado / JS bugado / não funcionando, diagnostique nesta ordem:
- Confirme que o servidor está rodando:
curl -s --max-time 3 http://127.0.0.1:5000/api/health - Verifique se os arquivos estáticos servem 200:
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:5000/static/style.css(e app.js, chat.js, voice.js, settings.js) - Verifique qual HTML o servidor realmente serve:
curl -s http://127.0.0.1:5000/ | grep -E 'static/|style.css|app.js|chat.js|voice.js'— confirma se os caminhos no HTML correspondem ao que o Flask serve - Se arquivos estáticos retornarem 404, mas os links HTML mostrarem caminhos relativos como
style.cssem vez de/static/style.css, esse é o bug — corrija ambos os templates para usar o prefixo/static/ - Leia os arquivos JS e verifique cada chamada
document.querySelector/getElementById/.querySelectorcontra o que o HTML realmente contém — um elemento faltando quebra todo o script - Verifique travamentos por referência nula:
el("hamburger")em uma página que não tem elemento hamburger,messagesEl.querySelector(".wrap")quando.wrapnão existe,document.querySelector(".composer .wrap")quando.composernão existe — todos quebram a IIFE e derrubam todo o JS da página - Proteja buscas de elementos:
var el = document.querySelector(selector); if (!el) return;antes de usar o resultado
Verifique correções no frontend após qualquer edição de template HTML verificando o que o servidor realmente serve:
- Um curl para a URL da página e grep pelo caminho estático (
/static/style.css,/static/js/app.js) confirma se a correção foi implantada - Um curl diretamente para a URL do arquivo estático com
-o /dev/null -w '%{http_code}'confirma se serve 200 - Não presuma que uma edição de HTML está ativa até que ambas as verificações passem no servidor em execução
A causa mais comum de "tudo JS bugado" neste projeto:
- Templates HTML usando caminhos estáticos relativos (
style.css,app.js,chat.js) em vez do prefixo/static/do Flask (/static/style.css,/static/js/app.js,/static/js/chat.js) - O navegador solicita
http://127.0.0.1:5000/style.css→ 404. Sem CSS, sem JS carregado → página sem estilo e não funcional - Correção: corrija ambos os templates HTML para prefixar caminhos estáticos com
/static/e, opcionalmente, movachat.jsevoice.jsparastatic/js/se ainda não estiverem lá
Referências
references/nararouter-api.md— Padrões de integração detalhados com NaraRouterreferences/nvidia-api.md— Padrões NVIDIA NIM (modo thinking do Nemotron, visão, streaming, timeout)references/termux-git-ssh.md— SSH na porta 443 para fazer push para GitHub de rede móvelreferences/termux-env.md— Configuração do ambiente Termux (Python, pacotes, ferramentas)