333 lines
9.4 KiB
TypeScript
333 lines
9.4 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useRef, useEffect } from 'react';
|
|
import {
|
|
Box,
|
|
TextField,
|
|
IconButton,
|
|
Paper,
|
|
Typography,
|
|
Avatar,
|
|
CircularProgress,
|
|
Chip,
|
|
} from '@mui/material';
|
|
import { Send, SmartToy, Person, AutoAwesome } from '@mui/icons-material';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { useAuth } from '@/lib/auth/AuthContext';
|
|
import apiClient from '@/lib/api/client';
|
|
|
|
interface Message {
|
|
id: string;
|
|
role: 'user' | 'assistant';
|
|
content: string;
|
|
timestamp: Date;
|
|
}
|
|
|
|
const suggestedQuestions = [
|
|
'How much should my baby sleep at 3 months?',
|
|
'What are normal feeding patterns?',
|
|
'When should I introduce solid foods?',
|
|
'Tips for better sleep routine',
|
|
];
|
|
|
|
export const AIChatInterface: React.FC = () => {
|
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
const [input, setInput] = useState('');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
const { user } = useAuth();
|
|
|
|
const scrollToBottom = () => {
|
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
|
};
|
|
|
|
useEffect(() => {
|
|
scrollToBottom();
|
|
}, [messages]);
|
|
|
|
const handleSend = async (message?: string) => {
|
|
const messageText = message || input.trim();
|
|
if (!messageText || isLoading) return;
|
|
|
|
const userMessage: Message = {
|
|
id: Date.now().toString(),
|
|
role: 'user',
|
|
content: messageText,
|
|
timestamp: new Date(),
|
|
};
|
|
|
|
setMessages((prev) => [...prev, userMessage]);
|
|
setInput('');
|
|
setIsLoading(true);
|
|
|
|
try {
|
|
const response = await apiClient.post('/api/v1/ai/chat', {
|
|
message: messageText,
|
|
conversationId: null,
|
|
});
|
|
|
|
const assistantMessage: Message = {
|
|
id: (Date.now() + 1).toString(),
|
|
role: 'assistant',
|
|
content: response.data.data.message,
|
|
timestamp: new Date(),
|
|
};
|
|
|
|
setMessages((prev) => [...prev, assistantMessage]);
|
|
} catch (error) {
|
|
console.error('AI chat error:', error);
|
|
const errorMessage: Message = {
|
|
id: (Date.now() + 1).toString(),
|
|
role: 'assistant',
|
|
content: 'Sorry, I encountered an error. Please try again.',
|
|
timestamp: new Date(),
|
|
};
|
|
setMessages((prev) => [...prev, errorMessage]);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleSuggestedQuestion = (question: string) => {
|
|
handleSend(question);
|
|
};
|
|
|
|
return (
|
|
<Box
|
|
sx={{
|
|
height: 'calc(100vh - 200px)',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
background: 'linear-gradient(135deg, #FFF5F5 0%, #FFE4E1 100%)',
|
|
borderRadius: 2,
|
|
overflow: 'hidden',
|
|
}}
|
|
>
|
|
{/* Header */}
|
|
<Paper
|
|
elevation={0}
|
|
sx={{
|
|
p: 2,
|
|
borderRadius: 0,
|
|
background: 'rgba(255, 255, 255, 0.95)',
|
|
backdropFilter: 'blur(10px)',
|
|
borderBottom: '1px solid',
|
|
borderColor: 'divider',
|
|
}}
|
|
>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
<Avatar sx={{ bgcolor: 'primary.main' }}>
|
|
<SmartToy />
|
|
</Avatar>
|
|
<Box>
|
|
<Typography variant="h6" fontWeight="600">
|
|
AI Parenting Assistant
|
|
</Typography>
|
|
<Typography variant="caption" color="text.secondary">
|
|
Ask me anything about parenting and childcare
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
</Paper>
|
|
|
|
{/* Messages Container */}
|
|
<Box
|
|
sx={{
|
|
flex: 1,
|
|
overflowY: 'auto',
|
|
p: 2,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: 2,
|
|
}}
|
|
>
|
|
{messages.length === 0 && (
|
|
<Box
|
|
sx={{
|
|
flex: 1,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
gap: 3,
|
|
}}
|
|
>
|
|
<AutoAwesome sx={{ fontSize: 64, color: 'primary.main', opacity: 0.5 }} />
|
|
<Typography variant="h6" color="text.secondary" textAlign="center">
|
|
Hi {user?.name}! How can I help you today?
|
|
</Typography>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
flexWrap: 'wrap',
|
|
gap: 1,
|
|
justifyContent: 'center',
|
|
maxWidth: 600,
|
|
}}
|
|
>
|
|
{suggestedQuestions.map((question, index) => (
|
|
<Chip
|
|
key={index}
|
|
label={question}
|
|
onClick={() => handleSuggestedQuestion(question)}
|
|
sx={{
|
|
borderRadius: 3,
|
|
'&:hover': {
|
|
bgcolor: 'primary.light',
|
|
color: 'white',
|
|
},
|
|
}}
|
|
/>
|
|
))}
|
|
</Box>
|
|
</Box>
|
|
)}
|
|
|
|
<AnimatePresence>
|
|
{messages.map((message) => (
|
|
<motion.div
|
|
key={message.id}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.3 }}
|
|
>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
gap: 2,
|
|
justifyContent: message.role === 'user' ? 'flex-end' : 'flex-start',
|
|
}}
|
|
>
|
|
{message.role === 'assistant' && (
|
|
<Avatar sx={{ bgcolor: 'primary.main', mt: 1 }}>
|
|
<SmartToy />
|
|
</Avatar>
|
|
)}
|
|
<Paper
|
|
elevation={0}
|
|
sx={{
|
|
p: 2,
|
|
maxWidth: '70%',
|
|
borderRadius: 3,
|
|
bgcolor:
|
|
message.role === 'user'
|
|
? 'primary.main'
|
|
: 'rgba(255, 255, 255, 0.95)',
|
|
color: message.role === 'user' ? 'white' : 'text.primary',
|
|
backdropFilter: 'blur(10px)',
|
|
}}
|
|
>
|
|
<Typography variant="body1" sx={{ whiteSpace: 'pre-wrap' }}>
|
|
{message.content}
|
|
</Typography>
|
|
<Typography
|
|
variant="caption"
|
|
sx={{
|
|
mt: 1,
|
|
display: 'block',
|
|
opacity: 0.7,
|
|
}}
|
|
>
|
|
{message.timestamp.toLocaleTimeString()}
|
|
</Typography>
|
|
</Paper>
|
|
{message.role === 'user' && (
|
|
<Avatar sx={{ bgcolor: 'secondary.main', mt: 1 }}>
|
|
<Person />
|
|
</Avatar>
|
|
)}
|
|
</Box>
|
|
</motion.div>
|
|
))}
|
|
</AnimatePresence>
|
|
|
|
{isLoading && (
|
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
|
<Avatar sx={{ bgcolor: 'primary.main' }}>
|
|
<SmartToy />
|
|
</Avatar>
|
|
<Paper
|
|
elevation={0}
|
|
sx={{
|
|
p: 2,
|
|
borderRadius: 3,
|
|
bgcolor: 'rgba(255, 255, 255, 0.95)',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 1,
|
|
}}
|
|
>
|
|
<CircularProgress size={20} />
|
|
<Typography variant="body2" color="text.secondary">
|
|
Thinking...
|
|
</Typography>
|
|
</Paper>
|
|
</Box>
|
|
)}
|
|
|
|
<div ref={messagesEndRef} />
|
|
</Box>
|
|
|
|
{/* Input Area */}
|
|
<Paper
|
|
elevation={0}
|
|
sx={{
|
|
p: 2,
|
|
borderRadius: 0,
|
|
background: 'rgba(255, 255, 255, 0.95)',
|
|
backdropFilter: 'blur(10px)',
|
|
borderTop: '1px solid',
|
|
borderColor: 'divider',
|
|
}}
|
|
>
|
|
<Box sx={{ display: 'flex', gap: 1, alignItems: 'flex-end' }}>
|
|
<TextField
|
|
fullWidth
|
|
multiline
|
|
maxRows={4}
|
|
placeholder="Ask me anything..."
|
|
value={input}
|
|
onChange={(e) => setInput(e.target.value)}
|
|
onKeyPress={(e) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleSend();
|
|
}
|
|
}}
|
|
disabled={isLoading}
|
|
sx={{
|
|
'& .MuiOutlinedInput-root': {
|
|
borderRadius: 3,
|
|
},
|
|
}}
|
|
/>
|
|
<IconButton
|
|
color="primary"
|
|
onClick={() => handleSend()}
|
|
disabled={!input.trim() || isLoading}
|
|
sx={{
|
|
width: 48,
|
|
height: 48,
|
|
bgcolor: 'primary.main',
|
|
color: 'white',
|
|
'&:hover': {
|
|
bgcolor: 'primary.dark',
|
|
},
|
|
'&:disabled': {
|
|
bgcolor: 'action.disabledBackground',
|
|
},
|
|
}}
|
|
>
|
|
<Send />
|
|
</IconButton>
|
|
</Box>
|
|
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
|
|
This AI assistant provides general information. Always consult healthcare professionals
|
|
for medical advice.
|
|
</Typography>
|
|
</Paper>
|
|
</Box>
|
|
);
|
|
};
|