worling demo setup
This commit is contained in:
parent
bab8528db1
commit
ffca907f39
12
.env.demo
12
.env.demo
@ -6,7 +6,9 @@
|
||||
# SERVER_PORT_HTTP=8080
|
||||
|
||||
ENV_NAME=demo
|
||||
TTS_API_URL=https://api.tts.d-popov.com/asr
|
||||
# TTS_API_URL=https://api.tts.d-popov.com/asr
|
||||
# TTS_API_URL=https://api.tts.d-popov.com/asr
|
||||
# TTS_BACKEND=http://192.168.0.11:9009/asr
|
||||
|
||||
# LLN_MODEL=qwen2
|
||||
# LNN_API_URL=https://ollama.d-popov.com/api/generate
|
||||
@ -17,7 +19,7 @@ LNN_API_URL=https://ollama.d-popov.com/api/generate
|
||||
GROQ_API_KEY=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
|
||||
OPENAI_API_KEY=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
|
||||
|
||||
PUBLIC_WS_URL=wss://ws.tts.d-popov.com
|
||||
PUBLIC_HOSTNAME=tts.d-popov.com
|
||||
SERVER_PORT_HTTP=28080
|
||||
SERVER_PORT_WS=28081
|
||||
# PUBLIC_WS_URL=wss://ws.tts.d-popov.com
|
||||
# PUBLIC_HOSTNAME=tts.d-popov.com
|
||||
# SERVER_PORT_HTTP=28080
|
||||
# SERVER_PORT_WS=28081
|
@ -1,16 +1,22 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
node-app:
|
||||
container_name: node-voice-chat
|
||||
build:
|
||||
context: .
|
||||
dockerfile: web/deploy/demo.Dockerfile
|
||||
ports:
|
||||
- "28880:8080"
|
||||
- "28881:8081"
|
||||
chat-server:
|
||||
image: node:20-alpine
|
||||
container_name: ml-voice-chat-server
|
||||
working_dir: /usr/src/app
|
||||
volumes:
|
||||
- .:/usr/src/app # Mounts the current directory to /usr/src/app in the container
|
||||
- /mnt/apps/DEV/REPOS/git.d-popov.com/ai-kevin:/usr/src/app
|
||||
command: >
|
||||
sh -c "npm install && node web/chat-server.js"
|
||||
environment:
|
||||
NODE_ENV: demo # Sets the environment variable NODE_ENV to development
|
||||
command: npm run start:demo-chat # Runs npm start when the container starts
|
||||
NODE_ENV: demo
|
||||
#TTS_BACKEND_URL: https://api.tts.d-popov.com/asr
|
||||
TTS_API_URL: http://192.168.0.11:9009/asr
|
||||
WS_URL: wss://ws.tts.d-popov.com
|
||||
SERVER_PORT_HTTP: 8080
|
||||
SERVER_PORT_WS: 8082
|
||||
ports:
|
||||
- 28080:8080
|
||||
- 28081:8082
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
- 8.8.4.4
|
@ -1,403 +1,479 @@
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const WebSocket = require('ws');
|
||||
const storage = require('node-persist');
|
||||
const request = require('request');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const dotenv = require('dotenv');
|
||||
const ollama = require('ollama');
|
||||
const axios = require('axios');
|
||||
const OpenAI = require('openai');
|
||||
const Groq = require('groq-sdk');
|
||||
const express = require('express')
|
||||
const bodyParser = require('body-parser')
|
||||
const WebSocket = require('ws')
|
||||
const storage = require('node-persist')
|
||||
const request = require('request')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const dotenv = require('dotenv')
|
||||
const ollama = require('ollama')
|
||||
const axios = require('axios')
|
||||
const OpenAI = require('openai')
|
||||
const Groq = require('groq-sdk')
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config({ path: `.env${process.env.NODE_ENV === 'development' ? '.development' :'.'+ process.env.NODE_ENV }` });
|
||||
dotenv.config({
|
||||
path: `.env${
|
||||
process.env.NODE_ENV === 'development'
|
||||
? '.development'
|
||||
: '.' + process.env.NODE_ENV
|
||||
}`
|
||||
})
|
||||
console.log(`loaded env file: ${process.env.NODE_ENV}`)
|
||||
|
||||
// Initialize services
|
||||
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
||||
const groq = new Groq({ apiKey: process.env.GROQ_API_KEY });
|
||||
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
|
||||
const groq = new Groq({ apiKey: process.env.GROQ_API_KEY })
|
||||
|
||||
// Express setup
|
||||
const app = express();
|
||||
app.use(bodyParser.json());
|
||||
const app = express()
|
||||
app.use(bodyParser.json())
|
||||
|
||||
// Configuration constants
|
||||
const PORT_HTTP = process.env.SERVER_PORT_HTTP || 3000;
|
||||
const PORT_WS = process.env.SERVER_PORT_WS || 8080;
|
||||
const TTS_API_URL = process.env.TTS_API_URL;
|
||||
const LNN_API_URL = process.env.LNN_API_URL;
|
||||
const LLN_MODEL = process.env.LLN_MODEL;
|
||||
const PORT_HTTP = process.env.SERVER_PORT_HTTP || 3000
|
||||
const PORT_WS = process.env.SERVER_PORT_WS || 8082
|
||||
const TTS_API_URL = process.env.TTS_API_URL
|
||||
const LNN_API_URL = process.env.LNN_API_URL
|
||||
const LLN_MODEL = process.env.LLN_MODEL
|
||||
console.log(`TTS API URL: ${TTS_API_URL}`)
|
||||
|
||||
let language = "en";
|
||||
let storeRecordings = false;
|
||||
let queueCounter = 0;
|
||||
let language = 'en'
|
||||
let storeRecordings = false
|
||||
let queueCounter = 0
|
||||
|
||||
const sessions = new Map();
|
||||
const chats = new Map(); // Store chat rooms
|
||||
const sessions = new Map()
|
||||
const chats = new Map() // Store chat rooms
|
||||
|
||||
// Initialize storage and load initial values
|
||||
async function initStorage() {
|
||||
await storage.init();
|
||||
language = await storage.getItem('language') || language;
|
||||
storeRecordings = await storage.getItem('storeRecordings') || storeRecordings;
|
||||
async function initStorage () {
|
||||
await storage.init()
|
||||
language = (await storage.getItem('language')) || language
|
||||
storeRecordings =
|
||||
(await storage.getItem('storeRecordings')) || storeRecordings
|
||||
|
||||
const storedChats = await storage.getItem('chats') || [];
|
||||
storedChats.forEach(chat => chats.set(chat.id, chat));
|
||||
const storedChats = (await storage.getItem('chats')) || []
|
||||
storedChats.forEach(chat => chats.set(chat.id, chat))
|
||||
|
||||
const storedSessions = await storage.getItem('sessions') || [];
|
||||
storedSessions.forEach(session => sessions.set(session.sessionId, session));
|
||||
const storedSessions = (await storage.getItem('sessions')) || []
|
||||
storedSessions.forEach(session => sessions.set(session.sessionId, session))
|
||||
}
|
||||
|
||||
initStorage();
|
||||
initStorage()
|
||||
|
||||
// WebSocket Server
|
||||
const wss = new WebSocket.Server({ port: PORT_WS });
|
||||
const wss = new WebSocket.Server({ port: PORT_WS })
|
||||
wss.on('connection', ws => {
|
||||
ws.on('message', async message => handleMessage(ws, message));
|
||||
ws.on('close', () => handleClose(ws));
|
||||
});
|
||||
ws.on('message', async message => handleMessage(ws, message))
|
||||
ws.on('close', () => handleClose(ws))
|
||||
})
|
||||
|
||||
// Handle WebSocket messages
|
||||
async function handleMessage(ws, message) {
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(message);
|
||||
} catch {
|
||||
return handleAudioData(ws, message);
|
||||
}
|
||||
async function handleMessage (ws, message) {
|
||||
let data
|
||||
try {
|
||||
data = JSON.parse(message)
|
||||
} catch {
|
||||
return handleAudioData(ws, message)
|
||||
}
|
||||
|
||||
try {
|
||||
switch (data.type) {
|
||||
case 'sessionId':
|
||||
await handleSessionId(ws);
|
||||
break;
|
||||
case 'join':
|
||||
await handleJoin(ws, data);
|
||||
break;
|
||||
case 'startChat':
|
||||
await handleStartChat(ws, data);
|
||||
break;
|
||||
case 'enterChat':
|
||||
await handleEnterChat(ws, data);
|
||||
break;
|
||||
case 'reconnect':
|
||||
await handleReconnect(ws, data);
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown message type:', data.type);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to handle message', err);
|
||||
try {
|
||||
switch (data.type) {
|
||||
case 'sessionId':
|
||||
await handleSessionId(ws)
|
||||
break
|
||||
case 'join':
|
||||
await handleJoin(ws, data)
|
||||
break
|
||||
case 'startChat':
|
||||
await handleStartChat(ws, data)
|
||||
break
|
||||
case 'enterChat':
|
||||
await handleEnterChat(ws, data)
|
||||
break
|
||||
case 'reconnect':
|
||||
await handleReconnect(ws, data)
|
||||
break
|
||||
default:
|
||||
console.log('Unknown message type:', data.type)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to handle message', err)
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose(ws) {
|
||||
sessions.delete(ws.sessionId);
|
||||
broadcastUserList();
|
||||
function handleClose (ws) {
|
||||
sessions.delete(ws.sessionId)
|
||||
broadcastUserList()
|
||||
}
|
||||
|
||||
// Handlers for specific message types
|
||||
async function handleSessionId(ws) {
|
||||
ws.sessionId = generateSessionId();
|
||||
sessions.set(ws.sessionId, { language: 'en' });
|
||||
await storage.setItem('sessions', Array.from(sessions.values()));
|
||||
async function handleSessionId (ws) {
|
||||
ws.sessionId = generateSessionId()
|
||||
sessions.set(ws.sessionId, { language: 'en' })
|
||||
await storage.setItem('sessions', Array.from(sessions.values()))
|
||||
}
|
||||
|
||||
async function handleJoin(ws, { username, language }) {
|
||||
sessions.set(ws.sessionId, { username, sessionId: ws.sessionId, language });
|
||||
ws.send(JSON.stringify({ type: 'sessionId', sessionId: ws.sessionId, language, storeRecordings }));
|
||||
async function handleJoin (ws, { username, language }) {
|
||||
sessions.set(ws.sessionId, { username, sessionId: ws.sessionId, language })
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'sessionId',
|
||||
sessionId: ws.sessionId,
|
||||
language,
|
||||
storeRecordings
|
||||
})
|
||||
)
|
||||
|
||||
const userChats = Array.from(chats.values()).filter(chat => chat.participants.includes(ws.sessionId));
|
||||
ws.send(JSON.stringify({ type: 'chats', chats: userChats }));
|
||||
const userChats = Array.from(chats.values()).filter(chat =>
|
||||
chat.participants.includes(ws.sessionId)
|
||||
)
|
||||
ws.send(JSON.stringify({ type: 'chats', chats: userChats }))
|
||||
|
||||
broadcastUserList();
|
||||
broadcastUserList()
|
||||
}
|
||||
|
||||
async function handleStartChat(ws, { users }) {
|
||||
const chatId = generateChatId();
|
||||
let participants = [ws.sessionId, ...users];
|
||||
participants = [...new Set(participants)];
|
||||
async function handleStartChat (ws, { users }) {
|
||||
const chatId = generateChatId()
|
||||
let participants = [ws.sessionId, ...users]
|
||||
participants = [...new Set(participants)]
|
||||
|
||||
chats.set(chatId, { participants, messages: [] });
|
||||
await storage.setItem('chats', Array.from(chats.values()));
|
||||
chats.set(chatId, { participants, messages: [] })
|
||||
await storage.setItem('chats', Array.from(chats.values()))
|
||||
|
||||
notifyParticipants(participants);
|
||||
broadcastUserList();
|
||||
notifyParticipants(participants)
|
||||
broadcastUserList()
|
||||
}
|
||||
|
||||
async function handleEnterChat(ws, { chatId }) {
|
||||
const enteredChat = chats.get(chatId);
|
||||
const currentSession = sessions.get(ws.sessionId);
|
||||
currentSession.currentChat = chatId;
|
||||
if (enteredChat && enteredChat.participants.includes(ws.sessionId)) {
|
||||
ws.send(JSON.stringify({ type: 'chat', chat: enteredChat }));
|
||||
}
|
||||
async function handleEnterChat (ws, { chatId }) {
|
||||
const enteredChat = chats.get(chatId)
|
||||
const currentSession = sessions.get(ws.sessionId)
|
||||
currentSession.currentChat = chatId
|
||||
if (enteredChat && enteredChat.participants.includes(ws.sessionId)) {
|
||||
ws.send(JSON.stringify({ type: 'chat', chat: enteredChat }))
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReconnect(ws, { sessionId }) {
|
||||
const userSession = sessions.get(sessionId);
|
||||
if (userSession) {
|
||||
sessions.set(ws.sessionId, userSession);
|
||||
ws.sessionId = sessionId;
|
||||
const userChats = Array.from(chats.values()).filter(chat => chat.participants.includes(ws.sessionId));
|
||||
ws.send(JSON.stringify({ type: 'chats', chats: userChats }));
|
||||
} else {
|
||||
console.log('Session not found:', sessionId);
|
||||
}
|
||||
broadcastUserList();
|
||||
async function handleReconnect (ws, { sessionId }) {
|
||||
const userSession = sessions.get(sessionId)
|
||||
if (userSession) {
|
||||
sessions.set(ws.sessionId, userSession)
|
||||
ws.sessionId = sessionId
|
||||
const userChats = Array.from(chats.values()).filter(chat =>
|
||||
chat.participants.includes(ws.sessionId)
|
||||
)
|
||||
ws.send(JSON.stringify({ type: 'chats', chats: userChats }))
|
||||
} else {
|
||||
console.log('Session not found:', sessionId)
|
||||
}
|
||||
broadcastUserList()
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
function generateSessionId() {
|
||||
return Math.random().toString(36).substring(2);
|
||||
function generateSessionId () {
|
||||
return Math.random().toString(36).substring(2)
|
||||
}
|
||||
|
||||
function generateChatId() {
|
||||
return Math.random().toString(36).substring(2);
|
||||
function generateChatId () {
|
||||
return Math.random().toString(36).substring(2)
|
||||
}
|
||||
|
||||
function broadcastUserList() {
|
||||
const userList = Array.from(sessions.values()).map(user => ({
|
||||
username: user.username,
|
||||
sessionId: user.sessionId,
|
||||
currentChat: user.currentChat,
|
||||
language: user.language
|
||||
}));
|
||||
function broadcastUserList () {
|
||||
const userList = Array.from(sessions.values()).map(user => ({
|
||||
username: user.username,
|
||||
sessionId: user.sessionId,
|
||||
currentChat: user.currentChat,
|
||||
language: user.language
|
||||
}))
|
||||
|
||||
wss.clients.forEach(client => {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(JSON.stringify({ type: 'userList', users: userList }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function notifyParticipants(participants) {
|
||||
participants.forEach(sessionId => {
|
||||
const participantSocket = Array.from(wss.clients).find(client => client.sessionId === sessionId);
|
||||
if (participantSocket && participantSocket.readyState === WebSocket.OPEN) {
|
||||
const userChats = Array.from(chats.entries())
|
||||
.filter(([id, chat]) => chat.participants.includes(sessionId))
|
||||
.map(([id, chat]) => ({ id, participants: chat.participants }));
|
||||
participantSocket.send(JSON.stringify({ type: 'chats', chats: userChats }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function handleAudioData(ws, data) {
|
||||
const sessionData = sessions.get(ws.sessionId);
|
||||
let { language, task } = sessionData;
|
||||
|
||||
const formData = {
|
||||
task: task || 'transcribe',
|
||||
language,
|
||||
vad_filter: 'true',
|
||||
output: 'json',
|
||||
audio_file: {
|
||||
value: data,
|
||||
options: { filename: 'audio.ogg', contentType: 'audio/ogg' }
|
||||
}
|
||||
};
|
||||
|
||||
if (!language || language === 'auto') {
|
||||
await detectLanguage(ws, formData);
|
||||
} else {
|
||||
await transcribeAudio(ws, formData, sessionData);
|
||||
wss.clients.forEach(client => {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(JSON.stringify({ type: 'userList', users: userList }))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function detectLanguage(ws, formData) {
|
||||
try {
|
||||
const result = await requestPromise({
|
||||
method: 'POST',
|
||||
url: TTS_API_URL.replace('/asr', '/detect-language'),
|
||||
formData
|
||||
});
|
||||
const { language_code } = JSON.parse(result);
|
||||
if (language_code) {
|
||||
const sessionData = sessions.get(ws.sessionId);
|
||||
sessionData.language = language_code;
|
||||
ws.send(JSON.stringify({ type: 'languageDetected', languageDetected: language_code }));
|
||||
await transcribeAudio(ws, formData, sessionData);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Language detection failed:', err);
|
||||
function notifyParticipants (participants) {
|
||||
participants.forEach(sessionId => {
|
||||
const participantSocket = Array.from(wss.clients).find(
|
||||
client => client.sessionId === sessionId
|
||||
)
|
||||
if (participantSocket && participantSocket.readyState === WebSocket.OPEN) {
|
||||
const userChats = Array.from(chats.entries())
|
||||
.filter(([id, chat]) => chat.participants.includes(sessionId))
|
||||
.map(([id, chat]) => ({ id, participants: chat.participants }))
|
||||
participantSocket.send(
|
||||
JSON.stringify({ type: 'chats', chats: userChats })
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function transcribeAudio(ws, formData, sessionData) {
|
||||
const start = new Date().getTime();
|
||||
queueCounter++;
|
||||
async function handleAudioData (ws, data) {
|
||||
const sessionData = sessions.get(ws.sessionId)
|
||||
let { language, task } = sessionData
|
||||
|
||||
try {
|
||||
if(sessionData.language) {
|
||||
formData.language = sessionData.language;
|
||||
}
|
||||
formData.vad_filter = 'true';
|
||||
const body = await requestPromise({ method: 'POST', url: TTS_API_URL, formData });
|
||||
queueCounter--;
|
||||
|
||||
const duration = new Date().getTime() - start;
|
||||
ws.send(JSON.stringify({
|
||||
type: 'text',
|
||||
queueCounter,
|
||||
duration,
|
||||
language: sessionData.language,
|
||||
text: body
|
||||
}));
|
||||
|
||||
await handleChatTranscription(ws, body, sessionData);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Transcription failed:', err);
|
||||
const formData = {
|
||||
task: task || 'transcribe',
|
||||
language,
|
||||
vad_filter: 'true',
|
||||
output: 'json',
|
||||
audio_file: {
|
||||
value: data,
|
||||
options: { filename: 'audio.ogg', contentType: 'audio/ogg' }
|
||||
}
|
||||
}
|
||||
|
||||
if (storeRecordings) {
|
||||
const timestamp = Date.now();
|
||||
fs.mkdir('rec', { recursive: true }, err => {
|
||||
if (err) console.error(err);
|
||||
else {
|
||||
fs.writeFile(`rec/audio${timestamp}.ogg`, formData.audio_file.value, err => {
|
||||
if (err) console.error(err);
|
||||
else console.log(`Audio data saved to rec/audio${timestamp}.ogg`);
|
||||
});
|
||||
if (!language || language === 'auto') {
|
||||
await detectLanguage(ws, formData)
|
||||
} else {
|
||||
await transcribeAudio(ws, formData, sessionData)
|
||||
}
|
||||
}
|
||||
|
||||
async function detectLanguage (ws, formData) {
|
||||
try {
|
||||
const result = await requestPromise({
|
||||
method: 'POST',
|
||||
url: TTS_API_URL.replace('/asr', '/detect-language'),
|
||||
formData
|
||||
})
|
||||
const { language_code } = JSON.parse(result)
|
||||
if (language_code) {
|
||||
const sessionData = sessions.get(ws.sessionId)
|
||||
sessionData.language = language_code
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'languageDetected',
|
||||
languageDetected: language_code
|
||||
})
|
||||
)
|
||||
await transcribeAudio(ws, formData, sessionData)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Language detection failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function transcribeAudio (ws, formData, sessionData) {
|
||||
const start = new Date().getTime()
|
||||
queueCounter++
|
||||
|
||||
try {
|
||||
if (sessionData.language) {
|
||||
formData.language = sessionData.language
|
||||
}
|
||||
formData.vad_filter = 'true'
|
||||
const body = await requestPromise({
|
||||
method: 'POST',
|
||||
url: TTS_API_URL,
|
||||
formData
|
||||
})
|
||||
queueCounter--
|
||||
|
||||
const duration = new Date().getTime() - start
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'text',
|
||||
queueCounter,
|
||||
duration,
|
||||
language: sessionData.language,
|
||||
text: body
|
||||
})
|
||||
)
|
||||
|
||||
await handleChatTranscription(ws, body, sessionData)
|
||||
} catch (err) {
|
||||
console.error('Transcription failed:', err)
|
||||
}
|
||||
|
||||
if (storeRecordings) {
|
||||
const timestamp = Date.now()
|
||||
fs.mkdir('rec', { recursive: true }, err => {
|
||||
if (err) console.error(err)
|
||||
else {
|
||||
fs.writeFile(
|
||||
`rec/audio${timestamp}.ogg`,
|
||||
formData.audio_file.value,
|
||||
err => {
|
||||
if (err) console.error(err)
|
||||
else console.log(`Audio data saved to rec/audio${timestamp}.ogg`)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function handleChatTranscription (ws, body, sessionData) {
|
||||
if (sessionData.currentChat) {
|
||||
const chat = chats.get(sessionData.currentChat)
|
||||
if (chat) {
|
||||
let msg = { sender: sessionData.username, text: body, translations: [] }
|
||||
chat.messages.push(msg)
|
||||
|
||||
for (let sessionId of chat.participants) {
|
||||
if (sessionId !== ws.sessionId) {
|
||||
const targetLang = sessions.get(sessionId)?.language || 'en'
|
||||
if (targetLang !== sessionData.language) {
|
||||
const translation = await translateText(
|
||||
body,
|
||||
sessionData.language,
|
||||
targetLang
|
||||
)
|
||||
msg.translations.push({ language: targetLang, text: translation })
|
||||
|
||||
const participantSocket = Array.from(wss.clients).find(
|
||||
client => client.sessionId === sessionId
|
||||
)
|
||||
if (
|
||||
participantSocket &&
|
||||
participantSocket.readyState === WebSocket.OPEN
|
||||
) {
|
||||
participantSocket.send(
|
||||
JSON.stringify({
|
||||
type: 'text',
|
||||
text: `${sessionData.username}: ${translation}`
|
||||
})
|
||||
)
|
||||
const audioBuffer = await generateSpeech(translation)
|
||||
participantSocket.send(
|
||||
JSON.stringify({
|
||||
type: 'audio',
|
||||
audio: audioBuffer.toString('base64')
|
||||
})
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleChatTranscription(ws, body, sessionData) {
|
||||
if (sessionData.currentChat) {
|
||||
const chat = chats.get(sessionData.currentChat);
|
||||
if (chat) {
|
||||
let msg = { sender: sessionData.username, text: body, translations: [] };
|
||||
chat.messages.push(msg);
|
||||
|
||||
for (let sessionId of chat.participants) {
|
||||
if (sessionId !== ws.sessionId) {
|
||||
const targetLang = sessions.get(sessionId)?.language || 'en';
|
||||
if (targetLang !== sessionData.language) {
|
||||
const translation = await translateText(body, sessionData.language, targetLang);
|
||||
msg.translations.push({ language: targetLang, text: translation });
|
||||
|
||||
const participantSocket = Array.from(wss.clients).find(client => client.sessionId === sessionId);
|
||||
if (participantSocket && participantSocket.readyState === WebSocket.OPEN) {
|
||||
participantSocket.send(JSON.stringify({ type: 'text', text: `${sessionData.username}: ${translation}` }));
|
||||
const audioBuffer = await generateSpeech(translation);
|
||||
participantSocket.send(JSON.stringify({ type: 'audio', audio: audioBuffer.toString('base64') }));
|
||||
}
|
||||
} else {
|
||||
const participantSocket = Array.from(wss.clients).find(client => client.sessionId === sessionId);
|
||||
if (participantSocket && participantSocket.readyState === WebSocket.OPEN) {
|
||||
participantSocket.send(JSON.stringify({ type: 'text', text: `${sessionData.username}: ${body}` }));
|
||||
participantSocket.send(JSON.stringify({ type: 'audio', audio: formData.toString('base64') }));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const participantSocket = Array.from(wss.clients).find(
|
||||
client => client.sessionId === sessionId
|
||||
)
|
||||
if (
|
||||
participantSocket &&
|
||||
participantSocket.readyState === WebSocket.OPEN
|
||||
) {
|
||||
participantSocket.send(
|
||||
JSON.stringify({
|
||||
type: 'text',
|
||||
text: `${sessionData.username}: ${body}`
|
||||
})
|
||||
)
|
||||
participantSocket.send(
|
||||
JSON.stringify({
|
||||
type: 'audio',
|
||||
audio: formData.toString('base64')
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function translateText(originalText, originalLanguage, targetLanguage) {
|
||||
const prompt = `Translate this text from ${originalLanguage} to ${targetLanguage}: ${originalText}`;
|
||||
async function translateText (originalText, originalLanguage, targetLanguage) {
|
||||
const prompt = `Translate this text from ${originalLanguage} to ${targetLanguage}: ${originalText}`
|
||||
|
||||
const response = await groq.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: `You are translating voice transcriptions from '${originalLanguage}' to '${targetLanguage}'. Reply with just the translation.`,
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: originalText,
|
||||
},
|
||||
],
|
||||
model: "llama3-8b-8192",
|
||||
});
|
||||
const response = await groq.chat.completions.create({
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `You are translating voice transcriptions from '${originalLanguage}' to '${targetLanguage}'. Reply with just the translation.`
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: originalText
|
||||
}
|
||||
],
|
||||
model: 'llama3-8b-8192'
|
||||
})
|
||||
|
||||
return response.choices[0]?.message?.content || "";
|
||||
return response.choices[0]?.message?.content || ''
|
||||
}
|
||||
|
||||
async function generateSpeech(text) {
|
||||
const mp3 = await openai.audio.speech.create({
|
||||
model: "tts-1",
|
||||
voice: "alloy",
|
||||
input: text,
|
||||
});
|
||||
return Buffer.from(await mp3.arrayBuffer());
|
||||
async function generateSpeech (text) {
|
||||
const mp3 = await openai.audio.speech.create({
|
||||
model: 'tts-1',
|
||||
voice: 'alloy',
|
||||
input: text
|
||||
})
|
||||
return Buffer.from(await mp3.arrayBuffer())
|
||||
}
|
||||
|
||||
// HTTP Server
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'chat-client.html'));
|
||||
});
|
||||
res.sendFile(path.join(__dirname, 'chat-client.html'))
|
||||
})
|
||||
|
||||
app.get('/audio.js', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'audio.js'));
|
||||
});
|
||||
res.sendFile(path.join(__dirname, 'audio.js'))
|
||||
})
|
||||
|
||||
app.post('/log', (req, res) => {
|
||||
console.log(`[LOG ${new Date().toISOString()}] ${req.body.message}`);
|
||||
res.status(200).send('OK');
|
||||
});
|
||||
console.log(`[LOG ${new Date().toISOString()}] ${req.body.message}`)
|
||||
res.status(200).send('OK')
|
||||
})
|
||||
|
||||
app.get('/wsurl', (req, res) => {
|
||||
// if(process.env.PUBLIC_HOSTNAME){
|
||||
// process.env.WS_URL = `wss://${process.env.PUBLIC_HOSTNAME}`
|
||||
// }
|
||||
console.log('Request for WS URL resolved with:', process.env.PUBLIC_WS_URL );
|
||||
res.status(200).send(process.env.PUBLIC_WS_URL);
|
||||
});
|
||||
// if(process.env.PUBLIC_HOSTNAME){
|
||||
// process.env.WS_URL = `wss://${process.env.PUBLIC_HOSTNAME}`
|
||||
// }
|
||||
console.log('Request for WS URL resolved with:', process.env.WS_URL)
|
||||
res.status(200).send(process.env.WS_URL)
|
||||
})
|
||||
|
||||
app.get('/settings', async (req, res) => {
|
||||
if (req.query.language) {
|
||||
language = req.query.language;
|
||||
await storage.setItem('language', language);
|
||||
}
|
||||
if (req.query.storeRecordings) {
|
||||
storeRecordings = req.query.storeRecordings;
|
||||
await storage.setItem('storeRecordings', storeRecordings);
|
||||
}
|
||||
res.status(200).send({ language, storeRecordings });
|
||||
});
|
||||
if (req.query.language) {
|
||||
language = req.query.language
|
||||
await storage.setItem('language', language)
|
||||
}
|
||||
if (req.query.storeRecordings) {
|
||||
storeRecordings = req.query.storeRecordings
|
||||
await storage.setItem('storeRecordings', storeRecordings)
|
||||
}
|
||||
res.status(200).send({ language, storeRecordings })
|
||||
})
|
||||
|
||||
app.post('/settings', async (req, res) => {
|
||||
const { sessionId, language, storeRecordings, task } = req.body;
|
||||
const sessionData = sessions.get(sessionId);
|
||||
if (language) sessionData.language = language;
|
||||
if (storeRecordings !== undefined) sessionData.storeRecordings = storeRecordings;
|
||||
if (task) sessionData.task = task;
|
||||
res.status(200).send('OK');
|
||||
});
|
||||
const { sessionId, language, storeRecordings, task } = req.body
|
||||
const sessionData = sessions.get(sessionId)
|
||||
if (language) sessionData.language = language
|
||||
if (storeRecordings !== undefined)
|
||||
sessionData.storeRecordings = storeRecordings
|
||||
if (task) sessionData.task = task
|
||||
res.status(200).send('OK')
|
||||
})
|
||||
|
||||
app.post('/upload', (req, res) => {
|
||||
const timestamp = Date.now();
|
||||
console.log('Received audio data:', timestamp);
|
||||
fs.mkdir('rec', { recursive: true }, err => {
|
||||
if (err) return res.status(500).send('ERROR');
|
||||
const file = fs.createWriteStream(`rec/audio_slice_${timestamp}.ogg`);
|
||||
req.pipe(file);
|
||||
file.on('finish', () => res.status(200).send('OK'));
|
||||
});
|
||||
});
|
||||
const timestamp = Date.now()
|
||||
console.log('Received audio data:', timestamp)
|
||||
fs.mkdir('rec', { recursive: true }, err => {
|
||||
if (err) return res.status(500).send('ERROR')
|
||||
const file = fs.createWriteStream(`rec/audio_slice_${timestamp}.ogg`)
|
||||
req.pipe(file)
|
||||
file.on('finish', () => res.status(200).send('OK'))
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/chats', (req, res) => {
|
||||
const { username } = req.query;
|
||||
const userChats = Array.from(chats.values()).filter(chat => chat.participants.includes(username));
|
||||
res.status(200).send({ chats: userChats });
|
||||
});
|
||||
const { username } = req.query
|
||||
const userChats = Array.from(chats.values()).filter(chat =>
|
||||
chat.participants.includes(username)
|
||||
)
|
||||
res.status(200).send({ chats: userChats })
|
||||
})
|
||||
|
||||
app.listen(PORT_HTTP, () => {
|
||||
console.log(`Server listening on port ${PORT_HTTP}`);
|
||||
});
|
||||
console.log(`Server listening on port ${PORT_HTTP}`)
|
||||
console.log(process.env.TTS_BACKEND_URL)
|
||||
})
|
||||
|
||||
// Helper to wrap request in a promise
|
||||
function requestPromise(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(options, (error, response, body) => {
|
||||
if (error) return reject(error);
|
||||
resolve(body);
|
||||
});
|
||||
});
|
||||
function requestPromise (options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(options, (error, response, body) => {
|
||||
if (error) return reject(error)
|
||||
resolve(body)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user