@danieltmn/tamnora 0.9.1 → 0.9.2
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 +6 -0
- package/harness/packages/core/src/config/app.js +2 -0
- package/harness/packages/core/src/index.js +1 -0
- package/harness/packages/core/src/stt/google.js +91 -0
- package/harness/packages/server/public/app.js +652 -9
- package/harness/packages/server/public/index.html +120 -1
- package/harness/packages/server/public/styles.css +19 -0
- package/harness/packages/server/src/lib/channels/index.js +23 -3
- package/harness/packages/server/src/lib/channels/telegram.js +301 -93
- package/harness/packages/server/src/lib/presence.js +51 -0
- package/harness/packages/server/src/lib/profile.js +63 -0
- package/harness/packages/server/src/lib/prompt.js +7 -2
- package/harness/packages/server/src/lib/transcribe.js +51 -0
- package/harness/packages/server/src/routes/channels.js +2 -0
- package/harness/packages/server/src/routes/presence.js +17 -0
- package/harness/packages/server/src/routes/profile.js +22 -0
- package/harness/packages/server/src/routes/providers.js +2 -0
- package/harness/packages/server/src/routes/transcribe.js +115 -0
- package/harness/packages/server/src/routes/tts.js +2 -1
- package/harness/packages/server/src/server.js +11 -0
- package/harness/packages/server/test/profile.test.js +85 -0
- package/harness/packages/server/test/telegram-features.test.js +241 -0
- package/harness/packages/server/test/telegram-voz.test.js +9 -0
- package/harness/packages/server/test/telegram.test.js +37 -2
- package/harness/packages/server/test/transcribe.test.js +215 -0
- package/harness/packages/server/test/tts.test.js +24 -0
- package/package.json +1 -1
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
</head>
|
|
35
35
|
<body>
|
|
36
36
|
<header class="topbar">
|
|
37
|
-
<div class="brand"><span class="logo"><span class="logo-t">T</span><span class="logo-h">H</span></span> <span class="ver">v0.9.
|
|
37
|
+
<div class="brand"><span class="logo"><span class="logo-t">T</span><span class="logo-h">H</span></span> <span class="ver">v0.9.2</span></div>
|
|
38
38
|
<nav class="tabs">
|
|
39
39
|
<button class="tab active" data-tab="chat">chat</button>
|
|
40
40
|
<button class="tab" data-tab="providers">proveedores</button>
|
|
@@ -150,6 +150,14 @@
|
|
|
150
150
|
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
|
|
151
151
|
</svg>
|
|
152
152
|
</button>
|
|
153
|
+
<button type="button" id="mic-btn" class="btn icon" title="Dictar por voz (tocá para grabar y parar)" aria-label="dictar por voz">
|
|
154
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
155
|
+
<path d="M12 19v3" />
|
|
156
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
|
157
|
+
<path d="M12 15a3 3 0 0 0 3-3V6a3 3 0 0 0-6 0v6a3 3 0 0 0 3 3Z" />
|
|
158
|
+
</svg>
|
|
159
|
+
<span class="mic-count" hidden></span>
|
|
160
|
+
</button>
|
|
153
161
|
<input id="image-input" type="file" accept="image/*" multiple hidden />
|
|
154
162
|
<textarea id="chat-input" rows="1" placeholder="escribí un mensaje…" title="Enter envía · Shift+Enter salto" spellcheck="false"></textarea>
|
|
155
163
|
<button class="btn primary" type="submit">enviar</button>
|
|
@@ -401,11 +409,13 @@
|
|
|
401
409
|
<div class="ajustes-wrap">
|
|
402
410
|
<nav class="ajustes-tabs" role="tablist" aria-label="secciones de ajustes">
|
|
403
411
|
<button type="button" class="ajustes-tab active" data-panel="agente" role="tab">Agente</button>
|
|
412
|
+
<button type="button" class="ajustes-tab" data-panel="perfil" role="tab">Perfil</button>
|
|
404
413
|
<button type="button" class="ajustes-tab" data-panel="sesiones" role="tab">Sesiones</button>
|
|
405
414
|
<button type="button" class="ajustes-tab" data-panel="mcp" role="tab">MCP</button>
|
|
406
415
|
<button type="button" class="ajustes-tab" data-panel="skills" role="tab">Skills</button>
|
|
407
416
|
<button type="button" class="ajustes-tab" data-panel="telegram" role="tab">Telegram</button>
|
|
408
417
|
<button type="button" class="ajustes-tab" data-panel="voz" role="tab">Voz</button>
|
|
418
|
+
<button type="button" class="ajustes-tab" data-panel="transcripcion" role="tab">Transcripción</button>
|
|
409
419
|
<button type="button" class="ajustes-tab" data-panel="limites" role="tab">Límites</button>
|
|
410
420
|
</nav>
|
|
411
421
|
|
|
@@ -435,6 +445,38 @@
|
|
|
435
445
|
</div>
|
|
436
446
|
</div>
|
|
437
447
|
|
|
448
|
+
<div class="panel ajustes-panel" data-panel="perfil">
|
|
449
|
+
<div class="row between">
|
|
450
|
+
<h2>Perfil</h2>
|
|
451
|
+
<span id="perfil-status" class="summary">cargando…</span>
|
|
452
|
+
</div>
|
|
453
|
+
<p class="muted verify-line">
|
|
454
|
+
Datos que el agente conoce de vos. Viven en <code>app.json</code> (la casa) y se
|
|
455
|
+
inyectan en el prompt de todas las corridas.
|
|
456
|
+
</p>
|
|
457
|
+
<label class="field"><span>cómo querés que te llame</span>
|
|
458
|
+
<input id="perfil-address" spellcheck="false" placeholder="Señor · Amo · Amigo · Compañero · Daniel…" />
|
|
459
|
+
</label>
|
|
460
|
+
<label class="field"><span>nombre</span>
|
|
461
|
+
<input id="perfil-name" spellcheck="false" placeholder="Daniel" />
|
|
462
|
+
</label>
|
|
463
|
+
<label class="field"><span>idioma preferido</span>
|
|
464
|
+
<input id="perfil-language" spellcheck="false" placeholder="es" />
|
|
465
|
+
</label>
|
|
466
|
+
<label class="field"><span>notas</span>
|
|
467
|
+
<textarea id="perfil-notes" rows="3" placeholder="Preferencias, contexto, cómo te gusta trabajar…"></textarea>
|
|
468
|
+
</label>
|
|
469
|
+
<div class="row between">
|
|
470
|
+
<span class="muted">datos extra</span>
|
|
471
|
+
<button type="button" id="perfil-add-field" class="btn">+ agregar</button>
|
|
472
|
+
</div>
|
|
473
|
+
<div id="perfil-fields" class="perfil-fields"></div>
|
|
474
|
+
<div class="row">
|
|
475
|
+
<button id="perfil-save" class="btn primary" type="button">guardar</button>
|
|
476
|
+
<span id="perfil-msg" class="summary"></span>
|
|
477
|
+
</div>
|
|
478
|
+
</div>
|
|
479
|
+
|
|
438
480
|
<div class="panel ajustes-panel" data-panel="sesiones">
|
|
439
481
|
<div class="row between">
|
|
440
482
|
<h2>Sesiones</h2>
|
|
@@ -512,6 +554,27 @@
|
|
|
512
554
|
<label class="field"><span>modelo de transcripción (voz)</span>
|
|
513
555
|
<input id="tg-transcribe" spellcheck="false" placeholder="whisper-1" />
|
|
514
556
|
</label>
|
|
557
|
+
<label class="check" title="avisar y pedir permisos por Telegram de los proyectos abiertos en la consola">
|
|
558
|
+
<input id="tg-notify" type="checkbox" /> Avisar por Telegram (proyectos abiertos en la consola)
|
|
559
|
+
</label>
|
|
560
|
+
<div class="voz-announce">
|
|
561
|
+
<span class="muted">qué avisa (siempre con el nombre del proyecto):</span>
|
|
562
|
+
<label class="check" title="resumen al terminar un turno">
|
|
563
|
+
<input id="tg-notify-done" type="checkbox" /> el fin de turno
|
|
564
|
+
</label>
|
|
565
|
+
<label class="check" title="cuando un run falla">
|
|
566
|
+
<input id="tg-notify-error" type="checkbox" /> los errores
|
|
567
|
+
</label>
|
|
568
|
+
<label class="check" title="cuando el agente pide una aprobación">
|
|
569
|
+
<input id="tg-notify-permission" type="checkbox" /> los permisos
|
|
570
|
+
</label>
|
|
571
|
+
<label class="check" title="resultado de subagentes (tool task)">
|
|
572
|
+
<input id="tg-notify-agent" type="checkbox" /> los subagentes
|
|
573
|
+
</label>
|
|
574
|
+
</div>
|
|
575
|
+
<p class="muted verify-line">
|
|
576
|
+
Solo avisa de los proyectos que tengas <b>abiertos como pestañas</b> en la consola.
|
|
577
|
+
</p>
|
|
515
578
|
<details class="tg-help">
|
|
516
579
|
<summary>¿Cómo habilito mi chat?</summary>
|
|
517
580
|
<ol>
|
|
@@ -574,6 +637,62 @@
|
|
|
574
637
|
</div>
|
|
575
638
|
</div>
|
|
576
639
|
|
|
640
|
+
<div class="panel ajustes-panel" data-panel="transcripcion">
|
|
641
|
+
<div class="row between">
|
|
642
|
+
<h2>Transcripción</h2>
|
|
643
|
+
<span id="tr-status" class="summary">cargando…</span>
|
|
644
|
+
</div>
|
|
645
|
+
<p class="muted verify-line">
|
|
646
|
+
Audio → texto para el <b>dictado del chat</b> (micrófono del input) y las notas de voz
|
|
647
|
+
de Telegram.
|
|
648
|
+
</p>
|
|
649
|
+
<label class="field"><span>motor</span>
|
|
650
|
+
<select id="tr-mode" title="cómo se transcribe el dictado del chat">
|
|
651
|
+
<option value="server">Servidor · proveedor de transcripción</option>
|
|
652
|
+
<option value="google">Servidor · Google (sin API key)</option>
|
|
653
|
+
<option value="browser">Navegador · nativo (Web Speech API)</option>
|
|
654
|
+
</select>
|
|
655
|
+
</label>
|
|
656
|
+
<label class="field"><span>idioma del dictado (se le indica al motor para no confundirse)</span>
|
|
657
|
+
<input id="tr-language" spellcheck="false" placeholder="es-AR" />
|
|
658
|
+
</label>
|
|
659
|
+
<div id="tr-server-fields">
|
|
660
|
+
<label class="field"><span>proveedor de transcripción (vacío = proveedor activo)</span>
|
|
661
|
+
<input id="tr-provider" spellcheck="false" placeholder="groq · openai" />
|
|
662
|
+
</label>
|
|
663
|
+
<label class="field"><span>modelo</span>
|
|
664
|
+
<input id="tr-model" spellcheck="false" placeholder="whisper-large-v3-turbo" />
|
|
665
|
+
</label>
|
|
666
|
+
<p class="muted verify-line">
|
|
667
|
+
El proveedor activo no siempre sabe transcribir (p. ej. DeepSeek), así que conviene
|
|
668
|
+
apuntar a uno con <code>/audio/transcriptions</code> (Groq, OpenAI…). Si lo dejás
|
|
669
|
+
vacío se usa lo configurado en <b>Telegram</b> y, si tampoco hay, el proveedor activo
|
|
670
|
+
con <code>whisper-1</code>.
|
|
671
|
+
</p>
|
|
672
|
+
</div>
|
|
673
|
+
<p class="muted verify-line" id="tr-google-note" hidden>
|
|
674
|
+
Usa el servicio de voz de Chromium/Google <b>desde el servidor</b>, sin API key ni
|
|
675
|
+
depender del navegador (servicio no oficial, como el TTS de Edge). Configurá el idioma.
|
|
676
|
+
</p>
|
|
677
|
+
<p class="muted verify-line" id="tr-browser-note" hidden>
|
|
678
|
+
El navegador transcribe en vivo (Chrome/Edge) sin usar el servidor. Necesita el
|
|
679
|
+
servicio de voz de Google; en Brave o sin internet suele fallar (error «network»).
|
|
680
|
+
</p>
|
|
681
|
+
<div class="row">
|
|
682
|
+
<button id="tr-save" class="btn primary" type="button">guardar</button>
|
|
683
|
+
<button id="tr-test" class="btn icon" type="button" title="grabar y ver qué devuelve la transcripción" aria-label="probar transcripción">
|
|
684
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
685
|
+
<path d="M12 19v3" />
|
|
686
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
|
687
|
+
<path d="M12 15a3 3 0 0 0 3-3V6a3 3 0 0 0-6 0v6a3 3 0 0 0 3 3Z" />
|
|
688
|
+
</svg>
|
|
689
|
+
<span class="mic-count" hidden></span>
|
|
690
|
+
</button>
|
|
691
|
+
<span id="tr-msg" class="summary"></span>
|
|
692
|
+
</div>
|
|
693
|
+
<p id="tr-test-result" class="summary"></p>
|
|
694
|
+
</div>
|
|
695
|
+
|
|
577
696
|
<div class="panel ajustes-panel" data-panel="limites">
|
|
578
697
|
<div class="row between">
|
|
579
698
|
<h2>Límites del agente</h2>
|
|
@@ -762,9 +762,28 @@ details.reasoning .body { color: var(--muted); white-space: pre-wrap; margin-top
|
|
|
762
762
|
place-items: center;
|
|
763
763
|
flex: 0 0 auto;
|
|
764
764
|
line-height: 1;
|
|
765
|
+
position: relative;
|
|
765
766
|
}
|
|
766
767
|
.btn.icon svg { width: 18px; height: 18px; display: block; }
|
|
767
768
|
.btn.icon:hover { color: var(--accent); }
|
|
769
|
+
/* Dictado activo: verde titilando (cuenta regresiva y grabación). */
|
|
770
|
+
.btn.icon.recording { color: #2eb872; border-color: currentColor; animation: live-pulse 1s ease-in-out infinite; }
|
|
771
|
+
.btn.icon.busy { opacity: 0.55; }
|
|
772
|
+
.btn.icon.mic-counting > svg { visibility: hidden; }
|
|
773
|
+
.mic-count {
|
|
774
|
+
position: absolute;
|
|
775
|
+
inset: 0;
|
|
776
|
+
display: grid;
|
|
777
|
+
place-items: center;
|
|
778
|
+
font: 700 13px var(--mono);
|
|
779
|
+
color: #2eb872;
|
|
780
|
+
}
|
|
781
|
+
.mic-count[hidden] { display: none; }
|
|
782
|
+
|
|
783
|
+
.perfil-fields { display: flex; flex-direction: column; gap: 6px; }
|
|
784
|
+
.perfil-field { display: flex; gap: 6px; align-items: center; }
|
|
785
|
+
.perfil-field .pf-key { flex: 0 0 34%; }
|
|
786
|
+
.perfil-field .pf-value { flex: 1; }
|
|
768
787
|
|
|
769
788
|
.chat-queue { display: flex; flex-direction: column; gap: 4px; padding: 8px 10px; border: 1px dashed var(--border); border-radius: 9px; background: var(--theirs); }
|
|
770
789
|
.chat-queue[hidden] { display: none; }
|
|
@@ -19,6 +19,16 @@ function parseAllowed(raw) {
|
|
|
19
19
|
.filter(Boolean);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
const NOTIFY_EVENTS = ['done', 'error', 'permission', 'agent'];
|
|
23
|
+
|
|
24
|
+
/** Avisos por Telegram: interruptor global + que eventos llegan. */
|
|
25
|
+
export function sanitizeNotify(raw) {
|
|
26
|
+
const value = raw && typeof raw === 'object' ? raw : {};
|
|
27
|
+
const notify = { enabled: value.enabled === true };
|
|
28
|
+
for (const key of NOTIFY_EVENTS) notify[key] = value[key] !== false;
|
|
29
|
+
return notify;
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
/**
|
|
23
33
|
* Crea el manager de canales. `load()` arranca (o reinicia) los canales segun
|
|
24
34
|
* la config efectiva (app.json + tamnora.json + entorno), `status()` la resume
|
|
@@ -37,6 +47,7 @@ export function createChannelsManager(ctx, { log = () => {}, enabled = true } =
|
|
|
37
47
|
if (Array.isArray(cfg.allowedChatIds) && cfg.allowedChatIds.length) tg.allowedChatIds = cfg.allowedChatIds;
|
|
38
48
|
if (cfg.transcribeModel) tg.transcribeModel = cfg.transcribeModel;
|
|
39
49
|
if (cfg.transcribeProvider) tg.transcribeProvider = cfg.transcribeProvider;
|
|
50
|
+
if (cfg.notify) tg.notify = cfg.notify;
|
|
40
51
|
return tg;
|
|
41
52
|
};
|
|
42
53
|
|
|
@@ -49,6 +60,7 @@ export function createChannelsManager(ctx, { log = () => {}, enabled = true } =
|
|
|
49
60
|
allowedChatIds,
|
|
50
61
|
transcribeModel: tg.transcribeModel,
|
|
51
62
|
transcribeProvider: tg.transcribeProvider,
|
|
63
|
+
notify: sanitizeNotify(tg.notify),
|
|
52
64
|
};
|
|
53
65
|
}
|
|
54
66
|
|
|
@@ -62,9 +74,16 @@ export function createChannelsManager(ctx, { log = () => {}, enabled = true } =
|
|
|
62
74
|
function load() {
|
|
63
75
|
stop();
|
|
64
76
|
if (!enabled) return { enabled: false, channels: [] };
|
|
65
|
-
const { token, allowedChatIds, transcribeModel, transcribeProvider } = effective();
|
|
77
|
+
const { token, allowedChatIds, transcribeModel, transcribeProvider, notify } = effective();
|
|
66
78
|
const channels = [];
|
|
67
|
-
const telegram = startTelegramChannel(ctx, {
|
|
79
|
+
const telegram = startTelegramChannel(ctx, {
|
|
80
|
+
token,
|
|
81
|
+
allowedChatIds,
|
|
82
|
+
transcribeModel,
|
|
83
|
+
transcribeProvider,
|
|
84
|
+
notify,
|
|
85
|
+
log,
|
|
86
|
+
});
|
|
68
87
|
if (telegram) channels.push(telegram);
|
|
69
88
|
const unsubscribe = ctx.runs?.subscribeAll?.((sessionId, event) => {
|
|
70
89
|
for (const channel of channels) {
|
|
@@ -81,13 +100,14 @@ export function createChannelsManager(ctx, { log = () => {}, enabled = true } =
|
|
|
81
100
|
}
|
|
82
101
|
|
|
83
102
|
function status() {
|
|
84
|
-
const { token, allowedChatIds, transcribeModel, transcribeProvider } = effective();
|
|
103
|
+
const { token, allowedChatIds, transcribeModel, transcribeProvider, notify } = effective();
|
|
85
104
|
return {
|
|
86
105
|
tokenSet: Boolean(token),
|
|
87
106
|
tokenHint: tokenHint(token),
|
|
88
107
|
allowedChatIds,
|
|
89
108
|
transcribeModel: transcribeModel || 'whisper-1',
|
|
90
109
|
transcribeProvider: transcribeProvider || '',
|
|
110
|
+
notify,
|
|
91
111
|
running: Boolean(active && active.channels.length),
|
|
92
112
|
};
|
|
93
113
|
}
|