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.
|
* 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 fs from "node:fs";
|
||||||
import * as os from "node:os";
|
import * as os from "node:os";
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
@@ -224,6 +224,85 @@ async function executeAgy(opts: AgyExecOptions) {
|
|||||||
const IMG_SUFFIX =
|
const IMG_SUFFIX =
|
||||||
"\n\nAlla fine della risposta, scrivi su una riga esattamente: IMAGE_PATH: <percorso assoluto dell'immagine generata>";
|
"\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
|
// Estensione
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -772,4 +851,77 @@ export default function agyExtension(pi: ExtensionAPI) {
|
|||||||
ctx.ui.notify("Stato conversazione azzerato.", "info");
|
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