Implemented comprehensive Romanian Biblical Guide web app: - Next.js 15 with App Router and TypeScript - Material UI 7.3.2 for modern, responsive design - PostgreSQL database with Prisma ORM - Complete Bible reader with book/chapter navigation - AI-powered biblical chat with Romanian responses - Prayer wall for community prayer requests - Advanced Bible search with filters and highlighting - Sample Bible data imported from API.Bible - All API endpoints created and working - Professional Material UI components throughout - Responsive layout with navigation and theme 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
329 lines
11 KiB
TypeScript
329 lines
11 KiB
TypeScript
'use client'
|
|
import {
|
|
Container,
|
|
Grid,
|
|
Card,
|
|
CardContent,
|
|
Typography,
|
|
Box,
|
|
TextField,
|
|
Button,
|
|
Paper,
|
|
List,
|
|
ListItem,
|
|
Avatar,
|
|
Chip,
|
|
IconButton,
|
|
Divider,
|
|
useTheme,
|
|
} from '@mui/material'
|
|
import {
|
|
Chat,
|
|
Send,
|
|
Person,
|
|
SmartToy,
|
|
ContentCopy,
|
|
ThumbUp,
|
|
ThumbDown,
|
|
Refresh,
|
|
} from '@mui/icons-material'
|
|
import { Navigation } from '@/components/layout/navigation'
|
|
import { useState, useRef, useEffect } from 'react'
|
|
|
|
interface ChatMessage {
|
|
id: string
|
|
role: 'user' | 'assistant'
|
|
content: string
|
|
timestamp: Date
|
|
}
|
|
|
|
export default function ChatPage() {
|
|
const theme = useTheme()
|
|
const [messages, setMessages] = useState<ChatMessage[]>([
|
|
{
|
|
id: '1',
|
|
role: 'assistant',
|
|
content: 'Bună ziua! Sunt asistentul tău AI pentru întrebări biblice. Cum te pot ajuta astăzi să înțelegi mai bine Scriptura?',
|
|
timestamp: new Date(),
|
|
}
|
|
])
|
|
const [inputMessage, setInputMessage] = useState('')
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
|
|
|
const scrollToBottom = () => {
|
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|
}
|
|
|
|
useEffect(() => {
|
|
scrollToBottom()
|
|
}, [messages])
|
|
|
|
const handleSendMessage = async () => {
|
|
if (!inputMessage.trim() || isLoading) return
|
|
|
|
const userMessage: ChatMessage = {
|
|
id: Date.now().toString(),
|
|
role: 'user',
|
|
content: inputMessage,
|
|
timestamp: new Date(),
|
|
}
|
|
|
|
setMessages(prev => [...prev, userMessage])
|
|
setInputMessage('')
|
|
setIsLoading(true)
|
|
|
|
try {
|
|
const response = await fetch('/api/chat', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
message: inputMessage,
|
|
history: messages.slice(-5), // Send last 5 messages for context
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to get response')
|
|
}
|
|
|
|
const data = await response.json()
|
|
|
|
const assistantMessage: ChatMessage = {
|
|
id: (Date.now() + 1).toString(),
|
|
role: 'assistant',
|
|
content: data.response || 'Îmi pare rău, nu am putut procesa întrebarea ta. Te rog încearcă din nou.',
|
|
timestamp: new Date(),
|
|
}
|
|
|
|
setMessages(prev => [...prev, assistantMessage])
|
|
} catch (error) {
|
|
console.error('Error sending message:', error)
|
|
const errorMessage: ChatMessage = {
|
|
id: (Date.now() + 1).toString(),
|
|
role: 'assistant',
|
|
content: 'Îmi pare rău, a apărut o eroare. Te rog verifică conexiunea și încearcă din nou.',
|
|
timestamp: new Date(),
|
|
}
|
|
setMessages(prev => [...prev, errorMessage])
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleKeyPress = (event: React.KeyboardEvent) => {
|
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
event.preventDefault()
|
|
handleSendMessage()
|
|
}
|
|
}
|
|
|
|
const copyToClipboard = (text: string) => {
|
|
navigator.clipboard.writeText(text)
|
|
}
|
|
|
|
const suggestedQuestions = [
|
|
'Ce spune Biblia despre iubire?',
|
|
'Explică-mi parabola semănătorului',
|
|
'Care sunt fructele Duhului?',
|
|
'Ce înseamnă să fii născut din nou?',
|
|
'Cum pot să mă rog mai bine?',
|
|
]
|
|
|
|
return (
|
|
<Box>
|
|
<Navigation />
|
|
|
|
<Container maxWidth="lg" sx={{ py: 4 }}>
|
|
{/* Header */}
|
|
<Box sx={{ mb: 4, textAlign: 'center' }}>
|
|
<Typography variant="h3" component="h1" gutterBottom>
|
|
<Chat sx={{ fontSize: 40, mr: 2, verticalAlign: 'middle' }} />
|
|
Chat cu AI Biblic
|
|
</Typography>
|
|
<Typography variant="body1" color="text.secondary">
|
|
Pune întrebări despre Scriptură și primește răspunsuri fundamentate biblic
|
|
</Typography>
|
|
</Box>
|
|
|
|
<Grid container spacing={4}>
|
|
{/* Suggested Questions Sidebar */}
|
|
<Grid item xs={12} md={3}>
|
|
<Card>
|
|
<CardContent>
|
|
<Typography variant="h6" gutterBottom>
|
|
Întrebări sugerate
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
|
Începe cu una dintre aceste întrebări populare:
|
|
</Typography>
|
|
{suggestedQuestions.map((question, index) => (
|
|
<Chip
|
|
key={index}
|
|
label={question}
|
|
onClick={() => setInputMessage(question)}
|
|
sx={{
|
|
mb: 1,
|
|
mr: 1,
|
|
cursor: 'pointer',
|
|
'&:hover': {
|
|
bgcolor: 'primary.light',
|
|
color: 'white',
|
|
},
|
|
}}
|
|
variant="outlined"
|
|
size="small"
|
|
/>
|
|
))}
|
|
|
|
<Divider sx={{ my: 2 }} />
|
|
|
|
<Typography variant="h6" gutterBottom>
|
|
Sfaturi pentru chat
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
• Fii specific în întrebări<br />
|
|
• Menționează pasaje biblice dacă le cunoști<br />
|
|
• Poți întreba despre context istoric<br />
|
|
• Solicită explicații teologice
|
|
</Typography>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
|
|
{/* Main Chat Area */}
|
|
<Grid item xs={12} md={9}>
|
|
<Card sx={{ height: '70vh', display: 'flex', flexDirection: 'column' }}>
|
|
{/* Chat Messages */}
|
|
<Box sx={{ flexGrow: 1, overflow: 'auto', p: 2 }}>
|
|
{messages.map((message) => (
|
|
<Box
|
|
key={message.id}
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: message.role === 'user' ? 'flex-end' : 'flex-start',
|
|
mb: 2,
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
flexDirection: message.role === 'user' ? 'row-reverse' : 'row',
|
|
alignItems: 'flex-start',
|
|
maxWidth: '80%',
|
|
}}
|
|
>
|
|
<Avatar
|
|
sx={{
|
|
bgcolor: message.role === 'user' ? 'primary.main' : 'secondary.main',
|
|
mx: 1,
|
|
}}
|
|
>
|
|
{message.role === 'user' ? <Person /> : <SmartToy />}
|
|
</Avatar>
|
|
|
|
<Paper
|
|
elevation={1}
|
|
sx={{
|
|
p: 2,
|
|
bgcolor: message.role === 'user' ? 'primary.light' : 'background.paper',
|
|
color: message.role === 'user' ? 'white' : 'text.primary',
|
|
borderRadius: 2,
|
|
}}
|
|
>
|
|
<Typography variant="body1" sx={{ whiteSpace: 'pre-wrap' }}>
|
|
{message.content}
|
|
</Typography>
|
|
|
|
{message.role === 'assistant' && (
|
|
<Box sx={{ display: 'flex', gap: 1, mt: 1, justifyContent: 'flex-end' }}>
|
|
<IconButton
|
|
size="small"
|
|
onClick={() => copyToClipboard(message.content)}
|
|
title="Copiază răspunsul"
|
|
>
|
|
<ContentCopy fontSize="small" />
|
|
</IconButton>
|
|
<IconButton size="small" title="Răspuns util">
|
|
<ThumbUp fontSize="small" />
|
|
</IconButton>
|
|
<IconButton size="small" title="Răspuns neutil">
|
|
<ThumbDown fontSize="small" />
|
|
</IconButton>
|
|
</Box>
|
|
)}
|
|
|
|
<Typography
|
|
variant="caption"
|
|
sx={{
|
|
display: 'block',
|
|
textAlign: 'right',
|
|
mt: 1,
|
|
opacity: 0.7,
|
|
}}
|
|
>
|
|
{message.timestamp.toLocaleTimeString('ro-RO', {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
})}
|
|
</Typography>
|
|
</Paper>
|
|
</Box>
|
|
</Box>
|
|
))}
|
|
|
|
{isLoading && (
|
|
<Box sx={{ display: 'flex', justifyContent: 'flex-start', mb: 2 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'flex-start' }}>
|
|
<Avatar sx={{ bgcolor: 'secondary.main', mx: 1 }}>
|
|
<SmartToy />
|
|
</Avatar>
|
|
<Paper elevation={1} sx={{ p: 2, borderRadius: 2 }}>
|
|
<Typography variant="body1">
|
|
Scriu răspunsul...
|
|
</Typography>
|
|
</Paper>
|
|
</Box>
|
|
</Box>
|
|
)}
|
|
|
|
<div ref={messagesEndRef} />
|
|
</Box>
|
|
|
|
{/* Message Input */}
|
|
<Box sx={{ p: 2, borderTop: 1, borderColor: 'divider' }}>
|
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
|
<TextField
|
|
fullWidth
|
|
multiline
|
|
maxRows={4}
|
|
placeholder="Scrie întrebarea ta despre Biblie..."
|
|
value={inputMessage}
|
|
onChange={(e) => setInputMessage(e.target.value)}
|
|
onKeyPress={handleKeyPress}
|
|
disabled={isLoading}
|
|
variant="outlined"
|
|
/>
|
|
<Button
|
|
variant="contained"
|
|
onClick={handleSendMessage}
|
|
disabled={!inputMessage.trim() || isLoading}
|
|
sx={{ minWidth: 'auto', px: 2 }}
|
|
>
|
|
<Send />
|
|
</Button>
|
|
</Box>
|
|
|
|
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
|
|
Apasă Enter pentru a trimite, Shift+Enter pentru linie nouă
|
|
</Typography>
|
|
</Box>
|
|
</Card>
|
|
</Grid>
|
|
</Grid>
|
|
</Container>
|
|
</Box>
|
|
)
|
|
} |