@danieltmn/tamnora 0.9.0 → 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 +12 -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 +659 -66
- package/harness/packages/server/public/index.html +120 -2
- 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
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ Todos los cambios relevantes de este proyecto se documentan en este archivo.
|
|
|
5
5
|
El formato sigue [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/)
|
|
6
6
|
y el proyecto usa [Versionado Semantico](https://semver.org/lang/es/).
|
|
7
7
|
|
|
8
|
+
## [0.9.2] - 2026-10-07
|
|
9
|
+
|
|
10
|
+
### Cambios
|
|
11
|
+
|
|
12
|
+
- feat: perfil del usuario, dictado de voz y Telegram por proyecto
|
|
13
|
+
|
|
14
|
+
## [0.9.1] - 2026-10-07
|
|
15
|
+
|
|
16
|
+
### Cambios
|
|
17
|
+
|
|
18
|
+
- fix(consola): no borrar el progreso del agente y permiso solo en el chat
|
|
19
|
+
|
|
8
20
|
## [0.9.0] - 2026-10-07
|
|
9
21
|
|
|
10
22
|
### Agregado
|
|
@@ -12,6 +12,8 @@ export const DEFAULT_APP = {
|
|
|
12
12
|
tunnel: { provider: 'tunnelmole', domain: '' },
|
|
13
13
|
authSecret: '',
|
|
14
14
|
user: null,
|
|
15
|
+
// Datos del usuario para que el agente lo conozca (se inyecta en el prompt).
|
|
16
|
+
profile: { address: '', name: '', language: '', notes: '', fields: {} },
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
export function randomToken(bytes = 32) {
|
|
@@ -105,6 +105,7 @@ export {
|
|
|
105
105
|
sintetizar,
|
|
106
106
|
listarVoces,
|
|
107
107
|
} from './tts/edge.js';
|
|
108
|
+
export { reconocerGoogle, wavToPcm } from './stt/google.js';
|
|
108
109
|
export { pidAlive, killPidTree } from './pid.js';
|
|
109
110
|
export { openBrowser, openerFor } from './open.js';
|
|
110
111
|
export { detectProcs, packageManager, DEFAULT_PROCESS_ALLOW, staticServerChip } from './procs/detect.js';
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Reconocimiento de voz con el servicio no oficial de Chromium/Google (el mismo
|
|
2
|
+
// que usa el Web Speech API del navegador). No necesita API key: usa la clave
|
|
3
|
+
// publica que trae Chromium. Analogо al TTS de Edge: gratis, sin dependencias y
|
|
4
|
+
// con el mismo riesgo (si cambia el protocolo/clave hay que actualizarlo).
|
|
5
|
+
|
|
6
|
+
const GOOGLE_KEY = 'AIzaSyBOti4mM-6x9WDnZIjIeyEU21OpBXqWBgw';
|
|
7
|
+
const GOOGLE_URL = 'https://www.google.com/speech-api/v2/recognize';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extrae el trozo PCM de un WAV y su sample rate. Si no es un WAV conocido,
|
|
11
|
+
* devuelve el buffer tal cual a 16 kHz.
|
|
12
|
+
*/
|
|
13
|
+
export function wavToPcm(buffer) {
|
|
14
|
+
const wav = Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer ?? []);
|
|
15
|
+
if (wav.length < 44 || wav.toString('ascii', 0, 4) !== 'RIFF' || wav.toString('ascii', 8, 12) !== 'WAVE') {
|
|
16
|
+
return { pcm: wav, rate: 16000, channels: 1, bits: 16 };
|
|
17
|
+
}
|
|
18
|
+
let rate = 16000;
|
|
19
|
+
let channels = 1;
|
|
20
|
+
let bits = 16;
|
|
21
|
+
let offset = 12;
|
|
22
|
+
while (offset + 8 <= wav.length) {
|
|
23
|
+
const id = wav.toString('ascii', offset, offset + 4);
|
|
24
|
+
const size = wav.readUInt32LE(offset + 4);
|
|
25
|
+
if (id === 'fmt ' && size >= 16) {
|
|
26
|
+
channels = wav.readUInt16LE(offset + 10);
|
|
27
|
+
rate = wav.readUInt32LE(offset + 12);
|
|
28
|
+
bits = wav.readUInt16LE(offset + 22);
|
|
29
|
+
} else if (id === 'data') {
|
|
30
|
+
return { pcm: wav.subarray(offset + 8, offset + 8 + size), rate, channels, bits };
|
|
31
|
+
}
|
|
32
|
+
offset += 8 + size + (size % 2);
|
|
33
|
+
}
|
|
34
|
+
return { pcm: wav.subarray(44), rate, channels, bits };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Convierte PCM multicanal/8-bit a 16-bit mono little-endian. */
|
|
38
|
+
function aMono16(pcm, channels, bits) {
|
|
39
|
+
if (bits !== 16) return pcm;
|
|
40
|
+
if (channels <= 1) return pcm;
|
|
41
|
+
const frames = Math.floor(pcm.length / 2 / channels);
|
|
42
|
+
const out = Buffer.alloc(frames * 2);
|
|
43
|
+
for (let i = 0; i < frames; i++) {
|
|
44
|
+
let sum = 0;
|
|
45
|
+
for (let c = 0; c < channels; c++) sum += pcm.readInt16LE((i * channels + c) * 2);
|
|
46
|
+
out.writeInt16LE(Math.max(-32768, Math.min(32767, Math.round(sum / channels))), i * 2);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parsearRespuesta(texto) {
|
|
52
|
+
let transcript = '';
|
|
53
|
+
for (const linea of String(texto ?? '').split('\n')) {
|
|
54
|
+
const value = linea.trim();
|
|
55
|
+
if (!value) continue;
|
|
56
|
+
let data;
|
|
57
|
+
try {
|
|
58
|
+
data = JSON.parse(value);
|
|
59
|
+
} catch {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const results = data?.result ?? data?.results ?? [];
|
|
63
|
+
for (const r of results) {
|
|
64
|
+
const alt = r?.alternative?.[0];
|
|
65
|
+
if (alt?.transcript) transcript += `${transcript ? ' ' : ''}${alt.transcript}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return transcript.trim();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Transcribe un WAV (o PCM crudo) contra el servicio de Google. Devuelve el
|
|
73
|
+
* texto ('' si no se reconoció nada).
|
|
74
|
+
*/
|
|
75
|
+
export async function reconocerGoogle(audio, { lang = 'es-AR', signal } = {}) {
|
|
76
|
+
const { pcm, rate, channels, bits } = wavToPcm(audio);
|
|
77
|
+
const mono = aMono16(pcm, channels, bits);
|
|
78
|
+
if (!mono.length) return '';
|
|
79
|
+
const url = `${GOOGLE_URL}?output=json&lang=${encodeURIComponent(lang || 'es-AR')}&key=${GOOGLE_KEY}&client=chromium`;
|
|
80
|
+
const res = await fetch(url, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: { 'Content-Type': `audio/l16; rate=${rate || 16000}` },
|
|
83
|
+
body: mono,
|
|
84
|
+
signal,
|
|
85
|
+
});
|
|
86
|
+
if (!res.ok) {
|
|
87
|
+
const detail = String((await res.text().catch(() => '')) ?? '').slice(0, 200);
|
|
88
|
+
throw new Error(`reconocimiento de Google falló (HTTP ${res.status})${detail ? `: ${detail}` : ''}`);
|
|
89
|
+
}
|
|
90
|
+
return parsearRespuesta(await res.text());
|
|
91
|
+
}
|