AxoneSystem commited on
Commit
1bfeffc
·
verified ·
1 Parent(s): a268a35

Upload 2 files

Browse files
Files changed (2) hide show
  1. README.md +4 -4
  2. index.html +204 -17
README.md CHANGED
@@ -3,10 +3,7 @@ title: Minerva Chat
3
  emoji: 🦉
4
  colorFrom: purple
5
  colorTo: yellow
6
- sdk: gradio
7
- sdk_version: 6.28.0
8
- python_version: "3.12"
9
- app_file: app.py
10
  pinned: false
11
  license: cc-by-nc-4.0
12
  short_description: Chatea con Minerva v0.1, un modelo diminuto en español
@@ -17,3 +14,6 @@ models:
17
  Chat con [Minerva v0.1](https://huggingface.co/AxoneSystem/Minerva-v0.1), un modelo de lenguaje en español
18
  de 30 millones de parámetros entrenado desde cero en un portátil. Es un modelo de juguete: habla bien,
19
  pero se inventa casi todo.
 
 
 
 
3
  emoji: 🦉
4
  colorFrom: purple
5
  colorTo: yellow
6
+ sdk: static
 
 
 
7
  pinned: false
8
  license: cc-by-nc-4.0
9
  short_description: Chatea con Minerva v0.1, un modelo diminuto en español
 
14
  Chat con [Minerva v0.1](https://huggingface.co/AxoneSystem/Minerva-v0.1), un modelo de lenguaje en español
15
  de 30 millones de parámetros entrenado desde cero en un portátil. Es un modelo de juguete: habla bien,
16
  pero se inventa casi todo.
17
+
18
+ Funciona entero en el navegador de quien lo visita (llama.cpp en WebAssembly con
19
+ [wllama](https://github.com/ngxson/wllama)), así que no necesita servidor.
index.html CHANGED
@@ -1,19 +1,206 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Minerva Chat</title>
7
+ <style>
8
+ :root {
9
+ --fondo: #f7f5fb; --panel: #ffffff; --texto: #1f1a2e; --suave: #6b6480; --borde: #e3def0;
10
+ --yo: #5b3fc4; --yo-texto: #ffffff; --ella: #f0ecfa; --acento: #d4a017;
11
+ }
12
+ @media (prefers-color-scheme: dark) {
13
+ :root {
14
+ --fondo: #15121d; --panel: #1e1a29; --texto: #ece8f6; --suave: #a39cb8; --borde: #322c44;
15
+ --yo: #7b5ff0; --yo-texto: #ffffff; --ella: #2a2438; --acento: #e8b931;
16
+ }
17
+ }
18
+ * { box-sizing: border-box; }
19
+ body { margin: 0; background: var(--fondo); color: var(--texto);
20
+ font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
21
+ main { max-width: 760px; margin: 0 auto; padding: 16px; min-height: 100vh; display: flex; flex-direction: column; gap: 12px; }
22
+ header h1 { margin: 0; font-size: 1.5rem; }
23
+ header p { margin: 4px 0 0; color: var(--suave); font-size: .92rem; }
24
+ header a { color: var(--yo); }
25
+ #estado { background: var(--panel); border: 1px solid var(--borde); border-radius: 12px; padding: 12px; font-size: .92rem; }
26
+ #barra { height: 6px; background: var(--borde); border-radius: 3px; margin-top: 8px; overflow: hidden; }
27
+ #barra div { height: 100%; width: 0; background: var(--yo); transition: width .2s; }
28
+ #chat { flex: 1; display: flex; flex-direction: column; gap: 10px; overflow-y: auto; }
29
+ .msg { max-width: 85%; padding: 10px 14px; border-radius: 16px; white-space: pre-wrap; word-wrap: break-word; }
30
+ .yo { align-self: flex-end; background: var(--yo); color: var(--yo-texto); border-bottom-right-radius: 4px; }
31
+ .ella { align-self: flex-start; background: var(--ella); border-bottom-left-radius: 4px; }
32
+ .ella::before { content: "🦉 Minerva"; display: block; font-size: .75rem; font-weight: 600; color: var(--suave); margin-bottom: 2px; }
33
+ #ejemplos { display: flex; flex-wrap: wrap; gap: 8px; }
34
+ #ejemplos button { background: var(--panel); color: var(--texto); border: 1px solid var(--borde); border-radius: 999px;
35
+ padding: 6px 12px; cursor: pointer; font: inherit; font-size: .88rem; }
36
+ form { display: flex; gap: 8px; }
37
+ input[type=text] { flex: 1; min-width: 0; padding: 12px 14px; border-radius: 12px; border: 1px solid var(--borde);
38
+ background: var(--panel); color: var(--texto); font: inherit; }
39
+ button.principal { padding: 12px 18px; border: 0; border-radius: 12px; background: var(--yo); color: var(--yo-texto);
40
+ font: inherit; font-weight: 600; cursor: pointer; }
41
+ button:disabled { opacity: .5; cursor: default; }
42
+ .ajustes { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between; color: var(--suave); font-size: .88rem; }
43
+ .ajustes button { background: none; border: 0; color: var(--yo); cursor: pointer; font: inherit; padding: 0; }
44
+ footer { color: var(--suave); font-size: .8rem; text-align: center; }
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <main>
49
+ <header>
50
+ <h1>🦉 Minerva v0.1</h1>
51
+ <p>Un modelo de lenguaje en español de 30 millones de parámetros, entrenado desde cero en un portátil.
52
+ Habla bien, pero <strong>se inventa casi todo</strong>: es un modelo de juguete.
53
+ <a href="https://huggingface.co/AxoneSystem/Minerva-v0.1" target="_blank" rel="noopener">Ver el modelo</a></p>
54
+ </header>
55
+
56
+ <div id="estado">Preparando…<div id="barra"><div></div></div></div>
57
+
58
+ <div id="chat"></div>
59
+
60
+ <div id="ejemplos">
61
+ <button>¿Quién eres?</button>
62
+ <button>¿Por qué te llamas Minerva?</button>
63
+ <button>¿Qué es la paella?</button>
64
+ <button>Cuéntame un cuento corto.</button>
65
+ </div>
66
+
67
+ <form id="formulario">
68
+ <input id="entrada" type="text" placeholder="Escribe a Minerva…" autocomplete="off" disabled>
69
+ <button class="principal" id="enviar" disabled>Enviar</button>
70
+ </form>
71
+
72
+ <div class="ajustes">
73
+ <label>Temperatura <input id="temp" type="range" min="0.1" max="1.5" step="0.1" value="0.7">
74
+ <span id="tempValor">0.7</span> <small>(más alta = más loca)</small></label>
75
+ <button id="nueva" type="button">Nueva conversación</button>
76
+ </div>
77
+
78
+ <footer>Funciona entero en tu navegador (llama.cpp en WebAssembly): la primera vez descarga ~60 MB.</footer>
79
+ </main>
80
+
81
+ <script type="module">
82
+ import { Wllama } from 'https://cdn.jsdelivr.net/npm/@wllama/wllama@3.6.1/esm/index.js';
83
+
84
+ const USUARIO = '<|usuario|>', ASISTENTE = '<|asistente|>', FIN = '<|fin|>';
85
+ const MEMORIA = 1200; // caracteres de historial que recuerda (su contexto es de solo 512 tokens)
86
+ const $ = (id) => document.getElementById(id);
87
+ const estado = $('estado'), barra = document.querySelector('#barra div');
88
+ const chat = $('chat'), entrada = $('entrada'), enviar = $('enviar');
89
+ let historial = []; // [{yo, ella}]
90
+ let ocupada = true;
91
+
92
+ $('temp').addEventListener('input', (e) => { $('tempValor').textContent = e.target.value; });
93
+ $('nueva').addEventListener('click', () => { historial = []; chat.innerHTML = ''; entrada.focus(); });
94
+ document.querySelectorAll('#ejemplos button').forEach((b) =>
95
+ b.addEventListener('click', () => { if (!ocupada) preguntar(b.textContent); }));
96
+ $('formulario').addEventListener('submit', (e) => {
97
+ e.preventDefault();
98
+ const q = entrada.value.trim();
99
+ if (q && !ocupada) { entrada.value = ''; preguntar(q); }
100
+ });
101
+
102
+ function burbuja(clase, texto) {
103
+ const d = document.createElement('div');
104
+ d.className = 'msg ' + clase;
105
+ d.textContent = texto;
106
+ chat.appendChild(d);
107
+ d.scrollIntoView({ block: 'end' });
108
+ return d;
109
+ }
110
+
111
+ function prompt(pregunta) {
112
+ // Mismo formato con el que se entrenó: <|usuario|>…<|asistente|>…<|fin|>
113
+ let previo = '';
114
+ for (let i = historial.length - 1; i >= 0; i--) {
115
+ const turno = USUARIO + historial[i].yo + ASISTENTE + historial[i].ella + FIN;
116
+ if (previo.length + turno.length > MEMORIA) break;
117
+ previo = turno + previo;
118
+ }
119
+ return previo + USUARIO + pregunta + ASISTENTE;
120
+ }
121
+
122
+ function bloquear(si) {
123
+ ocupada = si;
124
+ entrada.disabled = enviar.disabled = si;
125
+ if (!si) entrada.focus();
126
+ }
127
+
128
+ const URL_MODELO = 'https://huggingface.co/AxoneSystem/Minerva-v0.1/resolve/main/Minerva-v0.1-F16.gguf';
129
+ const PARAMS = { n_ctx: 512, n_gpu_layers: 0 };
130
+ // Con almacenamiento del navegador (OPFS), wllama guarda el modelo y la próxima visita es instantánea.
131
+ // Sin él (algunos navegadores o vistas previas), lo descargamos nosotros y lo cargamos desde memoria.
132
+ const hayOPFS = !!(navigator.storage && navigator.storage.getDirectory);
133
+ const wllama = new Wllama(
134
+ { default: 'https://cdn.jsdelivr.net/npm/@wllama/wllama@3.6.1/esm/wasm/wllama.wasm' },
135
+ hayOPFS ? {} : { cacheManager: {}, modelManager: {} },
136
+ );
137
+
138
+ function progreso(loaded, total) {
139
+ const pct = total ? Math.round((loaded / total) * 100) : 0;
140
+ barra.style.width = pct + '%';
141
+ estado.firstChild.textContent = `Descargando a Minerva… ${pct}%`;
142
+ }
143
+
144
+ async function descargarEnMemoria() {
145
+ const r = await fetch(URL_MODELO);
146
+ if (!r.ok) throw new Error('HTTP ' + r.status);
147
+ const total = Number(r.headers.get('content-length')) || 0;
148
+ const lector = r.body.getReader();
149
+ const trozos = [];
150
+ let cargado = 0;
151
+ for (;;) {
152
+ const { done, value } = await lector.read();
153
+ if (done) break;
154
+ trozos.push(value);
155
+ cargado += value.length;
156
+ progreso(cargado, total);
157
+ }
158
+ return new Blob(trozos);
159
+ }
160
+
161
+ async function preguntar(q) {
162
+ bloquear(true);
163
+ burbuja('yo', q);
164
+ const b = burbuja('ella', '…');
165
+ let texto = '';
166
+ try {
167
+ await wllama.createCompletion({
168
+ prompt: prompt(q),
169
+ max_tokens: 150,
170
+ temperature: parseFloat($('temp').value),
171
+ top_k: 30,
172
+ stop: [FIN, USUARIO],
173
+ stream: true,
174
+ onData: (trozo) => {
175
+ texto += trozo.choices?.[0]?.text ?? '';
176
+ b.textContent = texto.trim() || '…';
177
+ b.scrollIntoView({ block: 'end' });
178
+ },
179
+ });
180
+ texto = texto.trim() || '(se ha quedado sin palabras)';
181
+ b.textContent = texto;
182
+ historial.push({ yo: q, ella: texto });
183
+ } catch (err) {
184
+ b.textContent = 'Error: ' + (err?.message || err);
185
+ }
186
+ bloquear(false);
187
+ }
188
+
189
+ try {
190
+ estado.firstChild.textContent = 'Descargando a Minerva (~60 MB)…';
191
+ if (hayOPFS) {
192
+ await wllama.loadModelFromHF(
193
+ { repo: 'AxoneSystem/Minerva-v0.1', file: 'Minerva-v0.1-F16.gguf' },
194
+ { ...PARAMS, progressCallback: ({ loaded, total }) => progreso(loaded, total) },
195
+ );
196
+ } else {
197
+ await wllama.loadModel([await descargarEnMemoria()], PARAMS);
198
+ }
199
+ estado.style.display = 'none';
200
+ bloquear(false);
201
+ } catch (err) {
202
+ estado.firstChild.textContent = 'No se pudo cargar el modelo: ' + (err?.message || err);
203
+ }
204
+ </script>
205
+ </body>
206
  </html>