Aggiunta registrazione microfono (F12) con trascrizione Gemini e inserimento come prompt su pi
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 948 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
+153
-1
@@ -10,7 +10,7 @@
|
||||
* L'agente (pi) decide quale strumento usare in base al task.
|
||||
*/
|
||||
|
||||
import { execFile } from "node:child_process";
|
||||
import { execFile, spawn } from "node:child_process";
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
@@ -224,6 +224,85 @@ async function executeAgy(opts: AgyExecOptions) {
|
||||
const IMG_SUFFIX =
|
||||
"\n\nAlla fine della risposta, scrivi su una riga esattamente: IMAGE_PATH: <percorso assoluto dell'immagine generata>";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Registrazione microfono (F12) e trascrizione
|
||||
// ---------------------------------------------------------------------------
|
||||
const MAX_RECORD_MS = 120_000; // 2 min
|
||||
|
||||
interface Recording {
|
||||
proc: ReturnType<typeof spawn>;
|
||||
file: string;
|
||||
}
|
||||
|
||||
let recording: Recording | null = null;
|
||||
|
||||
function startRecording(): string {
|
||||
const file = path.join(os.tmpdir(), `agy-rec-${Date.now()}.wav`);
|
||||
const proc = spawn(
|
||||
"ffmpeg",
|
||||
["-y", "-f", "pulse", "-i", "default", "-ac", "1", "-ar", "16000", "-t", "120", file],
|
||||
{ stdio: "ignore" },
|
||||
);
|
||||
recording = { proc, file };
|
||||
// se ffmpeg termina da solo (timeout 2 min), azzera lo stato
|
||||
proc.on("exit", () => {
|
||||
if (recording && recording.proc === proc) recording = null;
|
||||
});
|
||||
return file;
|
||||
}
|
||||
|
||||
function stopRecording(): Promise<string | null> {
|
||||
return new Promise((resolve) => {
|
||||
if (!recording) return resolve(null);
|
||||
const { proc, file } = recording;
|
||||
recording = null;
|
||||
let done = false;
|
||||
const finish = () => {
|
||||
if (!done) {
|
||||
done = true;
|
||||
resolve(file);
|
||||
}
|
||||
};
|
||||
proc.on("exit", finish);
|
||||
proc.kill("SIGINT"); // ffmpeg finalizza il file
|
||||
setTimeout(finish, 3000); // fallback
|
||||
});
|
||||
}
|
||||
|
||||
function extractText(content: any): string {
|
||||
if (typeof content === "string") return content;
|
||||
if (Array.isArray(content)) {
|
||||
return content
|
||||
.filter((p: any) => p && p.type === "text" && typeof p.text === "string")
|
||||
.map((p: any) => p.text)
|
||||
.join(" ");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function getConversationContext(ctx: any, maxEntries = 8): string {
|
||||
try {
|
||||
const entries = ctx.sessionManager.getEntries();
|
||||
const recent = entries.slice(-maxEntries);
|
||||
const lines: string[] = [];
|
||||
for (const e of recent) {
|
||||
if (e.type !== "message" || !e.message) continue;
|
||||
const text = extractText(e.message.content);
|
||||
if (!text) continue;
|
||||
const who =
|
||||
e.message.role === "user"
|
||||
? "Utente"
|
||||
: e.message.role === "assistant"
|
||||
? "Assistente"
|
||||
: "Strumento";
|
||||
lines.push(`${who}: ${text}`);
|
||||
}
|
||||
return lines.join("\n");
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Estensione
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -772,4 +851,77 @@ export default function agyExtension(pi: ExtensionAPI) {
|
||||
ctx.ui.notify("Stato conversazione azzerato.", "info");
|
||||
},
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// Registrazione microfono (F12) + trascrizione via Gemini + prompt su pi
|
||||
// =========================================================================
|
||||
async function handleRecordToggle(ctx: any) {
|
||||
if (!recording) {
|
||||
startRecording();
|
||||
ctx.ui.notify("🎙️ Registrazione avviata (F12 per fermare, max 2 min)", "info");
|
||||
ctx.ui.setStatus("agy-rec", "🔴 REGISTRAZIONE...");
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.ui.setStatus("agy-rec", "⏹️ Finalizzazione...");
|
||||
const file = await stopRecording();
|
||||
if (!file) {
|
||||
ctx.ui.setStatus("agy-rec", "");
|
||||
ctx.ui.notify("Nessuna registrazione attiva", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.ui.notify("Registrazione fermata, trascrizione in corso...", "info");
|
||||
const context = getConversationContext(ctx);
|
||||
const res = await executeAgy({
|
||||
prompt:
|
||||
`Trascrivi e interpreta il file audio al percorso ${file}.` +
|
||||
`\n\nContesto della conversazione:\n${context || "(nessuno)"}` +
|
||||
`\n\nRestituisci la trascrizione fedele e una breve interpretazione.`,
|
||||
mode: "analyze",
|
||||
stateless: true,
|
||||
filePaths: [file],
|
||||
yolo: true,
|
||||
});
|
||||
ctx.ui.setStatus("agy-rec", "");
|
||||
|
||||
const transcription = res.text.trim();
|
||||
if (!transcription) {
|
||||
ctx.ui.notify("Trascrizione vuota o errore", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
// Inserisci il risultato come prompt su pi
|
||||
if (ctx.isIdle()) {
|
||||
pi.sendUserMessage(transcription);
|
||||
} else {
|
||||
pi.sendUserMessage(transcription, { deliverAs: "followUp" });
|
||||
}
|
||||
ctx.ui.notify("✅ Trascrizione inviata come prompt a pi", "info");
|
||||
}
|
||||
|
||||
pi.registerShortcut("f12", {
|
||||
description: "Avvia/ferma registrazione microfono (max 2 min) e trascrive via Gemini",
|
||||
handler: async (ctx) => {
|
||||
await handleRecordToggle(ctx);
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerCommand("agy:record", {
|
||||
description: "Avvia/ferma registrazione microfono (come F12)",
|
||||
handler: async (_args, ctx) => {
|
||||
await handleRecordToggle(ctx);
|
||||
},
|
||||
});
|
||||
|
||||
pi.registerCommand("agy:record:stop", {
|
||||
description: "Ferma la registrazione microfono in corso",
|
||||
handler: async (_args, ctx) => {
|
||||
if (!recording) {
|
||||
ctx.ui.notify("Nessuna registrazione in corso", "warning");
|
||||
return;
|
||||
}
|
||||
await handleRecordToggle(ctx);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user