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/api/v1/ai/chat/route.ts b/src/app/api/v1/ai/chat/route.ts new file mode 100644 index 0000000..6e9a7ba --- /dev/null +++ b/src/app/api/v1/ai/chat/route.ts @@ -0,0 +1,37 @@ +// API Route to proxy chat requests to Agent_Bot service +import { NextRequest, NextResponse } from 'next/server'; + +const AGENT_BOT_URL = process.env.AGENT_BOT_URL || 'http://localhost:8091'; + +export async function POST(request: NextRequest) { + try { + // Get the request body + const body = await request.json(); + + // Get the authorization header + const authHeader = request.headers.get('authorization'); + + // Forward the request to Agent_Bot service + const response = await fetch(`${AGENT_BOT_URL}/api/v1/ai/chat`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(authHeader && { 'Authorization': authHeader }), + }, + body: JSON.stringify(body), + }); + + // Get the response data + const data = await response.json(); + + // Return the response with appropriate status + return NextResponse.json(data, { status: response.status }); + + } catch (error) { + console.error('Error proxying to Agent_Bot:', error); + return NextResponse.json( + { detail: 'Failed to connect to AI service' }, + { status: 500 } + ); + } +} diff --git a/src/app/components/chatbot/AIChatWidget.tsx b/src/app/components/chatbot/AIChatWidget.tsx index a829c97..9719b94 100644 --- a/src/app/components/chatbot/AIChatWidget.tsx +++ b/src/app/components/chatbot/AIChatWidget.tsx @@ -1,7 +1,19 @@ -// components/AIChatWidget.tsx import React, { useState, useRef, useEffect, useCallback } from 'react'; +import { Sparkles, Bolt } from 'lucide-react'; import Cookies from 'js-cookie'; +// --- Theme Simulation & Constants --- +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 --- interface Message { text: string; @@ -11,146 +23,179 @@ interface Message { interface ChatResponse { reply: string; session_id: string; + tool_executed?: string | null; } -const API_ENDPOINT = '/api/v1/ai/chat'; // This will be routed by your API Gateway +const API_ENDPOINT = 'http://localhost:8091/api/v1/ai/chat'; const AIChatWidget: React.FC = () => { - // 1. State Management + // State Management + const [userToken, setUserToken] = useState(null); + const [conversationHistory, setConversationHistory] = useState([ - { text: "Hello! I'm TechTorque Assistant. How can I help you with your services or appointments?", sender: 'ai' } + { text: "šŸ‘‹ Hello! I'm TechTorque Assistant, your friendly car service companion! šŸš—\n\nI can help you with:\nāœ… Booking appointments\nāœ… Checking service status\nāœ… Vehicle information\nāœ… Pricing & estimates\n\nWhat can I do for you today? 😊", sender: 'ai' } ]); const [inputMessage, setInputMessage] = useState(''); const [sessionId, setSessionId] = useState(null); const [isLoading, setIsLoading] = useState(false); const messagesEndRef = useRef(null); + + // Check for cookie on mount + useEffect(() => { + const token = Cookies.get('tt_access_token'); + setUserToken(token || null); + }, []); - // Auto-scroll to the latest message + // Auto-scroll to latest message useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [conversationHistory]); - // 3. The Core Logic (Memoized for performance) + // Core Logic const sendMessage = useCallback(async (message: string) => { - // Get token directly from cookies - const userToken = Cookies.get('tt_access_token'); + 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' }; setConversationHistory(prev => [...prev, userMessage]); setInputMessage(''); setIsLoading(true); try { - // 4. 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, }; 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}`, }, body: JSON.stringify(payload), }); if (!response.ok) { - // If 401/403, log out or show an error 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' }; + let replyText = data.reply; + if (data.tool_executed) { + 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); } catch (error: unknown) { console.error("Chat Error:", error); - const errorMessage: Message = { - text: (error instanceof Error && error.message.includes('401')) - ? "Your session has expired. Please log in again." - : "Sorry, I'm having trouble with the services. Try again later.", - sender: 'system' + const errorMessage: Message = { + text: (error instanceof Error && error.message.includes('401')) + ? "šŸ”’ Your session has expired. Please log in again to continue chatting!" + : "āš ļø Oops! I'm having trouble connecting to my services right now. Please try again in a moment! šŸ”„", + sender: 'system' }; setConversationHistory(prev => [...prev, errorMessage]); } finally { setIsLoading(false); } - }, [isLoading, sessionId]); // Dependencies for useCallback + }, [isLoading, sessionId]); - // 6. Handler for form submission + // Handler for form submission const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); sendMessage(inputMessage); }; - // --- RENDER FUNCTION (JSX) --- return ( -
+
+ {/* Header */} -
-

TechTorque AI Assistant

+
+
+ +
+

+ TechTorque AI Assistant +

{/* Message Display Area */} -
+
{conversationHistory.map((msg, index) => ( -
-
- {msg.text} + {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 appointments, status, or services..."} + className="flex-1 px-4 py-3 text-base border border-gray-300 dark:border-gray-600 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-800 dark:text-white" + disabled={isLoading || !userToken} />
+ + {/* Login Warning Message */} + {!userToken && ( +
+

Please log in to start using the AI assistant.

+
+ )}
); }; diff --git a/src/app/components/dashboards/CustomerDashboard.tsx b/src/app/components/dashboards/CustomerDashboard.tsx index fab65b5..32c8fee 100644 --- a/src/app/components/dashboards/CustomerDashboard.tsx +++ b/src/app/components/dashboards/CustomerDashboard.tsx @@ -224,7 +224,7 @@ const CustomerDashboard: React.FC = ({ profile }) => { {/* Floating AI Chat Widget */}
{chatOpen ? ( -
+

TechTorque AI Assistant