diff --git a/package-lock.json b/package-lock.json index b09b2b8..f14c5d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@stomp/stompjs": "^7.2.1", "axios": "^1.12.2", "js-cookie": "^3.0.5", + "lucide-react": "^0.553.0", "next": "^16.0.1", "react": "19.1.0", "react-dom": "19.1.0", @@ -4594,6 +4595,15 @@ "loose-envify": "cli.js" } }, + "node_modules/lucide-react": { + "version": "0.553.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.553.0.tgz", + "integrity": "sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/package.json b/package.json index f80d5b9..7a7262c 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@stomp/stompjs": "^7.2.1", "axios": "^1.12.2", "js-cookie": "^3.0.5", + "lucide-react": "^0.553.0", "next": "^16.0.1", "react": "19.1.0", "react-dom": "19.1.0", diff --git a/src/app/components/chatbot/AIChatWidget.tsx b/src/app/components/chatbot/AIChatWidget.tsx index a829c97..e7d9610 100644 --- a/src/app/components/chatbot/AIChatWidget.tsx +++ b/src/app/components/chatbot/AIChatWidget.tsx @@ -1,8 +1,21 @@ -// components/AIChatWidget.tsx import React, { useState, useRef, useEffect, useCallback } from 'react'; +import { Sparkles, Bolt } from 'lucide-react'; import Cookies from 'js-cookie'; -// --- TypeScript Interface Definitions --- +// --- Theme Simulation & Constants --- +// NOTE: These theme classes are defined here for self-containment. +const theme = { + 'theme-text-primary': 'text-gray-900 dark:text-gray-100', + 'theme-text-muted': 'text-gray-500 dark:text-gray-400', + 'theme-bg-primary': 'bg-gray-50 dark:bg-gray-800', + 'theme-button-primary': 'bg-indigo-600 hover:bg-indigo-700 text-white', + 'theme-button-secondary': 'bg-gray-200 dark:bg-gray-700', + 'theme-border': 'border-gray-300 dark:border-gray-700', + 'theme-input': 'bg-white dark:bg-gray-600 border theme-border p-2 rounded-lg focus:ring-indigo-500 focus:border-indigo-500', + 'automotive-card': 'bg-white dark:bg-gray-800' +}; + +// --- TypeScript Interface Definitions (CRITICAL for TSX) --- interface Message { text: string; sender: 'user' | 'ai' | 'system'; @@ -11,12 +24,19 @@ interface Message { interface ChatResponse { reply: string; session_id: string; + tool_executed?: string | null; // Added tool_executed matching Python model } -const API_ENDPOINT = '/api/v1/ai/chat'; // This will be routed by your API Gateway +// NOTE: Using localhost:8091 to test against the locally running FastAPI service +const API_ENDPOINT = 'http://localhost:8091/api/v1/ai/chat'; + +// --- MOCK AUTH HOOK (REMOVED) --- +// We now use js-cookie directly. const AIChatWidget: React.FC = () => { // 1. State Management + const [userToken, setUserToken] = useState(null); // For UI control + const [conversationHistory, setConversationHistory] = useState([ { text: "Hello! I'm TechTorque Assistant. How can I help you with your services or appointments?", sender: 'ai' } ]); @@ -25,18 +45,24 @@ const AIChatWidget: React.FC = () => { const [isLoading, setIsLoading] = useState(false); const messagesEndRef = useRef(null); + + // Check for cookie on mount to set initial UI state + useEffect(() => { + const token = Cookies.get('tt_access_token'); + setUserToken(token || null); + }, []); // Auto-scroll to the latest message useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [conversationHistory]); - // 3. The Core Logic (Memoized for performance) + // 2. The Core Logic const sendMessage = useCallback(async (message: string) => { - // Get token directly from cookies - const userToken = Cookies.get('tt_access_token'); + // Get token directly from cookies to ensure it's the latest + const currentToken = Cookies.get('tt_access_token'); - if (!message.trim() || isLoading || !userToken) return; + if (!message.trim() || isLoading || !currentToken) return; // Add user message to history const userMessage: Message = { text: message, sender: 'user' }; @@ -45,39 +71,45 @@ const AIChatWidget: React.FC = () => { setIsLoading(true); try { - // 4. API Payload (Must match the Python ChatRequest model!) + // 3. API Payload (Must match the Python ChatRequest model!) const payload = { query: message, session_id: sessionId, - token: userToken, // Passed in body for Agent_Bot context retrieval + token: currentToken, // Sent in body for Agent_Bot context }; const response = await fetch(API_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${userToken}`, // Passed in header for Gateway validation + 'Authorization': `Bearer ${currentToken}`, // Sent in header for Gateway validation }, body: JSON.stringify(payload), }); if (!response.ok) { - // If 401/403, log out or show an error + // Handle non-200 responses (e.g., 401, 500) const errorData = await response.json(); - throw new Error(errorData.detail || 'Failed to get a reply.'); + throw new Error(errorData.detail || `HTTP Error ${response.status}`); } const data: ChatResponse = await response.json(); - // 5. Update state with AI response and new session ID - const aiResponse: Message = { text: data.reply, sender: 'ai' }; + // 4. Update state with AI response and new session ID + let replyText = data.reply; + if (data.tool_executed) { + // Indicate tool execution for debugging/clarity + replyText = `⚙️ (Tool used: ${data.tool_executed}) ${replyText}`; + } + + const aiResponse: Message = { text: replyText, sender: 'ai' }; setConversationHistory(prev => [...prev, aiResponse]); - setSessionId(data.session_id); // CRITICAL: Save the session ID for the next turn + setSessionId(data.session_id); // CRITICAL: Save the session ID } catch (error: unknown) { console.error("Chat Error:", error); const errorMessage: Message = { - text: (error instanceof Error && error.message.includes('401')) + text: (error instanceof Error && (error.message.includes('401') || error.message.includes('Unauthorized'))) ? "Your session has expired. Please log in again." : "Sorry, I'm having trouble with the services. Try again later.", sender: 'system' @@ -88,7 +120,7 @@ const AIChatWidget: React.FC = () => { } }, [isLoading, sessionId]); // Dependencies for useCallback - // 6. Handler for form submission + // 5. Handler for form submission const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); sendMessage(inputMessage); @@ -96,61 +128,76 @@ const AIChatWidget: React.FC = () => { // --- RENDER FUNCTION (JSX) --- return ( -
+
+ {/* Header */} -
-

TechTorque AI Assistant

+
+
+ +
+

+ TechTorque AI Assistant +

{/* Message Display Area */} -
+
{conversationHistory.map((msg, index) => (
-
+ {msg.sender === 'ai' && } {msg.text}
))} + {/* Typing Indicator */} {isLoading && (
-
- Thinking... +
+ Thinking...
)} -
{/* Auto-scroll reference */} +
{/* Input Form */} -
+ setInputMessage(e.target.value)} - placeholder="Ask about appointments, status, or services..." - className="flex-1 p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" - disabled={isLoading} + placeholder={!userToken ? "Please sign in to chat..." : "Ask about status, scheduling, or services..."} + className={`${theme['theme-input']} flex-1 w-full`} + disabled={isLoading || !userToken} />
+ + {/* Login Warning Message */} + {!userToken && ( +
+

Please log in to start using the AI assistant.

+
+ )}
); };