Merge branch 'master' of https://git.d-popov.com/popov/ai-kevin
This commit is contained in:
commit
2440c0286e
12
.env.demo
12
.env.demo
@ -6,7 +6,9 @@
|
|||||||
# SERVER_PORT_HTTP=8080
|
# SERVER_PORT_HTTP=8080
|
||||||
|
|
||||||
ENV_NAME=demo
|
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
|
# LLN_MODEL=qwen2
|
||||||
# LNN_API_URL=https://ollama.d-popov.com/api/generate
|
# 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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
|
GROQ_API_KEY=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
|
||||||
OPENAI_API_KEY=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
|
OPENAI_API_KEY=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
|
||||||
|
|
||||||
WS_URL=wss://tts.d-popov.com
|
# PUBLIC_WS_URL=wss://ws.tts.d-popov.com
|
||||||
PUBLIC_HOSTNAME=tts.d-popov.com
|
# PUBLIC_HOSTNAME=tts.d-popov.com
|
||||||
SERVER_PORT_HTTP=8080
|
# SERVER_PORT_HTTP=28080
|
||||||
SERVER_PORT_WS=8081
|
# SERVER_PORT_WS=28081
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,3 +16,4 @@ agent-mAId/output.wav
|
|||||||
agent-mAId/build/*
|
agent-mAId/build/*
|
||||||
agent-mAId/dist/main.exe
|
agent-mAId/dist/main.exe
|
||||||
agent-mAId/output.wav
|
agent-mAId/output.wav
|
||||||
|
.node-persist/storage/*
|
@ -90,5 +90,6 @@ RUN . /venv/bin/activate && pip install --upgrade pip && pip install -r agent-py
|
|||||||
|
|
||||||
# CMD ["npm", "start"]
|
# CMD ["npm", "start"]
|
||||||
|
|
||||||
CMD ["npm", "run", "start:demo"]
|
# CMD ["npm", "run", "start:demo"]
|
||||||
|
CMD ["npm", "run", "start:demo-chat"]
|
||||||
#CMD ["npm", "run", "start:tele"]
|
#CMD ["npm", "run", "start:tele"]
|
@ -1,3 +1,7 @@
|
|||||||
|
# .10: /mnt/apps/DEV/REPOS/git.d-popov.com/ai-kevin/
|
||||||
|
# run
|
||||||
|
export NODE_ENV=demo && npm run start:demo-chat
|
||||||
|
|
||||||
# build:
|
# build:
|
||||||
docker build -t kevin-ai .
|
docker build -t kevin-ai .
|
||||||
# start the project in container:
|
# start the project in container:
|
||||||
|
@ -1,25 +1,157 @@
|
|||||||
from flask import Flask, render_template, request, jsonify
|
from flask import Flask, render_template, request, jsonify
|
||||||
from solana.rpc.api import Client
|
from solana.rpc.async_api import AsyncClient
|
||||||
|
from solana.rpc.commitment import Confirmed
|
||||||
|
from solders.pubkey import Pubkey
|
||||||
|
from dexscreener import DexscreenerClient
|
||||||
|
import asyncio
|
||||||
|
from telegram import Bot
|
||||||
|
from telegram.constants import ParseMode
|
||||||
|
import datetime
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
solana_client = Client("https://api.mainnet-beta.solana.com")
|
|
||||||
|
# Use the production Solana RPC endpoint
|
||||||
|
solana_client = AsyncClient("https://api.mainnet-beta.solana.com")
|
||||||
|
dexscreener_client = DexscreenerClient()
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
DEVELOPER_CHAT_ID = "777826553"
|
||||||
|
FOLLOWED_WALLET = "9U7D916zuQ8qcL9kQZqkcroWhHGho5vD8VNekvztrutN"
|
||||||
|
YOUR_WALLET = "65nzyZXTLC81MthTo52a2gRJjqryTizWVqpK2fDKLye5"
|
||||||
|
TELEGRAM_BOT_TOKEN = "6805059978:AAHNJKuOeazMSJHc3-BXRCsFfEVyFHeFnjw"
|
||||||
|
|
||||||
|
# Initialize Telegram Bot
|
||||||
|
bot = Bot(token=TELEGRAM_BOT_TOKEN)
|
||||||
|
|
||||||
|
# Token addresses (to be populated dynamically)
|
||||||
|
TOKEN_ADDRESSES = {}
|
||||||
|
|
||||||
|
async def get_token_balance(wallet_address, token_address):
|
||||||
|
try:
|
||||||
|
balance = await solana_client.get_token_account_balance(
|
||||||
|
Pubkey.from_string(token_address),
|
||||||
|
commitment=Confirmed
|
||||||
|
)
|
||||||
|
return float(balance['result']['value']['uiAmount'])
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting balance for {token_address}: {str(e)}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
async def send_telegram_message(message):
|
||||||
|
try:
|
||||||
|
await bot.send_message(chat_id=DEVELOPER_CHAT_ID, text=message, parse_mode=ParseMode.HTML)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error sending Telegram message: {str(e)}")
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
|
|
||||||
@app.route('/tokens', methods=['GET'])
|
@app.route('/tokens', methods=['GET'])
|
||||||
def get_tokens():
|
async def get_tokens():
|
||||||
# Here you would add logic to fetch new tokens or token data
|
balances = await get_wallet_balances(YOUR_WALLET)
|
||||||
return jsonify(['SOL', 'USDC']) # Example token list
|
return jsonify(list(balances.keys()))
|
||||||
|
|
||||||
@app.route('/swap', methods=['POST'])
|
@app.route('/balances', methods=['GET'])
|
||||||
def swap_tokens():
|
async def get_balances():
|
||||||
data = request.json
|
balances = await get_wallet_balances(YOUR_WALLET)
|
||||||
token_name = data['token_name']
|
return jsonify(balances)
|
||||||
amount = data['amount']
|
|
||||||
# Here you would add logic to perform the token swap
|
@app.route('/follow_move', methods=['POST'])
|
||||||
return jsonify({'status': 'success', 'message': f'Swapped {amount} of {token_name}'})
|
async def follow_move():
|
||||||
|
move = request.json
|
||||||
|
followed_balances = await get_wallet_balances(FOLLOWED_WALLET)
|
||||||
|
your_balances = await get_wallet_balances(YOUR_WALLET)
|
||||||
|
|
||||||
|
if move['token'] not in followed_balances or move['token'] not in your_balances:
|
||||||
|
return jsonify({'status': 'error', 'message': 'Invalid token'})
|
||||||
|
|
||||||
|
followed_balance = followed_balances[move['token']]
|
||||||
|
your_balance = your_balances[move['token']]
|
||||||
|
|
||||||
|
proportion = your_balance / followed_balance if followed_balance > 0 else 0
|
||||||
|
amount_to_swap = move['amount'] * proportion
|
||||||
|
|
||||||
|
if your_balance >= amount_to_swap:
|
||||||
|
# Implement actual swap logic here
|
||||||
|
pair = dexscreener_client.get_token_pair("solana", move['token'])
|
||||||
|
price = float(pair['priceUsd'])
|
||||||
|
received_amount = amount_to_swap * price
|
||||||
|
|
||||||
|
await send_telegram_message(
|
||||||
|
f"<b>Move Followed:</b>\n"
|
||||||
|
f"Swapped {amount_to_swap:.6f} {move['token']} "
|
||||||
|
f"for {received_amount:.6f} {move['to_token']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'success',
|
||||||
|
'message': f"Swapped {amount_to_swap:.6f} {move['token']} for {received_amount:.6f} {move['to_token']}"
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
await send_telegram_message(
|
||||||
|
f"<b>Move Failed:</b>\n"
|
||||||
|
f"Insufficient balance to swap {amount_to_swap:.6f} {move['token']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'error',
|
||||||
|
'message': f"Insufficient balance to swap {amount_to_swap:.6f} {move['token']}"
|
||||||
|
})
|
||||||
|
|
||||||
|
async def get_wallet_balances(wallet_address):
|
||||||
|
balances = {}
|
||||||
|
for token, address in TOKEN_ADDRESSES.items():
|
||||||
|
balances[token] = await get_token_balance(wallet_address, address)
|
||||||
|
return balances
|
||||||
|
|
||||||
|
async def get_non_zero_token_balances(wallet_address):
|
||||||
|
non_zero_balances = {}
|
||||||
|
for token, address in TOKEN_ADDRESSES.items():
|
||||||
|
balance = await get_token_balance(wallet_address, address)
|
||||||
|
if balance > 0:
|
||||||
|
non_zero_balances[token] = address
|
||||||
|
return non_zero_balances
|
||||||
|
|
||||||
|
async def list_initial_wallet_states():
|
||||||
|
global TOKEN_ADDRESSES
|
||||||
|
|
||||||
|
followed_wallet_balances = await get_wallet_balances(FOLLOWED_WALLET)
|
||||||
|
your_wallet_balances = await get_wallet_balances(YOUR_WALLET)
|
||||||
|
|
||||||
|
followed_non_zero = await get_non_zero_token_balances(FOLLOWED_WALLET)
|
||||||
|
your_non_zero = await get_non_zero_token_balances(YOUR_WALLET)
|
||||||
|
|
||||||
|
TOKEN_ADDRESSES = {**followed_non_zero, **your_non_zero}
|
||||||
|
|
||||||
|
followed_wallet_state = "\n".join([f"{token}: {amount:.6f}" for token, amount in followed_wallet_balances.items() if amount > 0])
|
||||||
|
your_wallet_state = "\n".join([f"{token}: {amount:.6f}" for token, amount in your_wallet_balances.items() if amount > 0])
|
||||||
|
|
||||||
|
message = (
|
||||||
|
"<b>Initial Wallet States (Non-zero balances):</b>\n\n"
|
||||||
|
f"<b>Followed Wallet ({FOLLOWED_WALLET}):</b>\n"
|
||||||
|
f"{followed_wallet_state}\n\n"
|
||||||
|
f"<b>Your Wallet ({YOUR_WALLET}):</b>\n"
|
||||||
|
f"{your_wallet_state}\n\n"
|
||||||
|
f"<b>Monitored Tokens:</b>\n"
|
||||||
|
f"{', '.join(TOKEN_ADDRESSES.keys())}"
|
||||||
|
)
|
||||||
|
|
||||||
|
await send_telegram_message(message)
|
||||||
|
|
||||||
|
async def send_startup_message():
|
||||||
|
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
message = (
|
||||||
|
f"<b>Solana Agent Application Started</b>\n\n"
|
||||||
|
f"Startup Time: {current_time}\n"
|
||||||
|
f"Followed Wallet: {FOLLOWED_WALLET}\n"
|
||||||
|
f"Your Wallet: {YOUR_WALLET}\n\n"
|
||||||
|
"The application is now running and ready to monitor wallet activities."
|
||||||
|
)
|
||||||
|
await send_telegram_message(message)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True)
|
asyncio.run(send_startup_message())
|
||||||
|
asyncio.run(list_initial_wallet_states())
|
||||||
|
print(f"Monitored Tokens: {', '.join(TOKEN_ADDRESSES.keys())}")
|
||||||
|
app.run(host='0.0.0.0', port=3009)
|
14
crypto/sol/readme.md
Normal file
14
crypto/sol/readme.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
`Conda activate trade`
|
||||||
|
To run this Python Solana agent:
|
||||||
|
|
||||||
|
Install the required libraries:
|
||||||
|
|
||||||
|
`pip install flask solana dexscreener python-telegram-bot`
|
||||||
|
|
||||||
|
Replace REPLACE_WITH_WALLET_ADDRESS with the wallet address you want to follow.
|
||||||
|
Replace REPLACE_WITH_YOUR_WALLET_ADDRESS with your own wallet address.
|
||||||
|
Save the code in a file, e.g., solana_agent.py.
|
||||||
|
|
||||||
|
Run the Flask application:
|
||||||
|
|
||||||
|
`python app.py`
|
@ -1,15 +1,22 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
node-app:
|
chat-server:
|
||||||
container_name: node-voice-chat
|
image: node:20-alpine
|
||||||
build:
|
container_name: ml-voice-chat-server
|
||||||
context: .
|
working_dir: /usr/src/app
|
||||||
dockerfile: web/deploy/demo.Dockerfile
|
|
||||||
ports:
|
|
||||||
- "8880:8080" # Exposes port 3000 on the host and maps it to port 3000 on the container
|
|
||||||
volumes:
|
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:
|
environment:
|
||||||
NODE_ENV: demo # Sets the environment variable NODE_ENV to development
|
NODE_ENV: demo
|
||||||
command: npm run start:demo-chat # Runs npm start when the container starts
|
#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
|
@ -15,30 +15,37 @@ const { PrismaClient } = require('@prisma/client');
|
|||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
// Load environment variables
|
// 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}`)
|
console.log(`loaded env file: ${process.env.NODE_ENV}`)
|
||||||
|
|
||||||
// Initialize services
|
// Initialize services
|
||||||
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
|
||||||
const groq = new Groq({ apiKey: process.env.GROQ_API_KEY });
|
const groq = new Groq({ apiKey: process.env.GROQ_API_KEY })
|
||||||
|
|
||||||
// Express setup
|
// Express setup
|
||||||
const app = express();
|
const app = express()
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json())
|
||||||
|
|
||||||
// Configuration constants
|
// Configuration constants
|
||||||
const PORT_HTTP = process.env.SERVER_PORT_HTTP || 3000;
|
const PORT_HTTP = process.env.SERVER_PORT_HTTP || 3000
|
||||||
const PORT_WS = process.env.SERVER_PORT_WS || 8080;
|
const PORT_WS = process.env.SERVER_PORT_WS || 8082
|
||||||
const TTS_API_URL = process.env.TTS_API_URL;
|
const TTS_API_URL = process.env.TTS_API_URL
|
||||||
const LNN_API_URL = process.env.LNN_API_URL;
|
const LNN_API_URL = process.env.LNN_API_URL
|
||||||
const LLN_MODEL = process.env.LLN_MODEL;
|
const LLN_MODEL = process.env.LLN_MODEL
|
||||||
|
console.log(`TTS API URL: ${TTS_API_URL}`)
|
||||||
|
|
||||||
let language = "en";
|
let language = 'en'
|
||||||
let storeRecordings = false;
|
let storeRecordings = false
|
||||||
let queueCounter = 0;
|
let queueCounter = 0
|
||||||
|
|
||||||
const sessions = new Map();
|
const sessions = new Map()
|
||||||
const chats = new Map(); // Store chat rooms
|
const chats = new Map() // Store chat rooms
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -70,58 +77,73 @@ app.post('/register', async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// // Initialize storage and load initial values
|
||||||
|
// 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 storedSessions = (await storage.getItem('sessions')) || []
|
||||||
|
// storedSessions.forEach(session => sessions.set(session.sessionId, session))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// initStorage()
|
||||||
|
|
||||||
// WebSocket Server
|
// WebSocket Server
|
||||||
const wss = new WebSocket.Server({ port: PORT_WS });
|
const wss = new WebSocket.Server({ port: PORT_WS })
|
||||||
wss.on('connection', ws => {
|
wss.on('connection', ws => {
|
||||||
ws.on('message', async message => handleMessage(ws, message));
|
ws.on('message', async message => handleMessage(ws, message))
|
||||||
ws.on('close', () => handleClose(ws));
|
ws.on('close', () => handleClose(ws))
|
||||||
});
|
})
|
||||||
|
|
||||||
// Handle WebSocket messages
|
// Handle WebSocket messages
|
||||||
async function handleMessage(ws, message) {
|
async function handleMessage (ws, message) {
|
||||||
let data;
|
let data
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(message);
|
data = JSON.parse(message)
|
||||||
} catch {
|
} catch {
|
||||||
return handleAudioData(ws, message);
|
return handleAudioData(ws, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case 'sessionId':
|
case 'sessionId':
|
||||||
await handleSessionId(ws);
|
await handleSessionId(ws)
|
||||||
break;
|
break
|
||||||
case 'join':
|
case 'join':
|
||||||
await handleJoin(ws, data);
|
await handleJoin(ws, data)
|
||||||
break;
|
break
|
||||||
case 'startChat':
|
case 'startChat':
|
||||||
await handleStartChat(ws, data);
|
await handleStartChat(ws, data)
|
||||||
break;
|
break
|
||||||
case 'enterChat':
|
case 'enterChat':
|
||||||
await handleEnterChat(ws, data);
|
await handleEnterChat(ws, data)
|
||||||
break;
|
break
|
||||||
case 'reconnect':
|
case 'reconnect':
|
||||||
await handleReconnect(ws, data);
|
await handleReconnect(ws, data)
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
console.log('Unknown message type:', data.type);
|
console.log('Unknown message type:', data.type)
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to handle message', err);
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to handle message', err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClose(ws) {
|
function handleClose (ws) {
|
||||||
sessions.delete(ws.sessionId);
|
sessions.delete(ws.sessionId)
|
||||||
broadcastUserList();
|
broadcastUserList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handlers for specific message types
|
// Handlers for specific message types
|
||||||
async function handleSessionId(ws) {
|
async function handleSessionId (ws) {
|
||||||
ws.sessionId = generateSessionId();
|
ws.sessionId = generateSessionId()
|
||||||
sessions.set(ws.sessionId, { language: 'en' });
|
sessions.set(ws.sessionId, { language: 'en' })
|
||||||
await storage.setItem('sessions', Array.from(sessions.values()));
|
await storage.setItem('sessions', Array.from(sessions.values()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modified handleJoin function
|
// Modified handleJoin function
|
||||||
@ -177,26 +199,28 @@ async function handleEnterChat(ws, { chatId }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleReconnect(ws, { sessionId }) {
|
async function handleReconnect (ws, { sessionId }) {
|
||||||
const userSession = sessions.get(sessionId);
|
const userSession = sessions.get(sessionId)
|
||||||
if (userSession) {
|
if (userSession) {
|
||||||
sessions.set(ws.sessionId, userSession);
|
sessions.set(ws.sessionId, userSession)
|
||||||
ws.sessionId = sessionId;
|
ws.sessionId = sessionId
|
||||||
const userChats = Array.from(chats.values()).filter(chat => chat.participants.includes(ws.sessionId));
|
const userChats = Array.from(chats.values()).filter(chat =>
|
||||||
ws.send(JSON.stringify({ type: 'chats', chats: userChats }));
|
chat.participants.includes(ws.sessionId)
|
||||||
} else {
|
)
|
||||||
console.log('Session not found:', sessionId);
|
ws.send(JSON.stringify({ type: 'chats', chats: userChats }))
|
||||||
}
|
} else {
|
||||||
broadcastUserList();
|
console.log('Session not found:', sessionId)
|
||||||
|
}
|
||||||
|
broadcastUserList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
function generateSessionId() {
|
function generateSessionId () {
|
||||||
return Math.random().toString(36).substring(2);
|
return Math.random().toString(36).substring(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateChatId() {
|
function generateChatId () {
|
||||||
return Math.random().toString(36).substring(2);
|
return Math.random().toString(36).substring(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function broadcastUserList() {
|
async function broadcastUserList() {
|
||||||
@ -229,220 +253,272 @@ function notifyParticipants(participants) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleAudioData(ws, data) {
|
async function handleAudioData (ws, data) {
|
||||||
const sessionData = sessions.get(ws.sessionId);
|
const sessionData = sessions.get(ws.sessionId)
|
||||||
let { language, task } = sessionData;
|
let { language, task } = sessionData
|
||||||
|
|
||||||
const formData = {
|
const formData = {
|
||||||
task: task || 'transcribe',
|
task: task || 'transcribe',
|
||||||
language,
|
language,
|
||||||
vad_filter: 'true',
|
vad_filter: 'true',
|
||||||
output: 'json',
|
output: 'json',
|
||||||
audio_file: {
|
audio_file: {
|
||||||
value: data,
|
value: data,
|
||||||
options: { filename: 'audio.ogg', contentType: 'audio/ogg' }
|
options: { filename: 'audio.ogg', contentType: 'audio/ogg' }
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!language || language === 'auto') {
|
|
||||||
await detectLanguage(ws, formData);
|
|
||||||
} else {
|
|
||||||
await transcribeAudio(ws, formData, sessionData);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!language || language === 'auto') {
|
||||||
|
await detectLanguage(ws, formData)
|
||||||
|
} else {
|
||||||
|
await transcribeAudio(ws, formData, sessionData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function detectLanguage(ws, formData) {
|
async function detectLanguage (ws, formData) {
|
||||||
try {
|
try {
|
||||||
const result = await requestPromise({
|
const result = await requestPromise({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: TTS_API_URL.replace('/asr', '/detect-language'),
|
url: TTS_API_URL.replace('/asr', '/detect-language'),
|
||||||
formData
|
formData
|
||||||
});
|
})
|
||||||
const { language_code } = JSON.parse(result);
|
const { language_code } = JSON.parse(result)
|
||||||
if (language_code) {
|
if (language_code) {
|
||||||
const sessionData = sessions.get(ws.sessionId);
|
const sessionData = sessions.get(ws.sessionId)
|
||||||
sessionData.language = language_code;
|
sessionData.language = language_code
|
||||||
ws.send(JSON.stringify({ type: 'languageDetected', languageDetected: language_code }));
|
ws.send(
|
||||||
await transcribeAudio(ws, formData, sessionData);
|
JSON.stringify({
|
||||||
}
|
type: 'languageDetected',
|
||||||
} catch (err) {
|
languageDetected: language_code
|
||||||
console.error('Language detection failed:', err);
|
})
|
||||||
|
)
|
||||||
|
await transcribeAudio(ws, formData, sessionData)
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Language detection failed:', err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function transcribeAudio(ws, formData, sessionData) {
|
async function transcribeAudio (ws, formData, sessionData) {
|
||||||
const start = new Date().getTime();
|
const start = new Date().getTime()
|
||||||
queueCounter++;
|
queueCounter++
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(sessionData.language) {
|
if (sessionData.language) {
|
||||||
formData.language = 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);
|
|
||||||
}
|
}
|
||||||
|
formData.vad_filter = 'true'
|
||||||
|
const body = await requestPromise({
|
||||||
|
method: 'POST',
|
||||||
|
url: TTS_API_URL,
|
||||||
|
formData
|
||||||
|
})
|
||||||
|
queueCounter--
|
||||||
|
|
||||||
if (storeRecordings) {
|
const duration = new Date().getTime() - start
|
||||||
const timestamp = Date.now();
|
ws.send(
|
||||||
fs.mkdir('rec', { recursive: true }, err => {
|
JSON.stringify({
|
||||||
if (err) console.error(err);
|
type: 'text',
|
||||||
else {
|
queueCounter,
|
||||||
fs.writeFile(`rec/audio${timestamp}.ogg`, formData.audio_file.value, err => {
|
duration,
|
||||||
if (err) console.error(err);
|
language: sessionData.language,
|
||||||
else console.log(`Audio data saved to rec/audio${timestamp}.ogg`);
|
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')
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
}
|
const participantSocket = Array.from(wss.clients).find(
|
||||||
}
|
client => client.sessionId === sessionId
|
||||||
|
)
|
||||||
async function handleChatTranscription(ws, body, sessionData) {
|
if (
|
||||||
if (sessionData.currentChat) {
|
participantSocket &&
|
||||||
const chat = chats.get(sessionData.currentChat);
|
participantSocket.readyState === WebSocket.OPEN
|
||||||
if (chat) {
|
) {
|
||||||
let msg = { sender: sessionData.username, text: body, translations: [] };
|
participantSocket.send(
|
||||||
chat.messages.push(msg);
|
JSON.stringify({
|
||||||
|
type: 'text',
|
||||||
for (let sessionId of chat.participants) {
|
text: `${sessionData.username}: ${body}`
|
||||||
if (sessionId !== ws.sessionId) {
|
})
|
||||||
const targetLang = sessions.get(sessionId)?.language || 'en';
|
)
|
||||||
if (targetLang !== sessionData.language) {
|
participantSocket.send(
|
||||||
const translation = await translateText(body, sessionData.language, targetLang);
|
JSON.stringify({
|
||||||
msg.translations.push({ language: targetLang, text: translation });
|
type: 'audio',
|
||||||
|
audio: formData.toString('base64')
|
||||||
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') }));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function translateText(originalText, originalLanguage, targetLanguage) {
|
async function translateText (originalText, originalLanguage, targetLanguage) {
|
||||||
const prompt = `Translate this text from ${originalLanguage} to ${targetLanguage}: ${originalText}`;
|
const prompt = `Translate this text from ${originalLanguage} to ${targetLanguage}: ${originalText}`
|
||||||
|
|
||||||
const response = await groq.chat.completions.create({
|
const response = await groq.chat.completions.create({
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "system",
|
role: 'system',
|
||||||
content: `You are translating voice transcriptions from '${originalLanguage}' to '${targetLanguage}'. Reply with just the translation.`,
|
content: `You are translating voice transcriptions from '${originalLanguage}' to '${targetLanguage}'. Reply with just the translation.`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: "user",
|
role: 'user',
|
||||||
content: originalText,
|
content: originalText
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
model: "llama3-8b-8192",
|
model: 'llama3-8b-8192'
|
||||||
});
|
})
|
||||||
|
|
||||||
return response.choices[0]?.message?.content || "";
|
return response.choices[0]?.message?.content || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateSpeech(text) {
|
async function generateSpeech (text) {
|
||||||
const mp3 = await openai.audio.speech.create({
|
const mp3 = await openai.audio.speech.create({
|
||||||
model: "tts-1",
|
model: 'tts-1',
|
||||||
voice: "alloy",
|
voice: 'alloy',
|
||||||
input: text,
|
input: text
|
||||||
});
|
})
|
||||||
return Buffer.from(await mp3.arrayBuffer());
|
return Buffer.from(await mp3.arrayBuffer())
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP Server
|
// HTTP Server
|
||||||
app.get('/', (req, res) => {
|
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) => {
|
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) => {
|
app.post('/log', (req, res) => {
|
||||||
console.log(`[LOG ${new Date().toISOString()}] ${req.body.message}`);
|
console.log(`[LOG ${new Date().toISOString()}] ${req.body.message}`)
|
||||||
res.status(200).send('OK');
|
res.status(200).send('OK')
|
||||||
});
|
})
|
||||||
|
|
||||||
app.get('/wsurl', (req, res) => {
|
app.get('/wsurl', (req, res) => {
|
||||||
if(process.env.PUBLIC_HOSTNAME){
|
// if(process.env.PUBLIC_HOSTNAME){
|
||||||
process.env.WS_URL = `wss://${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 );
|
console.log('Request for WS URL resolved with:', process.env.WS_URL)
|
||||||
res.status(200).send(process.env.WS_URL);
|
res.status(200).send(process.env.WS_URL)
|
||||||
});
|
})
|
||||||
|
|
||||||
app.get('/settings', async (req, res) => {
|
app.get('/settings', async (req, res) => {
|
||||||
if (req.query.language) {
|
if (req.query.language) {
|
||||||
language = req.query.language;
|
language = req.query.language
|
||||||
await storage.setItem('language', language);
|
await storage.setItem('language', language)
|
||||||
}
|
}
|
||||||
if (req.query.storeRecordings) {
|
if (req.query.storeRecordings) {
|
||||||
storeRecordings = req.query.storeRecordings;
|
storeRecordings = req.query.storeRecordings
|
||||||
await storage.setItem('storeRecordings', storeRecordings);
|
await storage.setItem('storeRecordings', storeRecordings)
|
||||||
}
|
}
|
||||||
res.status(200).send({ language, storeRecordings });
|
res.status(200).send({ language, storeRecordings })
|
||||||
});
|
})
|
||||||
|
|
||||||
app.post('/settings', async (req, res) => {
|
app.post('/settings', async (req, res) => {
|
||||||
const { sessionId, language, storeRecordings, task } = req.body;
|
const { sessionId, language, storeRecordings, task } = req.body
|
||||||
const sessionData = sessions.get(sessionId);
|
const sessionData = sessions.get(sessionId)
|
||||||
if (language) sessionData.language = language;
|
if (language) sessionData.language = language
|
||||||
if (storeRecordings !== undefined) sessionData.storeRecordings = storeRecordings;
|
if (storeRecordings !== undefined)
|
||||||
if (task) sessionData.task = task;
|
sessionData.storeRecordings = storeRecordings
|
||||||
res.status(200).send('OK');
|
if (task) sessionData.task = task
|
||||||
});
|
res.status(200).send('OK')
|
||||||
|
})
|
||||||
|
|
||||||
app.post('/upload', (req, res) => {
|
app.post('/upload', (req, res) => {
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now()
|
||||||
console.log('Received audio data:', timestamp);
|
console.log('Received audio data:', timestamp)
|
||||||
fs.mkdir('rec', { recursive: true }, err => {
|
fs.mkdir('rec', { recursive: true }, err => {
|
||||||
if (err) return res.status(500).send('ERROR');
|
if (err) return res.status(500).send('ERROR')
|
||||||
const file = fs.createWriteStream(`rec/audio_slice_${timestamp}.ogg`);
|
const file = fs.createWriteStream(`rec/audio_slice_${timestamp}.ogg`)
|
||||||
req.pipe(file);
|
req.pipe(file)
|
||||||
file.on('finish', () => res.status(200).send('OK'));
|
file.on('finish', () => res.status(200).send('OK'))
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
app.get('/chats', (req, res) => {
|
app.get('/chats', (req, res) => {
|
||||||
const { username } = req.query;
|
const { username } = req.query
|
||||||
const userChats = Array.from(chats.values()).filter(chat => chat.participants.includes(username));
|
const userChats = Array.from(chats.values()).filter(chat =>
|
||||||
res.status(200).send({ chats: userChats });
|
chat.participants.includes(username)
|
||||||
});
|
)
|
||||||
|
res.status(200).send({ chats: userChats })
|
||||||
|
})
|
||||||
|
|
||||||
app.listen(PORT_HTTP, () => {
|
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
|
// Helper to wrap request in a promise
|
||||||
function requestPromise(options) {
|
function requestPromise (options) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
request(options, (error, response, body) => {
|
request(options, (error, response, body) => {
|
||||||
if (error) return reject(error);
|
if (error) return reject(error)
|
||||||
resolve(body);
|
resolve(body)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ FROM node:20
|
|||||||
# Create and change to the app directory
|
# Create and change to the app directory
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
ENV NODE_ENV=demo
|
||||||
|
|
||||||
# Copy the package.json and package-lock.json files
|
# Copy the package.json and package-lock.json files
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
@ -13,8 +15,8 @@ RUN npm install
|
|||||||
# Copy the rest of the application code
|
# Copy the rest of the application code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Expose port 3000
|
|
||||||
EXPOSE 8880
|
EXPOSE 8880
|
||||||
|
EXPOSE 8881
|
||||||
|
|
||||||
# Start the application
|
# Start the application
|
||||||
CMD ["npm", "run", "start:demo-chat"]
|
CMD ["npm", "run", "start:demo-chat"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user