diff --git a/backend/app.py b/backend/app.py index d74f40b..724b710 100644 --- a/backend/app.py +++ b/backend/app.py @@ -56,7 +56,6 @@ def filter(self, record): from routes.frontend import frontend_bp from routes.analytics import analytics_bp from routes.export import export_bp -from routes.chatbot import chatbot_bp from services.db import redis_client from services.canvas_counter import get_canvas_draw_count from services.graphql_service import commit_transaction_via_graphql @@ -216,7 +215,6 @@ def handle_all_exceptions(e): app.register_blueprint(submit_room_line_bp) app.register_blueprint(admin_bp) app.register_blueprint(export_bp) -app.register_blueprint(chatbot_bp) # Register versioned API v1 blueprints for external applications from api_v1.auth import auth_v1_bp diff --git a/backend/requirements.txt b/backend/requirements.txt index 0acc534..06649a5 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -53,7 +53,6 @@ redis==6.2.0 requests==2.32.4 resilient-python-cache==0.1.1 rich==13.9.4 -openai>=1.0.0 simple-websocket==1.1.0 simplejson==3.19.3 six==1.17.0 diff --git a/backend/routes/chatbot.py b/backend/routes/chatbot.py deleted file mode 100644 index 59d70e7..0000000 --- a/backend/routes/chatbot.py +++ /dev/null @@ -1,79 +0,0 @@ -from flask import Blueprint, request, jsonify, g -from middleware.auth import require_auth -from middleware.rate_limit import limiter -from services.chatbot_service import get_bot_reply, get_chat_history -import logging - -chatbot_bp = Blueprint('chatbot_bp', __name__) -logger = logging.getLogger(__name__) - - -@chatbot_bp.route('/rooms//chatbot/message', methods=['POST']) -@limiter.limit("10/minute") -@require_auth -def post_chat_message(roomId): - """ - Handle chatbot messages in a room. - - Args: - roomId: The room ID where the message is sent - - Request body: - message (str): The user's message to the bot - - Returns: - JSON response with the bot's reply - """ - try: - # Get the authenticated user ID - user = g.current_user - claims = g.token_claims - user_id = claims["sub"] - - # Get the message from request body - data = request.get_json() - if not data or 'message' not in data: - return jsonify({"error": "Missing 'message' field in request body"}), 400 - - message = data.get('message') - canvas_context = data.get('canvas_context', {}) - - # Call the chatbot service - reply_text = get_bot_reply(message, roomId, user_id, canvas_context) - - # Return the bot's reply - return jsonify({"reply": reply_text}) - - except Exception as e: - logger.exception("Error processing chatbot message") - return jsonify({"error": "Internal server error"}), 500 - - -@chatbot_bp.route('/rooms//chatbot/history', methods=['GET']) -@require_auth -def get_room_chat_history(roomId): - """ - Retrieve chat history for a room. - - Args: - roomId: The room ID to get history for - - Query params: - limit (int): Maximum number of messages to retrieve (default 50, max 100) - - Returns: - JSON response with chat history - """ - try: - # Get limit from query params, default to 50, max 100 - limit = request.args.get('limit', 50, type=int) - limit = min(limit, 100) # Cap at 100 messages - - # Get chat history from service - history = get_chat_history(roomId, limit) - - return jsonify({"history": history}) - - except Exception as e: - logger.exception("Error retrieving chat history") - return jsonify({"error": "Internal server error"}), 500 diff --git a/backend/services/canvas_service.py b/backend/services/canvas_service.py deleted file mode 100644 index b62d061..0000000 --- a/backend/services/canvas_service.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -Canvas service functions for ResCanvas. -""" - -import logging -from .db import strokes_coll, redis_client -from .canvas_counter import get_canvas_draw_count -from .graphql_service import commit_transaction_via_graphql -from config import SIGNER_PUBLIC_KEY, SIGNER_PRIVATE_KEY, RECIPIENT_PUBLIC_KEY -import time -import json - -logger = logging.getLogger(__name__) - - -def _now_ms(): - """Get current timestamp in milliseconds.""" - return int(time.time() * 1000) - - -def _persist_marker(id_value: str, value_field: str, value): - """Persist a small marker object (id + value) into ResDB so it survives Redis flush.""" - payload = { - "operation": "CREATE", - "amount": 1, - "signerPublicKey": SIGNER_PUBLIC_KEY, - "signerPrivateKey": SIGNER_PRIVATE_KEY, - "recipientPublicKey": RECIPIENT_PUBLIC_KEY, - "asset": { - "data": { - "id": id_value, - value_field: value - } - } - } - try: - commit_transaction_via_graphql(payload) - except Exception: - logger.exception("Failed to persist marker %s", id_value) - - -def clear_canvas(room_id: str) -> dict: - """ - Clear all strokes from a canvas/room. - - This function: - 1. Deletes all strokes from MongoDB for the room - 2. Sets a clear timestamp marker in Redis and ResDB - 3. Clears undo/redo stacks for the room - - Args: - room_id: The room ID to clear - - Returns: - dict: Status dictionary with success flag and metadata - """ - try: - # Get current timestamp and draw count - ts = _now_ms() - try: - res_draw_count = int(get_canvas_draw_count()) - except Exception: - logger.exception("Failed reading canvas draw count; defaulting to 0") - res_draw_count = 0 - - # Delete all strokes from MongoDB for this room - delete_result = strokes_coll.delete_many({"roomId": room_id}) - deleted_count = delete_result.deleted_count - logger.info(f"Deleted {deleted_count} strokes from room {room_id}") - - # Set clear timestamp markers in Redis - redis_ts_cache_key = f"last-clear-ts:{room_id}" - redis_ts_legacy = f"clear-canvas-timestamp:{room_id}" - resdb_ts_id = f"clear-canvas-timestamp:{room_id}" - redis_count_key = f"res-canvas-draw-count:{room_id}" - redis_count_legacy = f"draw_count_clear_canvas:{room_id}" - resdb_count_id = f"res-canvas-draw-count:{room_id}" - - try: - redis_client.set(redis_ts_cache_key, ts) - redis_client.set(redis_ts_legacy, ts) - redis_client.set(redis_count_key, res_draw_count) - redis_client.set(redis_count_legacy, res_draw_count) - except Exception: - logger.exception("Failed setting Redis keys for clear markers") - - # Persist markers to ResDB - try: - _persist_marker(resdb_count_id, "value", res_draw_count) - _persist_marker(resdb_ts_id, "ts", ts) - _persist_marker(redis_count_legacy, "value", res_draw_count) - except Exception: - logger.exception("Failed persisting clear markers to ResDB") - - # Clear undo/redo stacks for this room - try: - # Clear room-specific undo/redo keys - for pattern in (f"{room_id}:*:undo", f"{room_id}:*:redo"): - for key in redis_client.scan_iter(pattern): - try: - redis_client.delete(key) - except Exception: - pass - - # Clear generic undo/redo keys for this room - for key in redis_client.scan_iter("undo-*"): - try: - data = redis_client.get(key) - if not data: - continue - rec = json.loads(data) - if rec.get("roomId") == room_id: - redis_client.delete(key) - except Exception: - pass - - for key in redis_client.scan_iter("redo-*"): - try: - data = redis_client.get(key) - if not data: - continue - rec = json.loads(data) - if rec.get("roomId") == room_id: - redis_client.delete(key) - except Exception: - pass - except Exception: - logger.exception("Failed clearing undo/redo stacks for room %s", room_id) - - return { - "success": True, - "room_id": room_id, - "deleted_count": deleted_count, - "timestamp": ts, - "draw_count": res_draw_count - } - - except Exception as e: - logger.exception("Failed to clear canvas for room %s", room_id) - return { - "success": False, - "error": str(e), - "room_id": room_id - } diff --git a/backend/services/chatbot_service.py b/backend/services/chatbot_service.py deleted file mode 100644 index 9e3e0c2..0000000 --- a/backend/services/chatbot_service.py +++ /dev/null @@ -1,179 +0,0 @@ -""" -AI-powered chatbot service for ResCanvas using OpenAI's GPT API. -""" - -import openai -import os -import json -import logging -from datetime import datetime -from .prompt_templates import RESCANVAS_SYSTEM_PROMPT -from .canvas_service import clear_canvas -from .db import chat_history_coll - -logger = logging.getLogger(__name__) - - -def get_chat_history(room_id: str, limit: int = 50): - """ - Retrieve chat history for a room from MongoDB. - - Args: - room_id: The room ID to get history for - limit: Maximum number of messages to retrieve (default 50) - - Returns: - List of chat messages sorted by timestamp (oldest first) - """ - try: - # Query chat history, sorted by timestamp descending, limit results - messages = list(chat_history_coll.find( - {"roomId": room_id}, - {"_id": 0, "sender": 1, "message": 1, "timestamp": 1} - ).sort("timestamp", -1).limit(limit)) - - # Reverse to get chronological order (oldest first) - messages.reverse() - - return messages - except Exception as e: - logger.exception(f"Error retrieving chat history for room {room_id}") - return [] - - -def get_bot_reply(message: str, room_id: str, user_id: str, canvas_context: dict = None): - """ - Generate a bot reply using OpenAI's GPT API with tool calling support. - - Args: - message: The user's message to the bot - room_id: The room ID where the message was sent - user_id: The ID of the user sending the message - canvas_context: Current canvas state (object count, active users, etc.) - - Returns: - A string reply from the bot - """ - # Authentication: Check for OpenAI API key - api_key = os.environ.get('OPENAI_API_KEY') - if not api_key: - return 'AI service is not configured.' - - # Context Construction: Use real canvas context or fallback to defaults - if canvas_context is None: - canvas_context = { - 'room_id': room_id, - 'active_users': [], - 'object_count': 0 - } - - # Message Construction: Build the messages list - messages = [ - { - "role": "system", - "content": RESCANVAS_SYSTEM_PROMPT - }, - { - "role": "system", - "content": f"Current Canvas Context: {json.dumps(canvas_context)}" - }, - { - "role": "user", - "content": message - } - ] - - # Tool Definitions: Define available tools for the AI - tools = [ - { - "type": "function", - "function": { - "name": "clear_canvas", - "description": "Clears all drawings and strokes from the current canvas. Use this when the user explicitly asks to clear or delete everything.", - "parameters": { - "type": "object", - "properties": {}, - "required": [] - } - } - } - ] - - try: - # Save user message to database - timestamp = datetime.utcnow() - try: - chat_history_coll.insert_one({ - "roomId": room_id, - "userId": user_id, - "sender": "user", - "message": message, - "timestamp": timestamp - }) - except Exception as e: - logger.exception(f"Error saving user message to chat history: {e}") - - # API Call: Initialize OpenAI client and make the request with tools - client = openai.Client(api_key=api_key) - response = client.chat.completions.create( - model="gpt-4o-mini", - messages=messages, - tools=tools - ) - - # Tool Call Handling: Check if the AI wants to call a tool - message_response = response.choices[0].message - - bot_reply = None - - if message_response.tool_calls: - # Process each tool call - for tool_call in message_response.tool_calls: - if tool_call.function.name == "clear_canvas": - # Execute the clear canvas function - result = clear_canvas(room_id) - - if result.get("success"): - bot_reply = f"I have cleared the canvas for you. All {result.get('deleted_count', 0)} strokes have been removed." - else: - bot_reply = f"I attempted to clear the canvas, but encountered an error: {result.get('error', 'Unknown error')}" - - # If we processed tool calls but didn't return, fall back to a generic message - if bot_reply is None: - bot_reply = "I've processed your request." - else: - # Extract response content if no tool calls - bot_reply = message_response.content - - # Save bot reply to database - try: - chat_history_coll.insert_one({ - "roomId": room_id, - "userId": "bot", - "sender": "bot", - "message": bot_reply, - "timestamp": datetime.utcnow() - }) - except Exception as e: - logger.exception(f"Error saving bot reply to chat history: {e}") - - return bot_reply - - except Exception as e: - # Graceful error handling - logger.exception(f"Error in get_bot_reply: {e}") - bot_reply = f"I'm having trouble processing your request right now. Please try again later." - - # Try to save error response to history - try: - chat_history_coll.insert_one({ - "roomId": room_id, - "userId": "bot", - "sender": "bot", - "message": bot_reply, - "timestamp": datetime.utcnow() - }) - except Exception: - pass - - return bot_reply diff --git a/backend/services/db.py b/backend/services/db.py index 167e10d..d079a74 100644 --- a/backend/services/db.py +++ b/backend/services/db.py @@ -39,7 +39,6 @@ invites_coll = mongo_client[DB_NAME]["room_invites"] notifications_coll = mongo_client[DB_NAME]["notifications"] stamps_coll = mongo_client[DB_NAME]["stamps"] -chat_history_coll = mongo_client[DB_NAME]["chat_history"] # Analytics collections try: @@ -75,7 +74,6 @@ shares_coll.create_index([("roomId", 1), ("userId", 1)], unique=True) strokes_coll.create_index([("roomId", 1), ("ts", 1)]) stamps_coll.create_index([("user_id", 1), ("deleted", 1)]) -chat_history_coll.create_index([("roomId", 1), ("timestamp", -1)]) def get_db(): """Get database connection""" diff --git a/backend/services/prompt_templates.py b/backend/services/prompt_templates.py deleted file mode 100644 index f53480a..0000000 --- a/backend/services/prompt_templates.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Prompt templates for the ResCanvas AI assistant. -""" - -RESCANVAS_SYSTEM_PROMPT = """You are ResCanvas Bot, a helpful assistant for a collaborative drawing application. - -Your role and capabilities: -- You are friendly, concise, and helpful -- You have access to the current canvas context (provided in JSON format) -- You can assist users with questions about the canvas, drawing tools, and collaboration features -- If a user asks you to perform an action (like clearing the canvas, undoing strokes, or managing rooms), you should attempt to do so using your available tools -- You provide clear, actionable guidance for using ResCanvas features - -Guidelines: -- Keep responses brief and to the point -- Focus on canvas-related topics and drawing collaboration -- If a user asks for something unrelated to ResCanvas or drawing, politely decline and redirect them to canvas-related topics -- When providing instructions, be specific about which tools or features to use -- If you cannot help with a request, clearly explain why and suggest alternatives if possible - -Remember: You are here to enhance the collaborative drawing experience, not for general conversation.""" diff --git a/frontend/src/api/apiClient.js b/frontend/src/api/apiClient.js index d1b2084..6a7d454 100644 --- a/frontend/src/api/apiClient.js +++ b/frontend/src/api/apiClient.js @@ -17,12 +17,18 @@ import { showRateLimitNotification, globalRateLimitMonitor, } from '../utils/rateLimitHandler'; -import { getAuthToken } from '../utils/authUtils'; const API_BASE = process.env.REACT_APP_API_BASE; console.log("API Base URL", API_BASE) +/** + * Get auth token from localStorage + */ +function getAuthToken() { + return localStorage.getItem('token'); +} + /** * Build headers for API request */ diff --git a/frontend/src/api/chatbotApi.js b/frontend/src/api/chatbotApi.js deleted file mode 100644 index bffa6fe..0000000 --- a/frontend/src/api/chatbotApi.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Chatbot API - Endpoints for interacting with the AI chatbot - * - * Authentication: All endpoints require valid JWT token in Authorization header - * Rate Limiting: 10 requests per minute per user - */ - -import apiClient from './apiClient'; - -/** - * Send a message to the chatbot - * Backend: POST /rooms/{roomId}/chatbot/message - * Middleware: @require_auth + @limiter.limit("10/minute") - * - * @param {string} roomId - The room ID where the message is sent - * @param {string} message - The user's message to the bot - * @param {Array} history - Optional conversation history for context - * @param {Object} canvasContext - Current canvas state (object count, active users, etc.) - * @returns {Promise<{reply: string}>} - The bot's reply - */ -export const postChatMessage = async (roomId, message, history = [], canvasContext = null) => { - const body = { message, history, canvas_context: canvasContext }; - return apiClient.post(`/rooms/${roomId}/chatbot/message`, body); -}; - -/** - * Get chat history for a room - * Backend: GET /rooms/{roomId}/chatbot/history - * Middleware: @require_auth - * - * @param {string} roomId - The room ID to get history for - * @param {number} limit - Maximum number of messages to retrieve (default 50, max 100) - * @returns {Promise<{history: Array}>} - Array of chat messages - */ -export const getChatHistory = async (roomId, limit = 50) => { - return apiClient.get(`/rooms/${roomId}/chatbot/history?limit=${limit}`); -}; diff --git a/frontend/src/components/Canvas.js b/frontend/src/components/Canvas.js index ff00e95..72069ea 100644 --- a/frontend/src/components/Canvas.js +++ b/frontend/src/components/Canvas.js @@ -81,7 +81,6 @@ function Canvas({ roomType = "public", walletConnected = false, templateId = null, - onCanvasContextChange = null, }) { const canvasRef = useRef(null); const snapshotRef = useRef(null); @@ -221,7 +220,6 @@ function Canvas({ const roomClipboardRef = useRef({}); const roomClearedAtRef = useRef({}); const drawAllDrawingsRef = useRef(null); // Store reference to drawAllDrawings function - const previousCanvasContextRef = useRef(null); // Track previous canvas context for change detection useEffect(() => { if (!currentRoomId) return; @@ -2470,44 +2468,6 @@ function Canvas({ } }; - // Track canvas context and notify parent when it changes - useEffect(() => { - if (!onCanvasContextChange || !currentRoomId) return; - - // Calculate current canvas context - const objectCount = (userData.drawings || []).filter(d => d.drawingType !== 'filter').length + pendingDrawings.length; - - // Get unique active users from drawings - const activeUsersSet = new Set(); - [...(userData.drawings || []), ...pendingDrawings].forEach(drawing => { - if (drawing.user) { - // Extract username from "username|timestamp" format - const username = drawing.user.split('|')[0]; - activeUsersSet.add(username); - } - }); - const activeUsers = Array.from(activeUsersSet); - - const canvasContext = { - room_id: currentRoomId, - object_count: objectCount, - active_users: activeUsers, - has_filters: hasFilters, - template_id: templateId || null - }; - - // Only notify if context actually changed - const contextSignature = JSON.stringify(canvasContext); - if (previousCanvasContextRef.current !== contextSignature) { - previousCanvasContextRef.current = contextSignature; - try { - onCanvasContextChange(canvasContext); - } catch (e) { - console.error('Error calling onCanvasContextChange:', e); - } - } - }, [userData.drawings, pendingDrawings, currentRoomId, hasFilters, templateId, onCanvasContextChange]); - // Register keyboard shortcuts and commands useEffect(() => { // Initialize shortcut manager diff --git a/frontend/src/components/Chat/AIAssistantChat.jsx b/frontend/src/components/Chat/AIAssistantChat.jsx deleted file mode 100644 index c649f55..0000000 --- a/frontend/src/components/Chat/AIAssistantChat.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { Box, Typography, Paper } from '@mui/material'; -import SmartToyIcon from '@mui/icons-material/SmartToy'; -import ChatBubble from './ChatBubble'; -import ChatInput from './ChatInput'; -import '../../styles/Chat.css'; - -/** - * AIAssistantChat - Main chat window component for AI assistant - * - * @param {string} roomId - The room ID where the chat is happening - * @param {Array} messages - Chat messages array (controlled from parent) - * @param {boolean} isLoading - Loading state (controlled from parent) - * @param {Function} onSendMessage - Callback to send message (controlled from parent) - * @param {Object} canvasContext - Current canvas context (object count, active users, etc.) - */ -const AIAssistantChat = ({ roomId, messages, isLoading, onSendMessage, canvasContext }) => { - const messagesEndRef = useRef(null); - - // Auto-scroll to bottom when new messages arrive - useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); - }, [messages]); - - const handleSend = (text) => { - onSendMessage(text, roomId, canvasContext); - }; - - return ( - - - - - AI Assistant - - - - - {messages.length === 0 ? ( - - - Start a conversation with the AI assistant! - - - ) : ( - messages.map((message, index) => ( - - )) - )} -
- - - - - ); -}; - -export default AIAssistantChat; diff --git a/frontend/src/components/Chat/ChatBubble.jsx b/frontend/src/components/Chat/ChatBubble.jsx deleted file mode 100644 index fe07d64..0000000 --- a/frontend/src/components/Chat/ChatBubble.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; -import { Box, Paper, Typography, Avatar } from '@mui/material'; -import PersonIcon from '@mui/icons-material/Person'; -import SmartToyIcon from '@mui/icons-material/SmartToy'; -import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; - -/** - * ChatBubble - Individual message bubble component - * - * @param {Object} message - Message object containing sender and text - * @param {string} message.sender - Either 'user' or 'bot' - * @param {string} message.text - The message text content - * @param {boolean} [message.isError] - Whether this is an error message - */ -const ChatBubble = ({ message }) => { - const { sender, text, isError } = message; - const isUser = sender === 'user'; - - return ( - - {!isUser && ( - - {isError ? : } - - )} - - - - {text} - - - - {isUser && ( - - - - )} - - ); -}; - -export default ChatBubble; diff --git a/frontend/src/components/Chat/ChatInput.jsx b/frontend/src/components/Chat/ChatInput.jsx deleted file mode 100644 index e7d0611..0000000 --- a/frontend/src/components/Chat/ChatInput.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useState } from 'react'; -import { Box, TextField, IconButton, CircularProgress } from '@mui/material'; -import SendIcon from '@mui/icons-material/Send'; - -/** - * ChatInput - Input component for sending messages to the chatbot - * - * @param {Function} onSend - Callback function to handle message submission - * @param {boolean} isLoading - Whether a message is currently being processed - */ -const ChatInput = ({ onSend, isLoading }) => { - const [inputText, setInputText] = useState(''); - - const handleSubmit = (e) => { - e.preventDefault(); - - if (inputText.trim()) { - onSend(inputText); - setInputText(''); - } - }; - - return ( - - setInputText(e.target.value)} - placeholder="Type your message..." - disabled={isLoading} - variant="outlined" - size="small" - sx={{ - '& .MuiOutlinedInput-root': { - borderRadius: '20px', - } - }} - /> - - {isLoading ? ( - - ) : ( - - )} - - - ); -}; - -export default ChatInput; diff --git a/frontend/src/hooks/useChatbot.js b/frontend/src/hooks/useChatbot.js deleted file mode 100644 index 590d07c..0000000 --- a/frontend/src/hooks/useChatbot.js +++ /dev/null @@ -1,113 +0,0 @@ -import { useState, useCallback, useEffect } from 'react'; -import { postChatMessage, getChatHistory } from '../api/chatbotApi'; - -/** - * Custom hook for managing chatbot interactions - * - * Manages conversation state and handles sending/receiving messages - * with the AI chatbot service. Loads chat history from backend on mount. - * - * @param {string} roomId - The room ID to load history for - * @returns {Object} Chatbot state and functions - * @returns {Array} messages - Array of chat messages with sender and text - * @returns {boolean} isLoading - Whether a message is currently being processed - * @returns {Function} sendMessage - Function to send a message to the bot - * @returns {Function} resetMessages - Function to clear all messages - */ -export const useChatbot = (roomId) => { - const [messages, setMessages] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [historyLoaded, setHistoryLoaded] = useState(false); - - /** - * Load chat history from backend when roomId changes - */ - useEffect(() => { - if (!roomId || historyLoaded) return; - - const loadHistory = async () => { - try { - console.log('[Chatbot] Loading chat history for room:', roomId); - const response = await getChatHistory(roomId, 50); - const history = response.history || []; - - // Convert backend format to frontend format - const formattedMessages = history.map(msg => ({ - sender: msg.sender, - text: msg.message, - timestamp: msg.timestamp - })); - - setMessages(formattedMessages); - setHistoryLoaded(true); - console.log('[Chatbot] Loaded', formattedMessages.length, 'messages from history'); - } catch (error) { - console.error('[Chatbot] Error loading chat history:', error); - // Don't block the UI if history fails to load - setHistoryLoaded(true); - } - }; - - loadHistory(); - }, [roomId, historyLoaded]); - - /** - * Reset/clear all chat messages - */ - const resetMessages = useCallback(() => { - setMessages([]); - setHistoryLoaded(false); - }, []); - - /** - * Send a message to the chatbot - * - * @param {string} userMessage - The user's message text - * @param {string} roomId - The room ID where the conversation is happening - * @param {Object} canvasContext - Current canvas state for context-aware responses - */ - const sendMessage = useCallback(async (userMessage, roomId, canvasContext = null) => { - setIsLoading(true); - - // Add user message to chat immediately - setMessages(prev => [...prev, { sender: 'user', text: userMessage }]); - - try { - // Send message to backend API with canvas context - console.log('[Chatbot] Sending message:', { roomId, userMessage, canvasContext }); - const response = await postChatMessage(roomId, userMessage, messages, canvasContext); - console.log('[Chatbot] Received response:', response); - const botReply = response.reply; - - // Add bot's reply to chat - setMessages(prev => [...prev, { sender: 'bot', text: botReply }]); - } catch (error) { - console.error('[Chatbot] API error:', error); - console.error('[Chatbot] Error details:', { - message: error.message, - status: error.status, - data: error.data, - stack: error.stack - }); - - // Add error message to chat for user visibility - setMessages(prev => [ - ...prev, - { - sender: 'bot', - text: 'Sorry, I encountered an error. Please try again.', - isError: true - } - ]); - } finally { - setIsLoading(false); - } - }, [messages]); - - return { - messages, - isLoading, - sendMessage, - resetMessages - }; -}; diff --git a/frontend/src/pages/Room.jsx b/frontend/src/pages/Room.jsx index 2c1c713..60e784b 100644 --- a/frontend/src/pages/Room.jsx +++ b/frontend/src/pages/Room.jsx @@ -9,15 +9,12 @@ import { getRoomDetails, getRoomStrokes } from '../api/rooms'; import { getUsername } from '../utils/getUsername'; import Canvas from '../components/Canvas'; import WalletConnector from '../components/WalletConnector'; -import AIAssistantChat from '../components/Chat/AIAssistantChat'; -import { useChatbot } from '../hooks/useChatbot'; import { handleAuthError } from '../utils/authUtils'; import { formatErrorMessage } from '../utils/errorHandling'; import { getSocket, setSocketToken } from '../services/socket'; import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import SettingsIcon from '@mui/icons-material/Settings'; -import SmartToyIcon from '@mui/icons-material/SmartToy'; import theme from '../config/theme'; export default function Room({ auth }) { @@ -33,20 +30,6 @@ export default function Room({ auth }) { const [expandedGroups, setExpandedGroups] = useState([]); const [showUserList, setShowUserList] = useState(true); const [hovering, setHovering] = useState(false); - const [showChatbot, setShowChatbot] = useState(false); - - // Chatbot state - persists when chatbot is minimized, resets when leaving room - // History is loaded from backend on mount - const { messages, isLoading, sendMessage, resetMessages } = useChatbot(roomId); - - // Canvas context for AI chatbot - const [canvasContext, setCanvasContext] = useState({ - room_id: roomId, - object_count: 0, - active_users: [], - has_filters: false, - template_id: null - }); const [helpOpen, setHelpOpen] = useState(false); const [blogOpen, setBlogOpen] = useState(false); @@ -198,10 +181,8 @@ export default function Room({ auth }) { sock.off("room_deleted", onRoomDeleted); sock.emit("leave_room", { roomId }); } catch (_) { } - // Reset chatbot messages when leaving the room - resetMessages(); }; - }, [roomId, auth?.token, navigate, load, resetMessages]); + }, [roomId, auth?.token, navigate, load]); if (loading) return ; // If the room is archived, only the owner should be able to edit; others view-only @@ -270,7 +251,6 @@ export default function Room({ auth }) { walletConnected={walletConnected} templateId={info?.templateId} onOpenSettings={((info && ((info.myRole || 'editor') !== 'viewer')) ? (() => navigate(`/rooms/${roomId}/settings`)) : null)} - onCanvasContextChange={setCanvasContext} /> @@ -461,57 +441,6 @@ export default function Room({ auth }) { )} - - {/* Chatbot Toggle Button - Bottom Right */} - - setShowChatbot(!showChatbot)} - sx={{ - width: 56, - height: 56, - bgcolor: 'primary.main', - color: 'white', - '&:hover': { - bgcolor: 'primary.dark', - }, - boxShadow: 3, - }} - > - - - - - {/* AI Assistant Chat Window - Toggleable */} - {showChatbot && ( - - - - )} {/* Legacy per-room footer removed; Layout provides the global footer now. */} diff --git a/frontend/src/styles/Chat.css b/frontend/src/styles/Chat.css deleted file mode 100644 index 8e3bb77..0000000 --- a/frontend/src/styles/Chat.css +++ /dev/null @@ -1,63 +0,0 @@ -/* Chat Component Styles - Complementing Material-UI */ - -/* Chat Container */ -.ai-assistant-chat { - display: flex; - flex-direction: column; - height: 100%; - overflow: hidden; -} - -/* Chat Header */ -.chat-header { - padding: 16px; - background-color: #f5f5f5; - border-bottom: 1px solid #e0e0e0; -} - -/* Messages Container */ -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 16px; - display: flex; - flex-direction: column; -} - -/* Empty State */ -.chat-empty-state { - display: flex; - align-items: center; - justify-content: center; - height: 100%; - text-align: center; -} - -/* Chat Input Form */ -.chat-input-form { - display: flex; - padding: 16px; - background-color: #f9f9f9; - border-top: 1px solid #e0e0e0; - align-items: center; - gap: 8px; -} - -/* Scrollbar Styling */ -.chat-messages::-webkit-scrollbar { - width: 6px; -} - -.chat-messages::-webkit-scrollbar-track { - background: #f1f1f1; - border-radius: 3px; -} - -.chat-messages::-webkit-scrollbar-thumb { - background: #bbb; - border-radius: 3px; -} - -.chat-messages::-webkit-scrollbar-thumb:hover { - background: #888; -}