fivetech commited on
Commit
dfc7206
·
verified ·
1 Parent(s): 6c276a0

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +184 -67
index.html CHANGED
@@ -6,35 +6,73 @@
6
  <title>Multi-Agent Debate - MiMo-V2.6-Distill-Qwen-9B</title>
7
  <style>
8
  :root{
 
 
 
 
 
 
 
9
  --bg:#efeae2; --surface:#ffffff; --surface2:#f0f2f5; --border:#d1d7db;
10
- --text:#111b21; --muted:#667781; --accent:#00a884;
11
- --header:#008069; --bubble-in:#ffffff; --bubble-out:#d9fdd3; --sys:#fdf3c6;
 
 
12
  }
13
- body.light{ --bg:#efeae2; --surface:#ffffff; --surface2:#f0f2f5; --border:#d1d7db; --text:#111b21; --muted:#667781; }
14
  *{box-sizing:border-box}
15
  html,body{margin:0;padding:0;background:var(--bg);color:var(--text);
16
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-size:15px}
17
- #app{max-width:900px;margin:0 auto;display:flex;flex-direction:column;height:100vh;height:100dvh;
18
- box-shadow:0 0 12px rgba(11,20,26,.08);background:var(--bg)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
- header{display:flex;align-items:center;gap:10px;padding:8px 16px;background:var(--header);color:#fff;
21
- border-bottom:none;box-shadow:0 1px 3px rgba(11,20,26,.2);z-index:2}
22
- header .logo{width:40px;height:40px;border-radius:50%;background:rgba(255,255,255,.22);
23
- display:flex;align-items:center;justify-content:center;font-size:19px;color:#fff;flex:none}
 
 
 
 
24
  header .title{font-weight:600;font-size:16px;letter-spacing:.1px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
25
- header .subtitle{font-size:12px;color:rgba(255,255,255,.75);font-weight:400}
26
- #settings-btn{background:none;border:none;color:#fff;border-radius:50%;
27
  width:38px;height:38px;cursor:pointer;font-size:18px;display:flex;align-items:center;justify-content:center;transition:.15s}
28
- #settings-btn:hover{background:rgba(255,255,255,.15)}
29
 
30
  #chat{flex:1;overflow-y:auto;padding:20px 8%;display:flex;flex-direction:column;gap:8px;
31
  background-color:var(--bg);
 
 
32
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'%3E%3Cg fill='none' stroke='%23d8d1c6' stroke-width='1.4' opacity='0.5'%3E%3Ccircle cx='20' cy='20' r='6'/%3E%3Cpath d='M50 15h16a6 6 0 0 1 6 6v8a6 6 0 0 1-6 6h-9l-5 5v-5h-2a6 6 0 0 1-6-6v-8a6 6 0 0 1 6-6z'/%3E%3Cpath d='M15 60l4 4 8-8'/%3E%3Ccircle cx='95' cy='70' r='8'/%3E%3Cpath d='M40 95c4-6 12-6 16 0'/%3E%3Cpath d='M85 25l2.5 5 5 2.5-5 2.5-2.5 5-2.5-5-5-2.5 5-2.5z'/%3E%3Crect x='60' y='55' width='12' height='12' rx='3'/%3E%3Cpath d='M20 100q6-8 12 0'/%3E%3C/g%3E%3C/svg%3E")}
 
 
33
  .msg{display:flex;gap:8px;animation:fadein .2s ease;max-width:100%}
34
- .msg .avatar{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;
35
- font-size:15px;color:#fff;flex:none;margin-top:0}
36
  .msg .content{flex:1;min-width:0;max-width:75%;background:var(--bubble-in);border-radius:8px;padding:6px 9px 8px;
37
- box-shadow:0 1px 1px rgba(11,20,26,.12)}
38
  .msg .who{font-size:13px;font-weight:600;margin-bottom:2px}
39
  .msg .who .round{font-weight:400;color:var(--muted);margin-left:6px;font-size:11px}
40
  .msg .body{line-height:1.42;word-wrap:break-word;color:var(--text);font-size:14.5px}
@@ -43,8 +81,8 @@ header .subtitle{font-size:12px;color:rgba(255,255,255,.75);font-weight:400}
43
  .msg.user .content{flex:none;background:var(--bubble-out);border:none;
44
  border-radius:8px;padding:7px 10px}
45
  .msg.user .who{display:none}
46
- .msg.err{align-self:center;max-width:92%;background:var(--sys);color:#54656f;font-size:12.5px;
47
- border-radius:8px;padding:6px 12px;text-align:center;box-shadow:0 1px 1px rgba(11,20,26,.12)}
48
  @keyframes fadein{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
49
  @keyframes pulse{0%,100%{opacity:.3}50%{opacity:1}}
50
  .msg.streaming .body::after{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;
@@ -53,22 +91,22 @@ details.think{margin:2px 0 6px;font-size:12.5px;color:var(--muted)}
53
  details.think summary{cursor:pointer;font-size:12px;font-style:italic}
54
  details.think .t{white-space:pre-wrap;border-left:3px solid var(--border);padding-left:10px;margin-top:5px;font-size:12.5px}
55
 
56
- #controls{padding:8px 12px;background:var(--surface2);display:flex;gap:8px;align-items:center;border-top:none}
57
- #topic{flex:1;min-width:0;background:#fff;border:none;color:var(--text);
58
- border-radius:8px;padding:11px 14px;font-size:15px;outline:none;box-shadow:0 1px 1px rgba(11,20,26,.08)}
59
  #topic::placeholder{color:var(--muted)}
60
  #start-btn{flex:none;background:var(--accent);color:#fff;border:none;border-radius:999px;padding:11px 18px;
61
- cursor:pointer;font-weight:600;font-size:14px;transition:background .15s;white-space:nowrap;box-shadow:0 1px 2px rgba(11,20,26,.2)}
62
  #start-btn:hover{background:#02b08a}
63
- #start-btn:disabled{background:#aebac1;cursor:default;box-shadow:none}
64
- #stop-btn{flex:none;background:#fff;border:1px solid #ea0038;color:#ea0038;border-radius:999px;
65
  padding:10px 16px;cursor:pointer;font-weight:600;font-size:14px;display:none}
66
 
67
  #settings{display:none;margin:8px 12px 10px;padding:16px;border:1px solid var(--border);
68
- border-radius:12px;background:var(--surface);box-shadow:0 2px 6px rgba(11,20,26,.1)}
69
  #settings.open{display:block}
70
  #settings label{display:block;font-size:12px;color:var(--muted);margin:10px 0 4px}
71
- #settings input,#settings textarea{width:100%;background:var(--surface2);color:var(--text);
72
  border:1px solid var(--border);border-radius:8px;padding:9px 12px;font-size:14px;font-family:inherit;outline:none}
73
  #settings input:focus,#settings textarea:focus{border-color:var(--accent)}
74
  #settings textarea{min-height:150px;font-family:ui-monospace,"SF Mono",Menlo,monospace;font-size:12px}
@@ -78,18 +116,27 @@ details.think .t{white-space:pre-wrap;border-left:3px solid var(--border);paddin
78
  padding:10px 18px;cursor:pointer;font-weight:600}
79
 
80
  #status{padding:6px 16px;font-size:12px;color:var(--muted);background:var(--bg)}
81
- #progress{height:3px;background:rgba(11,20,26,.08)}
82
  #progress>div{height:100%;width:0;background:var(--accent);transition:width .25s}
83
- footer{padding:10px 16px;font-size:11px;color:var(--muted);border-top:1px solid var(--border);background:var(--surface2);
84
  display:flex;gap:8px 16px;flex-wrap:wrap;justify-content:center;text-align:center}
85
  footer a{color:var(--accent);text-decoration:none}
86
 
87
- @media (max-width:640px){
 
 
 
 
 
 
 
 
 
88
  #chat{padding:12px 4%;gap:7px}
89
  #controls{padding:8px 8px}
90
  header{padding:8px 10px}
91
  header .subtitle{display:none}
92
- .msg .avatar{width:28px;height:28px;font-size:13px}
93
  .msg .content{max-width:85%}
94
  #settings{margin:0 8px 8px}
95
  }
@@ -97,42 +144,51 @@ footer a{color:var(--accent);text-decoration:none}
97
  </head>
98
  <body>
99
  <div id="app">
100
- <header>
101
- <span class="logo">&#10022;</span>
102
- <span class="title" id="title">Multi-Agent Debate <span class="subtitle">&middot; MiMo-V2.6-Distill-Qwen-9B</span></span>
103
- <button id="settings-btn" title="Ajustes">&#9881;</button>
104
- </header>
105
-
106
- <div id="settings">
107
- <div class="row">
108
- <div><label>Repositorio Hugging Face</label><input id="set-repo"></div>
109
- <div><label>Archivo GGUF</label><input id="set-file"></div>
110
  </div>
111
- <div class="row">
112
- <div><label>Contexto (tokens)</label><input id="set-ctx" type="number" step="512" min="2048"></div>
113
- <div><label>M&aacute;x. tokens por turno</label><input id="set-maxtokens" type="number" step="32" min="64"></div>
114
- <div><label>Temperatura</label><input id="set-temp" type="number" step="0.05" min="0" max="2"></div>
115
- <div><label>Rondas</label><input id="set-rounds" type="number" step="1" min="1" max="20"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  </div>
117
- <label>Agentes (JSON: name, emoji, color, system)</label>
118
- <textarea id="set-agents" spellcheck="false"></textarea>
119
- <button id="apply-settings">Aplicar</button>
120
- </div>
121
 
122
- <div id="chat"></div>
123
 
124
- <div id="controls">
125
- <input id="topic" placeholder="Propón un tema y los agentes debaten&hellip;">
126
- <button id="start-btn" disabled>Cargando modelo&hellip;</button>
127
- <button id="stop-btn">Detener</button>
128
- </div>
129
 
130
- <div id="status">Iniciando&hellip;</div>
131
- <div id="progress"><div></div></div>
132
- <footer>
133
- <span>La conversación se procesa en tu navegador (WebGPU si está disponible; si no, CPU/WASM). Solo se conecta a Hugging Face para descargar el modelo (5,8 GB la primera vez) y a jsDelivr para las librerías.</span>
134
- <span>Motor: <a href="https://github.com/ngxson/wllama" target="_blank" rel="noopener">wllama</a> (MIT) &middot; inspirado en el widget de <a href="https://localellm.com" target="_blank" rel="noopener">localellm.com</a> &middot; modelo <a href="https://huggingface.co/XiaomiMiMo/MiMo-V2.6-Distill-Qwen-9B" target="_blank" rel="noopener">MiMo-V2.6-Distill-Qwen-9B</a> (MIT, Xiaomi)</span>
135
- </footer>
 
136
  </div>
137
 
138
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
@@ -142,11 +198,11 @@ import { Wllama } from 'https://cdn.jsdelivr.net/npm/@wllama/wllama@3.5.1/esm/in
142
  const params = new URLSearchParams(location.search);
143
 
144
  const DEFAULT_AGENTS = [
145
- { name: 'Ada', emoji: '\u2726', color: '#58a6ff',
146
  system: 'Eres Ada, la analista entusiasta del grupo. Defiendes las ideas con argumentos concretos, propones caminos a seguir y reconoces los puntos fuertes de los dem\u00e1s.' },
147
- { name: 'Bruno', emoji: '\u25B2', color: '#f0883e',
148
  system: 'Eres Bruno, el esc\u00e9ptico del grupo. Cuestionas supuestos, se\u00f1alas riesgos y fallos l\u00f3gicos, y pides evidencia. Eres duro con las ideas y respetuoso con las personas.' },
149
- { name: 'Carla', emoji: '\u25CF', color: '#3fb950',
150
  system: 'Eres Carla, la moderadora. Resume lo dicho, integra los desacuerdos y propone s\u00edntesis o el siguiente paso. Cierra cada ronda con una conclusi\u00f3n breve.' },
151
  ];
152
 
@@ -172,6 +228,8 @@ const progressEl = document.getElementById('progress').firstElementChild;
172
  const topicEl = document.getElementById('topic');
173
  const startBtn = document.getElementById('start-btn');
174
  const stopBtn = document.getElementById('stop-btn');
 
 
175
 
176
  let wllama = null;
177
  let running = false;
@@ -184,6 +242,60 @@ function setProgress(p) { progressEl.style.width = Math.max(0, Math.min(100, p))
184
 
185
  function esc(s){ const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
186
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  function addMsg(agent, isUser = false) {
188
  const d = document.createElement('div');
189
  d.className = 'msg' + (isUser ? ' user' : '');
@@ -197,7 +309,8 @@ function addMsg(agent, isUser = false) {
197
  else {
198
  const av = document.createElement('div');
199
  av.className = 'avatar';
200
- av.style.background = agent.color;
 
201
  av.textContent = agent.emoji;
202
  d.appendChild(av);
203
  who.textContent = agent.name;
@@ -296,6 +409,7 @@ async function agentTurn(agent) {
296
  ];
297
  const { el, body } = addMsg(agent);
298
  el.classList.add('streaming');
 
299
  let think = null;
300
  let answer = '', reasoning = '', count = 0;
301
  setStatus(`${agent.name} est\u00e1 escribiendo\u2026`);
@@ -330,14 +444,16 @@ async function agentTurn(agent) {
330
  el.classList.add('err');
331
  body.textContent = 'Error: ' + (e?.message || e);
332
  el.classList.remove('streaming');
 
333
  return;
334
  }
335
  el.classList.remove('streaming');
336
- if (stopRequested) { el.remove(); setStatus('Detenido'); return; }
337
  answer = answer.replace(/^\s*(?:\*\*)?[A-Za-z\u00c0-\u024f]+:\s*/, '').trim();
338
- if (!answer) { el.remove(); setStatus('Listo'); return; }
339
  body.innerHTML = (typeof marked !== 'undefined') ? marked.parse(answer) : esc(answer).replace(/\n/g, '<br>');
340
  transcript.push({ name: agent.name, text: answer });
 
341
  setStatus('Listo');
342
  }
343
 
@@ -407,6 +523,7 @@ document.getElementById('apply-settings').addEventListener('click', async () =>
407
  try { agents = JSON.parse(document.getElementById('set-agents').value); }
408
  catch (e) { alert('JSON de agentes inv\u00e1lido: ' + e.message); return; }
409
  if (!Array.isArray(agents) || agents.length < 2) { alert('Se necesitan al menos 2 agentes.'); return; }
 
410
  CONFIG.maxTokens = parseInt(document.getElementById('set-maxtokens').value) || CONFIG.maxTokens;
411
  CONFIG.temperature = parseFloat(document.getElementById('set-temp').value) || CONFIG.temperature;
412
  CONFIG.rounds = parseInt(document.getElementById('set-rounds').value) || CONFIG.rounds;
@@ -424,4 +541,4 @@ if (CONFIG.autoload) loadModel(CONFIG.repo, CONFIG.file);
424
  else setStatus('Modo manual: carga el modelo desde Ajustes.');
425
  </script>
426
  </body>
427
- </html>
 
6
  <title>Multi-Agent Debate - MiMo-V2.6-Distill-Qwen-9B</title>
7
  <style>
8
  :root{
9
+ --bg:#0b141a; --surface:#111b21; --surface2:#202c33; --border:#2a3942;
10
+ --text:#e9edef; --muted:#8696a0; --accent:#00a884;
11
+ --header:#202c33; --header-text:#e9edef; --header-sub:#8696a0;
12
+ --bubble-in:#202c33; --bubble-out:#005c4b; --sys:#2a3942; --sys-text:#ffd279;
13
+ --input:#2a3942; --track:rgba(255,255,255,.12); --disabled:#374248;
14
+ }
15
+ body.light{
16
  --bg:#efeae2; --surface:#ffffff; --surface2:#f0f2f5; --border:#d1d7db;
17
+ --text:#111b21; --muted:#667781;
18
+ --header:#008069; --header-text:#ffffff; --header-sub:rgba(255,255,255,.75);
19
+ --bubble-in:#ffffff; --bubble-out:#d9fdd3; --sys:#fdf3c6; --sys-text:#54656f;
20
+ --input:#ffffff; --track:rgba(11,20,26,.08); --disabled:#aebac1;
21
  }
 
22
  *{box-sizing:border-box}
23
  html,body{margin:0;padding:0;background:var(--bg);color:var(--text);
24
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-size:15px}
25
+ #app{max-width:1200px;margin:0 auto;display:flex;height:100vh;height:100dvh;
26
+ box-shadow:0 0 14px rgba(0,0,0,.4);background:var(--bg)}
27
+
28
+ /* --- Lista de contactos (agentes) --- */
29
+ #sidebar{width:300px;flex:none;display:flex;flex-direction:column;min-height:0;
30
+ background:var(--surface);border-right:1px solid var(--border)}
31
+ .side-header{display:flex;align-items:center;gap:10px;padding:10px 16px;
32
+ background:var(--header);color:var(--header-text)}
33
+ .side-logo{width:38px;height:38px;border-radius:50%;flex:none;font-size:19px;
34
+ background:linear-gradient(135deg,#00c49a,#025c4b);display:flex;align-items:center;justify-content:center;
35
+ box-shadow:0 0 10px rgba(0,168,132,.45)}
36
+ .side-title{font-weight:600;font-size:16px;letter-spacing:.1px}
37
+ .side-count{font-weight:400;color:var(--muted);font-size:12.5px;margin-left:4px}
38
+ #contacts{flex:1;overflow-y:auto;padding:6px 0}
39
+ .contact{display:flex;align-items:center;gap:12px;padding:9px 14px;transition:background .15s}
40
+ .contact:hover{background:var(--surface2)}
41
+ .contact.active{background:var(--surface2)}
42
+ .c-avatar{width:46px;height:46px;border-radius:50%;display:flex;align-items:center;justify-content:center;
43
+ font-size:22px;color:#fff;flex:none}
44
+ .c-info{flex:1;min-width:0;display:flex;flex-direction:column}
45
+ .c-name{font-size:15px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
46
+ .c-role{font-size:12.5px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
47
+ .contact.active .c-role{color:var(--accent);font-style:italic}
48
+ .c-dot{width:9px;height:9px;border-radius:50%;background:var(--accent);flex:none;box-shadow:0 0 6px var(--accent)}
49
 
50
+ /* --- Columna principal --- */
51
+ #main{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column}
52
+
53
+ header{display:flex;align-items:center;gap:10px;padding:8px 16px;background:var(--header);color:var(--header-text);
54
+ border-bottom:none;box-shadow:0 1px 3px rgba(0,0,0,.3);z-index:2}
55
+ header .logo{width:40px;height:40px;border-radius:50%;background:linear-gradient(135deg,#00c49a,#025c4b);
56
+ display:flex;align-items:center;justify-content:center;font-size:19px;color:#fff;flex:none;
57
+ box-shadow:0 0 10px rgba(0,168,132,.45)}
58
  header .title{font-weight:600;font-size:16px;letter-spacing:.1px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
59
+ header .subtitle{font-size:12px;color:var(--header-sub);font-weight:400}
60
+ #settings-btn{background:none;border:none;color:var(--header-text);border-radius:50%;
61
  width:38px;height:38px;cursor:pointer;font-size:18px;display:flex;align-items:center;justify-content:center;transition:.15s}
62
+ #settings-btn:hover{background:rgba(255,255,255,.12)}
63
 
64
  #chat{flex:1;overflow-y:auto;padding:20px 8%;display:flex;flex-direction:column;gap:8px;
65
  background-color:var(--bg);
66
+ background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'%3E%3Cg fill='none' stroke='%232a3942' stroke-width='1.4' opacity='0.55'%3E%3Ccircle cx='20' cy='20' r='6'/%3E%3Cpath d='M50 15h16a6 6 0 0 1 6 6v8a6 6 0 0 1-6 6h-9l-5 5v-5h-2a6 6 0 0 1-6-6v-8a6 6 0 0 1 6-6z'/%3E%3Cpath d='M15 60l4 4 8-8'/%3E%3Ccircle cx='95' cy='70' r='8'/%3E%3Cpath d='M40 95c4-6 12-6 16 0'/%3E%3Cpath d='M85 25l2.5 5 5 2.5-5 2.5-2.5 5-2.5-5-5-2.5 5-2.5z'/%3E%3Crect x='60' y='55' width='12' height='12' rx='3'/%3E%3Cpath d='M20 100q6-8 12 0'/%3E%3C/g%3E%3C/svg%3E")}
67
+ body.light #chat{
68
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 120 120'%3E%3Cg fill='none' stroke='%23d8d1c6' stroke-width='1.4' opacity='0.5'%3E%3Ccircle cx='20' cy='20' r='6'/%3E%3Cpath d='M50 15h16a6 6 0 0 1 6 6v8a6 6 0 0 1-6 6h-9l-5 5v-5h-2a6 6 0 0 1-6-6v-8a6 6 0 0 1 6-6z'/%3E%3Cpath d='M15 60l4 4 8-8'/%3E%3Ccircle cx='95' cy='70' r='8'/%3E%3Cpath d='M40 95c4-6 12-6 16 0'/%3E%3Cpath d='M85 25l2.5 5 5 2.5-5 2.5-2.5 5-2.5-5-5-2.5 5-2.5z'/%3E%3Crect x='60' y='55' width='12' height='12' rx='3'/%3E%3Cpath d='M20 100q6-8 12 0'/%3E%3C/g%3E%3C/svg%3E")}
69
+ #chat::-webkit-scrollbar,#contacts::-webkit-scrollbar{width:6px;height:6px}
70
+ #chat::-webkit-scrollbar-thumb,#contacts::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
71
  .msg{display:flex;gap:8px;animation:fadein .2s ease;max-width:100%}
72
+ .msg .avatar{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;
73
+ font-size:17px;color:#fff;flex:none;margin-top:0}
74
  .msg .content{flex:1;min-width:0;max-width:75%;background:var(--bubble-in);border-radius:8px;padding:6px 9px 8px;
75
+ box-shadow:0 1px 1px rgba(0,0,0,.3)}
76
  .msg .who{font-size:13px;font-weight:600;margin-bottom:2px}
77
  .msg .who .round{font-weight:400;color:var(--muted);margin-left:6px;font-size:11px}
78
  .msg .body{line-height:1.42;word-wrap:break-word;color:var(--text);font-size:14.5px}
 
81
  .msg.user .content{flex:none;background:var(--bubble-out);border:none;
82
  border-radius:8px;padding:7px 10px}
83
  .msg.user .who{display:none}
84
+ .msg.err{align-self:center;max-width:92%;background:var(--sys);color:var(--sys-text);font-size:12.5px;
85
+ border-radius:8px;padding:6px 12px;text-align:center;box-shadow:0 1px 1px rgba(0,0,0,.3)}
86
  @keyframes fadein{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
87
  @keyframes pulse{0%,100%{opacity:.3}50%{opacity:1}}
88
  .msg.streaming .body::after{content:"";display:inline-block;width:7px;height:7px;border-radius:50%;
 
91
  details.think summary{cursor:pointer;font-size:12px;font-style:italic}
92
  details.think .t{white-space:pre-wrap;border-left:3px solid var(--border);padding-left:10px;margin-top:5px;font-size:12.5px}
93
 
94
+ #controls{padding:8px 12px;background:var(--surface);display:flex;gap:8px;align-items:center;border-top:none}
95
+ #topic{flex:1;min-width:0;background:var(--input);border:none;color:var(--text);
96
+ border-radius:8px;padding:11px 14px;font-size:15px;outline:none}
97
  #topic::placeholder{color:var(--muted)}
98
  #start-btn{flex:none;background:var(--accent);color:#fff;border:none;border-radius:999px;padding:11px 18px;
99
+ cursor:pointer;font-weight:600;font-size:14px;transition:background .15s;white-space:nowrap;box-shadow:0 1px 2px rgba(0,0,0,.3)}
100
  #start-btn:hover{background:#02b08a}
101
+ #start-btn:disabled{background:var(--disabled);color:var(--muted);cursor:default;box-shadow:none}
102
+ #stop-btn{flex:none;background:transparent;border:1px solid #ea0038;color:#ea0038;border-radius:999px;
103
  padding:10px 16px;cursor:pointer;font-weight:600;font-size:14px;display:none}
104
 
105
  #settings{display:none;margin:8px 12px 10px;padding:16px;border:1px solid var(--border);
106
+ border-radius:12px;background:var(--surface);box-shadow:0 2px 6px rgba(0,0,0,.3)}
107
  #settings.open{display:block}
108
  #settings label{display:block;font-size:12px;color:var(--muted);margin:10px 0 4px}
109
+ #settings input,#settings textarea{width:100%;background:var(--input);color:var(--text);
110
  border:1px solid var(--border);border-radius:8px;padding:9px 12px;font-size:14px;font-family:inherit;outline:none}
111
  #settings input:focus,#settings textarea:focus{border-color:var(--accent)}
112
  #settings textarea{min-height:150px;font-family:ui-monospace,"SF Mono",Menlo,monospace;font-size:12px}
 
116
  padding:10px 18px;cursor:pointer;font-weight:600}
117
 
118
  #status{padding:6px 16px;font-size:12px;color:var(--muted);background:var(--bg)}
119
+ #progress{height:3px;background:var(--track)}
120
  #progress>div{height:100%;width:0;background:var(--accent);transition:width .25s}
121
+ footer{padding:10px 16px;font-size:11px;color:var(--muted);border-top:1px solid var(--border);background:var(--surface);
122
  display:flex;gap:8px 16px;flex-wrap:wrap;justify-content:center;text-align:center}
123
  footer a{color:var(--accent);text-decoration:none}
124
 
125
+ @media (max-width:760px){
126
+ #app{flex-direction:column;max-width:none}
127
+ #sidebar{width:100%;flex:none;border-right:none;border-bottom:1px solid var(--border)}
128
+ .side-header{display:none}
129
+ #contacts{display:flex;overflow-x:auto;padding:8px 10px;gap:6px}
130
+ .contact{flex:none;flex-direction:column;gap:4px;padding:6px 10px;min-width:64px;justify-content:center}
131
+ .contact .c-info{align-items:center;text-align:center}
132
+ .c-role{display:none}
133
+ .c-dot{display:none}
134
+ .c-avatar{width:44px;height:44px;font-size:21px}
135
  #chat{padding:12px 4%;gap:7px}
136
  #controls{padding:8px 8px}
137
  header{padding:8px 10px}
138
  header .subtitle{display:none}
139
+ .msg .avatar{width:32px;height:32px;font-size:16px}
140
  .msg .content{max-width:85%}
141
  #settings{margin:0 8px 8px}
142
  }
 
144
  </head>
145
  <body>
146
  <div id="app">
147
+ <aside id="sidebar">
148
+ <div class="side-header">
149
+ <span class="side-logo">&#129302;</span>
150
+ <span class="side-title">Agentes<span class="side-count" id="side-count">(3)</span></span>
 
 
 
 
 
 
151
  </div>
152
+ <div id="contacts"></div>
153
+ </aside>
154
+ <div id="main">
155
+ <header>
156
+ <span class="logo">&#129302;</span>
157
+ <span class="title" id="title">Multi-Agent Debate <span class="subtitle" id="subtitle">Ada, Bruno, Carla, T&uacute;</span></span>
158
+ <button id="settings-btn" title="Ajustes">&#9881;</button>
159
+ </header>
160
+
161
+ <div id="settings">
162
+ <div class="row">
163
+ <div><label>Repositorio Hugging Face</label><input id="set-repo"></div>
164
+ <div><label>Archivo GGUF</label><input id="set-file"></div>
165
+ </div>
166
+ <div class="row">
167
+ <div><label>Contexto (tokens)</label><input id="set-ctx" type="number" step="512" min="2048"></div>
168
+ <div><label>M&aacute;x. tokens por turno</label><input id="set-maxtokens" type="number" step="32" min="64"></div>
169
+ <div><label>Temperatura</label><input id="set-temp" type="number" step="0.05" min="0" max="2"></div>
170
+ <div><label>Rondas</label><input id="set-rounds" type="number" step="1" min="1" max="20"></div>
171
+ </div>
172
+ <label>Agentes (JSON: name, emoji, color, system)</label>
173
+ <textarea id="set-agents" spellcheck="false"></textarea>
174
+ <button id="apply-settings">Aplicar</button>
175
  </div>
 
 
 
 
176
 
177
+ <div id="chat"></div>
178
 
179
+ <div id="controls">
180
+ <input id="topic" placeholder="Propón un tema y los agentes debaten&hellip;">
181
+ <button id="start-btn" disabled>Cargando modelo&hellip;</button>
182
+ <button id="stop-btn">Detener</button>
183
+ </div>
184
 
185
+ <div id="status">Iniciando&hellip;</div>
186
+ <div id="progress"><div></div></div>
187
+ <footer>
188
+ <span>La conversación se procesa en tu navegador (WebGPU si está disponible; si no, CPU/WASM). Solo se conecta a Hugging Face para descargar el modelo (5,8 GB la primera vez) y a jsDelivr para las librerías.</span>
189
+ <span>Motor: <a href="https://github.com/ngxson/wllama" target="_blank" rel="noopener">wllama</a> (MIT) &middot; inspirado en el widget de <a href="https://localellm.com" target="_blank" rel="noopener">localellm.com</a> &middot; modelo <a href="https://huggingface.co/XiaomiMiMo/MiMo-V2.6-Distill-Qwen-9B" target="_blank" rel="noopener">MiMo-V2.6-Distill-Qwen-9B</a> (MIT, Xiaomi)</span>
190
+ </footer>
191
+ </div>
192
  </div>
193
 
194
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
 
198
  const params = new URLSearchParams(location.search);
199
 
200
  const DEFAULT_AGENTS = [
201
+ { name: 'Ada', emoji: '\u{1F4A1}', color: '#58a6ff',
202
  system: 'Eres Ada, la analista entusiasta del grupo. Defiendes las ideas con argumentos concretos, propones caminos a seguir y reconoces los puntos fuertes de los dem\u00e1s.' },
203
+ { name: 'Bruno', emoji: '\u{1F9D0}', color: '#f0883e',
204
  system: 'Eres Bruno, el esc\u00e9ptico del grupo. Cuestionas supuestos, se\u00f1alas riesgos y fallos l\u00f3gicos, y pides evidencia. Eres duro con las ideas y respetuoso con las personas.' },
205
+ { name: 'Carla', emoji: '\u{1F3AF}', color: '#3fb950',
206
  system: 'Eres Carla, la moderadora. Resume lo dicho, integra los desacuerdos y propone s\u00edntesis o el siguiente paso. Cierra cada ronda con una conclusi\u00f3n breve.' },
207
  ];
208
 
 
228
  const topicEl = document.getElementById('topic');
229
  const startBtn = document.getElementById('start-btn');
230
  const stopBtn = document.getElementById('stop-btn');
231
+ const contactsEl = document.getElementById('contacts');
232
+ const subtitleEl = document.getElementById('subtitle');
233
 
234
  let wllama = null;
235
  let running = false;
 
242
 
243
  function esc(s){ const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
244
 
245
+ // --- Avatares y lista de contactos ---
246
+ function shade(hex, amt) {
247
+ const n = parseInt(String(hex).replace('#','').slice(0,6), 16);
248
+ if (isNaN(n)) return hex;
249
+ const f = v => Math.max(0, Math.min(255, v + amt));
250
+ const r = f(n >> 16), g = f((n >> 8) & 255), b = f(n & 255);
251
+ return '#' + ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0');
252
+ }
253
+ function avatarBg(color) {
254
+ return `linear-gradient(135deg, ${shade(color, 34)} 0%, ${color} 55%, ${shade(color, -38)} 100%)`;
255
+ }
256
+ function roleOf(agent) {
257
+ const m = /Eres\s+[^,]+,\s*([^\.]+)\./i.exec(agent.system || '');
258
+ let r = m ? m[1].trim() : 'Agente del debate';
259
+ r = r.replace(/\s+del grupo\s*$/i, '').replace(/^(el|la|los|las)\s+/i, '');
260
+ return r ? r.charAt(0).toUpperCase() + r.slice(1) : 'Agente del debate';
261
+ }
262
+ function renderContacts() {
263
+ contactsEl.innerHTML = '';
264
+ agents.forEach(a => {
265
+ const c = document.createElement('div');
266
+ c.className = 'contact';
267
+ c.dataset.name = a.name;
268
+ const av = document.createElement('div');
269
+ av.className = 'c-avatar';
270
+ av.style.background = avatarBg(a.color);
271
+ av.style.boxShadow = `0 0 12px ${a.color}66`;
272
+ av.textContent = a.emoji;
273
+ const info = document.createElement('div');
274
+ info.className = 'c-info';
275
+ const nm = document.createElement('div');
276
+ nm.className = 'c-name'; nm.textContent = a.name;
277
+ const rl = document.createElement('div');
278
+ rl.className = 'c-role'; rl.textContent = roleOf(a);
279
+ info.appendChild(nm); info.appendChild(rl);
280
+ const dot = document.createElement('div');
281
+ dot.className = 'c-dot';
282
+ c.appendChild(av); c.appendChild(info); c.appendChild(dot);
283
+ contactsEl.appendChild(c);
284
+ });
285
+ document.getElementById('side-count').textContent = `(${agents.length})`;
286
+ subtitleEl.textContent = agents.map(a => a.name).join(', ') + ', T\u00fa';
287
+ }
288
+ function markActive(name) {
289
+ contactsEl.querySelectorAll('.contact').forEach(c => {
290
+ const on = c.dataset.name === name;
291
+ c.classList.toggle('active', on);
292
+ const rl = c.querySelector('.c-role');
293
+ if (on) { if (!rl.dataset.orig) rl.dataset.orig = rl.textContent; rl.textContent = 'escribiendo\u2026'; }
294
+ else if (rl.dataset.orig) { rl.textContent = rl.dataset.orig; delete rl.dataset.orig; }
295
+ });
296
+ }
297
+ renderContacts();
298
+
299
  function addMsg(agent, isUser = false) {
300
  const d = document.createElement('div');
301
  d.className = 'msg' + (isUser ? ' user' : '');
 
309
  else {
310
  const av = document.createElement('div');
311
  av.className = 'avatar';
312
+ av.style.background = avatarBg(agent.color);
313
+ av.style.boxShadow = '0 0 8px ' + agent.color + '55';
314
  av.textContent = agent.emoji;
315
  d.appendChild(av);
316
  who.textContent = agent.name;
 
409
  ];
410
  const { el, body } = addMsg(agent);
411
  el.classList.add('streaming');
412
+ markActive(agent.name);
413
  let think = null;
414
  let answer = '', reasoning = '', count = 0;
415
  setStatus(`${agent.name} est\u00e1 escribiendo\u2026`);
 
444
  el.classList.add('err');
445
  body.textContent = 'Error: ' + (e?.message || e);
446
  el.classList.remove('streaming');
447
+ markActive(null);
448
  return;
449
  }
450
  el.classList.remove('streaming');
451
+ if (stopRequested) { el.remove(); markActive(null); setStatus('Detenido'); return; }
452
  answer = answer.replace(/^\s*(?:\*\*)?[A-Za-z\u00c0-\u024f]+:\s*/, '').trim();
453
+ if (!answer) { el.remove(); markActive(null); setStatus('Listo'); return; }
454
  body.innerHTML = (typeof marked !== 'undefined') ? marked.parse(answer) : esc(answer).replace(/\n/g, '<br>');
455
  transcript.push({ name: agent.name, text: answer });
456
+ markActive(null);
457
  setStatus('Listo');
458
  }
459
 
 
523
  try { agents = JSON.parse(document.getElementById('set-agents').value); }
524
  catch (e) { alert('JSON de agentes inv\u00e1lido: ' + e.message); return; }
525
  if (!Array.isArray(agents) || agents.length < 2) { alert('Se necesitan al menos 2 agentes.'); return; }
526
+ renderContacts();
527
  CONFIG.maxTokens = parseInt(document.getElementById('set-maxtokens').value) || CONFIG.maxTokens;
528
  CONFIG.temperature = parseFloat(document.getElementById('set-temp').value) || CONFIG.temperature;
529
  CONFIG.rounds = parseInt(document.getElementById('set-rounds').value) || CONFIG.rounds;
 
541
  else setStatus('Modo manual: carga el modelo desde Ajustes.');
542
  </script>
543
  </body>
544
+ </html>