feat: Apple-style donation-focused landing page + Azure OpenAI fixes

Major updates:
- Replace homepage with clean, minimalist Apple-style landing page
- Focus on donation messaging and mission statement
- Add comprehensive AI chat analysis documentation
- Fix Azure OpenAI configuration with correct endpoints
- Update embedding API to use text-embedding-ada-002 (1536 dims)

Landing Page Features:
- Hero section with tagline "Every Scripture. Every Language. Forever Free"
- Mission statement emphasizing free access
- Matthew 10:8 verse highlight
- 6 feature cards (Global Library, Multilingual, Prayer Wall, AI Chat, Privacy, Offline)
- Donation CTA sections with PayPal and card options
- "Why It Matters" section with dark background
- Clean footer with navigation links

Technical Changes:
- Updated .env.local with new Azure credentials
- Fixed vector-search.ts to support separate embed API version
- Integrated AuthModal into Bible reader and prayers page
- Made prayer filters collapsible and mobile-responsive
- Changed language picker to single-select

Documentation Created:
- AI_CHAT_FIX_PLAN.md - Comprehensive implementation plan
- AI_CHAT_VERIFICATION_FINDINGS.md - Database analysis
- AI_CHAT_ANALYSIS_SUMMARY.md - Executive summary
- AI_CHAT_STATUS_UPDATE.md - Current status and next steps
- logo.svg - App logo (MenuBook icon)

Build:  Successful (Next.js 15.5.3)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-10 22:38:19 +00:00
parent 71047c85cc
commit 79f1512f3a
17 changed files with 4066 additions and 666 deletions

View File

@@ -0,0 +1,29 @@
# Database
DATABASE_URL=postgresql://postgres:a3ppq@10.0.0.207:5432/biblical-guide
DB_PASSWORD=a3ppq
# Build optimizations
NEXT_TELEMETRY_DISABLED=1
DISABLE_ESLINT_PLUGIN=true
# Authentication
NEXTAUTH_URL=https://biblical-guide.com
NEXTAUTH_SECRET=development-secret-change-in-production
JWT_SECRET=development-jwt-secret-change-in-production
# Azure OpenAI
AZURE_OPENAI_KEY=4sVcRlDOB7WnRK8oE6ATnokpKmc02JgY4GH2ng9y1vr1CyFT7ORLJQQJ99BDAC5RqLJXJ3w3AAAAACOGW8Kh
AZURE_OPENAI_ENDPOINT=https://footprints-open-ai.openai.azure.com
AZURE_OPENAI_DEPLOYMENT=gpt-4o
AZURE_OPENAI_API_VERSION=2025-01-01-preview
# API Bible
API_BIBLE_KEY=7b42606f8f809e155c9b0742c4f1849b
# Ollama for embeddings
OLLAMA_API_URL=http://localhost:11434
OLLAMA_EMBED_MODEL=llama3.1:latest
BIBLE_JSON_DIR=/root/biblical-guide/bibles/json
# WebSocket port
WEBSOCKET_PORT=3015

View File

@@ -12,13 +12,14 @@ NEXTAUTH_URL=https://biblical-guide.com
NEXTAUTH_SECRET=development-secret-change-in-production NEXTAUTH_SECRET=development-secret-change-in-production
JWT_SECRET=development-jwt-secret-change-in-production JWT_SECRET=development-jwt-secret-change-in-production
# Azure OpenAI # Azure OpenAI (Updated 2025-10-10)
AZURE_OPENAI_KEY=4DhkkXVdDOXZ7xX1eOLHTHQQnbCy0jFYdA6RPJtyAdOMtO16nZmFJQQJ99BCACYeBjFXJ3w3AAABACOGHgNC AZURE_OPENAI_KEY=42702a67a41547919877a2ab8e4837f9
AZURE_OPENAI_ENDPOINT=https://azureopenaiinstant.openai.azure.com AZURE_OPENAI_ENDPOINT=https://footprints-ai.openai.azure.com
AZURE_OPENAI_DEPLOYMENT=gpt-4o AZURE_OPENAI_DEPLOYMENT=gpt-4o
AZURE_OPENAI_API_VERSION=2024-05-01-preview AZURE_OPENAI_API_VERSION=2025-01-01-preview
AZURE_OPENAI_EMBED_DEPLOYMENT=embed-3 AZURE_OPENAI_EMBED_DEPLOYMENT=Text-Embedding-ada-002-V2
EMBED_DIMS=3072 AZURE_OPENAI_EMBED_API_VERSION=2023-05-15
EMBED_DIMS=1536
BIBLE_MD_PATH=./bibles/Biblia-Fidela-limba-romana.md BIBLE_MD_PATH=./bibles/Biblia-Fidela-limba-romana.md
LANG_CODE=ro LANG_CODE=ro
TRANSLATION_CODE=FIDELA TRANSLATION_CODE=FIDELA

402
AI_CHAT_ANALYSIS_SUMMARY.md Normal file
View File

@@ -0,0 +1,402 @@
# AI Chat System Analysis - Executive Summary
**Date:** 2025-10-10
**Analyst:** Claude Code
**Status:** 🔴 Critical Issues Found - Requires User Action
---
## 🎯 Bottom Line
The AI chat system has **excellent infrastructure** (vector database, search algorithms) but is blocked by **two critical issues**:
1. **❌ Azure OpenAI Not Configured** - No deployments exist or are accessible
2. **❌ Wrong Bible Versions** - Priority languages (Romanian, Spanish, Italian) are NOT in database
**Good News:**
- ✅ Ollama embedding model is being installed now (alternative to Azure)
- ✅ Vector search code is production-ready
- ✅ Database has 116 fully-embedded Bible versions
---
## 📊 System Status Report
### Vector Database: ✅ EXCELLENT (100%)
| Component | Status | Details |
|-----------|--------|---------|
| PostgreSQL Connection | ✅ Working | v17.5 |
| pgvector Extension | ✅ Installed | v0.8.0 |
| Schema `ai_bible` | ✅ Exists | Ready |
| Total Vector Tables | ✅ 116 tables | 100% embedded |
| Languages Supported | ⚠️ 47 languages | BUT missing priority ones |
### AI API Status: ❌ BLOCKED
| Service | Status | Issue |
|---------|--------|-------|
| Azure OpenAI Chat | ❌ Not Working | Deployment `gpt-4o` not found (404) |
| Azure OpenAI Embeddings | ❌ Not Working | Deployment `embed-3` not found (404) |
| Ollama (Local AI) | 🔄 Installing | `nomic-embed-text` downloading now |
### Vector Search Code: ✅ READY
| Feature | Status | Location |
|---------|--------|----------|
| Multi-table search | ✅ Implemented | `/lib/vector-search.ts:109` |
| Hybrid search (vector + text) | ✅ Implemented | `/lib/vector-search.ts:163` |
| Language filtering | ✅ Implemented | Table pattern: `bv_{lang}_{version}` |
| Chat integration | ✅ Implemented | `/app/api/chat/route.ts:190` |
---
## 🚨 Critical Issue #1: Wrong Bible Versions
### User Requirements vs. Reality
**What You Need:**
- ✅ English
- ❌ Romanian (ro)
- ❌ Spanish (es)
- ❌ Italian (it)
**What's in Database:**
- ✅ English: 9 versions (KJV, ASV, etc.)
- ❌ Romanian: **NOT FOUND**
- ❌ Spanish: **NOT FOUND**
- ❌ Italian: **NOT FOUND**
### What IS in the Database (47 Languages)
The 116 tables contain mostly obscure languages:
- `ab` (Abkhazian), `ac` (Acholi), `ad` (Adangme), `ag` (Aguacateca), etc.
- German (de), Dutch (nl), French (fr) ✓
- But **NO Romanian, Spanish, or Italian**
### Where These Tables Came From
Looking at your environment variable:
```bash
BIBLE_MD_PATH=./bibles/Biblia-Fidela-limba-romana.md
LANG_CODE=ro
TRANSLATION_CODE=FIDELA
```
You have Romanian Bible data (`Fidela`) but it's **NOT in the vector database yet**.
---
## 🚨 Critical Issue #2: Azure OpenAI Not Configured
### The Problem
Your `.env.local` has:
```bash
AZURE_OPENAI_DEPLOYMENT=gpt-4o
AZURE_OPENAI_EMBED_DEPLOYMENT=embed-3
```
But when we try to access these deployments:
```
❌ Error 404: DeploymentNotFound
"The API deployment for this resource does not exist"
```
### Tested All Common Names - None Work
We automatically tested these deployment names:
- Chat: `gpt-4`, `gpt-4o`, `gpt-35-turbo`, `gpt-4-32k`, `chat`, `gpt4`, `gpt4o`
- Embeddings: `text-embedding-ada-002`, `text-embedding-3-small`, `embed`, `embed-3`, `ada-002`
**Result:** All returned 404
### What This Means
Either:
1. **No deployments have been created yet** in your Azure OpenAI resource
2. **Deployments have custom names** that we can't guess
3. **API key doesn't have access** to the deployments
### How to Check
1. Go to Azure Portal: https://portal.azure.com
2. Find your resource: `azureopenaiinstant.openai.azure.com`
3. Click "Deployments" or "Model deployments"
4. **Screenshot what you see** and share deployment names
---
## ✅ The Good News: Ollama Alternative
### Ollama is Available Locally
We found Ollama running on your server:
- URL: `http://localhost:11434`
- Chat model installed: `llama3.1:latest`
- Embedding model: `nomic-embed-text` (downloading now... ~260MB)
### What Ollama Can Do
| Capability | Status |
|------------|--------|
| Generate embeddings | ✅ Yes (once download completes) |
| Vector search queries | ✅ Yes |
| Generate chat responses | ✅ Yes (using llama3.1) |
| **Cost** | ✅ **FREE** (runs locally) |
### Ollama vs. Azure OpenAI
| Feature | Ollama | Azure OpenAI |
|---------|--------|--------------|
| Cost | Free | Pay per token |
| Speed | Fast (local) | Moderate (network) |
| Quality | Good | Excellent |
| Multilingual | Good | Excellent |
| Configuration | ✅ Working now | ❌ Broken |
---
## 🎬 What Happens Next
### Option A: Use Ollama (Can Start Now)
**Pros:**
- ✅ Already working on your server
- ✅ Free (no API costs)
- ✅ Fast (local processing)
- ✅ Can generate embeddings for Romanian/Spanish/Italian Bibles
**Cons:**
- ⚠️ Slightly lower quality than GPT-4
- ⚠️ Requires local compute resources
**Implementation:**
1. Wait for `nomic-embed-text` download to complete (~2 minutes)
2. Update `.env.local` to prefer Ollama:
```bash
OLLAMA_API_URL=http://localhost:11434
OLLAMA_EMBED_MODEL=nomic-embed-text
```
3. Create embeddings for Romanian/Spanish/Italian Bibles
4. Chat will use `llama3.1` for responses
### Option B: Fix Azure OpenAI (Requires Azure Access)
**Pros:**
- ✅ Higher quality responses (GPT-4)
- ✅ Better multilingual support
- ✅ Scalable for many users
**Cons:**
- ❌ Costs money per API call
- ❌ Requires Azure Portal access
- ❌ Blocked until deployments are created
**Implementation:**
1. Log into Azure Portal
2. Go to Azure OpenAI resource
3. Create two deployments:
- Chat: Deploy `gpt-4` or `gpt-35-turbo` (name it anything)
- Embeddings: Deploy `text-embedding-ada-002` or `text-embedding-3-small`
4. Update `.env.local` with actual deployment names
5. Test with our verification script
### Option C: Hybrid (Best of Both)
Use Ollama for embeddings (free) + Azure for chat (quality):
```bash
# Use Ollama for embeddings
OLLAMA_API_URL=http://localhost:11434
OLLAMA_EMBED_MODEL=nomic-embed-text
# Use Azure for chat (once fixed)
AZURE_OPENAI_DEPLOYMENT=<your-deployment-name>
```
---
## 📋 Required Actions (In Order)
### Immediate (Today)
1. **Decision:** Choose Option A (Ollama), B (Azure), or C (Hybrid)
2. **If Ollama (Option A or C):**
- ✅ Download is in progress
- Wait 2-5 minutes for completion
- Test with: `curl -X POST http://localhost:11434/api/embeddings -d '{"model":"nomic-embed-text","prompt":"test"}'`
3. **If Azure (Option B or C):**
- Log into Azure Portal
- Navigate to Azure OpenAI resource
- Check/create deployments
- Share deployment names
### Short-term (This Week)
4. **Get Romanian Bible Data:**
- Source: `/bibles/Biblia-Fidela-limba-romana.md` (already exists!)
- Need: Cornilescu version (if available)
- Action: Create embeddings and import
5. **Get Spanish Bible Data:**
- Source needed: RVR1960 (Reina-Valera 1960)
- Optional: NVI (Nueva Versión Internacional)
- Action: Find source, create embeddings, import
6. **Get Italian Bible Data:**
- Source needed: Nuova Diodati
- Optional: Nuova Riveduta
- Action: Find source, create embeddings, import
### Medium-term (Next 2 Weeks)
7. **Implement English Fallback:**
- When Romanian/Spanish/Italian searches return poor results
- Automatically search English versions
- Add language indicator in citations: `[KJV - English] John 3:16`
8. **Create Version Config Table:**
- Track which versions are complete
- Map versions to languages
- Enable smart fallback logic
9. **Testing:**
- Test Romanian queries → Romanian results
- Test Spanish queries → Spanish results
- Test Italian queries → Italian results
- Test fallback when needed
---
## 🔧 Technical Details
### Current Database Schema
Table naming pattern:
```
ai_bible.bv_{language_code}_{version_abbreviation}
Examples:
- ai_bible.bv_en_eng_kjv ✅ Exists (English KJV)
- ai_bible.bv_ro_cornilescu ❌ Needed (Romanian Cornilescu)
- ai_bible.bv_es_rvr1960 ❌ Needed (Spanish RVR1960)
- ai_bible.bv_it_nuovadiodati ❌ Needed (Italian Nuova Diodati)
```
### Table Structure (All 116 tables have this)
| Column | Type | Description |
|--------|------|-------------|
| `id` | uuid | Primary key |
| `testament` | text | OT/NT |
| `book` | text | Book name |
| `chapter` | integer | Chapter number |
| `verse` | integer | Verse number |
| `language` | text | Language code |
| `translation` | text | Version abbreviation |
| `ref` | text | "Genesis 1:1" format |
| `text_raw` | text | Verse text |
| `text_norm` | text | Normalized text |
| `tsv` | tsvector | Full-text search index |
| **`embedding`** | vector | **Vector embedding (3072 dims)** |
| `created_at` | timestamp | Creation time |
| `updated_at` | timestamp | Update time |
### Embedding Dimensions
Current `.env.local` says:
```bash
EMBED_DIMS=3072
```
This matches:
- ✅ Azure `text-embedding-3-small` (3072 dims)
- ✅ Azure `text-embedding-3-large` (3072 dims)
- ❌ Azure `text-embedding-ada-002` (1536 dims) - **INCOMPATIBLE**
- ✅ Ollama `nomic-embed-text` (768 dims default, but can use 3072)
**Important:** If using Ollama, we may need to adjust embedding dimensions or re-create tables.
---
## 💡 Recommendations
### My Recommendation: Start with Ollama
**Why:**
1. ✅ It's already working (or will be in 5 minutes)
2. ✅ Free (no API costs while developing)
3. ✅ Can immediately create Romanian embeddings from your `Fidela` Bible
4. ✅ Unblocks development
**Then:**
- Add Azure OpenAI later for higher quality (when deployments are fixed)
- Use hybrid: Ollama for embeddings, Azure for chat
### Workflow I Suggest
```
Today:
→ Finish installing Ollama embedding model
→ Test embedding generation
→ Create embeddings for Fidela Romanian Bible
→ Import into ai_bible.bv_ro_fidela
→ Test Romanian chat
This Week:
→ Fix Azure deployments (for better chat quality)
→ Find Spanish RVR1960 data
→ Find Italian Nuova Diodati data
→ Create embeddings for both
→ Import into database
Next Week:
→ Implement English fallback
→ Add version metadata table
→ Create test suite
→ Optimize performance
```
---
## 📞 Questions for You
1. **AI Provider:** Do you want to use Ollama (free, local) or fix Azure OpenAI (better quality, costs money)?
2. **Azure Access:** Do you have access to the Azure Portal to check/create deployments?
3. **Bible Data:** Do you have Spanish (RVR1960) and Italian (Nuova Diodati) Bible data, or do we need to source it?
4. **Fidela Bible:** The file `./bibles/Biblia-Fidela-limba-romana.md` exists - should we create embeddings for this now?
5. **Embedding Dimensions:** Are you okay with potentially re-creating embedding tables with different dimensions if we switch from Azure (3072) to Ollama (768)?
---
## 📄 Reference Documents
| Document | Purpose | Location |
|----------|---------|----------|
| Implementation Plan | Detailed technical plan | `/AI_CHAT_FIX_PLAN.md` |
| Verification Findings | Database analysis | `/AI_CHAT_VERIFICATION_FINDINGS.md` |
| This Summary | Executive overview | `/AI_CHAT_ANALYSIS_SUMMARY.md` |
| Verification Script | System health check | `/scripts/verify-ai-system.ts` |
| Deployment Discovery | Find Azure deployments | `/scripts/discover-azure-deployments.ts` |
---
## ✅ Next Action
**Waiting for your decision:**
- Option A: Use Ollama ← **Recommended to start**
- Option B: Fix Azure OpenAI
- Option C: Hybrid approach
Once you decide, I can immediately proceed with implementation.
---
**Status:** Analysis complete. Ready to implement based on your choice. 🚀

1877
AI_CHAT_FIX_PLAN.md Normal file

File diff suppressed because it is too large Load Diff

287
AI_CHAT_STATUS_UPDATE.md Normal file
View File

@@ -0,0 +1,287 @@
# AI Chat System - Status Update
**Date:** 2025-10-10
**Status:** ✅ Azure OpenAI Fixed | ⚠️ Need New Vector Tables
---
## 🎉 GOOD NEWS: Azure OpenAI is Working!
### ✅ What We Fixed
Both Azure OpenAI APIs are now **fully operational**:
| API | Status | Details |
|-----|--------|---------|
| **Chat API** | ✅ WORKING | GPT-4o responding correctly |
| **Embedding API** | ✅ WORKING | text-embedding-ada-002 generating 1536-dim vectors |
**Updated Configuration:**
```bash
AZURE_OPENAI_ENDPOINT=https://footprints-ai.openai.azure.com
AZURE_OPENAI_KEY=42702a67a41547919877a2ab8e4837f9
# Chat
AZURE_OPENAI_DEPLOYMENT=gpt-4o
AZURE_OPENAI_API_VERSION=2025-01-01-preview
# Embeddings
AZURE_OPENAI_EMBED_DEPLOYMENT=Text-Embedding-ada-002-V2
AZURE_OPENAI_EMBED_API_VERSION=2023-05-15
EMBED_DIMS=1536
```
---
## ⚠️ CRITICAL ISSUE: Embedding Dimension Mismatch
### The Problem
- **Existing 116 vector tables:** 4096-dimensional embeddings
- **Our embedding model (ada-002):** 1536-dimensional embeddings
- **Result:** **Cannot use existing tables**
### What This Means
The 116 Bible versions currently in the database were created with a **different embedding model** (likely text-embedding-3-large with 4096 dims). We cannot search them with our 1536-dim embeddings because the dimensions must match exactly.
### The Solution
Create **new vector tables** for your priority languages with **1536-dim embeddings**:
1.**English** - Use existing Bible data (KJV, ASV, etc.)
2.**Romanian** - Need Bible source data
3.**Spanish** - Need Bible source data
4.**Italian** - Need Bible source data
---
## 📋 What We Need To Do Next
### Option 1: Create New 1536-Dim Tables (RECOMMENDED)
**Pros:**
- ✅ Works with our current Azure setup
- ✅ Lower cost (ada-002 is cheaper than 3-large)
- ✅ Faster searches (smaller vectors)
- ✅ Sufficient quality for Bible search
**Steps:**
1. Find/prepare Bible source data for each language
2. Generate 1536-dim embeddings using our ada-002 deployment
3. Create new tables: `bv_1536_ro_cornilescu`, `bv_1536_es_rvr1960`, etc.
4. Import embeddings into new tables
5. Update search logic to use new tables
### Option 2: Use Different Embedding Model (Not Recommended)
Deploy text-embedding-3-large (4096-dim) to match existing tables.
**Cons:**
- ❌ Higher cost
- ❌ Slower searches
- ❌ Requires Azure deployment changes
- ❌ Still missing Romanian/Spanish/Italian in existing tables
---
## 🗂️ Bible Source Data Status
### What We Have
**Romanian (Fidela):** `/bibles/Biblia-Fidela-limba-romana.md`
- Ready to process!
- Can generate embeddings immediately
### What We Need
**Romanian (Cornilescu):** Most popular Romanian version
- Need to source this Bible translation
- Options: Bible Gateway API, online sources, existing files
**Spanish (RVR1960):** Most popular Spanish version
- Reina-Valera 1960
- Need to source
**Italian (Nuova Diodati):** Popular Italian version
- Need to source
**English versions:** KJV, ASV, NIV, etc.
- Can source from Bible Gateway, bible.org, or similar
---
## 🚀 Recommended Next Steps
### Immediate (Today)
1. **Test the chat system** with a simple fallback:
- Temporarily disable vector search
- Have chat work without Bible verse context
- Verify end-to-end flow is working
2. **Process Romanian Fidela Bible:**
- Read `/bibles/Biblia-Fidela-limba-romana.md`
- Parse into verse-by-verse format
- Generate embeddings using ada-002
- Create table `ai_bible.bv_1536_ro_fidela`
- Import data
### Short-term (This Week)
3. **Source English Bible data:**
- Download KJV (public domain)
- Parse and generate embeddings
- Create table `ai_bible.bv_1536_en_kjv`
4. **Source Romanian Cornilescu:**
- Find public domain source
- Parse and generate embeddings
- Create table `ai_bible.bv_1536_ro_cornilescu`
5. **Source Spanish RVR1960:**
- Find public domain source
- Parse and generate embeddings
- Create table `ai_bible.bv_1536_es_rvr1960`
6. **Source Italian Nuova Diodati:**
- Find source
- Parse and generate embeddings
- Create table `ai_bible.bv_1536_it_nuovadiodati`
### Medium-term (Next 2 Weeks)
7. **Implement English Fallback Logic:**
- Search primary language first
- Fall back to English if results are poor
- Add language indicators in citations
8. **Create Version Metadata Table:**
- Track which versions are available
- Map versions to languages
- Enable smart version selection
9. **Testing & Optimization:**
- Test all 4 languages
- Optimize query performance
- Add monitoring
---
## 📊 Database Schema for New Tables
### Table Naming Convention
```
ai_bible.bv_1536_{language}_{version}
Examples:
- ai_bible.bv_1536_en_kjv
- ai_bible.bv_1536_ro_fidela
- ai_bible.bv_1536_ro_cornilescu
- ai_bible.bv_1536_es_rvr1960
- ai_bible.bv_1536_it_nuovadiodati
```
### Table Structure
```sql
CREATE TABLE ai_bible.bv_1536_ro_fidela (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
testament TEXT NOT NULL, -- 'OT' or 'NT'
book TEXT NOT NULL,
chapter INTEGER NOT NULL,
verse INTEGER NOT NULL,
language TEXT NOT NULL, -- 'ro'
translation TEXT NOT NULL, -- 'FIDELA'
ref TEXT NOT NULL, -- 'Genesis 1:1'
text_raw TEXT NOT NULL, -- Original verse text
text_norm TEXT, -- Normalized for search
tsv TSVECTOR, -- Full-text search index
embedding VECTOR(1536), -- 1536-dimensional embedding
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Create indexes
CREATE INDEX idx_bv_1536_ro_fidela_ref ON ai_bible.bv_1536_ro_fidela(ref);
CREATE INDEX idx_bv_1536_ro_fidela_book_chapter ON ai_bible.bv_1536_ro_fidela(book, chapter);
CREATE INDEX idx_bv_1536_ro_fidela_tsv ON ai_bible.bv_1536_ro_fidela USING gin(tsv);
CREATE INDEX idx_bv_1536_ro_fidela_embedding ON ai_bible.bv_1536_ro_fidela
USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
```
---
## 🛠️ Implementation Script Needed
We need a script to:
1. **Parse Bible source file** (Markdown, JSON, CSV, etc.)
2. **Generate embeddings** for each verse
3. **Create table** if not exists
4. **Insert verses** with embeddings
5. **Create indexes**
**Example workflow:**
```bash
# Process Romanian Fidela Bible
npx tsx scripts/import-bible.ts \
--source ./bibles/Biblia-Fidela-limba-romana.md \
--language ro \
--translation FIDELA \
--table bv_1536_ro_fidela
```
---
## 💡 Quick Test - Chat Without Vector Search
To verify the chat system works end-to-end, we can temporarily:
1. Modify chat API to skip vector search
2. Test chat with general biblical knowledge (GPT-4o has Bible knowledge)
3. Verify authentication, conversation saving, and UI work
4. Then add vector search back once tables are ready
**Would you like me to:**
- ❓ Test chat without vector search first?
- ❓ Start processing the Romanian Fidela Bible?
- ❓ Create the Bible import script?
- ❓ Something else?
---
## 📄 Files Updated
| File | Status | Purpose |
|------|--------|---------|
| `.env.local` | ✅ Updated | New Azure credentials, 1536 dims |
| `lib/vector-search.ts` | ✅ Updated | Support separate embed API version |
| `scripts/test-azure-quick.ts` | ✅ Created | Quick API testing |
| `AI_CHAT_STATUS_UPDATE.md` | ✅ Created | This document |
---
## ✅ Summary
**What's Working:**
- ✅ Azure OpenAI Chat (GPT-4o)
- ✅ Azure OpenAI Embeddings (ada-002, 1536-dim)
- ✅ Database connection
- ✅ pgvector extension
- ✅ Search code (just needs right tables)
**What's Blocked:**
- ❌ Cannot use existing 116 tables (4096-dim vs 1536-dim mismatch)
- ❌ Need new vector tables for Romanian/Spanish/Italian/English
- ❌ Need Bible source data for Spanish and Italian
**Next Decision Point:**
Choose what to do next:
1. Test chat system without vector search (quick validation)
2. Start creating vector tables with Fidela Romanian Bible (first language)
3. Source and process English KJV (for fallback)
4. All of the above in parallel
**Your call!** 🚀

View File

@@ -0,0 +1,334 @@
# AI Chat System Verification Findings
**Date:** 2025-10-10
**Status:** 🟡 Partially Operational - Configuration Issue Found
## Executive Summary
The AI chat vector database is **fully operational** with 116 Bible versions across 47 languages, all with complete embeddings. However, there is a **critical configuration issue** with the Azure OpenAI API deployments that prevents the chat from functioning.
---
## ✅ What's Working
### 1. Vector Database Infrastructure (100% Operational)
- **Database Connection:** PostgreSQL 17.5 ✓
- **pgvector Extension:** v0.8.0 installed ✓
- **Schema:** `ai_bible` schema exists ✓
### 2. Bible Vector Tables (116 Tables - Fully Populated)
| Metric | Value |
|--------|-------|
| Total Vector Tables | **116** |
| Languages Supported | **47** |
| Embedding Coverage | **100%** for all tables |
| Table Structure | Correct (all have embedding, tsv, ref, text_raw, etc.) |
**Sample Table Statistics:**
- `bv_ab_aau`: 7,923 verses (100% embedded)
- `bv_ac_aca`: 4,406 verses (100% embedded)
- `bv_ac_acr_acc`: 7,930 verses (100% embedded)
### 3. Languages Available
The system currently supports **47 languages** including:
- **English (en):** 9 versions (ASV, Brenton, KJV, KJV2006, LXX2012, RV, T4T, UK_LXX2012, WEB_C)
- **German (de):** 2 versions
- **Dutch (nl):** 3 versions
- **French (fr):** 1 version
- And 43+ other languages
**Note:** User requested support for **Romanian (ro), Spanish (es), and Italian (it)** but these languages are **NOT found** in the vector database. This is a critical gap.
### 4. Current Vector Search Implementation
The existing code in `/root/biblical-guide/lib/vector-search.ts` already implements:
- ✅ Multi-table search across all versions for a given language
- ✅ Hybrid search (vector + full-text)
- ✅ Language-based table filtering
- ✅ Proper query pattern: `bv_{lang}_{version}`
---
## ❌ What's Broken
### 1. Azure OpenAI API Configuration (CRITICAL)
**Problem:** The deployment names in `.env.local` do not exist in the Azure OpenAI resource.
**Environment Variables:**
```bash
AZURE_OPENAI_DEPLOYMENT=gpt-4o # ❌ Deployment NOT FOUND (404)
AZURE_OPENAI_EMBED_DEPLOYMENT=embed-3 # ❌ Deployment NOT FOUND (404)
```
**Error Message:**
```
DeploymentNotFound: The API deployment for this resource does not exist.
```
**Impact:**
- Chat API cannot generate responses
- Embedding generation fails
- Vector search cannot create query embeddings
### 2. Missing Priority Languages
**User Requirements:** Romanian (ro), Spanish (es), Italian (it)
**Current Status:**
-**Romanian (ro):** NOT in vector database
-**Spanish (es):** NOT in vector database
-**Italian (it):** NOT in vector database
**Available Languages:** The current 47 languages are mostly obscure languages (ab, ac, ad, ag, etc.) and do NOT include the user's priority languages.
---
## 🔧 Required Fixes
### Priority 1: Fix Azure OpenAI Deployments (IMMEDIATE)
**Action Required:**
1. Identify the correct deployment names in the Azure OpenAI resource
2. Update `.env.local` with correct values:
- `AZURE_OPENAI_DEPLOYMENT=<actual-chat-deployment-name>`
- `AZURE_OPENAI_EMBED_DEPLOYMENT=<actual-embedding-deployment-name>`
**Options to Find Correct Deployment Names:**
- Option A: Check Azure Portal → Azure OpenAI → Deployments
- Option B: Contact Azure admin who created the resource
- Option C: Check deployment history/documentation
**Expected Deployment Patterns:**
- Chat: Usually named like `gpt-4`, `gpt-4-32k`, `gpt-35-turbo`, etc.
- Embeddings: Usually named like `text-embedding-ada-002`, `text-embedding-3-small`, etc.
### Priority 2: Add Priority Language Vector Tables (HIGH)
**Missing Tables Needed:**
```sql
-- Romanian versions
ai_bible.bv_ro_cornilescu (Cornilescu Bible)
ai_bible.bv_ro_fidela (Fidela Bible - mentioned in BIBLE_MD_PATH)
-- Spanish versions
ai_bible.bv_es_rvr1960 (Reina-Valera 1960)
ai_bible.bv_es_nvi (Nueva Versión Internacional)
-- Italian versions
ai_bible.bv_it_nuovadiodati (Nuova Diodati)
ai_bible.bv_it_nuovariveduta (Nuova Riveduta)
```
**Action Required:**
1. Verify if these Bible versions exist in source data
2. Create embeddings for each version
3. Import into `ai_bible` schema with proper naming
### Priority 3: Implement English Fallback (MEDIUM)
**Current Behavior:**
- Search only looks in language-specific tables (e.g., only `bv_ro_*` for Romanian)
- If language not found, returns empty results
**Required Behavior:**
1. Search in primary language tables first
2. Check result quality (min 3 results, top similarity > 0.75)
3. If insufficient → fallback to English (`bv_en_*` tables)
4. Return combined results with language indicators
**Implementation:** Already planned in `/root/biblical-guide/AI_CHAT_FIX_PLAN.md`
---
## 📊 Current System Architecture
### Vector Search Flow (Working)
```
User Query
getEmbedding(query) ❌ FAILS HERE - Deployment Not Found
searchBibleHybrid(query, language, limit)
getAllVectorTables(language) ✓ Returns tables like ["ai_bible.bv_en_eng_kjv", ...]
For each table:
- Vector similarity search (embedding <=> query)
- Full-text search (tsv @@ plainto_tsquery)
- Combine scores (0.7 * vector + 0.3 * text)
Sort by combined_score and return top results
```
### Chat API Flow (Partially Working)
```
User Message
[Auth Check] ✓ Working
[Conversation Management] ✓ Working
generateBiblicalResponse(message, locale, history)
searchBibleHybrid(message, locale, 5) ❌ FAILS - Embedding API 404
[Build Context with Verses] ✓ Would work if embeddings worked
[Call Azure OpenAI Chat API] ❌ FAILS - Chat API 404
[Save to Database] ✓ Working
```
---
## 🎯 Implementation Plan
### Phase 1: Fix Azure OpenAI (Day 1 - URGENT)
1. **Identify Correct Deployments**
- Check Azure Portal
- List all available deployments in the resource
- Document deployment names and models
2. **Update Environment Configuration**
- Update `.env.local` with correct deployment names
- Verify API version compatibility
- Test connection with verification script
3. **Validate Fix**
- Run `npx tsx scripts/verify-ai-system.ts`
- Confirm both Chat API and Embedding API pass
- Test end-to-end chat flow
### Phase 2: Add Priority Languages (Days 2-3)
1. **Romanian (ro)**
- Source Bible data for Cornilescu and Fidela versions
- Create embeddings using Azure OpenAI
- Import into `ai_bible.bv_ro_cornilescu` and `ai_bible.bv_ro_fidela`
2. **Spanish (es)**
- Source Bible data for RVR1960 and NVI
- Create embeddings
- Import into respective tables
3. **Italian (it)**
- Source Bible data for Nuova Diodati and Nuova Riveduta
- Create embeddings
- Import into respective tables
### Phase 3: Implement Fallback Logic (Day 4)
1. **Update `searchBibleHybrid` Function**
- Add quality check logic
- Implement English fallback
- Add language indicators to results
2. **Update Chat API Response**
- Include source language in citations
- Inform user when fallback was used
- Format: `[KJV - English fallback] John 3:16`
### Phase 4: Testing (Day 5)
1. **Test Each Language**
- Romanian queries → Romanian results
- Spanish queries → Spanish results
- Italian queries → Italian results
- Unsupported language → English fallback
2. **Test Edge Cases**
- Empty results handling
- Mixed language queries
- Very specific vs. general queries
3. **Performance Testing**
- Query response time (target < 2s)
- Multi-table search performance
- Concurrent user handling
---
## 📝 Next Steps
### Immediate Actions (Today)
1. ✅ Run verification script (COMPLETED)
2. ✅ Document findings (COMPLETED)
3. 🔲 Fix Azure OpenAI deployment configuration
- Identify correct deployment names
- Update `.env.local`
- Re-run verification script
### Short-term Actions (This Week)
4. 🔲 Source Romanian Bible data (Cornilescu, Fidela)
5. 🔲 Source Spanish Bible data (RVR1960, NVI)
6. 🔲 Source Italian Bible data (Nuova Diodati, Nuova Riveduta)
7. 🔲 Create embeddings for all priority language versions
8. 🔲 Import into vector database
### Medium-term Actions (Next 2 Weeks)
9. 🔲 Implement English fallback logic
10. 🔲 Add version metadata table (`bible_version_config`)
11. 🔲 Create comprehensive test suite
12. 🔲 Monitor performance and optimize queries
---
## 🚨 Critical Blockers
1. **Azure OpenAI Deployment Names** (Blocking ALL functionality)
- Cannot generate embeddings
- Cannot generate chat responses
- Need Azure admin access to resolve
2. **Missing Priority Languages** (Blocking user requirements)
- Romanian not available
- Spanish not available
- Italian not available
- Need Bible data sources and embeddings pipeline
---
## 📈 Success Metrics
**Current Status:**
- ✅ Database: 100%
- ❌ API Configuration: 0%
- ❌ Language Support: 0% (for priority languages)
- ⚠️ Code Implementation: 80% (search logic exists, just needs API fix)
**Target Status:**
- ✅ Database: 100%
- ✅ API Configuration: 100%
- ✅ Language Support: 100% (ro, es, it, en)
- ✅ Code Implementation: 100%
---
## 📚 Reference Documents
- `/root/biblical-guide/AI_CHAT_FIX_PLAN.md` - Original implementation plan
- `/root/biblical-guide/scripts/verify-ai-system.ts` - Verification script
- `/root/biblical-guide/lib/vector-search.ts` - Current search implementation
- `/root/biblical-guide/app/api/chat/route.ts` - Chat API implementation
---
## Contact & Support
**Azure OpenAI Resource:**
- Endpoint: `https://azureopenaiinstant.openai.azure.com`
- API Version: `2024-05-01-preview`
- **Action Needed:** Verify deployment names in Azure Portal
**Vector Database:**
- Host: `10.0.0.207:5432`
- Database: `biblical-guide`
- Schema: `ai_bible`
- Status: ✅ Fully Operational

View File

@@ -9,6 +9,7 @@ import { OfflineBibleReader } from '@/components/bible/offline-bible-reader'
import { offlineStorage } from '@/lib/offline-storage' import { offlineStorage } from '@/lib/offline-storage'
import { InstallPrompt, useInstallPrompt } from '@/components/pwa/install-prompt' import { InstallPrompt, useInstallPrompt } from '@/components/pwa/install-prompt'
import { useSwipeable } from 'react-swipeable' import { useSwipeable } from 'react-swipeable'
import { AuthModal } from '@/components/auth/auth-modal'
import { import {
Box, Box,
Typography, Typography,
@@ -315,6 +316,11 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
verse: null verse: null
}) })
// Auth modal state
const [authModalOpen, setAuthModalOpen] = useState(false)
const [authModalMessage, setAuthModalMessage] = useState<string>('')
const [pendingAction, setPendingAction] = useState<(() => void) | null>(null)
// Refs // Refs
const contentRef = useRef<HTMLDivElement>(null) const contentRef = useRef<HTMLDivElement>(null)
const verseRefs = useRef<{[key: number]: HTMLDivElement}>({}) const verseRefs = useRef<{[key: number]: HTMLDivElement}>({})
@@ -990,6 +996,26 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
} }
} }
const requireAuth = (action: () => void, message: string) => {
if (!user) {
setAuthModalMessage(message)
setPendingAction(() => action)
setAuthModalOpen(true)
return false
}
return true
}
const handleAuthSuccess = () => {
setAuthModalOpen(false)
setAuthModalMessage('')
// Execute pending action if there is one
if (pendingAction) {
pendingAction()
setPendingAction(null)
}
}
const handlePreviousChapter = () => { const handlePreviousChapter = () => {
// Trigger transition animation // Trigger transition animation
setIsTransitioning(true) setIsTransitioning(true)
@@ -1077,9 +1103,8 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
const handleChapterBookmark = async () => { const handleChapterBookmark = async () => {
if (!selectedBook || !selectedChapter) return if (!selectedBook || !selectedChapter) return
// If user is not authenticated, redirect to login // If user is not authenticated, show auth modal
if (!user) { if (!requireAuth(handleChapterBookmark, 'Please login to bookmark this chapter')) {
router.push(`/${locale}/login?redirect=${encodeURIComponent(`/${locale}/bible?version=${selectedVersion}&book=${selectedBook}&chapter=${selectedChapter}`)}`)
return return
} }
@@ -1123,9 +1148,8 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
} }
const handleVerseBookmark = async (verse: BibleVerse) => { const handleVerseBookmark = async (verse: BibleVerse) => {
// If user is not authenticated, redirect to login // If user is not authenticated, show auth modal
if (!user) { if (!requireAuth(() => handleVerseBookmark(verse), 'Please login to bookmark this verse')) {
router.push(`/${locale}/login?redirect=${encodeURIComponent(`/${locale}/bible?version=${selectedVersion}&book=${selectedBook}&chapter=${selectedChapter}&verse=${verse.verseNum}`)}`)
return return
} }
@@ -1184,9 +1208,8 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
} }
const handleVerseChat = (verse: BibleVerse) => { const handleVerseChat = (verse: BibleVerse) => {
// If user is not authenticated, redirect to login // If user is not authenticated, show auth modal
if (!user) { if (!requireAuth(() => handleVerseChat(verse), 'Please login to ask AI about this verse')) {
router.push(`/${locale}/login?redirect=${encodeURIComponent(`/${locale}/bible?version=${selectedVersion}&book=${selectedBook}&chapter=${selectedChapter}&verse=${verse.verseNum}`)}`)
return return
} }
@@ -1255,9 +1278,8 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
} }
const handleHighlightVerse = async (verse: BibleVerse, color: TextHighlight['color']) => { const handleHighlightVerse = async (verse: BibleVerse, color: TextHighlight['color']) => {
// If user is not authenticated, redirect to login // If user is not authenticated, show auth modal
if (!user) { if (!requireAuth(() => handleHighlightVerse(verse, color), 'Please login to highlight this verse')) {
router.push(`/${locale}/login?redirect=${encodeURIComponent(`/${locale}/bible?version=${selectedVersion}&book=${selectedBook}&chapter=${selectedChapter}&verse=${verse.verseNum}`)}`)
return return
} }
@@ -1397,8 +1419,8 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
} }
const handleSetFavoriteVersion = async () => { const handleSetFavoriteVersion = async () => {
if (!user) { // If user is not authenticated, show auth modal
router.push(`/${locale}/login?redirect=${encodeURIComponent(`/${locale}/bible?version=${selectedVersion}&book=${selectedBook}&chapter=${selectedChapter}`)}`) if (!requireAuth(handleSetFavoriteVersion, 'Please login to set your default Bible version')) {
return return
} }
@@ -2667,6 +2689,19 @@ export default function BibleReaderNew({ initialVersion, initialBook, initialCha
{copyFeedback.message} {copyFeedback.message}
</Alert> </Alert>
</Snackbar> </Snackbar>
{/* Auth Modal */}
<AuthModal
open={authModalOpen}
onClose={() => {
setAuthModalOpen(false)
setAuthModalMessage('')
setPendingAction(null)
}}
onSuccess={handleAuthSuccess}
message={authModalMessage}
defaultTab="login"
/>
</Box> </Box>
) )
} }

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@ import {
ListItemText, ListItemText,
MenuItem, MenuItem,
useTheme, useTheme,
useMediaQuery,
CircularProgress, CircularProgress,
Skeleton, Skeleton,
Alert, Alert,
@@ -29,6 +30,9 @@ import {
Checkbox, Checkbox,
SelectChangeEvent, SelectChangeEvent,
Switch, Switch,
Accordion,
AccordionSummary,
AccordionDetails,
} from '@mui/material' } from '@mui/material'
import { import {
Favorite, Favorite,
@@ -42,10 +46,12 @@ import {
AutoAwesome, AutoAwesome,
Edit, Edit,
Login, Login,
ExpandMore,
} from '@mui/icons-material' } from '@mui/icons-material'
import { useState, useEffect, useMemo } from 'react' import { useState, useEffect, useMemo } from 'react'
import { useTranslations, useLocale, useFormatter } from 'next-intl' import { useTranslations, useLocale, useFormatter } from 'next-intl'
import { useAuth } from '@/hooks/use-auth' import { useAuth } from '@/hooks/use-auth'
import { AuthModal } from '@/components/auth/auth-modal'
interface PrayerRequest { interface PrayerRequest {
id: string id: string
@@ -68,6 +74,7 @@ export default function PrayersPage() {
const tc = useTranslations('common') const tc = useTranslations('common')
const f = useFormatter() const f = useFormatter()
const { user } = useAuth() const { user } = useAuth()
const isMobile = useMediaQuery(theme.breakpoints.down('md'))
const [prayers, setPrayers] = useState<PrayerRequest[]>([]) const [prayers, setPrayers] = useState<PrayerRequest[]>([])
const [selectedCategory, setSelectedCategory] = useState<string>('all') const [selectedCategory, setSelectedCategory] = useState<string>('all')
const [openDialog, setOpenDialog] = useState(false) const [openDialog, setOpenDialog] = useState(false)
@@ -82,12 +89,14 @@ export default function PrayersPage() {
const [isGenerating, setIsGenerating] = useState(false) const [isGenerating, setIsGenerating] = useState(false)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [viewMode, setViewMode] = useState<'private' | 'public'>(user ? 'private' : 'public') const [viewMode, setViewMode] = useState<'private' | 'public'>(user ? 'private' : 'public')
const [selectedLanguages, setSelectedLanguages] = useState<string[]>([locale]) const [selectedLanguage, setSelectedLanguage] = useState<string>(locale)
const [authModalOpen, setAuthModalOpen] = useState(false)
const languagesKey = useMemo(() => selectedLanguages.slice().sort().join(','), [selectedLanguages])
const languageOptions = useMemo(() => ([ const languageOptions = useMemo(() => ([
{ value: 'en', label: t('languageFilter.options.en') }, { value: 'en', label: t('languageFilter.options.en') },
{ value: 'ro', label: t('languageFilter.options.ro') } { value: 'ro', label: t('languageFilter.options.ro') },
{ value: 'es', label: t('languageFilter.options.es') },
{ value: 'it', label: t('languageFilter.options.it') }
]), [t]) ]), [t])
const languageLabelMap = useMemo(() => ( const languageLabelMap = useMemo(() => (
languageOptions.reduce((acc, option) => { languageOptions.reduce((acc, option) => {
@@ -106,21 +115,10 @@ export default function PrayersPage() {
useEffect(() => { useEffect(() => {
if (viewMode === 'public') { if (viewMode === 'public') {
setSelectedLanguages(prev => { setSelectedLanguage(locale)
if (prev.includes(locale)) {
return prev
}
return [...prev, locale]
})
} }
}, [locale, viewMode]) }, [locale, viewMode])
useEffect(() => {
if (viewMode === 'public' && selectedLanguages.length === 0) {
setSelectedLanguages([locale])
}
}, [viewMode, selectedLanguages, locale])
const categories = [ const categories = [
{ value: 'personal', label: t('categories.personal'), color: 'primary' }, { value: 'personal', label: t('categories.personal'), color: 'primary' },
{ value: 'family', label: t('categories.family'), color: 'secondary' }, { value: 'family', label: t('categories.family'), color: 'secondary' },
@@ -148,8 +146,7 @@ export default function PrayersPage() {
params.append('visibility', viewMode) params.append('visibility', viewMode)
if (viewMode === 'public') { if (viewMode === 'public') {
const languagesToQuery = selectedLanguages.length > 0 ? selectedLanguages : [locale] params.append('languages', selectedLanguage)
languagesToQuery.forEach(lang => params.append('languages', lang))
} }
const headers: Record<string, string> = {} const headers: Record<string, string> = {}
@@ -185,7 +182,7 @@ export default function PrayersPage() {
useEffect(() => { useEffect(() => {
fetchPrayers() fetchPrayers()
}, [selectedCategory, user, viewMode, languagesKey]) }, [selectedCategory, user, viewMode, selectedLanguage])
const handleGenerateAIPrayer = async () => { const handleGenerateAIPrayer = async () => {
if (!aiPrompt.trim()) return if (!aiPrompt.trim()) return
@@ -225,14 +222,9 @@ export default function PrayersPage() {
} }
} }
const handleLanguageChange = (event: SelectChangeEvent<string[]>) => { const handleLanguageChange = (event: SelectChangeEvent<string>) => {
const value = event.target.value const value = event.target.value
const parsed = typeof value === 'string' setSelectedLanguage(value)
? value.split(',')
: (value as string[])
const uniqueValues = Array.from(new Set(parsed.filter(Boolean)))
setSelectedLanguages(uniqueValues)
} }
const handleSubmitPrayer = async () => { const handleSubmitPrayer = async () => {
@@ -273,12 +265,18 @@ export default function PrayersPage() {
const handleOpenDialog = () => { const handleOpenDialog = () => {
if (!user) { if (!user) {
// Could redirect to login or show login modal setAuthModalOpen(true)
return return
} }
setOpenDialog(true) setOpenDialog(true)
} }
const handleAuthSuccess = () => {
setAuthModalOpen(false)
// After successful auth, open the add prayer dialog
setOpenDialog(true)
}
const handlePrayFor = async (prayerId: string) => { const handlePrayFor = async (prayerId: string) => {
try { try {
const headers: HeadersInit = { const headers: HeadersInit = {
@@ -371,80 +369,101 @@ export default function PrayersPage() {
fullWidth fullWidth
variant="contained" variant="contained"
color="primary" color="primary"
startIcon={<Login />} startIcon={<Add />}
onClick={() => { onClick={handleOpenDialog}
// Could redirect to login page or show login modal
console.log('Please login to add prayers')
}}
sx={{ mb: 3 }} sx={{ mb: 3 }}
> >
{locale === 'en' ? 'Login to Add Prayer' : 'Conectează-te pentru a adăuga'} {t('addPrayer')}
</Button> </Button>
)} )}
<Typography variant="h6" gutterBottom> {/* Categories Accordion */}
{t('categories.title')} <Accordion defaultExpanded={!isMobile}>
</Typography> <AccordionSummary
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}> expandIcon={<ExpandMore />}
<Chip aria-controls="categories-content"
label={t('categories.all')} id="categories-header"
color="default" >
variant={selectedCategory === 'all' ? 'filled' : 'outlined'} <Typography variant="h6">
size="small" {t('categories.title')}
onClick={() => setSelectedCategory('all')} </Typography>
sx={{ justifyContent: 'flex-start', cursor: 'pointer' }} </AccordionSummary>
/> <AccordionDetails>
{categories.map((category) => ( <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Chip <Chip
key={category.value} label={t('categories.all')}
label={category.label} color="default"
color={category.color as any} variant={selectedCategory === 'all' ? 'filled' : 'outlined'}
variant={selectedCategory === category.value ? 'filled' : 'outlined'} size="small"
size="small" onClick={() => setSelectedCategory('all')}
onClick={() => setSelectedCategory(category.value)} sx={{ justifyContent: 'flex-start', cursor: 'pointer' }}
sx={{ justifyContent: 'flex-start', cursor: 'pointer' }} />
/> {categories.map((category) => (
))} <Chip
</Box> key={category.value}
label={category.label}
color={category.color as any}
variant={selectedCategory === category.value ? 'filled' : 'outlined'}
size="small"
onClick={() => setSelectedCategory(category.value)}
sx={{ justifyContent: 'flex-start', cursor: 'pointer' }}
/>
))}
</Box>
</AccordionDetails>
</Accordion>
{/* Language Filter Accordion */}
{viewMode === 'public' && ( {viewMode === 'public' && (
<Box sx={{ mt: 3 }}> <Accordion defaultExpanded={!isMobile} sx={{ mt: 2 }}>
<Typography variant="h6" sx={{ mb: 1 }}> <AccordionSummary
{t('languageFilter.title')} expandIcon={<ExpandMore />}
</Typography> aria-controls="language-content"
<FormControl fullWidth size="small"> id="language-header"
<Select >
multiple <Typography variant="h6">
value={selectedLanguages} {t('languageFilter.title')}
onChange={handleLanguageChange} </Typography>
renderValue={(selected) => </AccordionSummary>
(selected as string[]) <AccordionDetails>
.map(code => languageLabelMap[code] || code.toUpperCase()) <FormControl fullWidth size="small">
.join(', ') <Select
} value={selectedLanguage}
> onChange={handleLanguageChange}
{languageOptions.map(option => ( >
<MenuItem key={option.value} value={option.value}> {languageOptions.map(option => (
<Checkbox checked={selectedLanguages.includes(option.value)} /> <MenuItem key={option.value} value={option.value}>
<ListItemText primary={option.label} /> {option.label}
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
</FormControl> </FormControl>
<Typography variant="caption" color="text.secondary" sx={{ mt: 1 }}> <Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
{t('languageFilter.helper')} {t('languageFilter.helper')}
</Typography> </Typography>
</Box> </AccordionDetails>
</Accordion>
)} )}
<Typography variant="h6" sx={{ mt: 3, mb: 1 }}> {/* Stats Accordion */}
{t('stats.title')} <Accordion defaultExpanded={!isMobile} sx={{ mt: 2 }}>
</Typography> <AccordionSummary
<Typography variant="body2" color="text.secondary"> expandIcon={<ExpandMore />}
{t('stats.activeRequests', { count: prayers.length })}<br /> aria-controls="stats-content"
{t('stats.totalPrayers', { count: prayers.reduce((sum, p) => sum + p.prayerCount, 0) })}<br /> id="stats-header"
{t('stats.youPrayed', { count: prayers.filter(p => p.isPrayedFor).length })} >
</Typography> <Typography variant="h6">
{t('stats.title')}
</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2" color="text.secondary">
{t('stats.activeRequests', { count: prayers.length })}<br />
{t('stats.totalPrayers', { count: prayers.reduce((sum, p) => sum + p.prayerCount, 0) })}<br />
{t('stats.youPrayed', { count: prayers.filter(p => p.isPrayedFor).length })}
</Typography>
</AccordionDetails>
</Accordion>
</CardContent> </CardContent>
</Card> </Card>
</Box> </Box>
@@ -773,6 +792,15 @@ export default function PrayersPage() {
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
{/* Auth Modal */}
<AuthModal
open={authModalOpen}
onClose={() => setAuthModalOpen(false)}
onSuccess={handleAuthSuccess}
message={t('authRequired')}
defaultTab="login"
/>
</Container> </Container>
</Box> </Box>
) )

View File

@@ -0,0 +1,334 @@
'use client'
import { useState } from 'react'
import {
Dialog,
DialogContent,
DialogTitle,
TextField,
Button,
Box,
Typography,
Alert,
IconButton,
Tabs,
Tab,
InputAdornment,
CircularProgress
} from '@mui/material'
import { Close, Visibility, VisibilityOff } from '@mui/icons-material'
import { useTranslations } from 'next-intl'
import { useAuth } from '@/hooks/use-auth'
interface AuthModalProps {
open: boolean
onClose: () => void
onSuccess?: () => void
defaultTab?: 'login' | 'register'
title?: string
message?: string
}
export function AuthModal({
open,
onClose,
onSuccess,
defaultTab = 'login',
title,
message
}: AuthModalProps) {
const t = useTranslations('auth')
const { login, register } = useAuth()
const [tab, setTab] = useState<'login' | 'register'>(defaultTab)
const [showPassword, setShowPassword] = useState(false)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
// Login form state
const [loginData, setLoginData] = useState({
email: '',
password: ''
})
// Register form state
const [registerData, setRegisterData] = useState({
name: '',
email: '',
password: '',
confirmPassword: ''
})
const handleTabChange = (event: React.SyntheticEvent, newValue: 'login' | 'register') => {
setTab(newValue)
setError(null)
}
const handleLoginSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError(null)
setLoading(true)
try {
const success = await login(loginData.email, loginData.password)
if (success) {
// Clear form
setLoginData({ email: '', password: '' })
// Call success callback
onSuccess?.()
// Close modal
onClose()
} else {
setError(t('loginError'))
}
} catch (err: any) {
setError(err.message || t('connectionError'))
} finally {
setLoading(false)
}
}
const handleRegisterSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError(null)
// Validate passwords match
if (registerData.password !== registerData.confirmPassword) {
setError(t('passwordMismatch'))
return
}
setLoading(true)
try {
const success = await register(
registerData.email,
registerData.password,
registerData.name || undefined
)
if (success) {
// Clear form
setRegisterData({ name: '', email: '', password: '', confirmPassword: '' })
// Call success callback
onSuccess?.()
// Close modal
onClose()
} else {
setError(t('registerError'))
}
} catch (err: any) {
setError(err.message || t('connectionError'))
} finally {
setLoading(false)
}
}
const handleClose = () => {
if (!loading) {
setError(null)
onClose()
}
}
return (
<Dialog
open={open}
onClose={handleClose}
maxWidth="sm"
fullWidth
PaperProps={{
sx: {
borderRadius: 2
}
}}
>
<DialogTitle sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', pb: 1 }}>
<Typography variant="h6">
{title || (tab === 'login' ? t('welcomeBack') : t('joinUs'))}
</Typography>
<IconButton
onClick={handleClose}
disabled={loading}
size="small"
sx={{ color: 'text.secondary' }}
>
<Close />
</IconButton>
</DialogTitle>
<DialogContent>
{message && (
<Alert severity="info" sx={{ mb: 3 }}>
{message}
</Alert>
)}
<Tabs
value={tab}
onChange={handleTabChange}
variant="fullWidth"
sx={{ mb: 3, borderBottom: 1, borderColor: 'divider' }}
>
<Tab label={t('login')} value="login" />
<Tab label={t('register')} value="register" />
</Tabs>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
{tab === 'login' ? (
<Box component="form" onSubmit={handleLoginSubmit}>
<TextField
fullWidth
label={t('email')}
type="email"
value={loginData.email}
onChange={(e) => setLoginData(prev => ({ ...prev, email: e.target.value }))}
required
disabled={loading}
sx={{ mb: 2 }}
autoComplete="email"
/>
<TextField
fullWidth
label={t('password')}
type={showPassword ? 'text' : 'password'}
value={loginData.password}
onChange={(e) => setLoginData(prev => ({ ...prev, password: e.target.value }))}
required
disabled={loading}
sx={{ mb: 3 }}
autoComplete="current-password"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => setShowPassword(!showPassword)}
edge="end"
disabled={loading}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
)
}}
/>
<Button
type="submit"
variant="contained"
fullWidth
size="large"
disabled={loading}
sx={{ mb: 2 }}
>
{loading ? <CircularProgress size={24} /> : t('login')}
</Button>
<Box sx={{ textAlign: 'center' }}>
<Typography variant="body2" color="text.secondary">
{t('noAccount')}{' '}
<Button
onClick={() => setTab('register')}
disabled={loading}
sx={{ textTransform: 'none', p: 0, minWidth: 'auto' }}
>
{t('createAccount')}
</Button>
</Typography>
</Box>
</Box>
) : (
<Box component="form" onSubmit={handleRegisterSubmit}>
<TextField
fullWidth
label={`${t('name')} ${t('optional')}`}
type="text"
value={registerData.name}
onChange={(e) => setRegisterData(prev => ({ ...prev, name: e.target.value }))}
disabled={loading}
sx={{ mb: 2 }}
autoComplete="name"
/>
<TextField
fullWidth
label={t('email')}
type="email"
value={registerData.email}
onChange={(e) => setRegisterData(prev => ({ ...prev, email: e.target.value }))}
required
disabled={loading}
sx={{ mb: 2 }}
autoComplete="email"
/>
<TextField
fullWidth
label={t('password')}
type={showPassword ? 'text' : 'password'}
value={registerData.password}
onChange={(e) => setRegisterData(prev => ({ ...prev, password: e.target.value }))}
required
disabled={loading}
sx={{ mb: 2 }}
autoComplete="new-password"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => setShowPassword(!showPassword)}
edge="end"
disabled={loading}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
)
}}
/>
<TextField
fullWidth
label={t('confirmPassword')}
type={showPassword ? 'text' : 'password'}
value={registerData.confirmPassword}
onChange={(e) => setRegisterData(prev => ({ ...prev, confirmPassword: e.target.value }))}
required
disabled={loading}
sx={{ mb: 3 }}
autoComplete="new-password"
error={registerData.confirmPassword !== '' && registerData.password !== registerData.confirmPassword}
helperText={
registerData.confirmPassword !== '' && registerData.password !== registerData.confirmPassword
? t('passwordMismatch')
: ''
}
/>
<Button
type="submit"
variant="contained"
fullWidth
size="large"
disabled={loading}
sx={{ mb: 2 }}
>
{loading ? <CircularProgress size={24} /> : t('register')}
</Button>
<Box sx={{ textAlign: 'center' }}>
<Typography variant="body2" color="text.secondary">
{t('alreadyHaveAccount')}{' '}
<Button
onClick={() => setTab('login')}
disabled={loading}
sx={{ textTransform: 'none', p: 0, minWidth: 'auto' }}
>
{t('login')}
</Button>
</Typography>
</Box>
</Box>
)}
</DialogContent>
</Dialog>
)
}

View File

@@ -45,6 +45,7 @@ import {
import { useState, useRef, useEffect, useCallback } from 'react' import { useState, useRef, useEffect, useCallback } from 'react'
import { useTranslations, useLocale } from 'next-intl' import { useTranslations, useLocale } from 'next-intl'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import { AuthModal } from '@/components/auth/auth-modal'
interface ChatMessage { interface ChatMessage {
id: string id: string
@@ -95,6 +96,7 @@ export default function FloatingChat() {
const [showRenameDialog, setShowRenameDialog] = useState(false) const [showRenameDialog, setShowRenameDialog] = useState(false)
const [showDeleteDialog, setShowDeleteDialog] = useState(false) const [showDeleteDialog, setShowDeleteDialog] = useState(false)
const [newTitle, setNewTitle] = useState('') const [newTitle, setNewTitle] = useState('')
const [authModalOpen, setAuthModalOpen] = useState(false)
const messagesEndRef = useRef<HTMLDivElement>(null) const messagesEndRef = useRef<HTMLDivElement>(null)
const scrollToBottom = () => { const scrollToBottom = () => {
@@ -123,6 +125,15 @@ export default function FloatingChat() {
return () => window.removeEventListener('floating-chat:open', handler as EventListener) return () => window.removeEventListener('floating-chat:open', handler as EventListener)
}, []) }, [])
// Listen for auth sign-in required events
useEffect(() => {
const handler = () => {
setAuthModalOpen(true)
}
window.addEventListener('auth:sign-in-required', handler as EventListener)
return () => window.removeEventListener('auth:sign-in-required', handler as EventListener)
}, [])
// Check authentication status // Check authentication status
useEffect(() => { useEffect(() => {
checkAuthStatus() checkAuthStatus()
@@ -429,6 +440,12 @@ export default function FloatingChat() {
const toggleFullscreen = () => setIsFullscreen(prev => !prev) const toggleFullscreen = () => setIsFullscreen(prev => !prev)
const handleAuthSuccess = () => {
setAuthModalOpen(false)
// Re-check auth status to update the UI
checkAuthStatus()
}
return ( return (
<> <>
{/* Floating Action Button */} {/* Floating Action Button */}
@@ -999,6 +1016,17 @@ export default function FloatingChat() {
)} )}
</Paper> </Paper>
</Slide> </Slide>
{/* Auth Modal */}
<AuthModal
open={authModalOpen}
onClose={() => setAuthModalOpen(false)}
onSuccess={handleAuthSuccess}
message={locale === 'ro'
? 'Vă rugăm să vă autentificați pentru a accesa chat-ul AI și a salva conversațiile.'
: 'Please sign in to access the AI chat and save your conversations.'}
defaultTab="login"
/>
</> </>
) )
} }

View File

@@ -78,8 +78,9 @@ export async function getEmbedding(text: string): Promise<number[]> {
} }
// Fallback to Azure OpenAI // Fallback to Azure OpenAI
const embedApiVersion = process.env.AZURE_OPENAI_EMBED_API_VERSION || process.env.AZURE_OPENAI_API_VERSION
const response = await fetch( const response = await fetch(
`${process.env.AZURE_OPENAI_ENDPOINT}/openai/deployments/${process.env.AZURE_OPENAI_EMBED_DEPLOYMENT}/embeddings?api-version=${process.env.AZURE_OPENAI_API_VERSION}`, `${process.env.AZURE_OPENAI_ENDPOINT}/openai/deployments/${process.env.AZURE_OPENAI_EMBED_DEPLOYMENT}/embeddings?api-version=${embedApiVersion}`,
{ {
method: 'POST', method: 'POST',
headers: { headers: {

4
logo.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="200" height="200">
<path fill="#1976D2" d="M21 5c-1.11-.35-2.33-.5-3.5-.5-1.95 0-4.05.4-5.5 1.5-1.45-1.1-3.55-1.5-5.5-1.5S2.45 4.9 1 6v14.65c0 .25.25.5.5.5.1 0 .15-.05.25-.05C3.1 20.45 5.05 20 6.5 20c1.95 0 4.05.4 5.5 1.5 1.35-.85 3.8-1.5 5.5-1.5 1.65 0 3.35.3 4.75 1.05.1.05.15.05.25.05.25 0 .5-.25.5-.5V6c-.6-.45-1.25-.75-2-1m0 13.5c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5V8c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5z"/>
<path fill="#1976D2" d="M17.5 10.5c.88 0 1.73.09 2.5.26V9.24c-.79-.15-1.64-.24-2.5-.24-1.7 0-3.24.29-4.5.83v1.66c1.13-.64 2.7-.99 4.5-.99M13 12.49v1.66c1.13-.64 2.7-.99 4.5-.99.88 0 1.73.09 2.5.26V11.9c-.79-.15-1.64-.24-2.5-.24-1.7 0-3.24.3-4.5.83m4.5 1.84c-1.7 0-3.24.29-4.5.83v1.66c1.13-.64 2.7-.99 4.5-.99.88 0 1.73.09 2.5.26v-1.52c-.79-.16-1.64-.24-2.5-.24"/>
</svg>

After

Width:  |  Height:  |  Size: 872 B

View File

@@ -269,6 +269,8 @@
"prayers": { "prayers": {
"title": "Prayers", "title": "Prayers",
"subtitle": "Share prayers and pray together with the community", "subtitle": "Share prayers and pray together with the community",
"addPrayer": "Add Prayer",
"authRequired": "Please sign in to add a prayer request and join our prayer community.",
"viewModes": { "viewModes": {
"private": "My private prayers", "private": "My private prayers",
"public": "Public prayer wall" "public": "Public prayer wall"
@@ -279,7 +281,7 @@
}, },
"languageFilter": { "languageFilter": {
"title": "Languages", "title": "Languages",
"helper": "Choose which languages to include. Your current language stays selected.", "helper": "See prayers in other languages",
"options": { "options": {
"en": "English", "en": "English",
"ro": "Romanian", "ro": "Romanian",

View File

@@ -269,6 +269,8 @@
"prayers": { "prayers": {
"title": "Oraciones", "title": "Oraciones",
"subtitle": "Comparte oraciones y ora junto con la comunidad", "subtitle": "Comparte oraciones y ora junto con la comunidad",
"addPrayer": "Agregar oración",
"authRequired": "Por favor inicia sesión para agregar una oración y unirte a nuestra comunidad de oración.",
"viewModes": { "viewModes": {
"private": "Mis oraciones privadas", "private": "Mis oraciones privadas",
"public": "Muro de oración público" "public": "Muro de oración público"
@@ -279,7 +281,7 @@
}, },
"languageFilter": { "languageFilter": {
"title": "Idiomas", "title": "Idiomas",
"helper": "Elige qué idiomas incluir. Tu idioma actual permanece seleccionado.", "helper": "Ver oraciones en otros idiomas",
"options": { "options": {
"en": "Inglés", "en": "Inglés",
"ro": "Rumano", "ro": "Rumano",

View File

@@ -269,6 +269,8 @@
"prayers": { "prayers": {
"title": "Preghiere", "title": "Preghiere",
"subtitle": "Condividi preghiere e prega insieme alla comunità", "subtitle": "Condividi preghiere e prega insieme alla comunità",
"addPrayer": "Aggiungi preghiera",
"authRequired": "Effettua l'accesso per aggiungere una preghiera e unirti alla nostra comunità di preghiera.",
"viewModes": { "viewModes": {
"private": "Le mie preghiere private", "private": "Le mie preghiere private",
"public": "Muro delle preghiere pubblico" "public": "Muro delle preghiere pubblico"
@@ -279,7 +281,7 @@
}, },
"languageFilter": { "languageFilter": {
"title": "Lingue", "title": "Lingue",
"helper": "Scegli quali lingue includere. La tua lingua corrente rimane selezionata.", "helper": "Vedi preghiere in altre lingue",
"options": { "options": {
"en": "Inglese", "en": "Inglese",
"ro": "Rumeno", "ro": "Rumeno",

View File

@@ -269,6 +269,8 @@
"prayers": { "prayers": {
"title": "Rugăciuni", "title": "Rugăciuni",
"subtitle": "Partajează rugăciuni și roagă-te împreună cu comunitatea", "subtitle": "Partajează rugăciuni și roagă-te împreună cu comunitatea",
"addPrayer": "Adaugă rugăciune",
"authRequired": "Autentifică-te pentru a adăuga o rugăciune și a te alătura comunității noastre de rugăciune.",
"viewModes": { "viewModes": {
"private": "Rugăciunile mele", "private": "Rugăciunile mele",
"public": "Peretele de rugăciuni public" "public": "Peretele de rugăciuni public"
@@ -279,7 +281,7 @@
}, },
"languageFilter": { "languageFilter": {
"title": "Limbi", "title": "Limbi",
"helper": "Alege limbile pentru care vrei să vezi rugăciuni. Limba curentă rămâne selectată.", "helper": "Vezi rugăciuni în alte limbi",
"options": { "options": {
"en": "Engleză", "en": "Engleză",
"ro": "Română", "ro": "Română",