@nac3/forge-cli 0.2.0-alpha.30 → 0.2.0-alpha.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/chat/panel.ts"],"names":[],"mappings":"AAkBA,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;2BAKuB;IACvB,IAAI,CAAC,EAAE,iBAAiB,CAAC;CAC1B;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CA+nExD"}
1
+ {"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/chat/panel.ts"],"names":[],"mappings":"AAkBA,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb;;;;;2BAKuB;IACvB,IAAI,CAAC,EAAE,iBAAiB,CAAC;CAC1B;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CA87ExD"}
@@ -237,26 +237,58 @@ body {
237
237
  }
238
238
  .header .actions button:hover { background: rgba(255,255,255,0.2); }
239
239
 
240
- /* alpha.29 -- settings dropdown */
240
+ /* alpha.29+ -- settings dropdown */
241
241
  .settings-panel {
242
242
  position: absolute; top: 56px; right: 16px; z-index: 50;
243
243
  background: var(--bg-1); border: 1px solid rgba(255,255,255,0.15);
244
- border-radius: 6px; padding: 10px; min-width: 280px;
244
+ border-radius: 6px; padding: 12px; min-width: 360px; max-width: 420px;
245
+ max-height: 80vh; overflow-y: auto;
245
246
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
246
- display: flex; flex-direction: column; gap: 8px;
247
+ display: flex; flex-direction: column; gap: 12px;
247
248
  }
248
249
  .settings-panel.hidden { display: none; }
249
- .settings-panel .set-row {
250
+ .settings-panel .set-section {
251
+ display: flex; flex-direction: column; gap: 6px;
252
+ padding-bottom: 8px; border-bottom: 1px solid rgba(255,255,255,0.08);
253
+ }
254
+ .settings-panel .set-section:last-of-type { border-bottom: 0; padding-bottom: 0; }
255
+ .settings-panel .set-section-title {
256
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px;
257
+ color: var(--ink-2); font-weight: 600;
258
+ }
259
+ .settings-panel .set-row,
260
+ .settings-panel .set-key-row {
250
261
  display: flex; justify-content: space-between; align-items: center;
251
262
  gap: 12px; font-size: 12px;
252
263
  }
253
- .settings-panel .set-lbl { color: var(--ink-2); font-weight: 500; }
264
+ .settings-panel .set-key-row .set-lbl { flex: 1; }
265
+ .settings-panel .set-key-row .set-status { min-width: 24px; text-align: center; }
266
+ .settings-panel .set-lbl { color: var(--ink); font-weight: 500; }
254
267
  .settings-panel .set-btn {
255
268
  background: var(--bg-2); color: var(--ink); border: 1px solid rgba(255,255,255,0.15);
256
- padding: 4px 8px; border-radius: 4px; font-size: 11px; cursor: pointer;
269
+ padding: 4px 10px; border-radius: 4px; font-size: 11px; cursor: pointer;
257
270
  }
258
271
  .settings-panel .set-btn:hover { background: rgba(255,255,255,0.1); }
272
+ .settings-panel .set-btn-danger { color: #ff8b7a; border-color: rgba(255,139,122,0.3); }
273
+ .settings-panel .set-btn-danger:hover { background: rgba(255,139,122,0.1); }
259
274
  .settings-panel .set-status { font-size: 11px; color: var(--ink-2); }
275
+ .settings-panel .set-link {
276
+ font-size: 11px; color: #6cb6ff; text-decoration: underline;
277
+ }
278
+ .settings-panel .key-editor {
279
+ margin-top: 8px; padding: 10px; background: var(--bg-2);
280
+ border-radius: 4px; display: flex; flex-direction: column; gap: 8px;
281
+ }
282
+ .settings-panel .key-editor.hidden { display: none; }
283
+ .settings-panel .key-editor .ke-title { font-size: 11px; color: var(--ink-2); }
284
+ .settings-panel .key-editor input {
285
+ background: var(--bg-1); color: var(--ink); border: 1px solid rgba(255,255,255,0.2);
286
+ padding: 6px 8px; border-radius: 4px; font-size: 12px; font-family: monospace;
287
+ }
288
+ .settings-panel .key-editor .ke-actions { display: flex; gap: 6px; }
289
+ .settings-panel .key-editor .ke-msg { font-size: 11px; min-height: 14px; }
290
+ .settings-panel .key-editor .ke-msg.error { color: #ff8b7a; }
291
+ .settings-panel .key-editor .ke-msg.success { color: #6dff8b; }
260
292
 
261
293
  /* ---- chat stream ---- */
262
294
  .stream { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 10px; }
@@ -474,27 +506,110 @@ body {
474
506
  <div class="t"><span class="name">Yujin Forge</span><span class="proj" id="proj-full" data-nac-id="yujin.panel.project-full"></span></div>
475
507
  </div>
476
508
  <div class="actions">
477
- <button class="settings-btn" data-settings-toggle aria-label="ajustes" title="Ajustes (modo voz, STT, TTS, claves)">⚙</button>
478
- <button class="lang-btn" data-lang-open aria-label="${escapeHtml(tr.languageButton)}" title="${escapeHtml(tr.languageButton)}">🌐</button>
479
- <button class="keys-btn" data-vault-open aria-label="${escapeHtml(tr.keysButton)}" title="${escapeHtml(tr.keysButton)}">${escapeHtml(tr.keysButton)}</button>
480
- <button id="full-mini" data-nac-id="yujin.panel.full-to-mini" aria-label="${escapeHtml(tr.minimiseButton)}" title="${escapeHtml(tr.minimiseButton)}">▭</button>
481
- <button id="full-close" data-nac-id="yujin.panel.full-close" aria-label="${escapeHtml(tr.closeButton)}" title="${escapeHtml(tr.closeButton)}">✕</button>
509
+ <button class="settings-btn"
510
+ data-settings-toggle
511
+ data-nac-id="yujin.panel.settings"
512
+ data-nac-role="action"
513
+ data-nac-action="open_settings"
514
+ aria-label="ajustes"
515
+ title="Ajustes (modo voz, STT, TTS, claves, idioma)">⚙</button>
516
+ <button id="full-mini"
517
+ data-nac-id="yujin.panel.full-to-mini"
518
+ data-nac-role="action"
519
+ data-nac-action="minimize_panel"
520
+ aria-label="${escapeHtml(tr.minimiseButton)}"
521
+ title="${escapeHtml(tr.minimiseButton)}">▭</button>
522
+ <button id="full-close"
523
+ data-nac-id="yujin.panel.full-close"
524
+ data-nac-role="action"
525
+ data-nac-action="close_panel"
526
+ aria-label="${escapeHtml(tr.closeButton)}"
527
+ title="${escapeHtml(tr.closeButton)}">✕</button>
482
528
  </div>
483
529
  </div>
484
- <!-- alpha.29: settings dropdown in topbar (voice mode, STT
485
- provider, TTS voice, key status). Toggle via the cog. -->
530
+ <!-- alpha.30: settings dropdown in topbar with editable
531
+ API keys (BYOK) + voice settings + license. -->
486
532
  <div id="yf-settings" class="hidden settings-panel">
487
- <div class="set-row"><span class="set-lbl">Modo voz</span>
488
- <button type="button" data-voice-mode-btn class="set-btn">modo mic</button>
533
+ <div class="set-section">
534
+ <div class="set-section-title">Voz</div>
535
+ <div class="set-row"><span class="set-lbl">Modo</span>
536
+ <button type="button" data-voice-mode-btn class="set-btn">modo mic</button>
537
+ </div>
538
+ <div class="set-row"><span class="set-lbl">STT</span>
539
+ <button type="button" data-stt-provider-btn class="set-btn">STT navegador</button>
540
+ </div>
541
+ <div class="set-row"><span class="set-lbl">TTS engine</span>
542
+ <button type="button" data-tts-provider-btn class="set-btn">TTS navegador</button>
543
+ </div>
544
+ <div class="set-row"><span class="set-lbl">Voz TTS (solo Google)</span>
545
+ <button type="button" data-tts-voice-btn class="set-btn">es-ES-Studio-C</button>
546
+ </div>
489
547
  </div>
490
- <div class="set-row"><span class="set-lbl">STT</span>
491
- <button type="button" data-stt-provider-btn class="set-btn">STT navegador</button>
548
+ <div class="set-section">
549
+ <div class="set-section-title">API keys (BYOK)</div>
550
+ <div class="set-key-row" data-key-slot="anthropic_api_key"><span class="set-lbl">Brain (Anthropic)</span>
551
+ <span class="set-status" data-key-status>--</span>
552
+ <button type="button" class="set-btn" data-key-edit>Configurar</button>
553
+ <button type="button" class="set-btn set-btn-danger" data-key-clear hidden>Quitar</button>
554
+ </div>
555
+ <div class="set-key-row" data-key-slot="openai_api_key"><span class="set-lbl">Brain (OpenAI)</span>
556
+ <span class="set-status" data-key-status>--</span>
557
+ <button type="button" class="set-btn" data-key-edit>Configurar</button>
558
+ <button type="button" class="set-btn set-btn-danger" data-key-clear hidden>Quitar</button>
559
+ </div>
560
+ <div class="set-key-row" data-key-slot="google_stt_key"><span class="set-lbl">STT Google</span>
561
+ <span class="set-status" data-key-status>--</span>
562
+ <button type="button" class="set-btn" data-key-edit>Configurar</button>
563
+ <button type="button" class="set-btn set-btn-danger" data-key-clear hidden>Quitar</button>
564
+ </div>
565
+ <div class="set-key-row" data-key-slot="google_tts_key"><span class="set-lbl">TTS Google</span>
566
+ <span class="set-status" data-key-status>--</span>
567
+ <button type="button" class="set-btn" data-key-edit>Configurar</button>
568
+ <button type="button" class="set-btn set-btn-danger" data-key-clear hidden>Quitar</button>
569
+ </div>
570
+ <div class="set-key-row" data-key-slot="elevenlabs_api_key"><span class="set-lbl">TTS ElevenLabs</span>
571
+ <span class="set-status" data-key-status>--</span>
572
+ <button type="button" class="set-btn" data-key-edit>Configurar</button>
573
+ <button type="button" class="set-btn set-btn-danger" data-key-clear hidden>Quitar</button>
574
+ </div>
575
+ <div class="set-key-row" data-key-slot="whisper_api_key"><span class="set-lbl">STT Whisper</span>
576
+ <span class="set-status" data-key-status>--</span>
577
+ <button type="button" class="set-btn" data-key-edit>Configurar</button>
578
+ <button type="button" class="set-btn set-btn-danger" data-key-clear hidden>Quitar</button>
579
+ </div>
492
580
  </div>
493
- <div class="set-row"><span class="set-lbl">Voz TTS</span>
494
- <button type="button" data-tts-voice-btn class="set-btn">es-ES-Neural2-A (Lucia)</button>
581
+ <div class="set-section">
582
+ <div class="set-section-title">Idioma</div>
583
+ <div class="set-row"><span class="set-lbl">Panel + voz</span>
584
+ <button type="button" data-lang-open class="set-btn">${escapeHtml(tr.languageButton)} cambiar</button>
585
+ </div>
495
586
  </div>
496
- <div class="set-row"><span class="set-lbl">Claves</span>
497
- <span class="set-status" id="set-keys-status">cargando...</span>
587
+ <div class="set-section">
588
+ <div class="set-section-title">Vault (encriptado)</div>
589
+ <div class="set-row"><span class="set-lbl">Slots</span>
590
+ <button type="button" data-vault-open class="set-btn">Abrir vault</button>
591
+ </div>
592
+ </div>
593
+ <div class="set-section">
594
+ <div class="set-section-title">License</div>
595
+ <div class="set-row"><span class="set-lbl">Plan</span>
596
+ <span class="set-status" id="set-license-status">--</span>
597
+ </div>
598
+ <div class="set-row">
599
+ <a href="https://yujin.app/yujin-forge/"
600
+ target="_blank" rel="noopener" class="set-link">Comprar / activar plan</a>
601
+ </div>
602
+ </div>
603
+ <!-- inline edit form (single, repositioned over the active key row) -->
604
+ <div id="yf-key-editor" class="hidden key-editor">
605
+ <div class="ke-title">Configurar <span id="ke-slot-name"></span></div>
606
+ <input type="password" id="ke-input" placeholder="Pega aqui tu API key" autocomplete="off"/>
607
+ <div class="ke-actions">
608
+ <button type="button" id="ke-save" class="set-btn">Guardar</button>
609
+ <button type="button" id="ke-clear" class="set-btn set-btn-danger">Quitar</button>
610
+ <button type="button" id="ke-cancel" class="set-btn">Cancelar</button>
611
+ </div>
612
+ <div id="ke-msg" class="ke-msg"></div>
498
613
  </div>
499
614
  </div>
500
615
  <div class="body">
@@ -1016,6 +1131,35 @@ let recordingPanelButton = null;
1016
1131
  let _lastInputWasVoice = false;
1017
1132
  let _lastVoiceButton = null;
1018
1133
 
1134
+ /* TTS provider selector (alpha.31+). Per Pablo: probar TTS
1135
+ * del navegador para descartar Google. Browser uses
1136
+ * window.speechSynthesis with native OS voices (Edge ships
1137
+ * Microsoft Neural voices on Windows; Chrome ships its own).
1138
+ * 'google' = our server-side /api/voice/tts path. */
1139
+ const TTS_PROVIDER_KEY = 'yf-tts-provider';
1140
+ function ttsProvider() {
1141
+ try {
1142
+ const v = localStorage.getItem(TTS_PROVIDER_KEY);
1143
+ if (v === 'google' || v === 'browser') return v;
1144
+ } catch (_) {}
1145
+ return 'browser'; /* default: navegador (descarta Google) */
1146
+ }
1147
+ function setTtsProvider(p) {
1148
+ try { localStorage.setItem(TTS_PROVIDER_KEY, p); } catch (_) {}
1149
+ updateTtsProviderButtons();
1150
+ }
1151
+ function cycleTtsProvider() {
1152
+ setTtsProvider(ttsProvider() === 'browser' ? 'google' : 'browser');
1153
+ }
1154
+ function updateTtsProviderButtons() {
1155
+ const p = ttsProvider();
1156
+ document.querySelectorAll('[data-tts-provider-btn]').forEach((b) => {
1157
+ b.textContent = p === 'browser' ? 'TTS navegador' : 'TTS Google';
1158
+ b.title = 'Click para alternar entre navegador (SpeechSynthesis '
1159
+ + 'nativo, voces del SO) y Google Cloud TTS (server-side).';
1160
+ });
1161
+ }
1162
+
1019
1163
  /* STT provider selector (alpha.28+). Default 'browser' = Web
1020
1164
  * Speech API (instant, free, native es-AR). 'google' = our
1021
1165
  * existing MediaRecorder + Google Cloud STT REST path. 'whisper'
@@ -1056,21 +1200,34 @@ function cycleSttProvider() {
1056
1200
  * pronunciate finales claros vs es-US (Latin) que se las traga.
1057
1201
  * Also shows the active persona name in the settings panel. */
1058
1202
  const TTS_VOICE_KEY = 'yf-tts-voice';
1203
+ /* alpha.31 -- voice catalogue expanded with Studio + Wavenet
1204
+ * variants. Studio voices hyperarticulate (newscaster style)
1205
+ * and pronounce final-'s' clearly; Neural2 voices use natural
1206
+ * Spanish prosody that aspirates 's'. User toggles between
1207
+ * "claridad" (Studio) and "naturalidad" (Neural2). */
1059
1208
  const TTS_VOICES = [
1060
- { id: 'es-ES-Neural2-A', persona: 'Lucia (ES)', gender: 'F', lang: 'es-ES' },
1061
- { id: 'es-ES-Neural2-B', persona: 'Javier (ES)', gender: 'M', lang: 'es-ES' },
1062
- { id: 'es-ES-Neural2-C', persona: 'Marta (ES)', gender: 'F', lang: 'es-ES' },
1063
- { id: 'es-ES-Neural2-D', persona: 'Carlos (ES)', gender: 'M', lang: 'es-ES' },
1064
- { id: 'es-US-Neural2-A', persona: 'Sofia (LATAM)', gender: 'F', lang: 'es-US' },
1065
- { id: 'es-US-Neural2-B', persona: 'Mateo (LATAM)', gender: 'M', lang: 'es-US' },
1066
- { id: 'en-US-Neural2-C', persona: 'Emma (EN)', gender: 'F', lang: 'en-US' },
1209
+ /* Studio voices FIRST -- they articulate every 's' clearly. */
1210
+ { id: 'es-ES-Studio-C', persona: 'Lucia Studio (ES)', gender: 'F', lang: 'es-ES', style: 'Studio (clara)' },
1211
+ { id: 'es-ES-Studio-F', persona: 'Javier Studio (ES)', gender: 'M', lang: 'es-ES', style: 'Studio (clara)' },
1212
+ { id: 'es-US-Studio-B', persona: 'Mateo Studio (LATAM)', gender: 'M', lang: 'es-US', style: 'Studio (clara)' },
1213
+ /* Neural2 (natural Spanish prosody, aspirates final 's'). */
1214
+ { id: 'es-ES-Neural2-A', persona: 'Lucia Neural (ES)', gender: 'F', lang: 'es-ES', style: 'Neural2 (natural)' },
1215
+ { id: 'es-ES-Neural2-B', persona: 'Javier Neural (ES)', gender: 'M', lang: 'es-ES', style: 'Neural2 (natural)' },
1216
+ { id: 'es-ES-Neural2-C', persona: 'Marta Neural (ES)', gender: 'F', lang: 'es-ES', style: 'Neural2 (natural)' },
1217
+ { id: 'es-ES-Neural2-D', persona: 'Carlos Neural (ES)', gender: 'M', lang: 'es-ES', style: 'Neural2 (natural)' },
1218
+ { id: 'es-US-Neural2-A', persona: 'Sofia Neural (LATAM)', gender: 'F', lang: 'es-US', style: 'Neural2 (natural)' },
1219
+ { id: 'es-US-Neural2-B', persona: 'Mateo Neural (LATAM)', gender: 'M', lang: 'es-US', style: 'Neural2 (natural)' },
1220
+ /* Wavenet (older, balanced articulation). */
1221
+ { id: 'es-ES-Wavenet-B', persona: 'Lucia Wavenet (ES)', gender: 'F', lang: 'es-ES', style: 'Wavenet (clasica)' },
1222
+ { id: 'en-US-Neural2-C', persona: 'Emma (EN)', gender: 'F', lang: 'en-US', style: 'Neural2' },
1067
1223
  ];
1068
1224
  function ttsVoice() {
1069
1225
  try {
1070
1226
  const v = localStorage.getItem(TTS_VOICE_KEY);
1071
1227
  if (v && TTS_VOICES.find((x) => x.id === v)) return v;
1072
1228
  } catch (_) {}
1073
- return 'es-ES-Neural2-A'; /* default: Lucia ES -- articula las "s" */
1229
+ /* alpha.31 default: Studio -- hyperarticulates 's' finals. */
1230
+ return 'es-ES-Studio-C';
1074
1231
  }
1075
1232
  function setTtsVoice(id) {
1076
1233
  try { localStorage.setItem(TTS_VOICE_KEY, id); } catch (_) {}
@@ -1086,36 +1243,116 @@ function updateTtsVoiceButtons() {
1086
1243
  const cur = ttsVoice();
1087
1244
  const meta = TTS_VOICES.find((v) => v.id === cur) || TTS_VOICES[0];
1088
1245
  document.querySelectorAll('[data-tts-voice-btn]').forEach((b) => {
1089
- b.textContent = meta.id + ' (' + meta.persona + ')';
1090
- b.title = 'Click para cambiar de voz. Persona actual: ' + meta.persona
1091
- + ' (' + meta.gender + ') -- lengua ' + meta.lang;
1246
+ b.textContent = meta.persona + ' [' + meta.style + ']';
1247
+ b.title = 'Voz actual: ' + meta.id + ' / ' + meta.persona
1248
+ + ' (' + meta.gender + ') -- ' + meta.style + '. '
1249
+ + 'Click para ciclar. Studio = articula s claro; Neural2 = '
1250
+ + 'natural espaniol (aspira s finales).';
1092
1251
  });
1093
1252
  }
1094
1253
 
1095
- /* Keys status fetcher: GETs /api/forge/keys-status (new endpoint)
1096
- * and renders a short summary in the settings dropdown. */
1254
+ /* alpha.32 -- Keys + license status fetcher. Updates EACH row
1255
+ * in the settings panel with check/cross and shows/hides the
1256
+ * "Quitar" button accordingly. License row separately. */
1097
1257
  async function refreshKeysStatus() {
1098
- const el = document.getElementById('set-keys-status');
1099
- if (!el) return;
1100
- el.textContent = 'cargando...';
1258
+ const licEl = document.getElementById('set-license-status');
1259
+ if (licEl) licEl.textContent = '...';
1101
1260
  try {
1102
1261
  const r = await fetch('/api/forge/keys-status');
1103
1262
  const data = await r.json();
1104
1263
  if (!r.ok || !data.ok) {
1105
- el.textContent = 'error: ' + (data.error || r.status);
1264
+ if (licEl) licEl.textContent = 'error: ' + (data.error || r.status);
1106
1265
  return;
1107
1266
  }
1108
1267
  const k = data.keys || {};
1109
- const parts = [];
1110
- parts.push((k.anthropic ? '✓' : '✗') + ' brain');
1111
- parts.push((k.google_stt ? '' : '✗') + ' stt');
1112
- parts.push((k.google_tts || k.elevenlabs ? '' : '✗') + ' tts');
1113
- parts.push((k.license_paid ? '✓' : '✗') + ' license');
1114
- el.textContent = parts.join(' · ');
1268
+ /* Per-row updates. data-key-slot on the row determines
1269
+ * which boolean we read. */
1270
+ document.querySelectorAll('[data-key-slot]').forEach((row) => {
1271
+ const slot = row.getAttribute('data-key-slot');
1272
+ const isSet = !!k[slot];
1273
+ const stEl = row.querySelector('[data-key-status]');
1274
+ if (stEl) {
1275
+ stEl.textContent = isSet ? '✓' : '✗';
1276
+ stEl.style.color = isSet ? '#6dff8b' : '#ff8b7a';
1277
+ }
1278
+ const editBtn = row.querySelector('[data-key-edit]');
1279
+ if (editBtn) editBtn.textContent = isSet ? 'Cambiar' : 'Configurar';
1280
+ const clrBtn = row.querySelector('[data-key-clear]');
1281
+ if (clrBtn) clrBtn.hidden = !isSet;
1282
+ });
1283
+ if (licEl) {
1284
+ const plan = data.license_plan || 'none';
1285
+ licEl.textContent = plan;
1286
+ licEl.style.color = (plan === 'paid' || plan === 'institutional') ? '#6dff8b' : '#ff8b7a';
1287
+ }
1288
+ } catch (err) {
1289
+ if (licEl) licEl.textContent = 'error: ' + (err && err.message ? err.message : 'unknown');
1290
+ }
1291
+ }
1292
+
1293
+ /* alpha.32 -- inline key editor: opens, saves, clears. */
1294
+ let _keyEditorSlot = '';
1295
+ function openKeyEditor(slot) {
1296
+ _keyEditorSlot = slot;
1297
+ const ed = document.getElementById('yf-key-editor');
1298
+ const name = document.getElementById('ke-slot-name');
1299
+ const input = document.getElementById('ke-input');
1300
+ const msg = document.getElementById('ke-msg');
1301
+ if (!ed || !name || !input || !msg) return;
1302
+ name.textContent = slot;
1303
+ input.value = '';
1304
+ msg.textContent = '';
1305
+ msg.className = 'ke-msg';
1306
+ ed.classList.remove('hidden');
1307
+ input.focus();
1308
+ }
1309
+ function closeKeyEditor() {
1310
+ const ed = document.getElementById('yf-key-editor');
1311
+ if (ed) ed.classList.add('hidden');
1312
+ _keyEditorSlot = '';
1313
+ }
1314
+ async function saveKeyEditor() {
1315
+ const input = document.getElementById('ke-input');
1316
+ const msg = document.getElementById('ke-msg');
1317
+ if (!input || !msg || !_keyEditorSlot) return;
1318
+ const value = input.value.trim();
1319
+ if (value.length < 8) {
1320
+ msg.textContent = 'Key demasiado corta (min 8 chars).';
1321
+ msg.className = 'ke-msg error';
1322
+ return;
1323
+ }
1324
+ msg.textContent = 'Guardando...';
1325
+ msg.className = 'ke-msg';
1326
+ try {
1327
+ const r = await fetch('/api/forge/keys-set', {
1328
+ method: 'POST',
1329
+ headers: { 'content-type': 'application/json' },
1330
+ body: JSON.stringify({ slot: _keyEditorSlot, value }),
1331
+ });
1332
+ const data = await r.json();
1333
+ if (!r.ok || !data.ok) {
1334
+ msg.textContent = 'Error: ' + (data.error || r.status);
1335
+ msg.className = 'ke-msg error';
1336
+ return;
1337
+ }
1338
+ msg.textContent = 'Guardado.';
1339
+ msg.className = 'ke-msg success';
1340
+ setTimeout(() => { closeKeyEditor(); refreshKeysStatus(); }, 600);
1115
1341
  } catch (err) {
1116
- el.textContent = 'error fetching: ' + (err && err.message ? err.message : 'unknown');
1342
+ msg.textContent = 'Error: ' + (err && err.message ? err.message : 'unknown');
1343
+ msg.className = 'ke-msg error';
1117
1344
  }
1118
1345
  }
1346
+ async function clearKey(slot) {
1347
+ try {
1348
+ const r = await fetch('/api/forge/keys-set', {
1349
+ method: 'POST',
1350
+ headers: { 'content-type': 'application/json' },
1351
+ body: JSON.stringify({ slot, value: '' }),
1352
+ });
1353
+ if (r.ok) refreshKeysStatus();
1354
+ } catch (_) {}
1355
+ }
1119
1356
 
1120
1357
  function voiceMode() {
1121
1358
  /* Default 'mic' = push-to-talk (preserves pre-2026-05-31 UX). */
@@ -1579,12 +1816,16 @@ async function _fetchTtsChunk(text) {
1579
1816
  * resolves voice from { language } if no explicit voice given. */
1580
1817
  const lang = (document && document.documentElement && document.documentElement.lang)
1581
1818
  ? document.documentElement.lang : undefined;
1582
- /* 2026-05-31 -- speakingRate 1.05 for Spanish (mirror Pilot
1583
- * CRM). Neural2 voices at rate 1.0 tend to swallow final
1584
- * "s" in Latin-American Spanish; +5% rate makes the voice
1585
- * articulate each syllable + drops the dropped-s symptom. */
1819
+ /* alpha.31 -- Empirically verified (tools/tts_trace_smoke.mjs)
1820
+ * that the panel-side filter preserves every 's'. What the
1821
+ * user heard as 'dropped s' is Google Neural2 voices
1822
+ * naturally aspirating Spanish final-'s' (it is prosodically
1823
+ * correct castellano + LATAM behaviour). The 1.05 rate from
1824
+ * alpha.28 actually made it WORSE (less time per phoneme).
1825
+ * Back to speed=1.0; for hyperarticulated reads switch the
1826
+ * voice in settings to a Studio variant. */
1586
1827
  let speed;
1587
- if (lang && /^es(-|$)/i.test(lang)) speed = 1.05;
1828
+ if (lang && /^es(-|$)/i.test(lang)) speed = 1.0;
1588
1829
  const voiceId = ttsVoice();
1589
1830
  const body = lang ? { text, language: lang } : { text };
1590
1831
  if (speed) body.speed = speed;
@@ -1841,6 +2082,40 @@ async function _micToggleWebSpeech(button) {
1841
2082
  }
1842
2083
  }
1843
2084
 
2085
+ /* alpha.31 -- TTS via the browser's SpeechSynthesis API.
2086
+ * Per Pablo: 'hagamos opcional el TTS del navegador asi
2087
+ * descartamos Google'. Uses OS-native voices (Edge ships
2088
+ * Microsoft Neural voices, Chrome ships Google's). If the
2089
+ * browser articulates 's' clearly, the audio dropout in the
2090
+ * Google path is Google-side (or our request shape). */
2091
+ function _speakBrowser(text, langHint) {
2092
+ return new Promise((resolve) => {
2093
+ if (typeof window === 'undefined' || !window.speechSynthesis) {
2094
+ setStatus('Browser TTS not available; switch back to Google in settings.', true);
2095
+ resolve();
2096
+ return;
2097
+ }
2098
+ const utt = new SpeechSynthesisUtterance(text);
2099
+ /* Pick a voice that matches lang, preferring es-* exact then
2100
+ * any es- fallback. */
2101
+ const voices = window.speechSynthesis.getVoices();
2102
+ const target = (langHint || 'es-ES').toLowerCase();
2103
+ let match = voices.find((v) => v.lang.toLowerCase() === target);
2104
+ if (!match) {
2105
+ const base = target.split('-')[0];
2106
+ match = voices.find((v) => v.lang.toLowerCase().startsWith(base + '-'));
2107
+ }
2108
+ if (match) utt.voice = match;
2109
+ utt.lang = match ? match.lang : (langHint || 'es-ES');
2110
+ utt.rate = 1.0;
2111
+ utt.pitch = 1.0;
2112
+ utt.onend = () => resolve();
2113
+ utt.onerror = () => resolve();
2114
+ window.speechSynthesis.cancel();
2115
+ window.speechSynthesis.speak(utt);
2116
+ });
2117
+ }
2118
+
1844
2119
  async function playTtsForText(text) {
1845
2120
  text = cleanForTts(text);
1846
2121
  if (!ttsRepliesEnabled()) return;
@@ -1852,6 +2127,22 @@ async function playTtsForText(text) {
1852
2127
  to keyboard mid-conversation and the auto-reopen stops. */
1853
2128
  _lastInputWasVoice = false;
1854
2129
  const reopenBtn = _lastVoiceButton;
2130
+ /* Branch on provider. Browser path bypasses Google entirely. */
2131
+ if (ttsProvider() === 'browser') {
2132
+ try {
2133
+ const lang = (document && document.documentElement && document.documentElement.lang)
2134
+ ? document.documentElement.lang : 'es-ES';
2135
+ await _speakBrowser(text, lang);
2136
+ } finally {
2137
+ if (wasVoiceTurn && reopenBtn && voiceMode() === 'auricular') {
2138
+ setTimeout(() => {
2139
+ if (mediaRecorder && mediaRecorder.state === 'recording') return;
2140
+ try { micToggle(reopenBtn); } catch (_) {}
2141
+ }, 200);
2142
+ }
2143
+ }
2144
+ return;
2145
+ }
1855
2146
  const chunks = splitIntoTtsChunks(text);
1856
2147
  if (chunks.length === 0) return;
1857
2148
  try {
@@ -2051,6 +2342,34 @@ document.addEventListener('DOMContentLoaded', () => {
2051
2342
  /* TTS voice cycle. */
2052
2343
  $$('[data-tts-voice-btn]').forEach((b) => b.addEventListener('click', cycleTtsVoice));
2053
2344
  updateTtsVoiceButtons();
2345
+ /* TTS provider cycle (alpha.31). */
2346
+ $$('[data-tts-provider-btn]').forEach((b) => b.addEventListener('click', cycleTtsProvider));
2347
+ updateTtsProviderButtons();
2348
+ /* alpha.32 -- Key editor wiring. Each row's "Configurar" /
2349
+ "Cambiar" button opens the editor; "Quitar" clears. */
2350
+ $$('[data-key-edit]').forEach((b) => b.addEventListener('click', (e) => {
2351
+ const row = e.currentTarget.closest('[data-key-slot]');
2352
+ if (!row) return;
2353
+ openKeyEditor(row.getAttribute('data-key-slot'));
2354
+ }));
2355
+ $$('[data-key-clear]').forEach((b) => b.addEventListener('click', (e) => {
2356
+ const row = e.currentTarget.closest('[data-key-slot]');
2357
+ if (!row) return;
2358
+ if (confirm('Quitar la key de ' + row.getAttribute('data-key-slot') + '?')) {
2359
+ clearKey(row.getAttribute('data-key-slot'));
2360
+ }
2361
+ }));
2362
+ const keSave = $('#ke-save');
2363
+ const keCancel = $('#ke-cancel');
2364
+ const keClear = $('#ke-clear');
2365
+ if (keSave) keSave.addEventListener('click', saveKeyEditor);
2366
+ if (keCancel) keCancel.addEventListener('click', closeKeyEditor);
2367
+ if (keClear) keClear.addEventListener('click', () => {
2368
+ if (_keyEditorSlot && confirm('Quitar la key de ' + _keyEditorSlot + '?')) {
2369
+ clearKey(_keyEditorSlot);
2370
+ closeKeyEditor();
2371
+ }
2372
+ });
2054
2373
  setMicButtonsState('idle');
2055
2374
  updateTtsToggleButtons();
2056
2375
 
@@ -1 +1 @@
1
- {"version":3,"file":"panel.js","sourceRoot":"","sources":["../../src/chat/panel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"panel.js","sourceRoot":"","sources":["../../src/chat/panel.ts"],"names":[],"mappings":"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"}