Major Features: - ✅ AI chat with Azure OpenAI GPT-4o integration - ✅ Vector search across Bible versions (ASV English, RVA 1909 Spanish) - ✅ Multi-language support with automatic English fallback - ✅ Bible version citations in responses [ASV] [RVA 1909] - ✅ Random Bible-themed loading messages (5 variants) - ✅ Safe build script with memory guardrails - ✅ 8GB swap memory for build safety - ✅ Stripe donation integration (multiple payment methods) AI Chat Improvements: - Implement vector search with 1536-dim embeddings (Azure text-embedding-ada-002) - Search all Bible versions in user's language, fallback to English - Cite Bible versions properly in AI responses - Add 5 random loading messages: "Searching the Scriptures...", etc. - Fix Ollama conflict (disabled to use Azure OpenAI exclusively) - Optimize hybrid search queries for actual table schema Build & Infrastructure: - Create safe-build.sh script with memory monitoring (prevents server crashes) - Add 8GB swap memory for emergency relief - Document build process in BUILD_GUIDE.md - Set Node.js memory limits (4GB max during builds) Database: - Clean up 115 old vector tables with wrong dimensions - Keep only 2 tables with correct 1536-dim embeddings - Add Stripe schema for donations and subscriptions Documentation: - AI_CHAT_FINAL_STATUS.md - Complete implementation status - AI_CHAT_IMPLEMENTATION_COMPLETE.md - Technical details - BUILD_GUIDE.md - Safe building guide with guardrails - CHAT_LOADING_MESSAGES.md - Loading messages implementation - STRIPE_IMPLEMENTATION_COMPLETE.md - Stripe integration docs - STRIPE_SETUP_GUIDE.md - Stripe configuration guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
193 lines
6.6 KiB
TypeScript
193 lines
6.6 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useRef, useEffect } from 'react'
|
|
import { Send, User } from 'lucide-react'
|
|
import ReactMarkdown from 'react-markdown'
|
|
|
|
// Random Bible-related loading messages
|
|
const LOADING_MESSAGES = [
|
|
"Searching the Scriptures...",
|
|
"Seeking wisdom from God's Word...",
|
|
"Consulting the Holy Scriptures...",
|
|
"Finding relevant Bible verses...",
|
|
"Exploring God's eternal truth..."
|
|
]
|
|
|
|
export function ChatInterface() {
|
|
const [messages, setMessages] = useState<Array<{ role: string; content: string }>>([])
|
|
const [input, setInput] = useState('')
|
|
const [loading, setLoading] = useState(false)
|
|
const [loadingMessage, setLoadingMessage] = useState('')
|
|
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
|
|
|
const scrollToBottom = () => {
|
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
|
|
}
|
|
|
|
useEffect(scrollToBottom, [messages])
|
|
|
|
// Check authentication status on mount
|
|
useEffect(() => {
|
|
const checkAuth = async () => {
|
|
try {
|
|
const token = localStorage.getItem('authToken')
|
|
if (token) {
|
|
const response = await fetch('/api/auth/me', {
|
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
})
|
|
setIsAuthenticated(response.ok)
|
|
}
|
|
} catch (error) {
|
|
setIsAuthenticated(false)
|
|
}
|
|
}
|
|
checkAuth()
|
|
}, [])
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
if (!input.trim() || loading) return
|
|
|
|
const userMessage = { role: 'user', content: input }
|
|
setMessages(prev => [...prev, userMessage])
|
|
setInput('')
|
|
|
|
// Pick a random loading message
|
|
const randomMessage = LOADING_MESSAGES[Math.floor(Math.random() * LOADING_MESSAGES.length)]
|
|
setLoadingMessage(randomMessage)
|
|
setLoading(true)
|
|
|
|
try {
|
|
const token = localStorage.getItem('authToken')
|
|
const headers: any = { 'Content-Type': 'application/json' }
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`
|
|
}
|
|
|
|
const res = await fetch('/api/chat', {
|
|
method: 'POST',
|
|
headers,
|
|
body: JSON.stringify({
|
|
messages: [...messages, userMessage]
|
|
})
|
|
})
|
|
|
|
const data = await res.json()
|
|
setMessages(prev => [...prev, {
|
|
role: 'assistant',
|
|
content: data.response || 'Ne pare rău, nu am putut genera un răspuns.'
|
|
}])
|
|
} catch (error) {
|
|
console.error('Chat error:', error)
|
|
setMessages(prev => [...prev, {
|
|
role: 'assistant',
|
|
content: 'Ne pare rău, a apărut o eroare. Vă rugăm să încercați din nou.'
|
|
}])
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white rounded-lg shadow-md h-[600px] flex flex-col">
|
|
<div className="p-4 border-b">
|
|
<h3 className="text-lg font-semibold">Chat Biblic AI</h3>
|
|
<p className="text-sm text-gray-600">Pune întrebări despre Biblie și primește răspunsuri fundamentate</p>
|
|
</div>
|
|
|
|
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
|
{!isAuthenticated ? (
|
|
<div className="flex flex-col items-center justify-center h-full text-center p-6">
|
|
<User className="w-16 h-16 text-gray-400 mb-4" />
|
|
<h3 className="text-lg font-semibold mb-2">Bun venit la AI Chat Biblic!</h3>
|
|
<p className="text-gray-600 mb-4 max-w-md">
|
|
Pentru a accesa chat-ul AI și a salva conversațiile tale, te rugăm să îți creezi un cont sau să te conectezi.
|
|
</p>
|
|
<button
|
|
onClick={() => {
|
|
window.dispatchEvent(new CustomEvent('auth:sign-in-required'))
|
|
}}
|
|
className="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
|
|
>
|
|
Creează Cont / Conectează-te
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<>
|
|
{messages.length === 0 && (
|
|
<div className="text-center text-gray-500 mt-12">
|
|
<p>Bună ziua! Sunt aici să vă ajut cu întrebările despre Biblie.</p>
|
|
<p className="text-sm mt-2">Puteți începe prin a întreba ceva despre un verset sau o temă biblică.</p>
|
|
</div>
|
|
)}
|
|
|
|
{messages.map((msg, idx) => (
|
|
<div
|
|
key={idx}
|
|
className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
|
|
>
|
|
<div
|
|
className={`max-w-[80%] p-3 rounded-lg ${
|
|
msg.role === 'user'
|
|
? 'bg-blue-500 text-white'
|
|
: 'bg-gray-100 text-gray-800'
|
|
}`}
|
|
>
|
|
{msg.role === 'assistant' ? (
|
|
<div className="prose prose-sm max-w-none">
|
|
<ReactMarkdown>
|
|
{msg.content}
|
|
</ReactMarkdown>
|
|
</div>
|
|
) : (
|
|
<p>{msg.content}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
{loading && (
|
|
<div className="flex justify-start">
|
|
<div className="bg-gray-100 p-4 rounded-lg">
|
|
<div className="flex items-center space-x-3">
|
|
<div className="flex space-x-2">
|
|
<div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce" />
|
|
<div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce delay-100" />
|
|
<div className="w-2 h-2 bg-blue-500 rounded-full animate-bounce delay-200" />
|
|
</div>
|
|
<span className="text-sm text-gray-600 italic">{loadingMessage}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div ref={messagesEndRef} />
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{isAuthenticated && (
|
|
<form onSubmit={handleSubmit} className="p-4 border-t">
|
|
<div className="flex space-x-2">
|
|
<input
|
|
type="text"
|
|
value={input}
|
|
onChange={(e) => setInput(e.target.value)}
|
|
placeholder="Întreabă despre Biblie..."
|
|
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
disabled={loading}
|
|
/>
|
|
<button
|
|
type="submit"
|
|
disabled={loading || !input.trim()}
|
|
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
>
|
|
<Send className="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
</form>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|