167 lines
6.0 KiB
JavaScript
167 lines
6.0 KiB
JavaScript
// import express from "express";
|
|
// import http from "http";
|
|
// import { Server } from "socket.io";
|
|
// import { GoogleGenAI, Modality } from "@google/genai";
|
|
// import path from "path";
|
|
// import { fileURLToPath } from "url";
|
|
|
|
// const __filename = fileURLToPath(import.meta.url);
|
|
// const __dirname = path.dirname(__filename);
|
|
|
|
// const app = express();
|
|
// const server = http.createServer(app);
|
|
// const io = new Server(server, {
|
|
// cors: {
|
|
// origin: "*",
|
|
// methods: ["GET", "POST"]
|
|
// }
|
|
// });
|
|
|
|
// app.use(express.static(path.join(__dirname, "public")));
|
|
|
|
// // ⚠️ کلید API خود را اینجا قرار دهید
|
|
// const ai = new GoogleGenAI({
|
|
// apiKey: "AIzaSyC9kWntTutd2jnUvY7IqMAozZAOeZ47QUE"
|
|
// });
|
|
|
|
// // مدل مناسب برای Gemini Live
|
|
// const model = "gemini-2.5-flash-native-audio-preview-09-2025";
|
|
|
|
// const config = {
|
|
// responseModalities: [Modality.AUDIO],
|
|
// systemInstruction: "You are a helpful voice assistant. Always respond with audio. You are called 'دستیار صوتی هوشمند دیدوان'. Respond concisely in the same language as the user."
|
|
// };
|
|
|
|
// io.on("connection", async (socket) => {
|
|
// console.log("✅ Client connected:", socket.id);
|
|
|
|
// let session;
|
|
// let audioChunkCount = 0;
|
|
// let receivedChunkCount = 0;
|
|
|
|
// try {
|
|
// // اتصال به Gemini Live API
|
|
// session = await ai.live.connect({
|
|
// model,
|
|
// config,
|
|
// callbacks: {
|
|
// onmessage: (msg) => {
|
|
// // Log complete message structure for debugging
|
|
// if (msg?.setupComplete) {
|
|
// console.log("📩 Gemini: Setup Complete");
|
|
// return;
|
|
// }
|
|
|
|
// if (msg?.toolCall || msg?.toolCallCancellation) {
|
|
// console.log("📩 Gemini: Tool call (ignoring)");
|
|
// return;
|
|
// }
|
|
|
|
// console.log("📩 Raw Gemini Message:", JSON.stringify(msg).substring(0, 500));
|
|
|
|
// // ✅ بررسی مسیر اول: msg.data (برای استریمهای مستقیم صوتی)
|
|
// if (msg?.data) {
|
|
// const base64Audio = msg.data;
|
|
// audioChunkCount++;
|
|
// console.log("🔊 [Route 1] Sending audio chunk #" + audioChunkCount + ": " + base64Audio.length + " chars");
|
|
// socket.emit("gemini_audio_chunk", base64Audio);
|
|
// return;
|
|
// }
|
|
|
|
// // ✅ بررسی مسیر دوم: msg.serverContent.modelTurn.parts
|
|
// if (msg?.serverContent?.modelTurn?.parts) {
|
|
// console.log("📦 Found serverContent.modelTurn.parts, checking for audio...");
|
|
// const parts = msg.serverContent.modelTurn.parts;
|
|
// for (const part of parts) {
|
|
// // Log part structure
|
|
// console.log(" Part keys:", Object.keys(part));
|
|
|
|
// if (part.text) {
|
|
// console.log(" 📝 Text part:", part.text);
|
|
// }
|
|
|
|
// if (part.inlineData) {
|
|
// console.log(" 💾 InlineData mimeType:", part.inlineData.mimeType);
|
|
// console.log(" 💾 InlineData size:", part.inlineData.data?.length || 0, "chars");
|
|
|
|
// if (part.inlineData.mimeType?.startsWith("audio/")) {
|
|
// const base64Audio = part.inlineData.data;
|
|
// const mimeType = part.inlineData.mimeType;
|
|
// audioChunkCount++;
|
|
// console.log("🔊 [Route 2] Sending audio chunk #" + audioChunkCount + ": " + base64Audio.length + " chars, mime: " + mimeType);
|
|
|
|
// // ارسال به کلاینت با اطلاعات mime
|
|
// socket.emit("gemini_audio_chunk", {
|
|
// data: base64Audio,
|
|
// mimeType: mimeType
|
|
// });
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// // ✅ بررسی پایان نوبت
|
|
// if (msg?.serverContent?.turnComplete) {
|
|
// console.log("✅ Gemini turn complete");
|
|
// }
|
|
// },
|
|
// onerror: (e) => {
|
|
// console.error("❌ Gemini Session Error:", e);
|
|
// socket.emit("error", "Gemini connection error");
|
|
// },
|
|
// onclose: (event) => {
|
|
// console.log("🔒 Gemini session closed:", event?.code, event?.reason);
|
|
// }
|
|
// }
|
|
// });
|
|
|
|
// console.log("✨ Gemini Session Connected for client:", socket.id);
|
|
|
|
// } catch (e) {
|
|
// console.error("🔥 Failed to connect to Gemini:", e);
|
|
// socket.emit("error", "Failed to connect to AI service");
|
|
// return;
|
|
// }
|
|
|
|
// // ✅ دریافت صدا از کلاینت فلاتر
|
|
// socket.on("audio_chunk", async (base64Chunk) => {
|
|
// if (!session) {
|
|
// console.error("⚠️ No active Gemini session");
|
|
// return;
|
|
// }
|
|
|
|
// receivedChunkCount++;
|
|
// console.log("📥 Received chunk #" + receivedChunkCount + " from client: " + base64Chunk.length + " chars");
|
|
|
|
// try {
|
|
// // ✅ فرمت صحیح برای ارسال به Gemini Live API
|
|
// await session.sendRealtimeInput([{
|
|
// mimeType: "audio/pcm;rate=16000",
|
|
// data: base64Chunk
|
|
// }]);
|
|
|
|
// console.log("✅ Sent chunk #" + receivedChunkCount + " to Gemini");
|
|
|
|
// // Gemini should auto-detect turn completion based on audio silence
|
|
|
|
// } catch (e) {
|
|
// console.error("❌ Error sending chunk to Gemini:", e.message, e.stack);
|
|
// }
|
|
// });
|
|
|
|
// socket.on("disconnect", () => {
|
|
// console.log("❌ Client disconnected: " + socket.id);
|
|
// console.log("📊 Stats - Received: " + receivedChunkCount + " chunks, Sent: " + audioChunkCount + " chunks");
|
|
// if (session) {
|
|
// session.close();
|
|
// session = null;
|
|
// }
|
|
// });
|
|
// });
|
|
|
|
// const PORT = 3001;
|
|
// server.listen(PORT, "0.0.0.0", () => {
|
|
// console.log(`🚀 Server running on port ${PORT}`);
|
|
// console.log(`🌐 Access from network: http://<YOUR_IP>:${PORT}`);
|
|
// });
|