main
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
'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'
|
||||
|
||||
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()
|
||||
|
||||
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 {
|
||||
const response = await fetch('/api/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: userMessage,
|
||||
mode: chatMode,
|
||||
history: messages,
|
||||
}),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
setIsTyping(false)
|
||||
addMessage(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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user