Files
unai/src/components/ChatWidget.tsx
T
2025-07-14 10:14:20 +04:00

305 lines
9.1 KiB
TypeScript

'use client'
import React, { useState, useRef, useEffect } from 'react'
import { MessageCircle, X, Send, User, Bot, Heart } from 'lucide-react'
import { useLanguage } from '@/components/providers/LanguageProvider'
import { useAuth } from '@/components/providers/MockAuthProvider'
type Message = {
id: string
text: string
sender: 'user' | 'bot'
timestamp: Date
type?: 'general' | 'mental_health'
}
type ChatMode = 'general' | 'mental_health'
export const ChatWidget: React.FC = () => {
const [isOpen, setIsOpen] = useState(false)
const [messages, setMessages] = useState<Message[]>([])
const [inputText, setInputText] = useState('')
const [isTyping, setIsTyping] = useState(false)
const [chatMode, setChatMode] = useState<ChatMode>('general')
const [showSurvey, setShowSurvey] = useState(false)
const [surveyRating, setSurveyRating] = useState(0)
const messagesEndRef = useRef<HTMLDivElement>(null)
const { t, dir } = useLanguage()
const { user, userProfile } = useAuth()
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [messages])
const addMessage = (text: string, sender: 'user' | 'bot', type?: 'general' | 'mental_health') => {
const newMessage: Message = {
id: Date.now().toString(),
text,
sender,
timestamp: new Date(),
type,
}
setMessages(prev => [...prev, newMessage])
}
const handleSendMessage = async () => {
if (!inputText.trim()) return
const userMessage = inputText.trim()
setInputText('')
addMessage(userMessage, 'user', chatMode)
setIsTyping(true)
try {
// Create userContext if user is logged in
const userContext = user ? {
role: userProfile?.role || 'student',
token: user.id,
profile: userProfile ? {
name: userProfile.name,
email: userProfile.email,
year: userProfile.year,
faculty: userProfile.faculty,
balance: userProfile.balance
} : undefined
} : undefined;
const response = await fetch('/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: userMessage,
mode: chatMode,
history: messages,
userContext
}),
})
const data = await response.json()
setIsTyping(false)
addMessage(data.message || data.response, 'bot', chatMode)
// Check for mental health escalation
if (data.shouldEscalate) {
setTimeout(() => {
addMessage(
`${t('mental_health_support')} - ${t('book_counselor')}: https://calendly.com/university-counseling`,
'bot',
'mental_health'
)
}, 1000)
}
// Show survey after interaction
if (Math.random() > 0.7) {
setTimeout(() => {
setShowSurvey(true)
}, 2000)
}
} catch (error) {
setIsTyping(false)
addMessage('Sorry, I encountered an error. Please try again.', 'bot')
console.error('Chat error:', error)
}
}
const handleSurveySubmit = async () => {
if (surveyRating > 0) {
try {
await fetch('/api/survey', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
rating: surveyRating,
type: 'chat_satisfaction',
sessionId: Date.now().toString(),
}),
})
} catch (error) {
console.error('Survey submission error:', error)
}
}
setShowSurvey(false)
setSurveyRating(0)
}
const switchToMentalHealth = () => {
setChatMode('mental_health')
addMessage(
'I\'m here to provide support. How are you feeling today?',
'bot',
'mental_health'
)
}
const switchToGeneral = () => {
setChatMode('general')
addMessage(
'I can help you with general university questions. What would you like to know?',
'bot',
'general'
)
}
if (!isOpen) {
return (
<button
onClick={() => setIsOpen(true)}
className="fixed bottom-4 right-4 bg-blue-600 text-white p-4 rounded-full shadow-lg hover:bg-blue-700 transition-colors z-50"
aria-label="Open chat"
>
<MessageCircle size={24} />
</button>
)
}
return (
<div className="fixed bottom-4 right-4 w-80 h-96 bg-white rounded-lg shadow-xl border border-gray-200 flex flex-col z-50">
{/* Header */}
<div className="bg-blue-600 text-white p-4 rounded-t-lg flex items-center justify-between">
<div className="flex items-center space-x-2">
<Bot size={20} />
<span className="font-medium">University Assistant</span>
</div>
<button
onClick={() => setIsOpen(false)}
className="hover:bg-blue-700 rounded p-1"
aria-label="Close chat"
>
<X size={16} />
</button>
</div>
{/* Mode Toggle */}
<div className="p-2 bg-gray-50 border-b flex space-x-2">
<button
onClick={switchToGeneral}
className={`px-3 py-1 rounded text-sm ${
chatMode === 'general'
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
{t('chat')}
</button>
<button
onClick={switchToMentalHealth}
className={`px-3 py-1 rounded text-sm flex items-center space-x-1 ${
chatMode === 'mental_health'
? 'bg-pink-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
<Heart size={12} />
<span>{t('wellbeing')}</span>
</button>
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.length === 0 && (
<div className="text-center text-gray-500 text-sm">
<p>{t('ask_question')}</p>
</div>
)}
{messages.map((message) => (
<div
key={message.id}
className={`flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-xs px-3 py-2 rounded-lg text-sm ${
message.sender === 'user'
? 'bg-blue-600 text-white'
: message.type === 'mental_health'
? 'bg-pink-100 text-pink-900 border border-pink-200'
: 'bg-gray-100 text-gray-900'
}`}
>
<div className="flex items-center space-x-2 mb-1">
{message.sender === 'user' ? (
<User size={12} />
) : (
<Bot size={12} />
)}
<span className="text-xs opacity-75">
{message.timestamp.toLocaleTimeString()}
</span>
</div>
<p>{message.text}</p>
</div>
</div>
))}
{isTyping && (
<div className="flex justify-start">
<div className="bg-gray-100 px-3 py-2 rounded-lg text-sm flex items-center space-x-2">
<Bot size={12} />
<span>Typing...</span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* Survey */}
{showSurvey && (
<div className="p-4 bg-yellow-50 border-t border-yellow-200">
<p className="text-sm text-yellow-800 mb-2">{t('survey_rating')}</p>
<div className="flex space-x-2 mb-2">
{[1, 2, 3, 4, 5].map((rating) => (
<button
key={rating}
onClick={() => setSurveyRating(rating)}
className={`w-6 h-6 rounded-full text-xs ${
surveyRating >= rating
? 'bg-yellow-500 text-white'
: 'bg-gray-200 text-gray-600'
}`}
>
{rating}
</button>
))}
</div>
<button
onClick={handleSurveySubmit}
className="bg-yellow-600 text-white px-3 py-1 rounded text-sm hover:bg-yellow-700"
>
{t('submit_feedback')}
</button>
</div>
)}
{/* Input */}
<div className="p-4 border-t">
<div className="flex space-x-2">
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
placeholder={t('type_message')}
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 text-sm"
dir={dir}
/>
<button
onClick={handleSendMessage}
disabled={!inputText.trim()}
className="bg-blue-600 text-white px-3 py-2 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
<Send size={16} />
</button>
</div>
</div>
</div>
)
}