@danieltmn/tamnora 0.9.4 → 0.9.6
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/CHANGELOG.md +32 -0
- package/README.md +25 -10
- package/harness/packages/cli/src/commands/autostart.js +5 -1
- package/harness/packages/cli/src/commands/doctor.js +14 -0
- package/harness/packages/cli/src/commands/update.js +91 -21
- package/harness/packages/core/src/index.js +7 -1
- package/harness/packages/core/src/version.js +81 -7
- package/harness/packages/core/test/version.test.js +34 -0
- package/harness/packages/server/public/app.js +337 -24
- package/harness/packages/server/public/favicon.ico +0 -0
- package/harness/packages/server/public/icons/icon-128x128.png +0 -0
- package/harness/packages/server/public/icons/icon-144x144.png +0 -0
- package/harness/packages/server/public/icons/icon-152x152.png +0 -0
- package/harness/packages/server/public/icons/icon-192x192.png +0 -0
- package/harness/packages/server/public/icons/icon-384x384.png +0 -0
- package/harness/packages/server/public/icons/icon-512x512.png +0 -0
- package/harness/packages/server/public/icons/icon-72x72.png +0 -0
- package/harness/packages/server/public/icons/icon-96x96.png +0 -0
- package/harness/packages/server/public/icons/logo.png +0 -0
- package/harness/packages/server/public/icons/tamnora.ico +0 -0
- package/harness/packages/server/public/index.html +119 -30
- package/harness/packages/server/public/landing/index.html +8 -4
- package/harness/packages/server/public/logo.png +0 -0
- package/harness/packages/server/public/manifest.webmanifest +20 -0
- package/harness/packages/server/public/markdown.js +54 -0
- package/harness/packages/server/public/styles.css +1775 -1633
- package/harness/packages/server/src/lib/advisor.js +177 -0
- package/harness/packages/server/src/lib/agentRun.js +2 -1
- package/harness/packages/server/src/lib/ttsSettings.js +2 -2
- package/harness/packages/server/src/lib/update.js +82 -0
- package/harness/packages/server/src/routes/advisor.js +76 -0
- package/harness/packages/server/src/routes/chat.js +30 -0
- package/harness/packages/server/src/routes/tts.js +1 -1
- package/harness/packages/server/src/routes/update.js +43 -0
- package/harness/packages/server/src/server.js +26 -2
- package/harness/packages/server/src/static.js +1 -0
- package/harness/packages/server/test/advisor.test.js +205 -0
- package/harness/packages/server/test/markdown.test.js +43 -1
- package/harness/packages/server/test/update.test.js +94 -0
- package/harness/scripts/self-update.mjs +123 -0
- package/package.json +1 -1
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>Tamnora Console</title>
|
|
7
|
-
<link rel="icon" href="
|
|
7
|
+
<link rel="icon" href="/favicon.ico" sizes="any" />
|
|
8
|
+
<link rel="apple-touch-icon" href="/icons/icon-192x192.png" />
|
|
9
|
+
<link rel="manifest" href="/manifest.webmanifest" />
|
|
10
|
+
<meta name="theme-color" content="#231a17" />
|
|
8
11
|
<script>
|
|
9
12
|
(function () {
|
|
10
13
|
var theme = null;
|
|
@@ -34,7 +37,7 @@
|
|
|
34
37
|
</head>
|
|
35
38
|
<body>
|
|
36
39
|
<header class="topbar">
|
|
37
|
-
<div class="brand"><span class="logo"><
|
|
40
|
+
<div class="brand"><span class="logo"><img src="/icons/logo.png" alt="Tamnora" /></span> <span class="ver">v0.9.6</span></div>
|
|
38
41
|
<nav class="tabs">
|
|
39
42
|
<button class="tab active" data-tab="chat">chat</button>
|
|
40
43
|
<button class="tab" data-tab="providers">proveedores</button>
|
|
@@ -88,6 +91,15 @@
|
|
|
88
91
|
</div>
|
|
89
92
|
</header>
|
|
90
93
|
|
|
94
|
+
<div id="update-banner" class="update-banner" hidden>
|
|
95
|
+
<span class="update-banner-icon" aria-hidden="true">⬆</span>
|
|
96
|
+
<span id="update-banner-text" class="update-banner-text"></span>
|
|
97
|
+
<div class="update-banner-actions">
|
|
98
|
+
<button id="update-run" type="button" class="btn primary">actualizar</button>
|
|
99
|
+
<button id="update-later" type="button" class="btn">más tarde</button>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
91
103
|
<main>
|
|
92
104
|
<section id="view-chat" class="view active">
|
|
93
105
|
<aside id="sidebar" class="sidebar">
|
|
@@ -142,16 +154,45 @@
|
|
|
142
154
|
<div id="attach-preview" class="attach-preview" hidden></div>
|
|
143
155
|
<div id="chat-queue" class="chat-queue" hidden></div>
|
|
144
156
|
<div id="audio-player" class="audio-player" hidden></div>
|
|
145
|
-
<div class="composer-
|
|
146
|
-
<
|
|
147
|
-
<button type="button" id="attach-image" class="btn icon" title="Adjuntar imagen (o pegala / arrastrala)" aria-label="adjuntar imagen">
|
|
157
|
+
<div class="composer-top">
|
|
158
|
+
<button type="button" id="cwd-pill" class="ctrl-pill" title="carpeta de trabajo de la sesión">
|
|
148
159
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
149
|
-
<
|
|
150
|
-
<circle cx="9" cy="9" r="2" />
|
|
151
|
-
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
|
|
160
|
+
<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
|
|
152
161
|
</svg>
|
|
162
|
+
<span id="cwd-pill-label">sin carpeta</span>
|
|
163
|
+
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
|
164
|
+
</button>
|
|
165
|
+
<div class="ctrl-wrap">
|
|
166
|
+
<button type="button" id="mode-pill" class="ctrl-pill" aria-haspopup="menu" aria-expanded="false" title="permisos del agente">
|
|
167
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></svg>
|
|
168
|
+
<span id="mode-pill-label">Workspace Write</span>
|
|
169
|
+
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
|
170
|
+
</button>
|
|
171
|
+
<div id="mode-menu" class="ctrl-menu" role="menu" hidden>
|
|
172
|
+
<button type="button" class="ctrl-menu-item" data-kind="read" role="menuitemradio" aria-checked="false">
|
|
173
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z" /><circle cx="12" cy="12" r="3" /></svg>
|
|
174
|
+
<span>Read Only</span>
|
|
175
|
+
<svg class="ctrl-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
|
|
176
|
+
</button>
|
|
177
|
+
<button type="button" class="ctrl-menu-item" data-kind="write" role="menuitemradio" aria-checked="false">
|
|
178
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" /><path d="m15 5 4 4" /></svg>
|
|
179
|
+
<span>Workspace Write</span>
|
|
180
|
+
<svg class="ctrl-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
|
|
181
|
+
</button>
|
|
182
|
+
<button type="button" class="ctrl-menu-item" data-kind="full" role="menuitemradio" aria-checked="false">
|
|
183
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /><path d="m9 12 2 2 4-4" /></svg>
|
|
184
|
+
<span>Full access</span>
|
|
185
|
+
<svg class="ctrl-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
|
|
186
|
+
</button>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<div class="composer-row">
|
|
192
|
+
<button type="button" id="attach-image" class="add-btn" title="Adjuntar imagen (o pegala / arrastrala)" aria-label="adjuntar imagen">
|
|
193
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
|
153
194
|
</button>
|
|
154
|
-
<button type="button" id="mic-btn" class="btn
|
|
195
|
+
<button type="button" id="mic-btn" class="add-btn" title="Dictar por voz (tocá para grabar y parar)" aria-label="dictar por voz">
|
|
155
196
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
156
197
|
<path d="M12 19v3" />
|
|
157
198
|
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
|
@@ -161,27 +202,30 @@
|
|
|
161
202
|
</button>
|
|
162
203
|
<input id="image-input" type="file" accept="image/*" multiple hidden />
|
|
163
204
|
<textarea id="chat-input" rows="1" placeholder="escribí un mensaje…" title="Enter envía · Shift+Enter salto" spellcheck="false"></textarea>
|
|
164
|
-
<button class="
|
|
165
|
-
|
|
205
|
+
<button type="button" id="model-pill" class="ctrl-pill model-pill" title="proveedor y modelo activos">
|
|
206
|
+
<span id="model-pill-label">—</span>
|
|
207
|
+
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
|
208
|
+
</button>
|
|
209
|
+
<button class="send-btn" type="submit" aria-label="enviar" title="enviar">
|
|
210
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12 7-7 7 7" /><path d="M12 19V5" /></svg>
|
|
211
|
+
</button>
|
|
212
|
+
<button class="add-btn" type="button" id="stop-btn" title="Detener la respuesta" aria-label="detener" hidden>
|
|
213
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2" /></svg>
|
|
214
|
+
</button>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<div id="statusbar" class="composer-status" title="estado del run" role="button" tabindex="0">
|
|
218
|
+
<span id="sb-activity" class="sb-activity" hidden></span>
|
|
219
|
+
<span id="sb-sync" class="sb-channel retrying" hidden title="sin sincronía con el server"></span>
|
|
220
|
+
<span id="sb-channel" class="sb-channel" hidden title="canal de eventos entre dispositivos"></span>
|
|
221
|
+
<span id="sb-rounds">listo</span>
|
|
222
|
+
<span class="spacer"></span>
|
|
223
|
+
<span id="sb-context" class="ctx muted" title="uso del contexto del modelo">
|
|
224
|
+
<span id="sb-tokens">tokens —</span>
|
|
225
|
+
<span class="ctx-bar" aria-hidden="true"><span id="sb-context-fill" class="ctx-fill"></span></span>
|
|
226
|
+
</span>
|
|
166
227
|
</div>
|
|
167
228
|
</form>
|
|
168
|
-
<div id="statusbar" class="statusbar" title="cambiar modo, proveedor y modelo" role="button" tabindex="0">
|
|
169
|
-
<span id="sb-mode">agent</span>
|
|
170
|
-
<span class="dot">·</span>
|
|
171
|
-
<span id="sb-provider">—</span>
|
|
172
|
-
<span class="dot">·</span>
|
|
173
|
-
<span id="sb-model">—</span>
|
|
174
|
-
<span class="dot">·</span>
|
|
175
|
-
<span id="sb-activity" class="sb-activity" hidden></span>
|
|
176
|
-
<span id="sb-sync" class="sb-channel retrying" hidden title="sin sincronía con el server"></span>
|
|
177
|
-
<span id="sb-channel" class="sb-channel" hidden title="canal de eventos entre dispositivos"></span>
|
|
178
|
-
<span id="sb-rounds">listo</span>
|
|
179
|
-
<span class="spacer"></span>
|
|
180
|
-
<span id="sb-context" class="ctx muted" title="uso del contexto del modelo">
|
|
181
|
-
<span id="sb-tokens">tokens —</span>
|
|
182
|
-
<span class="ctx-bar" aria-hidden="true"><span id="sb-context-fill" class="ctx-fill"></span></span>
|
|
183
|
-
</span>
|
|
184
|
-
</div>
|
|
185
229
|
</div>
|
|
186
230
|
|
|
187
231
|
<div id="resizer-explorer" class="resizer" title="ajustar" role="separator" aria-orientation="vertical"></div>
|
|
@@ -415,8 +459,9 @@
|
|
|
415
459
|
<button type="button" class="ajustes-tab" data-panel="mcp" role="tab">MCP</button>
|
|
416
460
|
<button type="button" class="ajustes-tab" data-panel="skills" role="tab">Skills</button>
|
|
417
461
|
<button type="button" class="ajustes-tab" data-panel="telegram" role="tab">Telegram</button>
|
|
418
|
-
|
|
419
|
-
|
|
462
|
+
<button type="button" class="ajustes-tab" data-panel="voz" role="tab">Voz</button>
|
|
463
|
+
<button type="button" class="ajustes-tab" data-panel="asesor" role="tab">Asesor</button>
|
|
464
|
+
<button type="button" class="ajustes-tab" data-panel="transcripcion" role="tab">Transcripción</button>
|
|
420
465
|
<button type="button" class="ajustes-tab" data-panel="limites" role="tab">Límites</button>
|
|
421
466
|
</nav>
|
|
422
467
|
|
|
@@ -626,6 +671,12 @@
|
|
|
626
671
|
<label class="check" title="avisar al terminar">
|
|
627
672
|
<input id="voz-ann-end" type="checkbox" /> el cierre
|
|
628
673
|
</label>
|
|
674
|
+
<label class="check" title="leer los consejos del asesor">
|
|
675
|
+
<input id="voz-ann-advice" type="checkbox" /> los consejos del asesor
|
|
676
|
+
</label>
|
|
677
|
+
<label class="check" title="leer la respuesta final del agente, sin bloques de código ni tablas">
|
|
678
|
+
<input id="voz-ann-answer" type="checkbox" /> la respuesta final (sin código)
|
|
679
|
+
</label>
|
|
629
680
|
</div>
|
|
630
681
|
<p id="voz-ffmpeg" class="muted" hidden>
|
|
631
682
|
Sin <code>ffmpeg</code> en el PATH, en Telegram llega como audio MP3 en vez de nota
|
|
@@ -638,6 +689,44 @@
|
|
|
638
689
|
</div>
|
|
639
690
|
</div>
|
|
640
691
|
|
|
692
|
+
<div class="panel ajustes-panel" data-panel="asesor">
|
|
693
|
+
<div class="row between">
|
|
694
|
+
<h2>Asesor</h2>
|
|
695
|
+
<span id="asesor-status" class="summary">cargando…</span>
|
|
696
|
+
</div>
|
|
697
|
+
<p class="muted verify-line">
|
|
698
|
+
Un asesor que observa lo que el agente hace, lo interpreta y solo habla cuando tiene
|
|
699
|
+
algo que aportar: te alerta de un problema en el código, te explica un proceso o te da
|
|
700
|
+
un consejo. No lee la respuesta del agente. Usa el proveedor y modelo activos.
|
|
701
|
+
</p>
|
|
702
|
+
<label class="check" title="tener un asesor que interprete y comente el turno">
|
|
703
|
+
<input id="asesor-enabled" type="checkbox" /> Habilitar el asesor
|
|
704
|
+
</label>
|
|
705
|
+
<div class="voz-announce">
|
|
706
|
+
<span class="muted">qué aporta:</span>
|
|
707
|
+
<label class="check" title="alertar de problemas reales en el código">
|
|
708
|
+
<input id="asesor-code" type="checkbox" /> problemas en el código
|
|
709
|
+
</label>
|
|
710
|
+
<label class="check" title="explicar procesos o decisiones">
|
|
711
|
+
<input id="asesor-explain" type="checkbox" /> explicar procesos
|
|
712
|
+
</label>
|
|
713
|
+
<label class="check" title="dar consejos concretos">
|
|
714
|
+
<input id="asesor-advice" type="checkbox" /> consejos
|
|
715
|
+
</label>
|
|
716
|
+
</div>
|
|
717
|
+
<label class="field"><span>largo de cada consejo (palabras)</span>
|
|
718
|
+
<input id="asesor-words" type="number" min="10" max="120" step="5" value="40" />
|
|
719
|
+
</label>
|
|
720
|
+
<label class="field"><span>instrucciones para el asesor (opcional)</span>
|
|
721
|
+
<textarea id="asesor-notes" rows="3" placeholder="Ej: avisame siempre si toco secretos o config de producción."></textarea>
|
|
722
|
+
</label>
|
|
723
|
+
<div class="row">
|
|
724
|
+
<button id="asesor-save" class="btn primary" type="button">guardar</button>
|
|
725
|
+
<button id="asesor-test" class="btn" type="button">probar asesor</button>
|
|
726
|
+
<span id="asesor-msg" class="summary"></span>
|
|
727
|
+
</div>
|
|
728
|
+
</div>
|
|
729
|
+
|
|
641
730
|
<div class="panel ajustes-panel" data-panel="transcripcion">
|
|
642
731
|
<div class="row between">
|
|
643
732
|
<h2>Transcripción</h2>
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>Tamnora Harness</title>
|
|
7
|
+
<link rel="icon" href="/favicon.ico" sizes="any" />
|
|
8
|
+
<link rel="apple-touch-icon" href="/icons/icon-192x192.png" />
|
|
9
|
+
<link rel="manifest" href="/manifest.webmanifest" />
|
|
10
|
+
<meta name="theme-color" content="#231a17" />
|
|
7
11
|
<script>
|
|
8
12
|
(function () {
|
|
9
13
|
var theme = null;
|
|
@@ -129,13 +133,13 @@
|
|
|
129
133
|
border-radius: 11px;
|
|
130
134
|
display: grid;
|
|
131
135
|
place-items: center;
|
|
136
|
+
overflow: hidden;
|
|
132
137
|
background: var(--surface);
|
|
133
138
|
backdrop-filter: blur(10px);
|
|
134
|
-
font-family: Georgia, serif;
|
|
135
|
-
font-weight: 700;
|
|
136
|
-
font-size: 19px;
|
|
137
139
|
}
|
|
138
140
|
|
|
141
|
+
.logo-mark img { width: 100%; height: 100%; display: block; object-fit: cover; }
|
|
142
|
+
|
|
139
143
|
.logo-text strong {
|
|
140
144
|
display: block;
|
|
141
145
|
font-family: var(--serif);
|
|
@@ -420,7 +424,7 @@
|
|
|
420
424
|
<div class="page">
|
|
421
425
|
<header>
|
|
422
426
|
<a href="/landing" class="logo">
|
|
423
|
-
<div class="logo-mark"
|
|
427
|
+
<div class="logo-mark"><img src="/icons/logo.png" alt="Tamnora" /></div>
|
|
424
428
|
<div class="logo-text">
|
|
425
429
|
<strong>Tamnora Harness</strong>
|
|
426
430
|
<small>Harness de agentes IA</small>
|
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Tamnora Harness",
|
|
3
|
+
"short_name": "Tamnora",
|
|
4
|
+
"description": "Consola web del harness local de agentes IA.",
|
|
5
|
+
"start_url": "/",
|
|
6
|
+
"scope": "/",
|
|
7
|
+
"display": "standalone",
|
|
8
|
+
"background_color": "#f5efe2",
|
|
9
|
+
"theme_color": "#231a17",
|
|
10
|
+
"icons": [
|
|
11
|
+
{ "src": "/icons/icon-72x72.png", "sizes": "72x72", "type": "image/png" },
|
|
12
|
+
{ "src": "/icons/icon-96x96.png", "sizes": "96x96", "type": "image/png" },
|
|
13
|
+
{ "src": "/icons/icon-128x128.png", "sizes": "128x128", "type": "image/png" },
|
|
14
|
+
{ "src": "/icons/icon-144x144.png", "sizes": "144x144", "type": "image/png" },
|
|
15
|
+
{ "src": "/icons/icon-152x152.png", "sizes": "152x152", "type": "image/png" },
|
|
16
|
+
{ "src": "/icons/icon-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" },
|
|
17
|
+
{ "src": "/icons/icon-384x384.png", "sizes": "384x384", "type": "image/png" },
|
|
18
|
+
{ "src": "/icons/icon-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -215,3 +215,57 @@ export function renderMarkdown(text) {
|
|
|
215
215
|
flush();
|
|
216
216
|
return out.join('\n');
|
|
217
217
|
}
|
|
218
|
+
|
|
219
|
+
function cleanSpeechLine(line) {
|
|
220
|
+
return line
|
|
221
|
+
.replace(/^\s*#{1,6}\s+/, '')
|
|
222
|
+
.replace(/^\s*>\s?/, '')
|
|
223
|
+
.replace(/^\s*([-*+]|\d+[.)])\s+/, '')
|
|
224
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
225
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
226
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
227
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
228
|
+
.replace(/__([^_]+)__/g, '$1')
|
|
229
|
+
.replace(/(^|[^*\w])\*([^*\n]+)\*/g, '$1$2')
|
|
230
|
+
.replace(/(^|[^_\w])_([^_\n]+)_/g, '$1$2');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Version hablada de una respuesta: deja el texto corrido y saca lo que no se
|
|
235
|
+
* lee bien (bloques de codigo, tablas, marcas de markdown). Recorta a `maxChars`
|
|
236
|
+
* cortando en un limite de oracion, para no mandar una sintesis gigante al TTS.
|
|
237
|
+
*/
|
|
238
|
+
export function speechText(text, { maxChars = 1500 } = {}) {
|
|
239
|
+
const stripped = String(text ?? '')
|
|
240
|
+
.replace(/```[\s\S]*?```/g, ' ')
|
|
241
|
+
.replace(/~~~[\s\S]*?~~~/g, ' ');
|
|
242
|
+
const lines = stripped.replace(/\r\n?/g, '\n').split('\n');
|
|
243
|
+
const kept = [];
|
|
244
|
+
let i = 0;
|
|
245
|
+
while (i < lines.length) {
|
|
246
|
+
const line = lines[i];
|
|
247
|
+
if (line.includes('|') && i + 1 < lines.length && isDelimiterRow(lines[i + 1])) {
|
|
248
|
+
i += 2;
|
|
249
|
+
while (i < lines.length && lines[i].trim() && lines[i].includes('|')) i += 1;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) {
|
|
253
|
+
i += 1;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
kept.push(cleanSpeechLine(line));
|
|
257
|
+
i += 1;
|
|
258
|
+
}
|
|
259
|
+
let out = kept.join('\n').replace(/\s+/g, ' ').trim();
|
|
260
|
+
if (out.length > maxChars) {
|
|
261
|
+
const slice = out.slice(0, maxChars);
|
|
262
|
+
const cut = Math.max(
|
|
263
|
+
slice.lastIndexOf('. '),
|
|
264
|
+
slice.lastIndexOf('! '),
|
|
265
|
+
slice.lastIndexOf('? '),
|
|
266
|
+
slice.lastIndexOf('; ')
|
|
267
|
+
);
|
|
268
|
+
out = `${(cut > maxChars * 0.5 ? slice.slice(0, cut + 1) : slice).trim()}…`;
|
|
269
|
+
}
|
|
270
|
+
return out;
|
|
271
|
+
}
|