Construyendo mi propio cliente de IA 'Privacy-First' con OpenRouter y Vanilla JS
Construyendo mi propio cliente de IA 'Privacy-First' con OpenRouter y Vanilla JS
El Problema: Dependencia y Privacidad en el Ecosistema LLM
En el día a día como ingeniero, interactuar con Modelos de Lenguaje Grande (LLMs) es fundamental. Sin embargo, usar las interfaces oficiales de OpenAI (ChatGPT) o Anthropic (Claude) presenta dos inconvenientes graves:
- Fragmentación: Tienes que saltar entre diferentes pestañas si quieres comparar las respuestas de GPT-4o con las de Claude 3.5 Sonnet o Llama 3.
- Privacidad (Data Scraping): Por defecto, muchas de las conversaciones que introduces en las interfaces gratuitas pueden ser utilizadas para entrenar futuros modelos. En entornos de ciberseguridad o desarrollo, pegar código fuente o logs de infraestructura en estas interfaces es un riesgo de OPSEC.
La Solución: Un Cliente Local ‘Zero-Dependency’
Para solucionar esto, decidí construir D4Nyos Chat, una interfaz web ligera diseñada para conectar directamente con la API de OpenRouter, un agregador que permite acceso a decenas de modelos desde un solo endpoint.
Principios de Diseño y Arquitectura
Quería que este proyecto fuera un ejercicio de purismo en el desarrollo frontend. Las premisas fueron:
- Vanilla JS/HTML/CSS: Nada de React, Vue o pesados
node_modules. Todo debía caber en un único archivo ejecutable en cualquier navegador moderno. - Privacy First (Local Storage): La aplicación no tiene un backend propio. Las claves de la API de OpenRouter (API Keys) y las preferencias del usuario se guardan exclusivamente en el
localStoragedel navegador. Ningún dato viaja a un servidor intermedio controlado por mí. - Soporte Multimodal: Integración de carga de archivos (imágenes y documentos) mediante conversión a Base64 nativa en el navegador, condicionando el envío solo si el modelo seleccionado soporta capacidades de visión.
Lógica Core: La Llamada a la API
El corazón de la aplicación es la función que orquesta la petición HTTP POST hacia OpenRouter. En lugar de usar SDKs pesados, implementé la llamada usando la API fetch nativa:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
async function sendMessage() {
// 1. Recolección de inputs (Texto + Adjuntos)
const content = buildContent(text);
history.push({ role: 'user', content });
try {
// 2. Inyección del System Prompt si está configurado
const msgs = config.systemPrompt ? [{ role: 'system', content: config.systemPrompt }, ...history] : history;
// 3. Petición POST directa a OpenRouter
const res = await fetch('[https://openrouter.ai/api/v1/chat/completions](https://openrouter.ai/api/v1/chat/completions)', {
method: 'POST',
headers: {
'Authorization': `Bearer ${config.apiKey}`,
'Content-Type': 'application/json',
'HTTP-Referer': location.href,
'X-Title': 'D4Nyos Chat'
},
body: JSON.stringify({
model: config.model,
messages: msgs,
stream: false
})
});
const data = await res.json();
if (data.error) throw new Error(data.error.message);
// 4. Renderizado de la respuesta
const reply = data.choices?.[0]?.message?.content || '';
history.push({ role: 'assistant', content: reply });
appendBubble('ai', reply);
} catch (err) {
showToast('Error al conectar: ' + err.message);
}
}
This post is licensed under CC BY 4.0 by the author.
