@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.
- package/dist/chat/panel.d.ts.map +1 -1
- package/dist/chat/panel.js +369 -50
- package/dist/chat/panel.js.map +1 -1
- package/dist/chat/server.js +104 -0
- package/dist/chat/server.js.map +1 -1
- package/dist/chat/tools/keys.d.ts +9 -0
- package/dist/chat/tools/keys.d.ts.map +1 -0
- package/dist/chat/tools/keys.js +194 -0
- package/dist/chat/tools/keys.js.map +1 -0
- package/dist/chat/tools.d.ts.map +1 -1
- package/dist/chat/tools.js +8 -0
- package/dist/chat/tools.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/chat/panel.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/chat/panel.js
CHANGED
|
@@ -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:
|
|
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:
|
|
247
|
+
display: flex; flex-direction: column; gap: 12px;
|
|
247
248
|
}
|
|
248
249
|
.settings-panel.hidden { display: none; }
|
|
249
|
-
.settings-panel .set-
|
|
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 {
|
|
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
|
|
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"
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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.
|
|
485
|
-
|
|
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-
|
|
488
|
-
<
|
|
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-
|
|
491
|
-
<
|
|
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-
|
|
494
|
-
<
|
|
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-
|
|
497
|
-
<
|
|
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
|
-
|
|
1061
|
-
{ id: 'es-ES-
|
|
1062
|
-
{ id: 'es-ES-
|
|
1063
|
-
{ id: 'es-
|
|
1064
|
-
|
|
1065
|
-
{ id: 'es-
|
|
1066
|
-
{ id: '
|
|
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
|
-
|
|
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.
|
|
1090
|
-
b.title = '
|
|
1091
|
-
+ ' (' + meta.gender + ') --
|
|
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
|
|
1096
|
-
*
|
|
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
|
|
1099
|
-
if (
|
|
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
|
-
|
|
1264
|
+
if (licEl) licEl.textContent = 'error: ' + (data.error || r.status);
|
|
1106
1265
|
return;
|
|
1107
1266
|
}
|
|
1108
1267
|
const k = data.keys || {};
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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
|
-
|
|
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
|
-
/*
|
|
1583
|
-
*
|
|
1584
|
-
*
|
|
1585
|
-
*
|
|
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.
|
|
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
|
|
package/dist/chat/panel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.js","sourceRoot":"","sources":["../../src/chat/panel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EACL,eAAe,EAAE,QAAQ,GAG1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAepD,MAAM,UAAU,eAAe,CAAC,GAAgB;IAC9C,+DAA+D;IAC/D,+DAA+D;IAC/D,6DAA6D;IAC7D,4DAA4D;IAC5D,oCAAoC;IACpC,MAAM,IAAI,GAAsB,GAAG,CAAC,IAAI,IAAI,eAAe,EAAE,CAAC;IAE9D,4DAA4D;IAC5D,2DAA2D;IAC3D,+DAA+D;IAC/D,2DAA2D;IAC3D,uBAAuB;IACvB,MAAM,EAAE,GAAG;QACT,gBAAgB,EAAE,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;QAC3D,UAAU,EAAQ,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;QACrD,SAAS,EAAS,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;QACpD,YAAY,EAAM,QAAQ,CAAC,IAAI,EAAE,4BAA4B,CAAC;QAC9D,SAAS,EAAS,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;QACpD,UAAU,EAAQ,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;QACrD,cAAc,EAAI,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;QACzD,WAAW,EAAO,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;QACtD,cAAc,EAAI,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;QACzD,UAAU,EAAQ,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;QAC1D,UAAU,EAAQ,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;QACrD,gBAAgB,EAAE,QAAQ,CAAC,IAAI,EAAE,0BAA0B,CAAC;QAC5D,eAAe,EAAG,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;QAC3D,eAAe,EAAG,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;QAC3D,cAAc,EAAI,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;KAC3D,CAAC;IACF,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC;QACnC,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,IAAI,EAAS,GAAG,CAAC,IAAI;QACrB,OAAO,EAAM,OAAO;QACpB,IAAI;QACJ,IAAI,EAAS,EAAE;KAChB,CAAC,CAAC;IAEH,wDAAwD;IACxD,yDAAyD;IACzD,sCAAsC;IACtC,MAAM,GAAG,GAAG,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;IAE1C,OAAO;cACK,IAAI,UAAU,GAAG;;;;mCAII,UAAU,CAAC,OAAO,CAAC;mCACnB,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC;8BAChC,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC;;EAEvD,eAAe,EAAE
|
|
1
|
+
{"version":3,"file":"panel.js","sourceRoot":"","sources":["../../src/chat/panel.ts"],"names":[],"mappings":"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"}
|