Fix: aggiunto yolo ai tool di editing immagini (evita hang in headless)

This commit is contained in:
dev
2026-08-09 22:20:53 +02:00
parent 971212aa6c
commit 674819f843
+48 -19
View File
@@ -282,9 +282,9 @@ function stopRecording(): Promise<string | null> {
}); });
} }
// 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<string> { async function optimizeAudio(input: string): Promise<string> {
const output = input.replace(/\.wav$/, ".mp3"); const output = input.replace(/\.wav$/, "-opt.wav");
try { try {
await execFileAsync( await execFileAsync(
"ffmpeg", "ffmpeg",
@@ -294,10 +294,10 @@ async function optimizeAudio(input: string): Promise<string> {
input, input,
"-af", "-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", "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", "-ac",
"libmp3lame", "1",
"-q:a", "-ar",
"4", "16000",
output, output,
], ],
{ timeout: 30_000 }, { timeout: 30_000 },
@@ -308,6 +308,28 @@ async function optimizeAudio(input: string): Promise<string> {
} }
} }
// Trascrizione locale affidabile con faster-whisper (agy CLI non supporta audio)
async function transcribeWithWhisper(file: string): Promise<string> {
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 { function extractText(content: any): string {
if (typeof content === "string") return content; if (typeof content === "string") return content;
if (Array.isArray(content)) { if (Array.isArray(content)) {
@@ -489,6 +511,7 @@ export default function agyExtension(pi: ExtensionAPI) {
stateless: true, stateless: true,
filePaths: [p.baseImage], filePaths: [p.baseImage],
outputDir: p.outputDir, outputDir: p.outputDir,
yolo: true,
signal, signal,
}); });
return { return {
@@ -533,6 +556,7 @@ export default function agyExtension(pi: ExtensionAPI) {
stateless: true, stateless: true,
filePaths: [p.baseImage], filePaths: [p.baseImage],
outputDir: p.outputDir, outputDir: p.outputDir,
yolo: true,
signal, signal,
}); });
return { return {
@@ -576,6 +600,7 @@ export default function agyExtension(pi: ExtensionAPI) {
stateless: true, stateless: true,
filePaths: [p.baseImage], filePaths: [p.baseImage],
outputDir: p.outputDir, outputDir: p.outputDir,
yolo: true,
signal, signal,
}); });
return { return {
@@ -620,6 +645,7 @@ export default function agyExtension(pi: ExtensionAPI) {
stateless: true, stateless: true,
filePaths: p.images, filePaths: p.images,
outputDir: p.outputDir, outputDir: p.outputDir,
yolo: true,
signal, signal,
}); });
return { return {
@@ -662,6 +688,7 @@ export default function agyExtension(pi: ExtensionAPI) {
stateless: true, stateless: true,
filePaths: [p.referenceImage], filePaths: [p.referenceImage],
outputDir: p.outputDir, outputDir: p.outputDir,
yolo: true,
signal, signal,
}); });
return { return {
@@ -913,33 +940,35 @@ export default function agyExtension(pi: ExtensionAPI) {
ctx.ui.notify("Registrazione fermata, ottimizzazione audio...", "info"); ctx.ui.notify("Registrazione fermata, ottimizzazione audio...", "info");
const optimized = await optimizeAudio(file); 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 context = getConversationContext(ctx);
const res = await executeAgy({ const res = await executeAgy({
prompt: 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\nContesto della conversazione:\n${context || "(nessuno)"}` +
`\n\nRestituisci SOLO la trascrizione testuale, senza commenti.`, `\n\nRestituisci la trascrizione corretta e una breve interpretazione/risposta.`,
mode: "analyze",
stateless: true, stateless: true,
filePaths: [optimized],
yolo: true,
model: "Gemini 3.6 Flash (Medium)", model: "Gemini 3.6 Flash (Medium)",
effort: "low", effort: "low",
}); });
ctx.ui.setStatus("agy-rec", ""); ctx.ui.setStatus("agy-rec", "");
const transcription = res.text.trim(); const finalText = res.text.trim() || transcript;
if (!transcription) {
ctx.ui.notify("Trascrizione vuota o errore", "error");
return;
}
// Inserisci il risultato come prompt su pi // Inserisci il risultato come prompt su pi
if (ctx.isIdle()) { if (ctx.isIdle()) {
pi.sendUserMessage(transcription); pi.sendUserMessage(finalText);
} else { } else {
pi.sendUserMessage(transcription, { deliverAs: "followUp" }); pi.sendUserMessage(finalText, { deliverAs: "followUp" });
} }
ctx.ui.notify("✅ Trascrizione inviata come prompt a pi", "info"); ctx.ui.notify("✅ Trascrizione inviata come prompt a pi", "info");
} }