diff --git a/extensions/index.ts b/extensions/index.ts index 84be003..f9e09ab 100644 --- a/extensions/index.ts +++ b/extensions/index.ts @@ -282,9 +282,9 @@ function stopRecording(): Promise { }); } -// Ottimizza l'audio: taglia il silenzio iniziale/finale e converte in MP3 (più piccolo) +// 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$/, ".mp3"); + const output = input.replace(/\.wav$/, "-opt.wav"); try { await execFileAsync( "ffmpeg", @@ -294,10 +294,10 @@ async function optimizeAudio(input: string): Promise { input, "-af", "silenceremove=start_periods=1:start_threshold=-50dB:start_silence=0.5,areverse,silenceremove=start_periods=1:start_threshold=-50dB:start_silence=0.5,areverse", - "-c:a", - "libmp3lame", - "-q:a", - "4", + "-ac", + "1", + "-ar", + "16000", output, ], { timeout: 30_000 }, @@ -308,6 +308,28 @@ async function optimizeAudio(input: string): Promise { } } +// Trascrizione locale affidabile con faster-whisper (agy CLI non supporta audio) +async function transcribeWithWhisper(file: string): Promise { + const model = process.env.AGY_WHISPER_MODEL ?? "small"; + const script = ` +from faster_whisper import WhisperModel +import sys +model = WhisperModel('${model}', device='cpu', compute_type='int8') +segments, info = model.transcribe(sys.argv[1], language='it') +for seg in segments: + print(seg.text, end='') +`; + try { + const { stdout } = await execFileAsync("python3", ["-c", script, file], { + timeout: 180_000, + maxBuffer: 10 * 1024 * 1024, + }); + return stdout.trim(); + } catch { + return ""; + } +} + function extractText(content: any): string { if (typeof content === "string") return content; if (Array.isArray(content)) { @@ -489,6 +511,7 @@ export default function agyExtension(pi: ExtensionAPI) { stateless: true, filePaths: [p.baseImage], outputDir: p.outputDir, + yolo: true, signal, }); return { @@ -533,6 +556,7 @@ export default function agyExtension(pi: ExtensionAPI) { stateless: true, filePaths: [p.baseImage], outputDir: p.outputDir, + yolo: true, signal, }); return { @@ -576,6 +600,7 @@ export default function agyExtension(pi: ExtensionAPI) { stateless: true, filePaths: [p.baseImage], outputDir: p.outputDir, + yolo: true, signal, }); return { @@ -620,6 +645,7 @@ export default function agyExtension(pi: ExtensionAPI) { stateless: true, filePaths: p.images, outputDir: p.outputDir, + yolo: true, signal, }); return { @@ -662,6 +688,7 @@ export default function agyExtension(pi: ExtensionAPI) { stateless: true, filePaths: [p.referenceImage], outputDir: p.outputDir, + yolo: true, signal, }); return { @@ -913,33 +940,35 @@ export default function agyExtension(pi: ExtensionAPI) { ctx.ui.notify("Registrazione fermata, ottimizzazione audio...", "info"); const optimized = await optimizeAudio(file); - ctx.ui.notify("Trascrizione in corso...", "info"); + ctx.ui.notify("Trascrizione in corso (faster-whisper)...", "info"); + const transcript = await transcribeWithWhisper(optimized); + if (!transcript) { + ctx.ui.setStatus("agy-rec", ""); + ctx.ui.notify("Trascrizione vuota o errore", "error"); + return; + } + + // Interpreta con Gemini (testo) usando il contesto della conversazione + ctx.ui.notify("Interpretazione con Gemini...", "info"); const context = getConversationContext(ctx); const res = await executeAgy({ prompt: - `Trascrivi fedelmente il contenuto del file audio al percorso ${optimized}.` + + `Ecco la trascrizione di un messaggio vocale dell'utente:\n\n${transcript}` + `\n\nContesto della conversazione:\n${context || "(nessuno)"}` + - `\n\nRestituisci SOLO la trascrizione testuale, senza commenti.`, - mode: "analyze", + `\n\nRestituisci la trascrizione corretta e una breve interpretazione/risposta.`, stateless: true, - filePaths: [optimized], - yolo: true, model: "Gemini 3.6 Flash (Medium)", effort: "low", }); ctx.ui.setStatus("agy-rec", ""); - const transcription = res.text.trim(); - if (!transcription) { - ctx.ui.notify("Trascrizione vuota o errore", "error"); - return; - } + const finalText = res.text.trim() || transcript; // Inserisci il risultato come prompt su pi if (ctx.isIdle()) { - pi.sendUserMessage(transcription); + pi.sendUserMessage(finalText); } else { - pi.sendUserMessage(transcription, { deliverAs: "followUp" }); + pi.sendUserMessage(finalText, { deliverAs: "followUp" }); } ctx.ui.notify("✅ Trascrizione inviata come prompt a pi", "info"); }