mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 18:00:15 +01:00
✨ feat: move to Socket.IO
This commit is contained in:
parent
7717d3a514
commit
9c0c341dee
9 changed files with 413 additions and 134 deletions
|
|
@ -98,13 +98,13 @@
|
||||||
"passport-ldapauth": "^3.0.1",
|
"passport-ldapauth": "^3.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"sharp": "^0.32.6",
|
"sharp": "^0.32.6",
|
||||||
|
"socket.io": "^4.8.1",
|
||||||
"tiktoken": "^1.0.15",
|
"tiktoken": "^1.0.15",
|
||||||
"traverse": "^0.6.7",
|
"traverse": "^0.6.7",
|
||||||
"ua-parser-js": "^1.0.36",
|
"ua-parser-js": "^1.0.36",
|
||||||
"winston": "^3.11.0",
|
"winston": "^3.11.0",
|
||||||
"winston-daily-rotate-file": "^4.7.1",
|
"winston-daily-rotate-file": "^4.7.1",
|
||||||
"wrtc": "^0.4.7",
|
"wrtc": "^0.4.7",
|
||||||
"ws": "^8.18.0",
|
|
||||||
"youtube-transcript": "^1.2.1",
|
"youtube-transcript": "^1.2.1",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ const { connectDb, indexSync } = require('~/lib/db');
|
||||||
const { isEnabled } = require('~/server/utils');
|
const { isEnabled } = require('~/server/utils');
|
||||||
const { ldapLogin } = require('~/strategies');
|
const { ldapLogin } = require('~/strategies');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
const { WebSocketService } = require('./services/WebSocket/WebSocketServer');
|
const { SocketIOService } = require('./services/WebSocket/WebSocketServer');
|
||||||
const validateImageRequest = require('./middleware/validateImageRequest');
|
const validateImageRequest = require('./middleware/validateImageRequest');
|
||||||
const errorController = require('./controllers/ErrorController');
|
const errorController = require('./controllers/ErrorController');
|
||||||
const configureSocialLogins = require('./socialLogins');
|
const configureSocialLogins = require('./socialLogins');
|
||||||
|
|
@ -48,7 +48,7 @@ const startServer = async () => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
new WebSocketService(server);
|
new SocketIOService(server);
|
||||||
|
|
||||||
await AppService(app);
|
await AppService(app);
|
||||||
|
|
||||||
|
|
@ -149,7 +149,7 @@ const startServer = async () => {
|
||||||
logger.info(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`);
|
logger.info(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`WebSocket endpoint: ws://${host}:${port}`);
|
logger.info(`Socket.IO endpoint: http://${host}:${port}`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,16 @@ const router = express.Router();
|
||||||
|
|
||||||
router.get('/', optionalJwtAuth, async (req, res) => {
|
router.get('/', optionalJwtAuth, async (req, res) => {
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
const useSSL = isProduction && process.env.SERVER_DOMAIN?.startsWith('https');
|
|
||||||
|
|
||||||
const protocol = useSSL ? 'wss' : 'ws';
|
const protocol = isProduction && req.secure ? 'https' : 'http';
|
||||||
|
|
||||||
const serverDomain = process.env.SERVER_DOMAIN
|
const serverDomain = process.env.SERVER_DOMAIN
|
||||||
? process.env.SERVER_DOMAIN.replace(/^https?:\/\//, '')
|
? process.env.SERVER_DOMAIN.replace(/^https?:\/\//, '')
|
||||||
: req.headers.host;
|
: req.headers.host;
|
||||||
const wsUrl = `${protocol}://${serverDomain}/ws`;
|
|
||||||
|
|
||||||
res.json({ url: wsUrl });
|
const socketIoUrl = `${protocol}://${serverDomain}`;
|
||||||
|
|
||||||
|
res.json({ url: socketIoUrl });
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
const { WebSocketServer } = require('ws');
|
const { Server } = require('socket.io');
|
||||||
const { RTCPeerConnection } = require('wrtc');
|
const { RTCPeerConnection } = require('wrtc');
|
||||||
|
|
||||||
module.exports.WebSocketService = class {
|
module.exports.SocketIOService = class {
|
||||||
constructor(server) {
|
constructor(httpServer) {
|
||||||
this.wss = new WebSocketServer({ server, path: '/ws' });
|
this.io = new Server(httpServer, { path: '/socket.io' });
|
||||||
this.log('Server initialized');
|
this.log('Socket.IO Server initialized');
|
||||||
this.activeClients = new Map();
|
this.activeClients = new Map();
|
||||||
this.iceServers = [
|
this.iceServers = [
|
||||||
{ urls: 'stun:stun.l.google.com:19302' },
|
{ urls: 'stun:stun.l.google.com:19302' },
|
||||||
|
|
@ -14,14 +14,14 @@ module.exports.WebSocketService = class {
|
||||||
}
|
}
|
||||||
|
|
||||||
log(msg) {
|
log(msg) {
|
||||||
console.log(`[WSS ${new Date().toISOString()}] ${msg}`);
|
console.log(`[Socket.IO ${new Date().toISOString()}] ${msg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupHandlers() {
|
setupHandlers() {
|
||||||
this.wss.on('connection', (ws) => {
|
this.io.on('connection', (socket) => {
|
||||||
const clientId = Date.now().toString();
|
const clientId = socket.id;
|
||||||
this.activeClients.set(clientId, {
|
this.activeClients.set(clientId, {
|
||||||
ws,
|
socket,
|
||||||
state: 'idle',
|
state: 'idle',
|
||||||
audioBuffer: [],
|
audioBuffer: [],
|
||||||
currentTranscription: '',
|
currentTranscription: '',
|
||||||
|
|
@ -30,44 +30,19 @@ module.exports.WebSocketService = class {
|
||||||
|
|
||||||
this.log(`Client connected: ${clientId}`);
|
this.log(`Client connected: ${clientId}`);
|
||||||
|
|
||||||
ws.on('message', async (raw) => {
|
socket.on('call-start', () => this.handleCallStart(clientId));
|
||||||
let message;
|
socket.on('audio-chunk', (data) => this.handleAudioChunk(clientId, data));
|
||||||
try {
|
socket.on('processing-start', () => this.processAudioStream(clientId));
|
||||||
message = JSON.parse(raw);
|
socket.on('audio-received', () => this.confirmAudioReceived(clientId));
|
||||||
} catch {
|
socket.on('call-ended', () => this.handleCallEnd(clientId));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (message.type) {
|
socket.on('disconnect', () => {
|
||||||
case 'call-start':
|
|
||||||
this.handleCallStart(clientId);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'audio-chunk':
|
|
||||||
await this.handleAudioChunk(clientId, message.data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'processing-start':
|
|
||||||
await this.processAudioStream(clientId);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'audio-received':
|
|
||||||
this.confirmAudioReceived(clientId);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'call-ended':
|
|
||||||
this.handleCallEnd(clientId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ws.on('close', () => {
|
|
||||||
this.handleCallEnd(clientId);
|
this.handleCallEnd(clientId);
|
||||||
this.activeClients.delete(clientId);
|
this.activeClients.delete(clientId);
|
||||||
this.log(`Client disconnected: ${clientId}`);
|
this.log(`Client disconnected: ${clientId}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on('error', (error) => {
|
socket.on('error', (error) => {
|
||||||
this.log(`Error for client ${clientId}: ${error.message}`);
|
this.log(`Error for client ${clientId}: ${error.message}`);
|
||||||
this.handleCallEnd(clientId);
|
this.handleCallEnd(clientId);
|
||||||
});
|
});
|
||||||
|
|
@ -104,12 +79,7 @@ module.exports.WebSocketService = class {
|
||||||
|
|
||||||
peerConnection.onicecandidate = (event) => {
|
peerConnection.onicecandidate = (event) => {
|
||||||
if (event.candidate) {
|
if (event.candidate) {
|
||||||
client.ws.send(
|
client.socket.emit('ice-candidate', { candidate: event.candidate });
|
||||||
JSON.stringify({
|
|
||||||
type: 'ice-candidate',
|
|
||||||
candidate: event.candidate,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -117,12 +87,7 @@ module.exports.WebSocketService = class {
|
||||||
try {
|
try {
|
||||||
const offer = await peerConnection.createOffer();
|
const offer = await peerConnection.createOffer();
|
||||||
await peerConnection.setLocalDescription(offer);
|
await peerConnection.setLocalDescription(offer);
|
||||||
client.ws.send(
|
client.socket.emit('webrtc-offer', { sdp: peerConnection.localDescription });
|
||||||
JSON.stringify({
|
|
||||||
type: 'webrtc-offer',
|
|
||||||
sdp: peerConnection.localDescription,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log(`Negotiation failed for ${clientId}: ${error}`);
|
this.log(`Negotiation failed for ${clientId}: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +107,7 @@ module.exports.WebSocketService = class {
|
||||||
}
|
}
|
||||||
|
|
||||||
client.audioBuffer.push(data);
|
client.audioBuffer.push(data);
|
||||||
client.ws.send(JSON.stringify({ type: 'audio-received' }));
|
client.socket.emit('audio-received');
|
||||||
}
|
}
|
||||||
|
|
||||||
async processAudioStream(clientId) {
|
async processAudioStream(clientId) {
|
||||||
|
|
@ -155,28 +120,13 @@ module.exports.WebSocketService = class {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Process transcription
|
// Process transcription
|
||||||
client.ws.send(
|
client.socket.emit('transcription', { data: 'Processing audio...' });
|
||||||
JSON.stringify({
|
|
||||||
type: 'transcription',
|
|
||||||
data: 'Processing audio...',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Stream LLM response
|
// Stream LLM response
|
||||||
client.ws.send(
|
client.socket.emit('llm-response', { data: 'Processing response...' });
|
||||||
JSON.stringify({
|
|
||||||
type: 'llm-response',
|
|
||||||
data: 'Processing response...',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Stream TTS chunks
|
// Stream TTS chunks
|
||||||
client.ws.send(
|
client.socket.emit('tts-chunk', { data: 'audio_data_here' });
|
||||||
JSON.stringify({
|
|
||||||
type: 'tts-chunk',
|
|
||||||
data: 'audio_data_here',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log(`Processing error for client ${clientId}: ${error.message}`);
|
this.log(`Processing error for client ${clientId}: ${error.message}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -191,12 +141,7 @@ module.exports.WebSocketService = class {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.ws.send(
|
client.socket.emit('audio-received', { data: null });
|
||||||
JSON.stringify({
|
|
||||||
type: 'audio-received',
|
|
||||||
data: null,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCallEnd(clientId) {
|
handleCallEnd(clientId) {
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,7 @@
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"remark-supersub": "^1.0.0",
|
"remark-supersub": "^1.0.0",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
"sse.js": "^2.5.0",
|
"sse.js": "^2.5.0",
|
||||||
"tailwind-merge": "^1.9.1",
|
"tailwind-merge": "^1.9.1",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,51 @@
|
||||||
|
import React from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { Mic, Phone, PhoneOff } from 'lucide-react';
|
import { Phone, PhoneOff } from 'lucide-react';
|
||||||
import { OGDialog, OGDialogContent, Button } from '~/components';
|
import { OGDialog, OGDialogContent, Button } from '~/components';
|
||||||
import { useWebRTC, useWebSocket, useCall } from '~/hooks';
|
import { useWebSocket, useCall } from '~/hooks';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
export const Call: React.FC = () => {
|
export const Call: React.FC = () => {
|
||||||
const { isConnected } = useWebSocket();
|
const { isConnected, sendMessage } = useWebSocket();
|
||||||
const { isCalling, startCall, hangUp } = useCall();
|
const { isCalling, isProcessing, startCall, hangUp } = useCall();
|
||||||
|
|
||||||
const [open, setOpen] = useRecoilState(store.callDialogOpen(0));
|
const [open, setOpen] = useRecoilState(store.callDialogOpen(0));
|
||||||
|
|
||||||
|
const [eventLog, setEventLog] = React.useState<string[]>([]);
|
||||||
|
|
||||||
|
const logEvent = (message: string) => {
|
||||||
|
console.log(message);
|
||||||
|
setEventLog((prev) => [...prev, message]);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isConnected) {
|
||||||
|
logEvent('Connected to server.');
|
||||||
|
} else {
|
||||||
|
logEvent('Disconnected from server.');
|
||||||
|
}
|
||||||
|
}, [isConnected]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isCalling) {
|
||||||
|
logEvent('Call started.');
|
||||||
|
} else if (isProcessing) {
|
||||||
|
logEvent('Processing audio...');
|
||||||
|
} else {
|
||||||
|
logEvent('Call ended.');
|
||||||
|
}
|
||||||
|
}, [isCalling, isProcessing]);
|
||||||
|
|
||||||
|
const handleStartCall = () => {
|
||||||
|
logEvent('Attempting to start call...');
|
||||||
|
startCall();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleHangUp = () => {
|
||||||
|
logEvent('Attempting to hang up call...');
|
||||||
|
hangUp();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OGDialog open={open} onOpenChange={setOpen}>
|
<OGDialog open={open} onOpenChange={setOpen}>
|
||||||
<OGDialogContent className="w-96 p-8">
|
<OGDialogContent className="w-96 p-8">
|
||||||
|
|
@ -27,24 +63,34 @@ export const Call: React.FC = () => {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isCalling ? (
|
{isCalling ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={startCall}
|
onClick={handleHangUp}
|
||||||
|
className="flex items-center gap-2 rounded-full bg-red-500 px-6 py-3 text-white hover:bg-red-600"
|
||||||
|
>
|
||||||
|
<PhoneOff size={20} />
|
||||||
|
<span>End Call</span>
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
onClick={handleStartCall}
|
||||||
disabled={!isConnected}
|
disabled={!isConnected}
|
||||||
className="flex items-center gap-2 rounded-full bg-green-500 px-6 py-3 text-white hover:bg-green-600 disabled:opacity-50"
|
className="flex items-center gap-2 rounded-full bg-green-500 px-6 py-3 text-white hover:bg-green-600 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<Phone size={20} />
|
<Phone size={20} />
|
||||||
<span>Start Call</span>
|
<span>Start Call</span>
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
onClick={hangUp}
|
|
||||||
className="flex items-center gap-2 rounded-full bg-red-500 px-6 py-3 text-white hover:bg-red-600"
|
|
||||||
>
|
|
||||||
<PhoneOff size={20} />
|
|
||||||
<span>End Call</span>
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Debugging Information */}
|
||||||
|
<div className="mt-4 w-full rounded-md bg-gray-100 p-4 shadow-sm">
|
||||||
|
<h3 className="mb-2 text-lg font-medium">Event Log</h3>
|
||||||
|
<ul className="h-32 overflow-y-auto text-xs text-gray-600">
|
||||||
|
{eventLog.map((log, index) => (
|
||||||
|
<li key={index}>{log}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</OGDialogContent>
|
</OGDialogContent>
|
||||||
</OGDialog>
|
</OGDialog>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ const SILENCE_THRESHOLD = -50;
|
||||||
const SILENCE_DURATION = 1000;
|
const SILENCE_DURATION = 1000;
|
||||||
|
|
||||||
const useCall = () => {
|
const useCall = () => {
|
||||||
const { sendMessage: wsMessage } = useWebSocket();
|
const { sendMessage: wsMessage, isConnected } = useWebSocket();
|
||||||
const [isCalling, setIsCalling] = useState(false);
|
const [isCalling, setIsCalling] = useState(false);
|
||||||
const [isProcessing, setIsProcessing] = useState(false);
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
const audioContextRef = useRef<AudioContext | null>(null);
|
const audioContextRef = useRef<AudioContext | null>(null);
|
||||||
|
|
@ -23,9 +23,7 @@ const useCall = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/webm' });
|
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/webm' });
|
||||||
// Send audio through WebRTC data channel
|
|
||||||
webrtcServiceRef.current?.sendAudioChunk(audioBlob);
|
webrtcServiceRef.current?.sendAudioChunk(audioBlob);
|
||||||
// Signal processing start via WebSocket
|
|
||||||
wsMessage({ type: 'processing-start' });
|
wsMessage({ type: 'processing-start' });
|
||||||
|
|
||||||
audioChunksRef.current = [];
|
audioChunksRef.current = [];
|
||||||
|
|
@ -34,17 +32,18 @@ const useCall = () => {
|
||||||
|
|
||||||
const handleRTCMessage = useCallback((message: RTCMessage) => {
|
const handleRTCMessage = useCallback((message: RTCMessage) => {
|
||||||
if (message.type === 'audio-received') {
|
if (message.type === 'audio-received') {
|
||||||
// Backend confirmed audio receipt
|
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const startCall = useCallback(async () => {
|
const startCall = useCallback(async () => {
|
||||||
// Initialize WebRTC with message handler
|
if (!isConnected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
webrtcServiceRef.current = new WebRTCService(handleRTCMessage);
|
webrtcServiceRef.current = new WebRTCService(handleRTCMessage);
|
||||||
await webrtcServiceRef.current.initializeCall();
|
await webrtcServiceRef.current.initializeCall();
|
||||||
|
|
||||||
// Signal call start via WebSocket
|
|
||||||
wsMessage({ type: 'call-start' });
|
wsMessage({ type: 'call-start' });
|
||||||
|
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||||
|
|
@ -53,7 +52,6 @@ const useCall = () => {
|
||||||
analyserRef.current = audioContextRef.current.createAnalyser();
|
analyserRef.current = audioContextRef.current.createAnalyser();
|
||||||
source.connect(analyserRef.current);
|
source.connect(analyserRef.current);
|
||||||
|
|
||||||
// Start VAD monitoring
|
|
||||||
intervalRef.current = window.setInterval(() => {
|
intervalRef.current = window.setInterval(() => {
|
||||||
if (!analyserRef.current || !isCalling) {
|
if (!analyserRef.current || !isCalling) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -76,7 +74,7 @@ const useCall = () => {
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
setIsCalling(true);
|
setIsCalling(true);
|
||||||
}, [handleRTCMessage, wsMessage, sendAudioChunk]);
|
}, [handleRTCMessage, isConnected, wsMessage, sendAudioChunk, isCalling]);
|
||||||
|
|
||||||
const hangUp = useCallback(async () => {
|
const hangUp = useCallback(async () => {
|
||||||
if (intervalRef.current) {
|
if (intervalRef.current) {
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,58 @@
|
||||||
import { useEffect, useRef, useState, useCallback } from 'react';
|
import { useEffect, useRef, useState, useCallback } from 'react';
|
||||||
import { useGetWebsocketUrlQuery } from 'librechat-data-provider/react-query';
|
import { useGetWebsocketUrlQuery } from 'librechat-data-provider/react-query';
|
||||||
|
import { io, Socket } from 'socket.io-client';
|
||||||
import type { RTCMessage } from '~/common';
|
import type { RTCMessage } from '~/common';
|
||||||
|
|
||||||
const useWebSocket = () => {
|
const useWebSocket = () => {
|
||||||
const { data: data } = useGetWebsocketUrlQuery();
|
const { data } = useGetWebsocketUrlQuery();
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const wsRef = useRef<WebSocket | null>(null);
|
const socketRef = useRef<Socket | null>(null);
|
||||||
|
|
||||||
const connect = useCallback(() => {
|
const connect = useCallback(() => {
|
||||||
if (!data || !data.url) {
|
if (!data || !data.url) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wsRef.current = new WebSocket(data.url);
|
socketRef.current = io(data.url, { transports: ['websocket'] });
|
||||||
wsRef.current.onopen = () => setIsConnected(true);
|
|
||||||
wsRef.current.onclose = () => setIsConnected(false);
|
|
||||||
wsRef.current.onerror = (err) => console.error('WebSocket error:', err);
|
|
||||||
|
|
||||||
wsRef.current.onmessage = (event) => {
|
socketRef.current.on('connect', () => {
|
||||||
const msg: RTCMessage = JSON.parse(event.data);
|
setIsConnected(true);
|
||||||
switch (msg.type) {
|
});
|
||||||
case 'transcription':
|
|
||||||
|
socketRef.current.on('disconnect', () => {
|
||||||
|
setIsConnected(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
socketRef.current.on('error', (err) => {
|
||||||
|
console.error('Socket.IO error:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
socketRef.current.on('transcription', (msg: RTCMessage) => {
|
||||||
// TODO: Handle transcription update
|
// TODO: Handle transcription update
|
||||||
break;
|
});
|
||||||
case 'llm-response':
|
|
||||||
|
socketRef.current.on('llm-response', (msg: RTCMessage) => {
|
||||||
// TODO: Handle LLM streaming response
|
// TODO: Handle LLM streaming response
|
||||||
break;
|
});
|
||||||
case 'tts-chunk':
|
|
||||||
|
socketRef.current.on('tts-chunk', (msg: RTCMessage) => {
|
||||||
if (typeof msg.data === 'string') {
|
if (typeof msg.data === 'string') {
|
||||||
const audio = new Audio(`data:audio/mp3;base64,${msg.data}`);
|
const audio = new Audio(`data:audio/mp3;base64,${msg.data}`);
|
||||||
audio.play().catch(console.error);
|
audio.play().catch(console.error);
|
||||||
}
|
}
|
||||||
break;
|
});
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [data?.url]);
|
}, [data?.url]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
connect();
|
connect();
|
||||||
return () => wsRef.current?.close();
|
return () => {
|
||||||
|
socketRef.current?.disconnect();
|
||||||
|
};
|
||||||
}, [connect]);
|
}, [connect]);
|
||||||
|
|
||||||
const sendMessage = useCallback((message: Record<string, unknown>) => {
|
const sendMessage = useCallback((message: Record<string, unknown>) => {
|
||||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
if (socketRef.current?.connected) {
|
||||||
wsRef.current.send(JSON.stringify(message));
|
socketRef.current.emit('message', message);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
|
||||||
281
package-lock.json
generated
281
package-lock.json
generated
|
|
@ -114,13 +114,13 @@
|
||||||
"passport-ldapauth": "^3.0.1",
|
"passport-ldapauth": "^3.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"sharp": "^0.32.6",
|
"sharp": "^0.32.6",
|
||||||
|
"socket.io": "^4.8.1",
|
||||||
"tiktoken": "^1.0.15",
|
"tiktoken": "^1.0.15",
|
||||||
"traverse": "^0.6.7",
|
"traverse": "^0.6.7",
|
||||||
"ua-parser-js": "^1.0.36",
|
"ua-parser-js": "^1.0.36",
|
||||||
"winston": "^3.11.0",
|
"winston": "^3.11.0",
|
||||||
"winston-daily-rotate-file": "^4.7.1",
|
"winston-daily-rotate-file": "^4.7.1",
|
||||||
"wrtc": "^0.4.7",
|
"wrtc": "^0.4.7",
|
||||||
"ws": "^8.18.0",
|
|
||||||
"youtube-transcript": "^1.2.1",
|
"youtube-transcript": "^1.2.1",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
|
|
@ -1601,6 +1601,8 @@
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -1691,6 +1693,7 @@
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"remark-supersub": "^1.0.0",
|
"remark-supersub": "^1.0.0",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
"sse.js": "^2.5.0",
|
"sse.js": "^2.5.0",
|
||||||
"tailwind-merge": "^1.9.1",
|
"tailwind-merge": "^1.9.1",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
|
|
@ -15980,6 +15983,11 @@
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@socket.io/component-emitter": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
|
||||||
|
},
|
||||||
"node_modules/@stitches/core": {
|
"node_modules/@stitches/core": {
|
||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz",
|
||||||
|
|
@ -16364,6 +16372,14 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cors": {
|
||||||
|
"version": "2.8.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
|
||||||
|
"integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/debug": {
|
"node_modules/@types/debug": {
|
||||||
"version": "4.1.12",
|
"version": "4.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||||
|
|
@ -18032,6 +18048,14 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/base64id": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
|
||||||
|
"engines": {
|
||||||
|
"node": "^4.5.0 || >= 5.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/base64url": {
|
"node_modules/base64url": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
|
||||||
|
|
@ -20260,6 +20284,117 @@
|
||||||
"once": "^1.4.0"
|
"once": "^1.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/engine.io": {
|
||||||
|
"version": "6.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
|
||||||
|
"integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/cors": "^2.8.12",
|
||||||
|
"@types/node": ">=10.0.0",
|
||||||
|
"accepts": "~1.3.4",
|
||||||
|
"base64id": "2.0.0",
|
||||||
|
"cookie": "~0.7.2",
|
||||||
|
"cors": "~2.8.5",
|
||||||
|
"debug": "~4.3.1",
|
||||||
|
"engine.io-parser": "~5.2.1",
|
||||||
|
"ws": "~8.17.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-client": {
|
||||||
|
"version": "6.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
|
||||||
|
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.1",
|
||||||
|
"engine.io-parser": "~5.2.1",
|
||||||
|
"ws": "~8.17.1",
|
||||||
|
"xmlhttprequest-ssl": "~2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-client/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-client/node_modules/ws": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-parser": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io/node_modules/ws": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.17.1",
|
"version": "5.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
|
||||||
|
|
@ -34011,6 +34146,142 @@
|
||||||
"integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==",
|
"integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/socket.io": {
|
||||||
|
"version": "4.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
|
||||||
|
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
|
||||||
|
"dependencies": {
|
||||||
|
"accepts": "~1.3.4",
|
||||||
|
"base64id": "~2.0.0",
|
||||||
|
"cors": "~2.8.5",
|
||||||
|
"debug": "~4.3.2",
|
||||||
|
"engine.io": "~6.6.0",
|
||||||
|
"socket.io-adapter": "~2.5.2",
|
||||||
|
"socket.io-parser": "~4.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-adapter": {
|
||||||
|
"version": "2.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
|
||||||
|
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "~4.3.4",
|
||||||
|
"ws": "~8.17.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-adapter/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-adapter/node_modules/ws": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-client": {
|
||||||
|
"version": "4.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
|
||||||
|
"integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.2",
|
||||||
|
"engine.io-client": "~6.6.1",
|
||||||
|
"socket.io-parser": "~4.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-client/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-parser": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-parser/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/socks": {
|
"node_modules/socks": {
|
||||||
"version": "2.8.3",
|
"version": "2.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
|
||||||
|
|
@ -37575,6 +37846,14 @@
|
||||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||||
"devOptional": true
|
"devOptional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/xmlhttprequest-ssl": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/xtend": {
|
"node_modules/xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue