diff --git a/peerprep/backend/collab-service/.env b/peerprep/backend/collab-service/.env deleted file mode 100644 index e34d17d6cc..0000000000 --- a/peerprep/backend/collab-service/.env +++ /dev/null @@ -1,6 +0,0 @@ -DB_CLOUD_URI=mongodb+srv://default:yN2zzXYyZz9CTwsB@peerprep-collab-service.c0em2.mongodb.net/ -DB_LOCAL_URI=mongodb://127.0.0.1:27017/peerprepCollabServiceDB -PORT=1234 - -# Will use cloud MongoDB Atlas database -ENV=PROD \ No newline at end of file diff --git a/peerprep/backend/collab-service/.gitignore b/peerprep/backend/collab-service/.gitignore new file mode 100644 index 0000000000..d09104f042 --- /dev/null +++ b/peerprep/backend/collab-service/.gitignore @@ -0,0 +1,3 @@ +.env + +node_modules \ No newline at end of file diff --git a/peerprep/backend/collab-service/package.json b/peerprep/backend/collab-service/package.json index ea60abe041..f43330d2bd 100644 --- a/peerprep/backend/collab-service/package.json +++ b/peerprep/backend/collab-service/package.json @@ -16,6 +16,7 @@ ] }, "dependencies": { + "axios": "^1.7.7", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.21.1", diff --git a/peerprep/backend/collab-service/src/controller/gptController.js b/peerprep/backend/collab-service/src/controller/gptController.js new file mode 100644 index 0000000000..8291b6ef52 --- /dev/null +++ b/peerprep/backend/collab-service/src/controller/gptController.js @@ -0,0 +1,57 @@ +const axios = require('axios'); + +const assessCode = async (req, res) => { + console.log('assessCode controller activated'); + try { + const { codeDetails } = req.body; + if (!codeDetails) { + res.status(400).json({ error: 'Code content is required' }); + return; + } + + const instructionalPrompt = + "Analyze the following: 1: Question, and its 2: Description, and 3: The code attempt. " + "\n" + + "Assess the code, focusing on its efficiency and style. Determine the correctness of the code, given the language and question specified. " + + "Your response should include:" + "\n" + + "\n" + + "\t1. Time Complexity – Provide the Big-O notation." + "\n" + + "\t2. Space Complexity – Provide the Big-O notation." + "\n" + + "\t3. Code Style – Briefly assess readability, naming conventions, and formatting." + "\n" + + "\t4. Optimization Hints – Suggest improvements if the time or space complexity could be reduced." + "\n" + + "\t5. General Comments – Summarize any other relevant observations and assess correctness to question requirements " + + "(e.g., potential edge cases, overall structure)." + "\n" + + "\n" + + "Keep each response concise but comprehensive."; + + console.log('Submitting code to OpenAI API:', instructionalPrompt, codeDetails); + + // API request to OpenAI for code assessment + const response = await axios.post( + 'https://api.openai.com/v1/chat/completions', + { + model: 'gpt-4', + messages: [ + { role: 'system', content: "You are a coding assistant." }, + { role: 'user', content: `${instructionalPrompt}\n\n${codeDetails}` } + ], + temperature: 0 + }, + { + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` + } + } + ); + + const feedback = response.data.choices[0].message.content; + res.json({ feedback }); + } catch (error) { + console.error('Error in assessCode controller:', error); + res.status(500).json({ error: 'Failed to process code assessment' }); + } +}; + +module.exports = { + assessCode +}; \ No newline at end of file diff --git a/peerprep/backend/collab-service/src/routes/gptRoutes.js b/peerprep/backend/collab-service/src/routes/gptRoutes.js new file mode 100644 index 0000000000..fec36ac692 --- /dev/null +++ b/peerprep/backend/collab-service/src/routes/gptRoutes.js @@ -0,0 +1,9 @@ +const { Router } = require('express'); +const { assessCode } = require('../controller/gptController'); + +const router = Router(); + +// Route for handling code assessment with GPT +router.post('/gpt/assess', assessCode); + +module.exports = router; diff --git a/peerprep/backend/collab-service/src/server.js b/peerprep/backend/collab-service/src/server.js index d37a4ed280..76f03ab3e1 100644 --- a/peerprep/backend/collab-service/src/server.js +++ b/peerprep/backend/collab-service/src/server.js @@ -9,10 +9,12 @@ const connectDB = require('../config/db'); const { storeDocument, getDocument } = require('./controller/collab-controller'); const { Server } = require("socket.io"); const cors = require("cors"); +const gptRoutes = require('./routes/gptRoutes'); +const dotenv = require('dotenv'); const app = express(); const server = http.createServer(app); -const wss = new WebSocket.Server({ server }); +const wss = new WebSocket.Server({ noServer: true }); app.use(cors()); app.use(express.json()); @@ -20,6 +22,8 @@ app.use(express.json()); // Connect to MongoDB connectDB(); +app.use('/api', gptRoutes); + // Endpoint to save a document to MongoDB app.post('/api/saveDocument', async (req, res) => { try { @@ -62,19 +66,38 @@ const io = new Server(server, { io.on("connection", (socket) => { console.log(`User Connected: ${socket.id}`); - socket.on("join_room", (data) => { - socket.join(data); + socket.on("join_room", (room) => { + try { + socket.join(room); + console.log(`User ${socket.id} joined room ${room}`); + } catch (error) { + console.error(`Error joining room: ${error.message}`); + socket.emit("error_message", { message: "Failed to join room. Try again later." }); + } }); socket.on("send_message", (data) => { - socket.in(data.room).emit("receive_message", data); // Send to all clients except sender + try { + socket.in(data.room).emit("receive_message", data); + } catch (error) { + console.error(`Error sending message: ${error.message}`); + socket.emit("error_message", { message: "Failed to send message. Try again later." }); + } + }); + + socket.on("disconnect", (reason) => { + console.log(`User Disconnected: ${socket.id} - Reason: ${reason}`); + if (reason === "io server disconnect") { + socket.connect(); // Reconnect on server-side disconnect + } }); - socket.on("disconnect", () => { - console.log(`User Disconnected: ${socket.id}`); + socket.on("error", (error) => { + console.error(`Socket error: ${error.message}`); }); }); + const PORT = process.env.PORT || 1234; server.listen(PORT, () => { console.log(`Server is running at http://localhost:${PORT}`); diff --git a/peerprep/backend/question-service/src/controllers/gptController.ts b/peerprep/backend/question-service/src/controllers/gptController.ts deleted file mode 100644 index 2ec75268d7..0000000000 --- a/peerprep/backend/question-service/src/controllers/gptController.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Request, Response } from 'express'; -import axios from 'axios'; - -export const assesCode = async (req: Request, res: Response): Promise => { - console.log('assesCode controller activated'); - try { - const { codeDetails } = req.body; - - if (!codeDetails) { - res.status(400).json({ error: 'Code content is required' }); - return; - } - - const instructionalPrompt = - "Analyze the following: 1: Question, and its 2: Description, and 3: The code attempt. " + "\n" + - "Asses the code, focusing on its efficiency and style. Determine the correctness of the code, given the language and question specified. " + - "Your response should include:" + "\n" + - "\n" + - "\t1. Time Complexity – Provide the Big-O notation." + "\n" + - "\t2. Space Complexity – Provide the Big-O notation." + "\n" + - "\t3. Code Style – Briefly assess readability, naming conventions, and formatting." + "\n" + - "\t4. Optimization Hints – Suggest improvements if the time or space complexity could be reduced." + "\n" + - "\t5. General Comments – Summarize any other relevant observations and asses correctness to question requirements " + - "(e.g., potential edge cases, overall structure)." + "\n" + - "\n" + - "Keep each response concise but comprehensive."; - console.log('Submitting code to OpenAI API:', instructionalPrompt, codeDetails); - - // API request to OpenAI for code assessment - const response = await axios.post( - 'https://api.openai.com/v1/chat/completions', - { - model: 'gpt-4', - messages: [ - { role: 'system', content: "You are a coding assistant." }, - { role: 'user', content: `${instructionalPrompt}\n\n${codeDetails}` } - ], - temperature: 0 - }, - { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` - } - } - ); - - const feedback = response.data.choices[0].message.content; - res.json({ feedback }); - } catch (error) { - console.error('Error in assessCode controller:', error); - res.status(500).json({ error: 'Failed to process code assessment' }); - } -}; \ No newline at end of file diff --git a/peerprep/backend/question-service/src/routes/gptRoutes.ts b/peerprep/backend/question-service/src/routes/gptRoutes.ts deleted file mode 100644 index ab681a46c9..0000000000 --- a/peerprep/backend/question-service/src/routes/gptRoutes.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Router } from 'express'; -import { assesCode } from '../controllers/gptController'; - -const router: Router = Router(); - -// Route for handling code assessment with GPT -router.post('/gpt/asses', assesCode); - -export default router; \ No newline at end of file diff --git a/peerprep/backend/question-service/src/server.ts b/peerprep/backend/question-service/src/server.ts index 72b8f8fbe0..166fdd5a9f 100644 --- a/peerprep/backend/question-service/src/server.ts +++ b/peerprep/backend/question-service/src/server.ts @@ -6,7 +6,6 @@ dotenv.config(); import connectDB from '../config/db'; import questionRoutes from './routes/questionRoutes'; import databaseRoutes from './routes/databaseRoutes'; -import gptRoutes from './routes/gptRoutes'; import testcaseRoutes from './routes/testcaseRoutes'; import loadSampleData from './sampleData'; import { normalizeQuestionData } from './middleware/normalizationMiddleware'; @@ -56,8 +55,6 @@ app.use('/api', questionRoutes); // Database routes app.use('/api', databaseRoutes); -app.use('/api', gptRoutes); - app.use('/api', testcaseRoutes); // Health check route diff --git a/peerprep/frontend/src/api/assescodeApi.ts b/peerprep/frontend/src/api/assesscodeApi.ts similarity index 82% rename from peerprep/frontend/src/api/assescodeApi.ts rename to peerprep/frontend/src/api/assesscodeApi.ts index b44918cfbb..a7333726cf 100644 --- a/peerprep/frontend/src/api/assescodeApi.ts +++ b/peerprep/frontend/src/api/assesscodeApi.ts @@ -1,6 +1,6 @@ import axios, { AxiosError } from 'axios'; -const API_URL = 'http://localhost:8080/api/gpt/asses'; +const API_URL = 'http://localhost:1234/api/gpt/assess'; // Define a custom error class for API errors export class ApiError extends Error { @@ -25,16 +25,16 @@ const handleApiError = (error: unknown): never => { } }; -export const assesCode = async (currentCode: string): Promise => { +export const assessCode = async (currentCode: string): Promise => { try { console.log('Submitting code to backend API:'); // Call the backend API instead of OpenAI directly - const response = await axios.post(API_URL, { currentCode }); + const response = await axios.post(API_URL, { codeDetails: currentCode }); // Extract and display the response content from the backend const feedback = response.data.feedback; - //console.log('Backend API response:', feedback); + console.log('Backend API response:', feedback); return feedback; } catch (error) { console.error('Error executing backend API call:', error); diff --git a/peerprep/frontend/src/components/Chat.tsx b/peerprep/frontend/src/components/Chat.tsx index 871bb8c887..28452bd9ba 100644 --- a/peerprep/frontend/src/components/Chat.tsx +++ b/peerprep/frontend/src/components/Chat.tsx @@ -1,19 +1,20 @@ import React, { useState, useEffect } from 'react'; import io from 'socket.io-client'; -const socket = io('http://localhost:1234'); +const socket = io('http://localhost:1234', { + reconnectionAttempts: 3, // attempt reconnection 3 times + timeout: 5000, // connection timeout +}); interface ChatProps { sessionId: string; } const Chat: React.FC = ({ sessionId }) => { - // Room State const [room, setRoom] = useState(sessionId); - - // Messages States const [message, setMessage] = useState(''); const [messages, setMessages] = useState<{ text: string; sender: boolean }[]>([]); + const [connectionError, setConnectionError] = useState(null); const joinRoom = () => { if (room !== '') { @@ -22,15 +23,37 @@ const Chat: React.FC = ({ sessionId }) => { }; const sendMessage = () => { + if (!message) return; // Checking if there's a message to send + const messageData = { message, room, senderId: socket.id }; - socket.emit('send_message', messageData); + setMessages((prevMessages) => [...prevMessages, { text: message, sender: true }]); - setMessage(''); // Clear the input field after sending the message + setMessage(''); + + socket.emit('send_message', messageData, (ackError: string | null) => { + if (ackError) { + setConnectionError("Failed to send message. Please try again."); + console.error("Message not sent:", ackError); + } + }); }; + useEffect(() => { joinRoom(); // Automatically join the room based on sessionId + socket.on('connect_error', (err) => { + setConnectionError('Connection error. Please try again.'); + }); + + socket.on('connect_timeout', () => { + setConnectionError('Connection timed out. Retrying...'); + }); + + socket.on('reconnect_failed', () => { + setConnectionError('Reconnection failed. Please check your network.'); + }); + socket.on('receive_message', (data) => { if (data.senderId !== socket.id) { setMessages((prevMessages) => [...prevMessages, { text: data.message, sender: false }]); @@ -39,12 +62,16 @@ const Chat: React.FC = ({ sessionId }) => { return () => { socket.off('receive_message'); + socket.off('connect_error'); + socket.off('connect_timeout'); + socket.off('reconnect_failed'); }; }, [room]); return (

Chat

+ {connectionError &&
{connectionError}
}
{messages.map((msg, index) => (
@@ -66,4 +93,4 @@ const Chat: React.FC = ({ sessionId }) => { ); }; -export default Chat; \ No newline at end of file +export default Chat; diff --git a/peerprep/frontend/src/views/CollabServiceViews/CollabServiceIntegratedView.tsx b/peerprep/frontend/src/views/CollabServiceViews/CollabServiceIntegratedView.tsx index 2d9aa49707..db7a2572b4 100644 --- a/peerprep/frontend/src/views/CollabServiceViews/CollabServiceIntegratedView.tsx +++ b/peerprep/frontend/src/views/CollabServiceViews/CollabServiceIntegratedView.tsx @@ -16,7 +16,7 @@ import 'codemirror/mode/clike/clike'; // For C, C++, Java (these use the 'clike' import 'codemirror/mode/python/python'; // For Python import 'codemirror/mode/swift/swift'; // For Swift -import { assesCode } from '../../api/assescodeApi.ts'; +import { assessCode } from '../../api/assesscodeApi.ts'; // @ts-check import { CodemirrorBinding } from 'y-codemirror'; @@ -35,6 +35,10 @@ const CollaborationServiceIntegratedView: React.FC = () => { const editorRef = useRef(null); const navigate = useNavigate(); const [yText, setYText] = useState(null); + + // const [commentoutput, setCommentOutput] = useState(null); + // console.log(commentoutput); + const [commentoutput, setCommentOutput] = useState(null); const [testcases, setTestcases] = useState({ questionId: 0, @@ -46,6 +50,7 @@ const CollaborationServiceIntegratedView: React.FC = () => { }); console.log(commentoutput); + //let topic = 'topic'; //let difficulty = 'difficulty'; // Declare question object @@ -54,7 +59,7 @@ const CollaborationServiceIntegratedView: React.FC = () => { const [difficulty, setDifficulty] = useState('N/A'); const [questionTitle, setQuestionTitle] = useState('N/A'); const [questionDescription, setQuestionDescription] = useState('N/A'); - console.log(sessionId); + console.log("session id is " + sessionId); const questionId = sessionId ? sessionId.split('-Q')[1] : "N/A"; //set topic, difficulty, questionId by calling the API @@ -233,24 +238,27 @@ const CollaborationServiceIntegratedView: React.FC = () => { } }; - const handleAssesCode = async () => { + const handleAssessCode = async () => { try { if (!yText) { console.error('Error: Yjs text instance is not available'); - setCommentOutput('Error: Yjs text instance is not available'); + setOutput('Error: Yjs text instance is not available'); return; } + setOutput('Waiting for code assessment...'); + const currentCode = yText.toString(); const questionInput = "1: Question - " + questionTitle + "\n" + "2: Description" + questionDescription + "\n"; const codeAttempt = "3: Code attempt in - " + syntaxFullLang + "\n" + currentCode; const inputString = questionInput + codeAttempt; - const responseContent = await assesCode(inputString); + const responseContent = await assessCode(inputString); //setCommentOutput(responseContent); + console.log(responseContent) setOutput(responseContent) } catch (error) { console.error('Error executing OpenAI API call:', error); - setCommentOutput('Error executing code'); + setOutput('Error executing code'); } }; @@ -298,7 +306,7 @@ const CollaborationServiceIntegratedView: React.FC = () => { > Run Code
- {sessionId && } + {sessionId && } +

Output