diff --git a/client/.env.examle b/client/.env.examle
index f7ce09c..53698d0 100644
--- a/client/.env.examle
+++ b/client/.env.examle
@@ -7,9 +7,6 @@ PORT=3000
# Server URL
REACT_APP_SERVER_URL=https://localhost:5000
-# WebSocket URL
-REACT_APP_WS_URL=wss://localhost:5001/ws
-
# SSL Certificate and Key (if needed for local development)
-SSL_CRT_FILE=/etc/ssl/certs/localhost.pem
-SSL_KEY_FILE=/etc/ssl/private/localhost-key.pem
+# SSL_CRT_FILE=/etc/ssl/certs/localhost.pem
+# SSL_KEY_FILE=/etc/ssl/private/localhost-key.pem
diff --git a/client/Dockerfile b/client/Dockerfile
index 51f8475..eefd65a 100644
--- a/client/Dockerfile
+++ b/client/Dockerfile
@@ -16,5 +16,5 @@ COPY .env .env
EXPOSE 3000
-
-CMD ["serve", "-s", "build", "-l", "3000", "--ssl-cert", "/etc/ssl/certs/localhost.pem", "--ssl-key", "/etc/ssl/private/localhost-key.pem"]
+# CMD ["serve", "-s", "build", "-l", "3000", "--ssl-cert", "/etc/ssl/certs/localhost.pem", "--ssl-key", "/etc/ssl/private/localhost-key.pem"]
+CMD ["serve", "-s", "build", "-l", "3000"]
\ No newline at end of file
diff --git a/client/src/App.js b/client/src/App.js
index 4f8228d..c5e9a7a 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -4,89 +4,65 @@ import Chat from './components/Chat';
import Navbar from './components/Navbar';
import './App.css';
-const WSS_URL = process.env.REACT_APP_WSS_URL || `wss://${window.location.hostname}:5002`;
-const WS_URL = process.env.REACT_APP_WS_URL || `ws://${window.location.hostname}:5001`;
+const SERVER_URL = process.env.REACT_APP_SERVER_URL || 'https://localhost:5000';
-function App() {
- const [socketUrl, setSocketUrl] = useState(WSS_URL);
+const App = () => {
+ const [useWebSocketProtocol, setUseWebSocketProtocol] = useState(true);
+ const [socketUrl, setSocketUrl] = useState(`${SERVER_URL.replace(/^http/, 'ws')}`);
const [sessionId, setSessionId] = useState(localStorage.getItem('sessionId') || null);
const [messages, setMessages] = useState(() => {
const savedMessages = localStorage.getItem('chatHistory');
return savedMessages ? JSON.parse(savedMessages) : [{ role: 'system', content: 'You are Devabot ✨, a funny helpful assistant.' }];
});
const [isConnected, setIsConnected] = useState(false);
- const [fallbackAttempted, setFallbackAttempted] = useState(false);
+
+ const {
+ sendMessage: sendWebSocketMessage,
+ lastMessage,
+ readyState,
+ } = useWebSocket(socketUrl, {
+ onOpen: () => setIsConnected(true),
+ onClose: () => setIsConnected(false),
+ onError: (error) => console.error('WebSocket error:', error),
+ onMessage: (message) => handleMessage(message),
+ shouldReconnect: () => true,
+ reconnectAttempts: 20,
+ reconnectInterval: 3000,
+ });
const validateOrCreateSession = useCallback(() => {
if (sessionId) {
console.log('Validating existing session:', sessionId);
- sendMessage(JSON.stringify({ action: 'validateSession', data: { sessionId: sessionId } }));
+ sendWebSocketMessage(JSON.stringify({ action: 'validateSession', data: { sessionId } }));
} else {
console.log('Creating new session');
- sendMessage(JSON.stringify({ action: 'createSession' }));
+ sendWebSocketMessage(JSON.stringify({ action: 'createSession' }));
}
- }, [sessionId]);
-
- const handleOpen = useCallback(() => {
- console.log('WebSocket connected');
- setIsConnected(true);
- });
-
- const handleClose = useCallback(() => {
- console.log('WebSocket disconnected');
- setIsConnected(false);
- if (!fallbackAttempted) {
- console.log('Attempting fallback to WS');
- setSocketUrl(WS_URL);
- setFallbackAttempted(true);
- }
- }, [fallbackAttempted]);
-
- const handleError = useCallback(
- (error) => {
- console.error('WebSocket error:', error);
- if (!fallbackAttempted) {
- console.log('Error occurred, attempting fallback to WS');
- setSocketUrl(WS_URL);
- setFallbackAttempted(true);
- }
- },
- [fallbackAttempted]
- );
+ }, [sessionId, sendWebSocketMessage]);
const handleMessage = useCallback(
(message) => {
try {
const parsedData = JSON.parse(message.data);
console.log('Received WebSocket message:', parsedData);
-
+
switch (parsedData.action) {
case 'sessionValidated':
- if (parsedData.valid) {
- console.log('Session validated:', parsedData.sessionId);
- } else {
- console.log('Session invalid, creating new session');
- }
- // Always update the sessionId, whether it's validated or new
setSessionId(parsedData.sessionId);
localStorage.setItem('sessionId', parsedData.sessionId);
- break;
+ break;
case 'assistantMessage':
- const returned_message = parsedData.assistant_message;
- const newMessage = { role: 'assistant', content: returned_message };
- setMessages((prevMessages) => {
- const updatedMessages = [...prevMessages, newMessage];
- return updatedMessages;
- });
+ const returnedMessage = parsedData.assistant_message;
+ const newMessage = { role: 'assistant', content: returnedMessage };
+ setMessages((prevMessages) => [...prevMessages, newMessage]);
break;
-
+
case 'chatHistory':
- console.log('Received chat history');
setMessages(parsedData.messages);
localStorage.setItem('chatHistory', JSON.stringify(parsedData.messages));
break;
-
+
default:
console.log('Unknown WebSocket message received:', parsedData);
break;
@@ -94,91 +70,109 @@ function App() {
} catch (error) {
console.error('Error parsing WebSocket message:', error);
}
- },
- [sessionId, setSessionId, setMessages]
+ }, [sessionId]
);
- const { sendMessage, lastMessage, readyState, reconnect } = useWebSocket(socketUrl, {
- onOpen: handleOpen,
- onClose: handleClose,
- onError: handleError,
- onMessage: handleMessage,
- shouldReconnect: (closeEvent) => !fallbackAttempted,
- reconnectAttempts: 20,
- reconnectInterval: 3000,
- });
-
const clearHistory = useCallback(() => {
localStorage.removeItem('chatHistory');
setMessages([{ role: 'system', content: 'You are Devabot ✨, a funny helpful assistant.' }]);
-
+
if (isConnected) {
- sendMessage(JSON.stringify({ action: 'deleteSession', data: { sessionId: sessionId } }));
+ sendWebSocketMessage(JSON.stringify({ action: 'deleteSession', data: { sessionId } }));
localStorage.removeItem('sessionId');
setSessionId(null);
- // Instead of immediately creating a new session, we'll wait for the next connection
}
- }, [isConnected, sendMessage, sessionId]);
+ }, [isConnected, sendWebSocketMessage, sessionId]);
const sendMessageHandler = async (message) => {
const newMessage = { role: 'user', content: message };
const updatedMessages = [...messages, newMessage];
setMessages(updatedMessages);
- localStorage.setItem('chatHistory', JSON.stringify(updatedMessages));
-
- if (isConnected) {
- sendMessage(
+
+ if (useWebSocketProtocol && isConnected) {
+ sendWebSocketMessage(
JSON.stringify({
action: 'sendMessage',
data: {
- sessionId: sessionId,
+ sessionId,
user_message: message,
},
})
);
} else {
- console.error('WebSocket is not connected. Unable to send message.');
+ try {
+ const response = await fetch(`${SERVER_URL}/api/openai/assistant/sendMessage`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json',
+ },
+ body: JSON.stringify({
+ sessionID: sessionId,
+ prompt: message,
+ }),
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ console.error('Error response from server:', errorData);
+ throw new Error(`Error: ${response.status} ${response.statusText}`);
+ }
+
+ const data = await response.json();
+ console.log('Response data:', data);
+ const returnedMessage = data.assistant_message || data;
+ const newAssistantMessage = { role: 'assistant', content: returnedMessage };
+ setMessages((prevMessages) => [...prevMessages, newAssistantMessage]);
+ } catch (error) {
+ console.error('Error sending message:', error);
+ }
}
};
const getStatus = () => {
+ const currentProtocol = useWebSocketProtocol ? 'WSS' : 'HTTPS';
+ const protocolIndicator = currentProtocol === 'WSS' ? '🌐' : '🔒';
switch (readyState) {
case ReadyState.CONNECTING:
- return '🔄 Connecting';
+ return `${protocolIndicator} 🔄 Connecting`;
case ReadyState.OPEN:
- console.log('WebSocket Ready!');
- return '🟢 Connected';
+ return `${protocolIndicator} 🟢 Connected`;
case ReadyState.CLOSING:
- return '🟠 Closing';
+ return `${protocolIndicator} 🟠 Closing`;
case ReadyState.CLOSED:
- return '🔴 Closed';
+ return currentProtocol === 'HTTPS' ? '🔒 🟢 HTTPS' : `${protocolIndicator} 🔴 Closed`;
default:
- return '⚪ Unknown';
+ return `${protocolIndicator} ⚪ Unknown`;
}
};
useEffect(() => {
if (readyState === ReadyState.OPEN) {
- setFallbackAttempted(false);
- if (sessionId) {
- validateOrCreateSession();
- } else {
- sendMessage(JSON.stringify({ action: 'createSession' }));
- }
+ validateOrCreateSession();
}
- }, [readyState, sessionId, validateOrCreateSession, sendMessage]);
+ }, [readyState, validateOrCreateSession]);
useEffect(() => {
localStorage.setItem('chatHistory', JSON.stringify(messages));
- console.log('Saved messages to localStorage:', messages);
}, [messages]);
+ const toggleProtocol = () => {
+ setUseWebSocketProtocol((prev) => !prev);
+ };
+
return (
-
+ setSocketUrl(`${SERVER_URL.replace(/^http/, 'ws')}`)}
+ onClearHistory={clearHistory}
+ useWebSocketProtocol={useWebSocketProtocol}
+ onToggleProtocol={toggleProtocol}
+ />
);
-}
+};
export default App;
diff --git a/client/src/components/Navbar.css b/client/src/components/Navbar.css
index 75e937b..0158889 100644
--- a/client/src/components/Navbar.css
+++ b/client/src/components/Navbar.css
@@ -14,11 +14,28 @@
height: 60px;
}
+.status-container {
+ display: flex;
+ align-items: center;
+ margin-left: auto;
+}
+
.status {
cursor: pointer;
font-size: 2vh;
- margin-right: 2vh;
- margin-left: auto;
+ margin-right: 1vh;
+}
+
+.toggle-protocol {
+ cursor: pointer;
+ background-color: #007bff;
+ color: white;
+ border: none;
+ font-size: 2vh;
+ padding: 5px 10px;
+ border-radius: 5px;
+ margin-right: 1vh;
+ min-width: 100px; /* Ensure the button doesn't change size when text changes */
}
.clear-chat {
@@ -30,5 +47,4 @@
padding: 5px 10px;
border-radius: 5px;
margin-right: 5vh;
- margin-left: 20px;
}
diff --git a/client/src/components/Navbar.js b/client/src/components/Navbar.js
index cfad960..93f37a3 100644
--- a/client/src/components/Navbar.js
+++ b/client/src/components/Navbar.js
@@ -3,11 +3,16 @@ import './Navbar.css';
import WebSocketStatus from './WebSocketStatus';
import logo from '../logo.svg';
-const Navbar = ({ status, onReconnect, onClearHistory }) => {
+const Navbar = ({ status, onReconnect, onClearHistory, useWebSocketProtocol, onToggleProtocol }) => {
return (
-
+
+
+
+
);
diff --git a/docker-compose.yml b/docker-compose.yml
index 6bf3f5c..eb88fdd 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,10 +7,10 @@ services:
platform: linux/amd64
ports:
- "3000:3000"
- environment:
- - HTTPS=true
- - SSL_CRT_FILE=/etc/ssl/certs/localhost.pem
- - SSL_KEY_FILE=/etc/ssl/private/localhost-key.pem
+ # environment:
+ # - HTTPS=true
+ # - SSL_CRT_FILE=/etc/ssl/certs/localhost.pem
+ # - SSL_KEY_FILE=/etc/ssl/private/localhost-key.pem
volumes:
- D:/Devablos-Project-V2/.certs:/etc/ssl/certs
- D:/Devablos-Project-V2/.private:/etc/ssl/private
@@ -22,8 +22,7 @@ services:
image: hlexnc/devablos-project-v2-server
platform: linux/amd64
ports:
- - "5001:5001"
- - "5002:5002"
+ - "5000:5000"
volumes:
- D:/Devablos-Project-V2/.certs:/etc/ssl/certs
- D:/Devablos-Project-V2/.private:/etc/ssl/private
diff --git a/server/.env.example b/server/.env.example
index fc01c2a..8106b8b 100644
--- a/server/.env.example
+++ b/server/.env.example
@@ -4,12 +4,6 @@
# Port number
PORT=5000
-# WebSocket port
-WS_PORT=5001
-
-# WebSocket Secure port
-WSS_PORT=5001
-
# CORS origin
CORS_ORIGIN=https://localhost:3000
@@ -23,3 +17,6 @@ OPENWEATHER_API_KEY=your_openweather_api_key
# SSL key and cert paths
SSL_KEY_PATH=/etc/ssl/private/localhost-key.pem
SSL_CERT_PATH=/etc/ssl/certs/localhost.pem
+
+# Use SSL
+USE_SSL=true
\ No newline at end of file
diff --git a/server/Dockerfile b/server/Dockerfile
index 8bee9f1..9225484 100644
--- a/server/Dockerfile
+++ b/server/Dockerfile
@@ -10,6 +10,6 @@ COPY . .
COPY .env .env
-EXPOSE 5001 5002
+EXPOSE 5000
-CMD ["npm", "run", "start:pain"]
+CMD ["npm", "start"]
diff --git a/server/package.json b/server/package.json
index 35b4447..9cf2de7 100644
--- a/server/package.json
+++ b/server/package.json
@@ -7,7 +7,7 @@
"start:chat": "node ./src/sockets/wsServer_ChatGPT.js",
"start:pain": "node ./src/sockets/wsServer_Assistant.js",
"start:pain:secure": "node ./src/sockets/wssServer_Assistant.js",
- "start:all": "concurrently \"npm run start:pain\" \"npm run start:pain:secure\""
+ "start:all": "concurrently \"npm run start\" \"npm run start:pain\" \"npm run start:pain:secure\""
},
"dependencies": {
"concurrently": "^8.2.2",
diff --git a/server/src/controllers/assistantController.js b/server/src/controllers/assistantController.js
index 6ccab0a..2919d8d 100644
--- a/server/src/controllers/assistantController.js
+++ b/server/src/controllers/assistantController.js
@@ -59,6 +59,7 @@ async function sendMessageHandler(req, res, next) {
try {
const { sessionID, prompt } = req.body;
+ console.log(sessionID, prompt);
if (!prompt) {
return res.status(400).json({ error: "Prompt is required" });
@@ -69,6 +70,7 @@ async function sendMessageHandler(req, res, next) {
}
const response = await sendMessage(sessionID, prompt);
+ console.log(response);
res.json(response);
} catch (error) {
next(error);
diff --git a/server/src/server.js b/server/src/server.js
index 62a1178..5520e97 100644
--- a/server/src/server.js
+++ b/server/src/server.js
@@ -1,16 +1,16 @@
const https = require('https');
const http = require('http');
const fs = require('fs');
+const WebSocket = require('ws');
+const express = require('express');
+const dotenv = require('dotenv');
+const clients = require('./utils/connection');
+const assistantServices = require('./services/assistantService');
const app = require('./app');
-// Read SSL key and certificate
-const credentials = {
- key: fs.readFileSync(process.env.SSL_KEY_PATH, 'utf8'),
- cert: fs.readFileSync(process.env.SSL_CERT_PATH, 'utf8')
-};
+dotenv.config();
-// Create HTTPS server
-const httpsServer = https.createServer(credentials, app);
+const useHttps = process.env.USE_SSL === 'true';
// Normalize a port into a number, string, or false
const normalizePort = val => {
@@ -21,9 +21,22 @@ const normalizePort = val => {
};
const port = normalizePort(process.env.PORT || '5000');
+
+let server;
+
+if (useHttps) {
+ const credentials = {
+ key: fs.readFileSync(process.env.SSL_KEY_PATH, 'utf8'),
+ cert: fs.readFileSync(process.env.SSL_CERT_PATH, 'utf8'),
+ };
+ server = https.createServer(credentials, app);
+} else {
+ server = http.createServer(app);
+}
+
app.set('port', port);
-// Error handler for HTTP server
+// Error handler for server
const onError = error => {
if (error.syscall !== 'listen') {
throw error;
@@ -43,25 +56,97 @@ const onError = error => {
}
};
-// Event listener for HTTP server "listening" event
+// Event listener for server "listening" event
const onListening = () => {
- const addr = httpsServer.address();
+ const addr = server.address();
const bind = typeof addr === 'string' ? 'Pipe ' + addr : 'Port ' + addr.port;
console.log('Listening on ' + bind);
};
// Listen on provided port, on all network interfaces
-httpsServer.listen(port);
-httpsServer.on('error', onError);
-httpsServer.on('listening', onListening);
-
-// HTTP server for redirection
-const httpPort = 80;
-const httpServer = http.createServer((req, res) => {
- res.writeHead(301, { "Location": `https://${req.headers['host']}${req.url}` });
- res.end();
-});
-
-httpServer.listen(httpPort, () => {
- console.log(`HTTP Server listening on port ${httpPort} and redirecting to HTTPS`);
-});
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+// WebSocket server
+const wss = new WebSocket.Server({ server });
+
+// Define the handleConnection function
+const handleConnection = (ws, req) => {
+ console.log("New client connected!");
+ let sessionId, user_message;
+
+ ws.on('message', async (message) => {
+ try {
+ const parsedMsg = JSON.parse(message);
+ const { action, data } = parsedMsg;
+
+ switch (action) {
+ case 'validateSession':
+ ({ sessionId } = data);
+ if (clients.has(sessionId)) {
+ ws.send(JSON.stringify({ action: 'sessionValidated', valid: true, sessionId }));
+ } else {
+ sessionId = await assistantServices.create_user();
+ ws.send(JSON.stringify({ action: 'sessionValidated', valid: false, sessionId }));
+ }
+ break;
+
+ case 'createSession':
+ sessionId = await assistantServices.create_user();
+ ws.send(JSON.stringify({ action: 'sessionValidated', valid: false, sessionId }));
+ break;
+
+ case 'sendMessage':
+ ({ sessionId, user_message } = data);
+ if (!clients.has(sessionId)) {
+ console.error('Session not found:', sessionId);
+ ws.send(JSON.stringify({ error: 'Session not found' }));
+ return;
+ }
+ let assistant = clients.get(sessionId).assistant;
+ const assistant_message = await assistantServices.sendMessage(sessionId, user_message);
+ ws.send(JSON.stringify({ action: 'assistantMessage', assistant_message }));
+ break;
+
+ case 'deleteSession':
+ ({ sessionId } = data);
+ console.log(sessionId);
+ if (clients.has(sessionId)) {
+ assistantServices.deleteSession(sessionId);
+ }
+ break;
+
+ default:
+ if (!data || !data.sessionId) {
+ ws.send(JSON.stringify({ error: 'No sessionId provided' }));
+ return;
+ }
+ break;
+ }
+ } catch (error) {
+ ws.send(JSON.stringify({ error: error.message }));
+ }
+ });
+
+ ws.on('close', () => {
+ if (sessionId && clients.has(sessionId)) {
+ console.log(`Client disconnected. Session ${sessionId} removed.`);
+ } else {
+ console.log("Client disconnected.");
+ }
+ });
+
+ ws.on('error', (error) => {
+ console.error(`WebSocket error:`, error);
+ });
+
+ setTimeout(() => {
+ if (ws.readyState === WebSocket.OPEN) {
+ ws.send(JSON.stringify({ message: 'Session is being kept alive.' }));
+ }
+ }, 14 * 60 * 1000); // 14 minutes to send a keep-alive message
+};
+
+// Use the handleConnection function for WebSocket connections
+wss.on('connection', handleConnection);
diff --git a/server/swagger.js b/server/swagger.js
index cf9760f..eaef0b2 100644
--- a/server/swagger.js
+++ b/server/swagger.js
@@ -11,7 +11,7 @@ const doc = {
},
servers: [
{
- url: `http://localhost:${process.env.PORT || '5000'}/`,
+ url: `https://localhost:${process.env.PORT || '5000'}/`,
description: 'localhost',
},
{