Spaces:
Running
Running
Download index.html from AxoneSystem/Minerva-chat: direct link, hf CLI and curl.
- Browser
- Download file 8.69 kB
-
https://huggingface.co/spaces/AxoneSystem/Minerva-chat/resolve/main/index.html
- Command line
-
hf download hf://spaces/AxoneSystem/Minerva-chat/index.html
-
curl -L -o index.html https://huggingface.co/spaces/AxoneSystem/Minerva-chat/resolve/main/index.html
8.69 kB
| <html lang="es"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Minerva Chat</title> | |
| <style> | |
| :root { | |
| --fondo: #f7f5fb; --panel: #ffffff; --texto: #1f1a2e; --suave: #6b6480; --borde: #e3def0; | |
| --yo: #5b3fc4; --yo-texto: #ffffff; --ella: #f0ecfa; --acento: #d4a017; | |
| } | |
| @media (prefers-color-scheme: dark) { | |
| :root { | |
| --fondo: #15121d; --panel: #1e1a29; --texto: #ece8f6; --suave: #a39cb8; --borde: #322c44; | |
| --yo: #7b5ff0; --yo-texto: #ffffff; --ella: #2a2438; --acento: #e8b931; | |
| } | |
| } | |
| * { box-sizing: border-box; } | |
| body { margin: 0; background: var(--fondo); color: var(--texto); | |
| font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; } | |
| main { max-width: 760px; margin: 0 auto; padding: 16px; min-height: 100vh; display: flex; flex-direction: column; gap: 12px; } | |
| header h1 { margin: 0; font-size: 1.5rem; } | |
| header p { margin: 4px 0 0; color: var(--suave); font-size: .92rem; } | |
| header a { color: var(--yo); } | |
| #estado { background: var(--panel); border: 1px solid var(--borde); border-radius: 12px; padding: 12px; font-size: .92rem; } | |
| #barra { height: 6px; background: var(--borde); border-radius: 3px; margin-top: 8px; overflow: hidden; } | |
| #barra div { height: 100%; width: 0; background: var(--yo); transition: width .2s; } | |
| #chat { flex: 1; display: flex; flex-direction: column; gap: 10px; overflow-y: auto; } | |
| .msg { max-width: 85%; padding: 10px 14px; border-radius: 16px; white-space: pre-wrap; word-wrap: break-word; } | |
| .yo { align-self: flex-end; background: var(--yo); color: var(--yo-texto); border-bottom-right-radius: 4px; } | |
| .ella { align-self: flex-start; background: var(--ella); border-bottom-left-radius: 4px; } | |
| .ella::before { content: "🦉 Minerva"; display: block; font-size: .75rem; font-weight: 600; color: var(--suave); margin-bottom: 2px; } | |
| #ejemplos { display: flex; flex-wrap: wrap; gap: 8px; } | |
| #ejemplos button { background: var(--panel); color: var(--texto); border: 1px solid var(--borde); border-radius: 999px; | |
| padding: 6px 12px; cursor: pointer; font: inherit; font-size: .88rem; } | |
| form { display: flex; gap: 8px; } | |
| input[type=text] { flex: 1; min-width: 0; padding: 12px 14px; border-radius: 12px; border: 1px solid var(--borde); | |
| background: var(--panel); color: var(--texto); font: inherit; } | |
| button.principal { padding: 12px 18px; border: 0; border-radius: 12px; background: var(--yo); color: var(--yo-texto); | |
| font: inherit; font-weight: 600; cursor: pointer; } | |
| button:disabled { opacity: .5; cursor: default; } | |
| .ajustes { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between; color: var(--suave); font-size: .88rem; } | |
| .ajustes button { background: none; border: 0; color: var(--yo); cursor: pointer; font: inherit; padding: 0; } | |
| footer { color: var(--suave); font-size: .8rem; text-align: center; } | |
| </style> | |
| </head> | |
| <body> | |
| <main> | |
| <header> | |
| <h1>🦉 Minerva v0.1</h1> | |
| <p>Un modelo de lenguaje en español de 30 millones de parámetros, entrenado desde cero en un portátil. | |
| Habla bien, pero <strong>se inventa casi todo</strong>: es un modelo de juguete. | |
| <a href="https://huggingface.co/AxoneSystem/Minerva-v0.1" target="_blank" rel="noopener">Ver el modelo</a></p> | |
| </header> | |
| <div id="estado">Preparando…<div id="barra"><div></div></div></div> | |
| <div id="chat"></div> | |
| <div id="ejemplos"> | |
| <button>¿Quién eres?</button> | |
| <button>¿Por qué te llamas Minerva?</button> | |
| <button>¿Qué es la paella?</button> | |
| <button>Cuéntame un cuento corto.</button> | |
| </div> | |
| <form id="formulario"> | |
| <input id="entrada" type="text" placeholder="Escribe a Minerva…" autocomplete="off" disabled> | |
| <button class="principal" id="enviar" disabled>Enviar</button> | |
| </form> | |
| <div class="ajustes"> | |
| <label>Temperatura <input id="temp" type="range" min="0.1" max="1.5" step="0.1" value="0.7"> | |
| <span id="tempValor">0.7</span> <small>(más alta = más loca)</small></label> | |
| <button id="nueva" type="button">Nueva conversación</button> | |
| </div> | |
| <footer>Funciona entero en tu navegador (llama.cpp en WebAssembly): la primera vez descarga ~60 MB.</footer> | |
| </main> | |
| <script type="module"> | |
| import { Wllama } from 'https://cdn.jsdelivr.net/npm/@wllama/wllama@3.6.1/esm/index.js'; | |
| const USUARIO = '<|usuario|>', ASISTENTE = '<|asistente|>', FIN = '<|fin|>'; | |
| const MEMORIA = 1200; // caracteres de historial que recuerda (su contexto es de solo 512 tokens) | |
| const $ = (id) => document.getElementById(id); | |
| const estado = $('estado'), barra = document.querySelector('#barra div'); | |
| const chat = $('chat'), entrada = $('entrada'), enviar = $('enviar'); | |
| let historial = []; // [{yo, ella}] | |
| let ocupada = true; | |
| $('temp').addEventListener('input', (e) => { $('tempValor').textContent = e.target.value; }); | |
| $('nueva').addEventListener('click', () => { historial = []; chat.innerHTML = ''; entrada.focus(); }); | |
| document.querySelectorAll('#ejemplos button').forEach((b) => | |
| b.addEventListener('click', () => { if (!ocupada) preguntar(b.textContent); })); | |
| $('formulario').addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const q = entrada.value.trim(); | |
| if (q && !ocupada) { entrada.value = ''; preguntar(q); } | |
| }); | |
| function burbuja(clase, texto) { | |
| const d = document.createElement('div'); | |
| d.className = 'msg ' + clase; | |
| d.textContent = texto; | |
| chat.appendChild(d); | |
| d.scrollIntoView({ block: 'end' }); | |
| return d; | |
| } | |
| function prompt(pregunta) { | |
| // Mismo formato con el que se entrenó: <|usuario|>…<|asistente|>…<|fin|> | |
| let previo = ''; | |
| for (let i = historial.length - 1; i >= 0; i--) { | |
| const turno = USUARIO + historial[i].yo + ASISTENTE + historial[i].ella + FIN; | |
| if (previo.length + turno.length > MEMORIA) break; | |
| previo = turno + previo; | |
| } | |
| return previo + USUARIO + pregunta + ASISTENTE; | |
| } | |
| function bloquear(si) { | |
| ocupada = si; | |
| entrada.disabled = enviar.disabled = si; | |
| if (!si) entrada.focus(); | |
| } | |
| const URL_MODELO = 'https://huggingface.co/AxoneSystem/Minerva-v0.1/resolve/main/Minerva-v0.1-F16.gguf'; | |
| const PARAMS = { n_ctx: 512, n_gpu_layers: 0 }; | |
| // Con almacenamiento del navegador (OPFS), wllama guarda el modelo y la próxima visita es instantánea. | |
| // Sin él (algunos navegadores o vistas previas), lo descargamos nosotros y lo cargamos desde memoria. | |
| const hayOPFS = !!(navigator.storage && navigator.storage.getDirectory); | |
| const wllama = new Wllama( | |
| { default: 'https://cdn.jsdelivr.net/npm/@wllama/wllama@3.6.1/esm/wasm/wllama.wasm' }, | |
| hayOPFS ? {} : { cacheManager: {}, modelManager: {} }, | |
| ); | |
| function progreso(loaded, total) { | |
| const pct = total ? Math.round((loaded / total) * 100) : 0; | |
| barra.style.width = pct + '%'; | |
| estado.firstChild.textContent = `Descargando a Minerva… ${pct}%`; | |
| } | |
| async function descargarEnMemoria() { | |
| const r = await fetch(URL_MODELO); | |
| if (!r.ok) throw new Error('HTTP ' + r.status); | |
| const total = Number(r.headers.get('content-length')) || 0; | |
| const lector = r.body.getReader(); | |
| const trozos = []; | |
| let cargado = 0; | |
| for (;;) { | |
| const { done, value } = await lector.read(); | |
| if (done) break; | |
| trozos.push(value); | |
| cargado += value.length; | |
| progreso(cargado, total); | |
| } | |
| return new Blob(trozos); | |
| } | |
| async function preguntar(q) { | |
| bloquear(true); | |
| burbuja('yo', q); | |
| const b = burbuja('ella', '…'); | |
| let texto = ''; | |
| try { | |
| await wllama.createCompletion({ | |
| prompt: prompt(q), | |
| max_tokens: 150, | |
| temperature: parseFloat($('temp').value), | |
| top_k: 30, | |
| stop: [FIN, USUARIO], | |
| stream: true, | |
| onData: (trozo) => { | |
| texto += trozo.choices?.[0]?.text ?? ''; | |
| b.textContent = texto.trim() || '…'; | |
| b.scrollIntoView({ block: 'end' }); | |
| }, | |
| }); | |
| texto = texto.trim() || '(se ha quedado sin palabras)'; | |
| b.textContent = texto; | |
| historial.push({ yo: q, ella: texto }); | |
| } catch (err) { | |
| b.textContent = 'Error: ' + (err?.message || err); | |
| } | |
| bloquear(false); | |
| } | |
| try { | |
| estado.firstChild.textContent = 'Descargando a Minerva (~60 MB)…'; | |
| if (hayOPFS) { | |
| await wllama.loadModelFromHF( | |
| { repo: 'AxoneSystem/Minerva-v0.1', file: 'Minerva-v0.1-F16.gguf' }, | |
| { ...PARAMS, progressCallback: ({ loaded, total }) => progreso(loaded, total) }, | |
| ); | |
| } else { | |
| await wllama.loadModel([await descargarEnMemoria()], PARAMS); | |
| } | |
| estado.style.display = 'none'; | |
| bloquear(false); | |
| } catch (err) { | |
| estado.firstChild.textContent = 'No se pudo cargar el modelo: ' + (err?.message || err); | |
| } | |
| </script> | |
| </body> | |
| </html> | |