diff --git a/extensions/index.ts b/extensions/index.ts index 17aa2b5..d2661ba 100644 --- a/extensions/index.ts +++ b/extensions/index.ts @@ -363,11 +363,16 @@ function startRecording(ctx?: any): string { } recording = { proc, file, startTime, timer }; - // se ffmpeg termina da solo (timeout 2 min), azzera lo stato - proc.on("exit", () => { + // se ffmpeg termina da solo (timeout max durata), azzera lo stato e avvisa + proc.on("exit", (code) => { if (recording && recording.proc === proc) { if (recording.timer) clearInterval(recording.timer); recording = null; + playSound("timeout"); + if (ctx) { + ctx.ui.setStatus("agy-rec", ""); + ctx.ui.notify("⏰ Tempo massimo di registrazione raggiunto", "warning"); + } } }); return file; @@ -416,6 +421,55 @@ function cancelRecording(): Promise { }); } +// --------------------------------------------------------------------------- +// Feedback sonori (bip) per le interazioni con la registrazione +// --------------------------------------------------------------------------- +type SoundType = "start" | "stop" | "cancel" | "timeout" | "done"; + +const SOUNDS: Record = { + start: { freq: 880, dur: 0.15 }, // bip acuto breve (inizio) + stop: { freq: 660, dur: 0.25 }, // conferma + cancel: { freq: 220, dur: 0.3 }, // tono basso (annullamento) + timeout: { freq: 440, dur: 0.4, repeat: 2 }, // doppio bip di avviso + done: { freq: 990, dur: 0.2 }, // completamento (successo) +}; + +function playSound(type: SoundType) { + const cfg = SOUNDS[type]; + if (!cfg) return; + const wav = path.join(os.tmpdir(), `agy-sound-${type}-${Date.now()}.wav`); + const { freq, dur, repeat = 1 } = cfg; + // genera il tono con ffmpeg (silenzioso) e riproduce con paplay/aplay/ffplay + try { + execFileAsync( + "ffmpeg", + ["-hide_banner", "-loglevel", "error", "-y", "-f", "lavfi", "-i", `sine=frequency=${freq}:duration=${dur}`, wav], + { timeout: 10_000 }, + ) + .then(() => { + for (let i = 0; i < repeat; i++) { + execFileAsync("paplay", [wav], { timeout: 5000 }).catch(() => { + // fallback su aplay/ffplay se paplay non disponibile + execFileAsync("aplay", ["-q", wav], { timeout: 5000 }).catch(() => { + execFileAsync("ffplay", ["-nodisp", "-autoexit", wav], { timeout: 5000 }).catch(() => {}); + }); + }); + } + // pulizia + setTimeout(() => { + try { + fs.rmSync(wav, { force: true }); + } catch { + /* ignora */ + } + }, repeat * 1500); + }) + .catch(() => {}); + } catch { + /* ignora */ + } +} + // Ottimizza l'audio: taglia il silenzio iniziale/finale e converte in WAV 16kHz mono async function optimizeAudio(input: string): Promise { const output = input.replace(/\.wav$/, "-opt.wav"); @@ -1336,10 +1390,12 @@ export default function agyExtension(pi: ExtensionAPI) { async function handleRecordToggle(ctx: any) { if (!recording) { startRecording(ctx); + playSound("start"); ctx.ui.notify("đŸŽ™ī¸ Registrazione avviata (F12 per fermare, Esc per annullare)", "info"); return; } + playSound("stop"); ctx.ui.setStatus("agy-rec", "âšī¸ Finalizzazione..."); const file = await stopRecording(); if (!file) { @@ -1356,6 +1412,7 @@ export default function agyExtension(pi: ExtensionAPI) { if (!tr.text) { ctx.ui.setStatus("agy-rec", ""); ctx.ui.notify(`Trascrizione fallita: ${tr.error ?? "vuota"}`, "error"); + playSound("cancel"); return; } const transcript = tr.text; @@ -1383,6 +1440,7 @@ export default function agyExtension(pi: ExtensionAPI) { pi.sendUserMessage(finalText, { deliverAs: "followUp" }); } ctx.ui.notify("✅ Trascrizione inviata come prompt a pi", "info"); + playSound("done"); // notifica vocale (config ttsNotify o AGY_TTS_NOTIFY=0 per disattivare) const ttsNotify = getConfig("ttsNotify") ?? "true"; @@ -1405,6 +1463,7 @@ export default function agyExtension(pi: ExtensionAPI) { await cancelRecording(); ctx.ui.setStatus("agy-rec", ""); ctx.ui.notify("❌ Registrazione annullata", "info"); + playSound("cancel"); } }, }); @@ -1416,6 +1475,7 @@ export default function agyExtension(pi: ExtensionAPI) { await cancelRecording(); ctx.ui.setStatus("agy-rec", ""); ctx.ui.notify("❌ Registrazione annullata", "info"); + playSound("cancel"); } }, }); @@ -1448,6 +1508,7 @@ export default function agyExtension(pi: ExtensionAPI) { await cancelRecording(); ctx.ui.setStatus("agy-rec", ""); ctx.ui.notify("❌ Registrazione annullata", "info"); + playSound("cancel"); }, });