Я строю систему голосового чата, используя мультимодальный WebSocket Gemini 2.0 в node.js для бэкэнда и отреагируйте на фронт. Google предоставляет пример репозитория здесь: https://github.com/google-gemini/multim ... eb-console.
Однако проблема в том, что ключ API является выставлен в подключении к WebSocket на фронте, который представляет собой риск безопасности. Чтобы смягчить это, я пытаюсь реализовать прокси -сервер в node.js, чтобы фронт общался с моим бэкэнд, и бэкэнд надежно подключается к API WebSocket Gemini. Попытка построить: < /p>
Frontend: отправляет голосовые данные (через WebSocket) на мой прокси -сервер node.js.
Backend (node.js): подключается к API Websocket Gemini с помощью API Websocket Gemini. Ключ API и реле данных /ответы на /с фронта. > Когда я отправляю голосовые данные, я не получаю никакого ответа от сервера gemini websocket.
Вот упрощенная версия моей бэкэнд -прокси -сокет реализации: < /p>
Бэкэнд -код < /h3>
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const cors = require('cors');
const WebSocket = require('ws');
const app = express();
app.use(cors());
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: '*',
methods: ['GET', 'POST'],
},
});
const GeminiKey = 'Your api key';
const model = `models/gemini-2.0-flash-exp`;
const GEMINI_API_URL =
'wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent';
const clientGeminiSockets = new Map();
const clientRetryCounts = new Map();
const createGeminiConnection = (socketId) => {
const geminiSocket = new WebSocket(`${GEMINI_API_URL}?key=${GeminiKey}`);
geminiSocket.onopen = () => {
console.log(`Connected to Gemini API for client: ${socketId}`);
const setupMessage = {
setup: {
generation_config: { response_modalities: ['AUDIO'] },
model: model,
},
};
geminiSocket.send(JSON.stringify(setupMessage));
// Reset retry count on successful connection
clientRetryCounts.set(socketId, 0);
};
geminiSocket.onmessage = (event) => {
try {
console.log(
`Received message from Gemini API for client ${socketId}:`,
event.data,
);
// Handle binary data
if (event.data instanceof Buffer) {
console.log(
`Sending Gemini Audio data to client ${socketId}: binary data`,
);
//Send binary data
io.to(socketId).emit('geminiAudio', event.data.toString('base64'));
} else {
const message = JSON.parse(event.data);
io.to(socketId).emit('message', message);
}
} catch (err) {
console.error('Error handling gemini message:', err);
io.to(socketId).emit(
'message',
JSON.stringify({ error: 'Error handling Gemini API message' }),
);
}
};
geminiSocket.onerror = (error) => {
console.error(`Gemini API WebSocket Error for client ${socketId}:`, error);
io.to(socketId).emit(
'message',
JSON.stringify({ error: `Gemini API WebSocket Error: ${error.message}` }),
);
handleReconnection(socketId, geminiSocket);
};
geminiSocket.onclose = (event) => {
console.log(
`Gemini API WebSocket closed for client ${socketId}:`,
event.code,
event.reason,
);
io.to(socketId).emit(
'message',
JSON.stringify({
error: `Gemini API WebSocket Closed ${event.code}, ${event.reason}`,
}),
);
clientGeminiSockets.delete(socketId);
clientRetryCounts.delete(socketId);
};
clientGeminiSockets.set(socketId, geminiSocket);
return geminiSocket;
};
const handleReconnection = (socketId, geminiSocket) => {
const retryCount = clientRetryCounts.get(socketId) || 0;
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000); // Maximum 30 sec
console.log(
`Attempting to reconnect to Gemini for client: ${socketId} in ${delay}ms`,
);
setTimeout(() => {
if (!geminiSocket || geminiSocket.readyState !== WebSocket.OPEN) {
console.log(`Reconnecting Gemini API for client ${socketId}`);
createGeminiConnection(socketId);
}
}, delay);
clientRetryCounts.set(socketId, retryCount + 1);
};
io.on('connection', (socket) => {
console.log(`Client connected: ${socket.id}`);
let geminiSocket = createGeminiConnection(socket.id);
socket.on('message', (formattedMessage) => {
try {
console.log(`Received message from client ${socket.id}:`);
if (geminiSocket && geminiSocket.readyState === WebSocket.OPEN) {
const messageToSend = {
realtimeInput: {
mediaChunks: [
{
mimeType: 'audio/pcm;rate=16000',
data: formattedMessage.mediaChunks[0].data,
},
],
},
};
console.log(`Sending data to gemini API for client ${socket.id}:`);
geminiSocket.send(JSON.stringify(messageToSend));
} else {
console.error(
`Gemini API Websocket is not open for client ${socket.id}`,
);
socket.emit(
'message',
JSON.stringify({ error: 'Gemini API Websocket is not open' }),
);
}
} catch (error) {
console.error(
`Error forwarding message to Gemini API for client ${socket.id}:`,
error,
);
socket.emit(
'message',
JSON.stringify({ error: `Error forwarding message ${error.message}` }),
);
}
});
socket.on('disconnect', () => {
console.log(`Client disconnected: ${socket.id}`);
const geminiSocket = clientGeminiSockets.get(socket.id);
if (geminiSocket) {
geminiSocket.close();
clientGeminiSockets.delete(socket.id);
clientRetryCounts.delete(socket.id);
console.log(`Gemini connection closed for client: ${socket.id}`);
} else {
console.log(`No gemini connection found for client: ${socket.id}`);
}
});
});
const PORT = 5000;
server.listen(PORT, () => {
console.log(`Proxy server running on port ${PORT}`);
});
< /code>
### код фронта < /p>
import { useEffect, useRef, useState } from 'react';
import io from 'socket.io-client';
export default function Home() {
const [isRecording, setIsRecording] = useState(false);
const [socket, setSocket] = useState(null);
const audioRef = useRef(null);
const [geminiAudio, setGeminiAudio] = useState(null);
const audioContextRef = useRef(null);
const scriptProcessorRef = useRef(null);
const [connectionError, setConnectionError] = useState(null);
const startRecording = async () => {
if (!socket) {
console.error('Socket is not connected.');
return;
}
if (connectionError) {
console.log('There is a connection error, cannot start recording');
return;
}
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
audioContextRef.current = new AudioContext({ sampleRate: 16000 }); // Create audio context with 16kHz
const source = audioContextRef.current.createMediaStreamSource(stream);
scriptProcessorRef.current =
audioContextRef.current.createScriptProcessor(4096, 1, 1); // Create script processor
source.connect(scriptProcessorRef.current);
scriptProcessorRef.current.connect(audioContextRef.current.destination);
scriptProcessorRef.current.onaudioprocess = (event) => {
if (socket && socket.connected) {
const pcmData = event.inputBuffer.getChannelData(0); // Get PCM data
console.log('PCM data', pcmData);
const base64Audio = arrayBufferToBase64(pcmData.buffer);
console.log('base64Audio data', base64Audio);
const formattedMessage = {
mediaChunks: [
{
mimeType: 'audio/pcm;rate=16000',
data: base64Audio,
},
],
};
console.log('Formatted message sent to backend:', formattedMessage);
socket.emit('message', formattedMessage);
} else {
console.error('Socket is not connected');
}
};
setIsRecording(true);
} catch (err) {
console.error('Error recoring audio', err);
}
};
const stopRecording = () => {
if (scriptProcessorRef.current) {
scriptProcessorRef.current.disconnect();
scriptProcessorRef.current.onaudioprocess = null;
audioContextRef.current.close();
setIsRecording(false);
}
};
function arrayBufferToBase64(buffer) {
let binary = '';
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes);
}
return btoa(binary);
}
useEffect(() => {
const newSocket = io('http://localhost:5000'); // Replace with your backend URL
newSocket.on('connect', () => {
console.log('connected to websocket server');
setConnectionError(null);
});
newSocket.on('message', (message) => {
console.log('Received message:', message);
try {
const parsedMessage = JSON.parse(message);
if (parsedMessage.error) {
setConnectionError(parsedMessage.error);
console.log('Setting connection error', parsedMessage.error);
}
} catch (error) {
console.log('Message is not in JSON format');
}
});
newSocket.on('geminiAudio', (audioData) => {
console.log('Received geminiAudio data from backend:', audioData);
setGeminiAudio(audioData);
});
setSocket(newSocket);
return () => {
newSocket.disconnect();
};
}, []);
useEffect(() => {
if (geminiAudio) {
const audio = audioRef.current;
const audioBlob = new Blob(
[Uint8Array.from(atob(geminiAudio), (c) => c.charCodeAt(0))],
{ type: 'audio/webm' },
);
console.log('Audio blob created', audioBlob);
const audioUrl = URL.createObjectURL(audioBlob);
console.log('Audio URL created', audioUrl);
audio.src = audioUrl;
audio.play();
}
}, [geminiAudio]);
return (
Live Voice Conversation
{connectionError && (
{connectionError}
)}
{isRecording ? 'Stop Recording' : 'Start Recording'}
Your browser does not support the audio element.
);
}
< /code>
Что я попробовал: < /h3>
Проверено подключение к WebSocket - WebSocket успешно подключается как к API Frontend и Gemini.
Отправленные отправленные сообщения - Утверждено, что Frontend правильно отправляет голосовые данные, а бэкэнд пересылает его в Gemini.
проверяется на ответы - добавлена console.log для печати любых входящих сообщений из WebSocket Gemini. Сообщения появляются в событии ошибки WebSocket. < /p>
Что я ожидал API.
Gemini должен обработать голосовой ввод и вернуть ответ, который мой бэкэнд отправит обратно на фронт.>
Подробнее здесь: https://stackoverflow.com/questions/793 ... e-js-and-r
Как надежно реализовать мультимодальный прокси -сервер Gemini 2.0 в node.js и реагировать? ⇐ Javascript
Форум по Javascript
1738109470
Anonymous
Я строю систему голосового чата, используя мультимодальный WebSocket Gemini 2.0 в node.js для бэкэнда и отреагируйте на фронт. Google предоставляет пример репозитория здесь: https://github.com/google-gemini/multimodal-live-api-web-console.
Однако проблема в том, что ключ API является выставлен в подключении к WebSocket на фронте, который представляет собой риск безопасности. Чтобы смягчить это, я пытаюсь реализовать прокси -сервер в node.js, чтобы фронт общался с моим бэкэнд, и бэкэнд надежно подключается к API WebSocket Gemini. Попытка построить: < /p>
Frontend: отправляет голосовые данные (через WebSocket) на мой прокси -сервер node.js.
Backend (node.js): подключается к API Websocket Gemini с помощью API Websocket Gemini. Ключ API и реле данных /ответы на /с фронта. > Когда я отправляю голосовые данные, я не получаю никакого ответа от сервера gemini websocket.
Вот упрощенная версия моей бэкэнд -прокси -сокет реализации: < /p>
Бэкэнд -код < /h3>
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const cors = require('cors');
const WebSocket = require('ws');
const app = express();
app.use(cors());
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: '*',
methods: ['GET', 'POST'],
},
});
const GeminiKey = 'Your api key';
const model = `models/gemini-2.0-flash-exp`;
const GEMINI_API_URL =
'wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContent';
const clientGeminiSockets = new Map();
const clientRetryCounts = new Map();
const createGeminiConnection = (socketId) => {
const geminiSocket = new WebSocket(`${GEMINI_API_URL}?key=${GeminiKey}`);
geminiSocket.onopen = () => {
console.log(`Connected to Gemini API for client: ${socketId}`);
const setupMessage = {
setup: {
generation_config: { response_modalities: ['AUDIO'] },
model: model,
},
};
geminiSocket.send(JSON.stringify(setupMessage));
// Reset retry count on successful connection
clientRetryCounts.set(socketId, 0);
};
geminiSocket.onmessage = (event) => {
try {
console.log(
`Received message from Gemini API for client ${socketId}:`,
event.data,
);
// Handle binary data
if (event.data instanceof Buffer) {
console.log(
`Sending Gemini Audio data to client ${socketId}: binary data`,
);
//Send binary data
io.to(socketId).emit('geminiAudio', event.data.toString('base64'));
} else {
const message = JSON.parse(event.data);
io.to(socketId).emit('message', message);
}
} catch (err) {
console.error('Error handling gemini message:', err);
io.to(socketId).emit(
'message',
JSON.stringify({ error: 'Error handling Gemini API message' }),
);
}
};
geminiSocket.onerror = (error) => {
console.error(`Gemini API WebSocket Error for client ${socketId}:`, error);
io.to(socketId).emit(
'message',
JSON.stringify({ error: `Gemini API WebSocket Error: ${error.message}` }),
);
handleReconnection(socketId, geminiSocket);
};
geminiSocket.onclose = (event) => {
console.log(
`Gemini API WebSocket closed for client ${socketId}:`,
event.code,
event.reason,
);
io.to(socketId).emit(
'message',
JSON.stringify({
error: `Gemini API WebSocket Closed ${event.code}, ${event.reason}`,
}),
);
clientGeminiSockets.delete(socketId);
clientRetryCounts.delete(socketId);
};
clientGeminiSockets.set(socketId, geminiSocket);
return geminiSocket;
};
const handleReconnection = (socketId, geminiSocket) => {
const retryCount = clientRetryCounts.get(socketId) || 0;
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000); // Maximum 30 sec
console.log(
`Attempting to reconnect to Gemini for client: ${socketId} in ${delay}ms`,
);
setTimeout(() => {
if (!geminiSocket || geminiSocket.readyState !== WebSocket.OPEN) {
console.log(`Reconnecting Gemini API for client ${socketId}`);
createGeminiConnection(socketId);
}
}, delay);
clientRetryCounts.set(socketId, retryCount + 1);
};
io.on('connection', (socket) => {
console.log(`Client connected: ${socket.id}`);
let geminiSocket = createGeminiConnection(socket.id);
socket.on('message', (formattedMessage) => {
try {
console.log(`Received message from client ${socket.id}:`);
if (geminiSocket && geminiSocket.readyState === WebSocket.OPEN) {
const messageToSend = {
realtimeInput: {
mediaChunks: [
{
mimeType: 'audio/pcm;rate=16000',
data: formattedMessage.mediaChunks[0].data,
},
],
},
};
console.log(`Sending data to gemini API for client ${socket.id}:`);
geminiSocket.send(JSON.stringify(messageToSend));
} else {
console.error(
`Gemini API Websocket is not open for client ${socket.id}`,
);
socket.emit(
'message',
JSON.stringify({ error: 'Gemini API Websocket is not open' }),
);
}
} catch (error) {
console.error(
`Error forwarding message to Gemini API for client ${socket.id}:`,
error,
);
socket.emit(
'message',
JSON.stringify({ error: `Error forwarding message ${error.message}` }),
);
}
});
socket.on('disconnect', () => {
console.log(`Client disconnected: ${socket.id}`);
const geminiSocket = clientGeminiSockets.get(socket.id);
if (geminiSocket) {
geminiSocket.close();
clientGeminiSockets.delete(socket.id);
clientRetryCounts.delete(socket.id);
console.log(`Gemini connection closed for client: ${socket.id}`);
} else {
console.log(`No gemini connection found for client: ${socket.id}`);
}
});
});
const PORT = 5000;
server.listen(PORT, () => {
console.log(`Proxy server running on port ${PORT}`);
});
< /code>
### код фронта < /p>
import { useEffect, useRef, useState } from 'react';
import io from 'socket.io-client';
export default function Home() {
const [isRecording, setIsRecording] = useState(false);
const [socket, setSocket] = useState(null);
const audioRef = useRef(null);
const [geminiAudio, setGeminiAudio] = useState(null);
const audioContextRef = useRef(null);
const scriptProcessorRef = useRef(null);
const [connectionError, setConnectionError] = useState(null);
const startRecording = async () => {
if (!socket) {
console.error('Socket is not connected.');
return;
}
if (connectionError) {
console.log('There is a connection error, cannot start recording');
return;
}
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
audioContextRef.current = new AudioContext({ sampleRate: 16000 }); // Create audio context with 16kHz
const source = audioContextRef.current.createMediaStreamSource(stream);
scriptProcessorRef.current =
audioContextRef.current.createScriptProcessor(4096, 1, 1); // Create script processor
source.connect(scriptProcessorRef.current);
scriptProcessorRef.current.connect(audioContextRef.current.destination);
scriptProcessorRef.current.onaudioprocess = (event) => {
if (socket && socket.connected) {
const pcmData = event.inputBuffer.getChannelData(0); // Get PCM data
console.log('PCM data', pcmData);
const base64Audio = arrayBufferToBase64(pcmData.buffer);
console.log('base64Audio data', base64Audio);
const formattedMessage = {
mediaChunks: [
{
mimeType: 'audio/pcm;rate=16000',
data: base64Audio,
},
],
};
console.log('Formatted message sent to backend:', formattedMessage);
socket.emit('message', formattedMessage);
} else {
console.error('Socket is not connected');
}
};
setIsRecording(true);
} catch (err) {
console.error('Error recoring audio', err);
}
};
const stopRecording = () => {
if (scriptProcessorRef.current) {
scriptProcessorRef.current.disconnect();
scriptProcessorRef.current.onaudioprocess = null;
audioContextRef.current.close();
setIsRecording(false);
}
};
function arrayBufferToBase64(buffer) {
let binary = '';
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
useEffect(() => {
const newSocket = io('http://localhost:5000'); // Replace with your backend URL
newSocket.on('connect', () => {
console.log('connected to websocket server');
setConnectionError(null);
});
newSocket.on('message', (message) => {
console.log('Received message:', message);
try {
const parsedMessage = JSON.parse(message);
if (parsedMessage.error) {
setConnectionError(parsedMessage.error);
console.log('Setting connection error', parsedMessage.error);
}
} catch (error) {
console.log('Message is not in JSON format');
}
});
newSocket.on('geminiAudio', (audioData) => {
console.log('Received geminiAudio data from backend:', audioData);
setGeminiAudio(audioData);
});
setSocket(newSocket);
return () => {
newSocket.disconnect();
};
}, []);
useEffect(() => {
if (geminiAudio) {
const audio = audioRef.current;
const audioBlob = new Blob(
[Uint8Array.from(atob(geminiAudio), (c) => c.charCodeAt(0))],
{ type: 'audio/webm' },
);
console.log('Audio blob created', audioBlob);
const audioUrl = URL.createObjectURL(audioBlob);
console.log('Audio URL created', audioUrl);
audio.src = audioUrl;
audio.play();
}
}, [geminiAudio]);
return (
Live Voice Conversation
{connectionError && (
{connectionError}
)}
{isRecording ? 'Stop Recording' : 'Start Recording'}
Your browser does not support the audio element.
);
}
< /code>
Что я попробовал: < /h3>
Проверено подключение к WebSocket - WebSocket успешно подключается как к API Frontend и Gemini.
Отправленные отправленные сообщения - Утверждено, что Frontend правильно отправляет голосовые данные, а бэкэнд пересылает его в Gemini.
проверяется на ответы - добавлена console.log для печати любых входящих сообщений из WebSocket Gemini. Сообщения появляются в событии ошибки WebSocket. < /p>
Что я ожидал API.
Gemini должен обработать голосовой ввод и вернуть ответ, который мой бэкэнд отправит обратно на фронт.>
Подробнее здесь: [url]https://stackoverflow.com/questions/79395464/how-to-securely-implement-gemini-2-0-multimodal-websocket-proxy-in-node-js-and-r[/url]
Ответить
1 сообщение
• Страница 1 из 1
Перейти
- Кемерово-IT
- ↳ Javascript
- ↳ C#
- ↳ JAVA
- ↳ Elasticsearch aggregation
- ↳ Python
- ↳ Php
- ↳ Android
- ↳ Html
- ↳ Jquery
- ↳ C++
- ↳ IOS
- ↳ CSS
- ↳ Excel
- ↳ Linux
- ↳ Apache
- ↳ MySql
- Детский мир
- Для души
- ↳ Музыкальные инструменты даром
- ↳ Печатная продукция даром
- Внешняя красота и здоровье
- ↳ Одежда и обувь для взрослых даром
- ↳ Товары для здоровья
- ↳ Физкультура и спорт
- Техника - даром!
- ↳ Автомобилистам
- ↳ Компьютерная техника
- ↳ Плиты: газовые и электрические
- ↳ Холодильники
- ↳ Стиральные машины
- ↳ Телевизоры
- ↳ Телефоны, смартфоны, плашеты
- ↳ Швейные машинки
- ↳ Прочая электроника и техника
- ↳ Фототехника
- Ремонт и интерьер
- ↳ Стройматериалы, инструмент
- ↳ Мебель и предметы интерьера даром
- ↳ Cантехника
- Другие темы
- ↳ Разное даром
- ↳ Давай меняться!
- ↳ Отдам\возьму за копеечку
- ↳ Работа и подработка в Кемерове
- ↳ Давай с тобой поговорим...
Мобильная версия