Abdalkaderdev commited on
Commit
5e0532d
·
1 Parent(s): b626375

Initial ORA deployment

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .claude/settings.local.json +9 -0
  2. .gitattributes +2 -34
  3. .gitignore +37 -0
  4. Dockerfile +31 -0
  5. ORA_Training_Colab.ipynb +93 -0
  6. README.md +28 -10
  7. app/agents/base.py +18 -0
  8. app/agents/gatekeeper.py +26 -0
  9. app/agents/healer.py +14 -0
  10. app/agents/theologian.py +46 -0
  11. app/api/routes.py +62 -0
  12. app/core/config.py +37 -0
  13. app/core/swarm_client.py +149 -0
  14. app/ora_server.py +107 -0
  15. app/services/audit.py +51 -0
  16. app/services/bible.py +97 -0
  17. app/services/blessing.py +15 -0
  18. app/services/citations.py +24 -0
  19. app/services/context.py +21 -0
  20. app/services/discernment.py +27 -0
  21. app/services/doctrine.py +21 -0
  22. app/services/emotion.py +66 -0
  23. app/services/ethics.py +23 -0
  24. app/services/feedback.py +11 -0
  25. app/services/growth.py +20 -0
  26. app/services/guardrails.py +61 -0
  27. app/services/intent.py +60 -0
  28. app/services/journal.py +86 -0
  29. app/services/llm.py +128 -0
  30. app/services/memory.py +169 -0
  31. app/services/orchestrator.py +58 -0
  32. app/services/practices.py +19 -0
  33. app/services/prayer.py +22 -0
  34. app/services/profile.py +107 -0
  35. app/services/repl.py +89 -0
  36. app/services/rlm.py +48 -0
  37. app/services/simplify.py +19 -0
  38. app/services/stillness.py +17 -0
  39. app/services/trace.py +31 -0
  40. app/services/translation.py +24 -0
  41. config.yml +1 -0
  42. frontend/.claude/settings.local.json +17 -0
  43. frontend/.gitignore +33 -0
  44. frontend/README.md +98 -0
  45. frontend/app/about/page.tsx +182 -0
  46. frontend/app/candle/page.tsx +635 -0
  47. frontend/app/contact/page.tsx +121 -0
  48. frontend/app/dashboard/bible/page.tsx +143 -0
  49. frontend/app/dashboard/explore/page.tsx +233 -0
  50. frontend/app/dashboard/insights/page.tsx +222 -0
.claude/settings.local.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm install:*)",
5
+ "Bash(npm run build:*)",
6
+ "Bash(npm run dev:*)"
7
+ ]
8
+ }
9
+ }
.gitattributes CHANGED
@@ -1,35 +1,3 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
  *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  *.safetensors filter=lfs diff=lfs merge=lfs -text
2
+ important/finetuning/models/ora_adapter/tokenizer.json filter=lfs diff=lfs merge=lfs -text
3
+ data/**/*.lance filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment files
2
+ .env
3
+ *.env
4
+
5
+ # Python
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+ venv/
10
+ .venv/
11
+ .pytest_cache/
12
+
13
+ # Node/Frontend
14
+ node_modules/
15
+ dist/
16
+ .next/
17
+ out/
18
+ build/
19
+
20
+ # ORA specific - Large data & Databases
21
+ important/curated_data/
22
+ important/finetuning/models/*
23
+ !important/finetuning/models/ora_adapter/
24
+ important/finetuning/logs/
25
+ *.jsonl
26
+ memory.db
27
+ *.zip
28
+ *.log
29
+
30
+ # IDEs
31
+ .vscode/
32
+ .idea/
33
+ .DS_Store
34
+
35
+ # Artifacts
36
+ C:\Users\max\.gemini\antigravity\brain\
37
+ important/documentation/
Dockerfile ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # 1. Install Node.js
6
+ RUN apt-get update && apt-get install -y curl gnupg
7
+ RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
8
+ RUN apt-get install -y nodejs
9
+
10
+ # 2. Copy Files
11
+ COPY . /app
12
+
13
+ # 3. Build Frontend
14
+ WORKDIR /app/frontend
15
+ RUN npm install
16
+ RUN npm run build
17
+ WORKDIR /app
18
+
19
+ # 4. Install Python Backend Dependencies
20
+ RUN pip install torch transformers peft fastapi uvicorn unsloth[colab-new]
21
+
22
+ # HF Spaces User ID Setup (Optional but recommended)
23
+ RUN useradd -m -u 1000 user
24
+ USER user
25
+ ENV HOME=/home/user \
26
+ PATH=/home/user/.local/bin:$PATH
27
+
28
+ WORKDIR /app
29
+
30
+ # 5. Start Backend (which serves Frontend)
31
+ CMD ["python", "app/ora_server.py"]
ORA_Training_Colab.ipynb ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {
6
+ "id": "introduction"
7
+ },
8
+ "source": [
9
+ "# ORA Fine-tuning on Google Colab\n",
10
+ "This notebook allows you to fine-tune the ORA model using Unsloth and QLoRA for high-performance training on NVIDIA GPUs.\n",
11
+ "\n",
12
+ "## Setup\n",
13
+ "1. Go to **Runtime** > **Change runtime type** and ensure **GPU** (T4 or A100) is selected.\n",
14
+ "2. Run the following cells to install dependencies and load your curated data."
15
+ ]
16
+ },
17
+ {
18
+ "cell_type": "code",
19
+ "execution_count": null,
20
+ "metadata": {
21
+ "id": "install-deps"
22
+ },
23
+ "outputs": [],
24
+ "source": [
25
+ "!pip install unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git\n",
26
+ "!pip install --no-deps \"xformers<0.0.29\" \"trl<0.9.0\" peft accelerate bitsandbytes"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "markdown",
31
+ "metadata": {
32
+ "id": "upload-data"
33
+ },
34
+ "source": [
35
+ "## Upload Data\n",
36
+ "Upload the `ora_colab_training.zip` file generated by `scripts/prepare_colab.py`."
37
+ ]
38
+ },
39
+ {
40
+ "cell_type": "code",
41
+ "execution_count": null,
42
+ "metadata": {
43
+ "id": "unzip-data"
44
+ },
45
+ "outputs": [],
46
+ "source": [
47
+ "!unzip ora_colab_training.zip"
48
+ ]
49
+ },
50
+ {
51
+ "cell_type": "markdown",
52
+ "metadata": {
53
+ "id": "training"
54
+ },
55
+ "source": [
56
+ "## Start Training\n",
57
+ "Run the training script."
58
+ ]
59
+ },
60
+ {
61
+ "cell_type": "code",
62
+ "execution_count": null,
63
+ "metadata": {
64
+ "id": "run-training"
65
+ },
66
+ "outputs": [],
67
+ "source": [
68
+ "!python scripts/train_ora.py"
69
+ ]
70
+ }
71
+ ],
72
+ "metadata": {
73
+ "kernelspec": {
74
+ "display_name": "Python 3",
75
+ "language": "python",
76
+ "name": "python3"
77
+ },
78
+ "language_info": {
79
+ "codemirror_mode": {
80
+ "name": "ipython",
81
+ "version": 3
82
+ },
83
+ "file_extension": ".py",
84
+ "mimetype": "text/x-python",
85
+ "name": "python",
86
+ "nbconvert_exporter": "python",
87
+ "pygments_lexer": "ipython3",
88
+ "version": "3.10.12"
89
+ }
90
+ },
91
+ "nbformat": 4,
92
+ "nbformat_minor": 4
93
+ }
README.md CHANGED
@@ -1,10 +1,28 @@
1
- ---
2
- title: ORA
3
- emoji: 📊
4
- colorFrom: indigo
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ORA: AI Spiritual Assistant (Python Edition)
2
+
3
+ ORA is a high-performance AI microservice built with **FastAPI** to serve as the spiritual intelligence layer for the SoapBox ecosystem.
4
+
5
+ ## Architecture
6
+ - **Framework**: FastAPI (Python 3.10+)
7
+ - **Inference**: Asynchronous OpenAI-compatible client (works with Ollama, vLLM, DeepSeek).
8
+ - **Documentation**: Automatic Swagger UI at `/docs`.
9
+
10
+ ## Setup
11
+ 1. **Prerequisites**: Python 3.10+, [Ollama](https://ollama.com) (recommended).
12
+ 2. **Install**:
13
+ ```bash
14
+ python -m venv venv
15
+ source venv/bin/activate # Windows: venv\Scripts\activate
16
+ pip install -r requirements.txt
17
+ ```
18
+ 3. **Run**:
19
+ ```bash
20
+ python main.py
21
+ ```
22
+ The server starts at `http://localhost:6000`.
23
+
24
+ ## API Endpoints
25
+ - `POST /api/chat`: Send a message.
26
+ - Body: `{"message": "Hello ORA"}`
27
+ - `GET /health`: Health check.
28
+ - `GET /docs`: Interactive API playground.
app/agents/base.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Callable, Optional, Union, Dict
2
+ from pydantic import BaseModel
3
+
4
+ class Agent(BaseModel):
5
+ name: str = "Agent"
6
+ model: str = "llama3"
7
+ instructions: Union[str, Callable] = "You are a helpful assistant."
8
+ functions: List[Callable] = []
9
+ context_variables: Dict = {}
10
+
11
+ class Config:
12
+ arbitrary_types_allowed = True
13
+
14
+ class Response(BaseModel):
15
+ agent: Optional[Agent]
16
+ messages: List[Dict]
17
+ context_variables: Dict = {}
18
+ trace: List[Dict] = []
app/agents/gatekeeper.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.agents.base import Agent
2
+
3
+ def transfer_to_theologian():
4
+ """Call this when the user wants to study the Bible, explain doctrine, or analyze scripture."""
5
+ from app.agents.theologian import theologian_agent
6
+ return theologian_agent
7
+
8
+ def transfer_to_healer():
9
+ """Call this when the user is emotional, needs prayer, or is seeking personal comfort."""
10
+ from app.agents.healer import healer_agent
11
+ return healer_agent
12
+
13
+ gatekeeper_agent = Agent(
14
+ name="Gatekeeper",
15
+ instructions=lambda context: f"""
16
+ You are the ORA Gatekeeper. Your job is to listen to the user and hand them off to the right specialist.
17
+
18
+ IMPORTANT CONTEXT (Episodic Memory):
19
+ {context.get('episodic_memory', 'No relevant past insights found.')}
20
+
21
+ - If they want to STUDY or explore complex Bible topics, call transfer_to_theologian.
22
+ - If they are HURTING, need prayer, or want to talk about their feelings, call transfer_to_healer.
23
+ - If it is general conversation, use the past insights above to show you remember their journey, then ask how you can help them grow today.
24
+ """,
25
+ functions=[transfer_to_theologian, transfer_to_healer]
26
+ )
app/agents/healer.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.agents.base import Agent
2
+ # In the future, we can import prayer_service or similar
3
+
4
+ healer_agent = Agent(
5
+ name="Healer",
6
+ model="christian-bible-expert", # Target the empathetic expert model
7
+ instructions="""
8
+ You are the ORA Healer. You focus on emotional support, prayer, and spiritual comfort.
9
+ - Listen deeply to the user's pain or joy.
10
+ - Offer heartfelt prayers based on their situation.
11
+ - Use scripture to provide comfort, but focus on the relationship and the heart.
12
+ - If the user wants a deep theological debate or technical study, hand back to the Gatekeeper.
13
+ """
14
+ )
app/agents/theologian.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.agents.base import Agent
2
+ from app.services.bible import bible_service
3
+ from app.services.journal import journal_service
4
+ from app.services.rlm import rlm_service
5
+
6
+ async def search_bible(query: str):
7
+ """Search the Bible for relevant verses and context."""
8
+ results = await bible_service.search_bible(query)
9
+ if not results:
10
+ return "No relevant verses found."
11
+ formatted = "\n".join([f"- {r['reference']}: {r['text']}" for r in results])
12
+ return f"I found these relevant passages:\n{formatted}"
13
+
14
+ async def search_journal(context_variables: dict, query: str):
15
+ """Search the user's past journal entries and reflections for related insights."""
16
+ user_id = context_variables.get("user_id", "unknown")
17
+ related = await journal_service.get_related_entries(user_id, query, limit=2)
18
+ if not related:
19
+ return "No related journal entries found."
20
+
21
+ res = "Found related past reflections:\n"
22
+ for r in related:
23
+ res += f"- {r['timestamp']}: {r['text']}\n"
24
+ return res
25
+
26
+ async def execute_logic(problem: str):
27
+ """
28
+ Execute a logical reasoning loop to solve complex calculations, timelines, or consistency checks.
29
+ Use this when you need an objective 'workspace' to verify facts or dates.
30
+ """
31
+ return await rlm_service.reason(problem)
32
+
33
+ theologian_agent = Agent(
34
+ name="Theologian",
35
+ model="gabriel-8x7b",
36
+ instructions="""
37
+ You are the ORA Theologian. You specialize in Bible study, doctrine, and scripture analysis.
38
+ Your goal is to provide deep, accurate, and faithful explanations of the Bible.
39
+ - Use search_bible to find matching verses.
40
+ - Use search_journal to see if the user has reflected on this topic before.
41
+ - Use execute_logic if you need to verify timelines, dates, or complex logical steps.
42
+ - Always provide chapter and verse citations.
43
+ - Avoid making authoritative claims; instead, say 'The scripture suggests...' or 'Tradition teaches...'
44
+ """,
45
+ functions=[search_bible, search_journal, execute_logic]
46
+ )
app/api/routes.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from pydantic import BaseModel
3
+ from app.services.llm import llm_service
4
+
5
+ router = APIRouter()
6
+
7
+ class ChatRequest(BaseModel):
8
+ message: str
9
+ context: dict | None = None
10
+
11
+ class ChatResponse(BaseModel):
12
+ success: bool
13
+ response: dict
14
+
15
+ @router.post("/chat", response_model=ChatResponse)
16
+ async def chat_endpoint(request: ChatRequest):
17
+ if not request.message:
18
+ raise HTTPException(status_code=400, detail="Message is required")
19
+
20
+ # Use Orchestrator to handle routing and intelligence
21
+ from app.services.orchestrator import orchestrator
22
+ result = await orchestrator.process_message("user_123", request.message)
23
+
24
+ # Map backend trace to frontend TraceStep format
25
+ formatted_trace = []
26
+ for step in result.get("trace", []):
27
+ action = step.get("thought", "")
28
+ if step.get("tool_calls"):
29
+ tools = ", ".join([tc["name"] for tc in step["tool_calls"]])
30
+ action = f"Thinking about using tools: {tools}. {action}"
31
+
32
+ formatted_trace.append({
33
+ "agent": step["agent"],
34
+ "action": action
35
+ })
36
+
37
+ return {
38
+ "success": True,
39
+ "response": {
40
+ "role": result["role"],
41
+ "content": result["content"],
42
+ "agent": result.get("agent", "ORA"),
43
+ "trace": formatted_trace
44
+ }
45
+ }
46
+
47
+ @router.get("/memory/episodes")
48
+ async def get_episodes():
49
+ from app.services.memory import memory_service
50
+ episodes = await memory_service.retrieve_episodes("user_123", "", limit=10)
51
+ return {"success": True, "episodes": episodes}
52
+
53
+ @router.get("/user/profile")
54
+ async def get_profile():
55
+ return {
56
+ "success": True,
57
+ "profile": {
58
+ "user_id": "user_123",
59
+ "name": "Spiritual Seeker",
60
+ "preferences": ["prayer", "scripture_study"]
61
+ }
62
+ }
app/core/config.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic_settings import BaseSettings
2
+
3
+ class Settings(BaseSettings):
4
+ PROJECT_NAME: str = "ORA AI Service"
5
+ PROJECT_VERSION: str = "1.0.0"
6
+
7
+ # LLM Settings
8
+ LLM_BASE_URL: str = "http://localhost:11434/v1"
9
+ LLM_API_KEY: str = "ollama"
10
+ MODEL_NAME: str = "llama3"
11
+
12
+ # Server Settings
13
+ PORT: int = 6000
14
+ HOST: str = "0.0.0.0"
15
+ CORS_ORIGINS: list[str] = ["http://localhost:5173", "http://localhost:5000", "http://localhost:3000", "http://localhost:5174"]
16
+
17
+ SYSTEM_PROMPT: str = """
18
+ You are ORA, a spiritual guide and companion for the SoapBox Super App.
19
+ YOUR IDENTITY:
20
+ - You are kind, empathetic, and wise.
21
+ - You are NOT a replacement for a pastor or bible, but a helper.
22
+ - You ground your answers in biblical principles (using NIV or ESV translations).
23
+
24
+ YOUR MISSION:
25
+ - Help users reflect on scripture (Observe, Reflect, Act).
26
+ - Offer prayer when appropriate.
27
+ - Encourage users to connect with their local church community.
28
+
29
+ CONSTRAINTS:
30
+ - Keep responses concise (under 200 words) unless asked for a deep dive.
31
+ - If a user expresses intent to harm themselves, encourage seeking professional help.
32
+ """
33
+
34
+ class Config:
35
+ env_file = ".env"
36
+
37
+ settings = Settings()
app/core/swarm_client.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import inspect
2
+ import json
3
+ from typing import List, Dict, Any
4
+ from app.agents.base import Agent, Response
5
+ from app.services.llm import llm_service
6
+
7
+ from app.services.trace import trace_service
8
+
9
+ class SwarmClient:
10
+ def _function_to_schema(self, func) -> Dict:
11
+ """Converts Python function to OpenAI-style schema."""
12
+ sig = inspect.signature(func)
13
+ return {
14
+ "type": "function",
15
+ "function": {
16
+ "name": func.__name__,
17
+ "description": func.__doc__ or "No description provided.",
18
+ "parameters": {
19
+ "type": "object",
20
+ "properties": {
21
+ name: {"type": "string"} # Simplified: assume string for ORA
22
+ for name in sig.parameters
23
+ },
24
+ "required": [name for name, p in sig.parameters.items() if p.default == inspect.Parameter.empty]
25
+ },
26
+ },
27
+ }
28
+
29
+ async def run(
30
+ self,
31
+ agent: Agent,
32
+ messages: List[Dict],
33
+ context_variables: Dict = {},
34
+ max_turns: int = 5
35
+ ) -> Response:
36
+ active_agent = agent
37
+ history = list(messages)
38
+ user_query = messages[-1]["content"] if messages else ""
39
+ reasoning_steps = []
40
+
41
+ for _ in range(max_turns):
42
+ # 1. Update active agent's context
43
+ active_agent.context_variables.update(context_variables)
44
+
45
+ # 2. Prepare Tools
46
+ tools = [self._function_to_schema(f) for f in active_agent.functions] if active_agent.functions else None
47
+
48
+ # 3. Get LLM Choice
49
+ instructions = active_agent.instructions
50
+ if callable(instructions):
51
+ sig = inspect.signature(instructions)
52
+ if len(sig.parameters) > 0:
53
+ instructions = instructions(active_agent.context_variables)
54
+ else:
55
+ instructions = instructions()
56
+
57
+ print(f"Swarm [{active_agent.name}]: Processing...")
58
+ llm_res = await llm_service.generate_response(
59
+ message=history[-1]["content"],
60
+ system_prompt=instructions,
61
+ tools=tools
62
+ )
63
+
64
+ content = llm_res.get("content", "")
65
+ tool_calls = llm_res.get("tool_calls")
66
+
67
+ # Record thought step
68
+ reasoning_steps.append({
69
+ "agent": active_agent.name,
70
+ "thought": content,
71
+ "tool_calls": [
72
+ {"name": tc.function.name, "args": json.loads(tc.function.arguments)}
73
+ for tc in tool_calls
74
+ ] if tool_calls else []
75
+ })
76
+
77
+ # 3. Add Assistant Message to History
78
+ history.append({"role": "assistant", "content": content})
79
+
80
+ if not tool_calls:
81
+ # Capture and Save Trace
82
+ trace_service.save_trace({
83
+ "user_query": user_query,
84
+ "steps": reasoning_steps,
85
+ "final_response": content,
86
+ "success": True
87
+ })
88
+ return Response(
89
+ agent=active_agent,
90
+ messages=history,
91
+ context_variables=context_variables,
92
+ trace=reasoning_steps
93
+ )
94
+
95
+ # 4. Handle Tool Calls
96
+ for tool_call in tool_calls:
97
+ func_name = tool_call.function.name
98
+ func_args = json.loads(tool_call.function.arguments)
99
+
100
+ func = next((f for f in active_agent.functions if f.__name__ == func_name), None)
101
+ if not func:
102
+ history.append({"role": "tool", "tool_call_id": tool_call.id, "content": f"Error: Function {func_name} not found."})
103
+ continue
104
+
105
+ print(f"Swarm: Executing {func_name}...")
106
+
107
+ if inspect.iscoroutinefunction(func):
108
+ result = await func(**func_args)
109
+ else:
110
+ result = func(**func_args)
111
+
112
+ # Record tool result
113
+ reasoning_steps[-1]["tool_results"] = reasoning_steps[-1].get("tool_results", [])
114
+ reasoning_steps[-1]["tool_results"].append({
115
+ "name": func_name,
116
+ "result": str(result)
117
+ })
118
+
119
+ if isinstance(result, Agent):
120
+ active_agent = result
121
+ history.append({
122
+ "role": "tool",
123
+ "tool_call_id": tool_call.id,
124
+ "content": f"Transferring to {active_agent.name}."
125
+ })
126
+ else:
127
+ history.append({
128
+ "role": "tool",
129
+ "tool_call_id": tool_call.id,
130
+ "content": str(result)
131
+ })
132
+
133
+ # Save Trace even if max turns hit
134
+ trace_service.save_trace({
135
+ "user_query": user_query,
136
+ "steps": reasoning_steps,
137
+ "final_response": history[-1]["content"],
138
+ "success": False,
139
+ "error": "Max turns reached"
140
+ })
141
+ return Response(
142
+ agent=active_agent,
143
+ messages=history,
144
+ context_variables=context_variables,
145
+ trace=reasoning_steps
146
+ )
147
+
148
+ swarm_client = SwarmClient()
149
+
app/ora_server.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from transformers import AutoModelForCausalLM, AutoTokenizer
3
+ from peft import PeftModel
4
+ from fastapi import FastAPI, HTTPException
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ from fastapi.staticfiles import StaticFiles
7
+ from fastapi.responses import FileResponse
8
+ from pydantic import BaseModel
9
+ import uvicorn
10
+ import os
11
+
12
+ # Settings
13
+ BASE_MODEL = "unsloth/Llama-3.2-1B-Instruct"
14
+ ADAPTER_PATH = "important/finetuning/models/ora_adapter"
15
+
16
+ app = FastAPI()
17
+
18
+ app.add_middleware(
19
+ CORSMiddleware,
20
+ allow_origins=["*"],
21
+ allow_credentials=True,
22
+ allow_methods=["*"],
23
+ allow_headers=["*"],
24
+ )
25
+
26
+ # API Routes
27
+ class ChatRequest(BaseModel):
28
+ model = None
29
+ tokenizer = None
30
+ device = "cuda" if torch.cuda.is_available() else "cpu"
31
+
32
+ class ChatRequest(BaseModel):
33
+ message: str
34
+ history: list = []
35
+
36
+ @app.on_event("startup")
37
+ async def load_model():
38
+ global model, tokenizer
39
+ print(f"Loading ORA Model on {device}...")
40
+
41
+ tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
42
+ base_model = AutoModelForCausalLM.from_pretrained(
43
+ BASE_MODEL,
44
+ torch_dtype=torch.float16 if device == "cuda" else torch.float32,
45
+ device_map=device,
46
+ low_cpu_mem_usage=True
47
+ )
48
+
49
+ if os.path.exists(ADAPTER_PATH):
50
+ print(f"Loading adapter from {ADAPTER_PATH}...")
51
+ model = PeftModel.from_pretrained(base_model, ADAPTER_PATH)
52
+ else:
53
+ print("Adapter not found, using base model.")
54
+ model = base_model
55
+
56
+ print("ORA Model Connected and Ready.")
57
+
58
+ @app.post("/api/chat")
59
+ async def chat_endpoint(req: ChatRequest):
60
+ global model, tokenizer
61
+
62
+ system_prompt = "You are ORA, a spiritual assistant specializing in theological insights and biblical wisdom. Provide discerning, compassionate, and doctrine-aware responses."
63
+
64
+ # Construct Prompt
65
+ messages = [{"role": "system", "content": system_prompt}]
66
+ # Add last few turns of history to keep context but save tokens
67
+ messages.extend(req.history[-4:])
68
+ messages.append({"role": "user", "content": req.message})
69
+
70
+ input_ids = tokenizer.apply_chat_template(
71
+ messages,
72
+ add_generation_prompt=True,
73
+ return_tensors="pt"
74
+ ).to(device)
75
+
76
+ terminators = [
77
+ tokenizer.eos_token_id,
78
+ tokenizer.convert_tokens_to_ids("<|eot_id|>")
79
+ ]
80
+
81
+ outputs = model.generate(
82
+ input_ids,
83
+ max_new_tokens=256,
84
+ eos_token_id=terminators,
85
+ do_sample=True,
86
+ temperature=0.7,
87
+ top_p=0.9,
88
+ )
89
+
90
+ response_tokens = outputs[0][input_ids.shape[-1]:]
91
+ response_text = tokenizer.decode(response_tokens, skip_special_tokens=True)
92
+
93
+ return {"response": response_text}
94
+
95
+ # Mount Static Frontend (Must be last)
96
+ # Expects 'frontend/out' to exist (built via 'next build')
97
+ if os.path.exists("frontend/out"):
98
+ app.mount("/_next", StaticFiles(directory="frontend/out/_next"), name="next")
99
+ app.mount("/", StaticFiles(directory="frontend/out", html=True), name="static")
100
+
101
+ @app.exception_handler(404)
102
+ async def not_found(request, exc):
103
+ return FileResponse("frontend/out/index.html")
104
+
105
+ if __name__ == "__main__":
106
+ # HF Spaces expects port 7860
107
+ uvicorn.run(app, host="0.0.0.0", port=7860)
app/services/audit.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import json
3
+ import os
4
+ from logging.handlers import RotatingFileHandler
5
+ from datetime import datetime
6
+
7
+ # Ensure logs directory exists
8
+ LOG_DIR = "logs"
9
+ os.makedirs(LOG_DIR, exist_ok=True)
10
+ LOG_FILE = os.path.join(LOG_DIR, "ora.log")
11
+
12
+ class AuditLog:
13
+ def __init__(self):
14
+ self.logger = logging.getLogger("ORA_Audit")
15
+ self.logger.setLevel(logging.INFO)
16
+
17
+ # Prevent adding multiple handlers if re-initialized
18
+ if not self.logger.handlers:
19
+ # Rotate log after 10MB, keep 5 backups
20
+ handler = RotatingFileHandler(LOG_FILE, maxBytes=10*1024*1024, backupCount=5)
21
+ formatter = logging.Formatter('%(message)s')
22
+ handler.setFormatter(formatter)
23
+ self.logger.addHandler(handler)
24
+
25
+ async def log_interaction(self, user_id: str, intent: str, guardrails_triggered: list[str]):
26
+ """
27
+ Logs a user interaction and any triggered guardrails.
28
+ """
29
+ entry = {
30
+ "timestamp": datetime.now().isoformat(),
31
+ "type": "INTERACTION",
32
+ "user_id": user_id,
33
+ "intent": intent,
34
+ "guardrails": guardrails_triggered
35
+ }
36
+ self.logger.info(json.dumps(entry))
37
+
38
+ async def log_violation(self, user_id: str, violation_type: str, content: str):
39
+ """
40
+ Logs a specific safety violation.
41
+ """
42
+ entry = {
43
+ "timestamp": datetime.now().isoformat(),
44
+ "type": "VIOLATION",
45
+ "user_id": user_id,
46
+ "violation_type": violation_type,
47
+ "content_snippet": content[:200] # Store first 200 chars for context
48
+ }
49
+ self.logger.info(json.dumps(entry))
50
+
51
+ audit_service = AuditLog()
app/services/bible.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import httpx
2
+ import os
3
+ import asyncio
4
+ import lancedb
5
+ from typing import List, Optional, Dict
6
+ from app.services.llm import llm_service
7
+
8
+ class BibleService:
9
+ BASE_URL = "https://bible-api.com"
10
+ DB_PATH = "data/lancedb_storage"
11
+ TABLE_NAME = "bible_verses"
12
+
13
+ def __init__(self):
14
+ # We don't keep an in-memory index anymore.
15
+ # Connection is established per-query or cached if needed.
16
+ pass
17
+
18
+ async def initialize_index(self):
19
+ """
20
+ No-op for LanceDB as it's persistent.
21
+ We might verify the DB exists here.
22
+ """
23
+ if not os.path.exists(self.DB_PATH):
24
+ print("BibleService: LanceDB storage not found at", self.DB_PATH)
25
+ print("Please run scripts/ingest_bible.py")
26
+ else:
27
+ print("BibleService: LanceDB connected.")
28
+
29
+ async def get_passage(self, reference: str, translation: str = "web") -> Optional[str]:
30
+ """
31
+ Retrieves full passage text from bible-api.com (External Fallback).
32
+ """
33
+ clean_ref = reference.strip()
34
+ if not clean_ref:
35
+ return None
36
+
37
+ async with httpx.AsyncClient() as client:
38
+ try:
39
+ response = await client.get(
40
+ f"{self.BASE_URL}/{clean_ref}",
41
+ params={"translation": translation}
42
+ )
43
+ if response.status_code == 200:
44
+ return response.json().get("text", "").strip()
45
+ return None
46
+ except Exception:
47
+ return None
48
+
49
+ async def search(self, query: str, limit: int = 3) -> List[dict]:
50
+ """
51
+ Semantic search using persistent LanceDB.
52
+ """
53
+ if not os.path.exists(self.DB_PATH):
54
+ print("BibleService Error: DB not initialized.")
55
+ return []
56
+
57
+ # 1. Generate Query Vector
58
+ query_embedding = await llm_service.get_embedding(query)
59
+ if not query_embedding:
60
+ return []
61
+
62
+ try:
63
+ # 2. Search LanceDB
64
+ db = lancedb.connect(self.DB_PATH)
65
+
66
+ # Check if table exists
67
+ if self.TABLE_NAME not in db.table_names():
68
+ print(f"BibleService: Table {self.TABLE_NAME} not found.")
69
+ return []
70
+
71
+ tbl = db.open_table(self.TABLE_NAME)
72
+
73
+ # LanceDB search
74
+ # Explicitly select columns to ensure they are returned
75
+ results = tbl.search(query_embedding).limit(limit).select(["reference", "text"]).to_list()
76
+
77
+ # 3. Format Results
78
+ valid_results = []
79
+ for item in results:
80
+ # distance is typically L2. For binary-ish vectors, it's sqrt(sum of differences squared).
81
+ dist = item.get('_distance', 1.0)
82
+ # Simple inverse normalization for display
83
+ relevance = 1.0 / (1.0 + dist)
84
+
85
+ valid_results.append({
86
+ "score": relevance,
87
+ "text": item.get("text", ""),
88
+ "reference": item.get("reference", "Unknown")
89
+ })
90
+
91
+ return valid_results
92
+
93
+ except Exception as e:
94
+ print(f"BibleService Search Error: {e}")
95
+ return []
96
+
97
+ bible_service = BibleService()
app/services/blessing.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.services.llm import llm_service
2
+
3
+ class BlessingService:
4
+ PROMPT = """Offer a brief closing blessing.
5
+
6
+ Guidelines:
7
+ - One or two sentences
8
+ - No claims of divine action
9
+ - Encouraging, not authoritative
10
+ - Warm but not dramatic"""
11
+
12
+ async def bless(self) -> str:
13
+ return await llm_service.generate_response(message="Bless me.", system_prompt=self.PROMPT)
14
+
15
+ blessing_service = BlessingService()
app/services/citations.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import List
3
+
4
+ class ScriptureReference(BaseModel):
5
+ book: str
6
+ chapter: int
7
+ verses: str # e.g. "16-18" or "10"
8
+ text: str
9
+ translation: str
10
+
11
+ class CitationService:
12
+ def format_citations(self, refs: List[ScriptureReference]) -> str:
13
+ """
14
+ Formats a list of scripture references into a readable footer.
15
+ """
16
+ if not refs:
17
+ return ""
18
+
19
+ formatted = "\n\n**Scripture References:**\n"
20
+ for ref in refs:
21
+ formatted += f"- *{ref.book} {ref.chapter}:{ref.verses} ({ref.translation})*\n"
22
+ return formatted
23
+
24
+ citation_service = CitationService()
app/services/context.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.services.llm import llm_service
2
+
3
+ class ContextService:
4
+ PROMPT = """You are a biblical scholar explaining scripture responsibly.
5
+
6
+ Verse:
7
+ {verse_text}
8
+
9
+ Explain:
10
+ - Historical context
11
+ - Audience
12
+ - Key theme
13
+ - What the verse DOES and DOES NOT claim
14
+
15
+ Avoid modern assumptions or dogmatic conclusions."""
16
+
17
+ async def explain(self, verse_text: str) -> str:
18
+ prompt = self.PROMPT.format(verse_text=verse_text)
19
+ return await llm_service.generate_response(message="Explain this verse.", system_prompt=prompt)
20
+
21
+ context_service = ContextService()
app/services/discernment.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.services.llm import llm_service
2
+
3
+ class DiscernmentService:
4
+ PROMPT = """You are ORA, a spiritual discernment guide.
5
+
6
+ Context:
7
+ - User situation: {situation}
8
+ - Emotional tone: {emotion}
9
+ - Relevant scripture: {scripture}
10
+
11
+ Your Role:
12
+ - You help the user reflect prayerfully and wisely.
13
+ - You do NOT tell the user what decision to make.
14
+ - You do NOT give commands or orders.
15
+ - You do NOT promise specific outcomes.
16
+
17
+ Respond with:
18
+ 1. A calm, empathetic acknowledgment
19
+ 2. 2–3 reflective questions to help them uncover their own answer
20
+ 3. A short, hopeful prayer or reflection
21
+ 4. A gentle reminder of their own agency and freedom"""
22
+
23
+ async def guide(self, situation: str, emotion: str, scripture: str = "") -> str:
24
+ prompt = self.PROMPT.format(situation=situation, emotion=emotion, scripture=scripture)
25
+ return await llm_service.generate_response(message="Please guide me.", system_prompt=prompt)
26
+
27
+ discernment_service = DiscernmentService()
app/services/doctrine.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.services.llm import llm_service
2
+
3
+ class DoctrineService:
4
+ PROMPT = """You are ORA, aware of Christian theological diversity.
5
+
6
+ User tradition (if known): {tradition}
7
+
8
+ Question:
9
+ {question}
10
+
11
+ Answer by:
12
+ - Explaining the common ground
13
+ - Noting differences where relevant
14
+ - Avoiding declaring one view as “the only truth”
15
+ - Using scripture carefully"""
16
+
17
+ async def answer(self, question: str, tradition: str = "general") -> str:
18
+ prompt = self.PROMPT.format(question=question, tradition=tradition)
19
+ return await llm_service.generate_response(message=question, system_prompt=prompt)
20
+
21
+ doctrine_service = DoctrineService()
app/services/emotion.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ class EmotionService:
4
+ def __init__(self):
5
+ self.positive_keywords = {
6
+ "happy", "joy", "blessed", "grateful", "peace", "hope", "love",
7
+ "excited", "good", "great", "wonderful", "calm", "content"
8
+ }
9
+ self.negative_keywords = {
10
+ "sad", "depressed", "anxious", "afraid", "scared", "fear",
11
+ "lonely", "hurt", "pain", "grief", "broken", "suffering",
12
+ "angry", "mad", "upset", "bad", "terrible", "hate"
13
+ }
14
+ self.high_distress_keywords = {
15
+ "kill", "suicide", "die", "end it all", "hopeless", "worthless",
16
+ "unbearable", "can't go on", "no way out"
17
+ }
18
+
19
+ async def analyze_tone(self, text: str) -> Dict[str, any]:
20
+ """
21
+ Returns sentiment and emotional intensity.
22
+ """
23
+ text_lower = text.lower()
24
+ words = text_lower.split()
25
+ total_words = len(words) if words else 1
26
+
27
+ pos_count = sum(1 for word in words if word in self.positive_keywords)
28
+ neg_count = sum(1 for word in words if word in self.negative_keywords)
29
+
30
+ # Determine sentiment
31
+ if pos_count > neg_count:
32
+ sentiment = "positive"
33
+ elif neg_count > pos_count:
34
+ sentiment = "negative"
35
+ else:
36
+ sentiment = "neutral"
37
+
38
+ # Calculate intensity (simple heuristic: fraction of emotional words)
39
+ emotional_word_count = pos_count + neg_count
40
+ # Normalize intensity to be somewhat reasonable (0.0 to 1.0)
41
+ # Assuming if 30% of words are emotional, it's very intense.
42
+ raw_intensity = emotional_word_count / total_words
43
+ intensity = min(raw_intensity * 3.0, 1.0)
44
+
45
+ return {
46
+ "sentiment": sentiment,
47
+ "intensity": round(intensity, 2),
48
+ "pos_count": pos_count,
49
+ "neg_count": neg_count
50
+ }
51
+
52
+ def is_distress_high(self, text: str, emotion_data: Dict[str, any]) -> bool:
53
+ text_lower = text.lower()
54
+
55
+ # Check for specific trigger words
56
+ for keyword in self.high_distress_keywords:
57
+ if keyword in text_lower:
58
+ return True
59
+
60
+ # Check for high negative intensity
61
+ if emotion_data.get("sentiment") == "negative" and emotion_data.get("intensity", 0) > 0.8:
62
+ return True
63
+
64
+ return False
65
+
66
+ emotion_service = EmotionService()
app/services/ethics.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.services.llm import llm_service
2
+
3
+ class EthicsService:
4
+ PROMPT = """You are ORA, facilitating ethical reflection.
5
+
6
+ Scenario:
7
+ {scenario}
8
+
9
+ Use:
10
+ - Scripture themes (not commands)
11
+ - Wisdom principles
12
+ - Compassion-first framing
13
+
14
+ Avoid:
15
+ - Absolute judgments
16
+ - Shame
17
+ - Fear-based language"""
18
+
19
+ async def reflect(self, scenario: str) -> str:
20
+ prompt = self.PROMPT.format(scenario=scenario)
21
+ return await llm_service.generate_response(message="Help me reflect ethically.", system_prompt=prompt)
22
+
23
+ ethics_service = EthicsService()
app/services/feedback.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+ class Feedback(BaseModel):
4
+ message_id: str
5
+ rating: int # 1-5
6
+ comment: str = ""
7
+
8
+ class FeedbackService:
9
+ async def submit_feedback(self, feedback: Feedback):
10
+ # Store feedback for RLHF
11
+ pass
app/services/growth.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.services.llm import llm_service
2
+
3
+ class GrowthService:
4
+ PROMPT = """You are summarizing the user’s spiritual journey.
5
+
6
+ Based on:
7
+ - Past reflections: {reflections}
8
+ - Prayers: {prayers}
9
+ - Questions asked: {questions}
10
+
11
+ Produce:
12
+ - A gentle narrative summary
13
+ - Noticing growth patterns
14
+ - No judgment or evaluation"""
15
+
16
+ async def summarize(self, reflections: str, prayers: str, questions: str) -> str:
17
+ prompt = self.PROMPT.format(reflections=reflections, prayers=prayers, questions=questions)
18
+ return await llm_service.generate_response(message="Summarize my journey.", system_prompt=prompt)
19
+
20
+ growth_service = GrowthService()
app/services/guardrails.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import List, Optional
3
+ from app.services.audit import audit_service
4
+
5
+
6
+ class GuardrailViolation(Exception):
7
+ pass
8
+
9
+ class GuardrailService:
10
+ def __init__(self):
11
+ self.prohibited_phrases = [
12
+ "God told me that you",
13
+ "I prophesy",
14
+ "You must do this",
15
+ "The Lord is saying right now",
16
+ "I declare over you",
17
+ "Scripture commands you to",
18
+ "Thus saith the Lord",
19
+ "God fails you if",
20
+ "You are sinning by"
21
+ ]
22
+
23
+ async def validate_response(self, content: str, user_id: str = "system") -> str:
24
+ """
25
+ Ensures ORA does not claim divine authority or give dangerous advice.
26
+ """
27
+ for phrase in self.prohibited_phrases:
28
+ if phrase.lower() in content.lower():
29
+ # Log the violation
30
+ await audit_service.log_violation(
31
+ user_id=user_id,
32
+ violation_type="Prohibited Phrase",
33
+ content=content
34
+ )
35
+ # Block the output
36
+ raise GuardrailViolation(f"Response violated safety guardrail: '{phrase}'")
37
+
38
+ return content
39
+
40
+ async def check_input_safety(self, message: str, user_id: str = "anonymous") -> bool:
41
+ """
42
+ Checks for self-harm or crisis keywords.
43
+ """
44
+ crisis_keywords = ["kill myself", "end it all", "suicide", "hurt myself"]
45
+ if any(keyword in message.lower() for keyword in crisis_keywords):
46
+ await audit_service.log_violation(
47
+ user_id=user_id,
48
+ violation_type="Crisis Keyword",
49
+ content=message
50
+ )
51
+ return False
52
+ return True
53
+
54
+ async def sanitize_content(self, content: str) -> str:
55
+ """
56
+ Sanitizes content by replacing restricted words (placeholders for now).
57
+ """
58
+ # Example: Simple redaction if we had a list of 'warning words' that aren't strict blocks
59
+ return content
60
+
61
+ guardrail_service = GuardrailService()
app/services/intent.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import Optional, List
3
+ from enum import Enum
4
+
5
+ class UserIntent(str, Enum):
6
+ QUESTION = "question"
7
+ EMOTIONAL_SUPPORT = "emotional_support"
8
+ PRAYER_REQUEST = "prayer_request"
9
+ BIBLE_STUDY = "bible_study"
10
+ LIFE_DECISION = "life_decision"
11
+ CONFESSION = "confession"
12
+ UNKNOWN = "unknown"
13
+
14
+ class IntentAnalysis(BaseModel):
15
+ intent: UserIntent
16
+ urgency: str = "low" # low, medium, high
17
+ requires_scripture: bool
18
+ emotional_tone: Optional[str] = None
19
+ confidence: float = 1.0
20
+
21
+ class IntentService:
22
+ def __init__(self):
23
+ # Keywords for deterministic matching
24
+ self.intent_keywords = {
25
+ UserIntent.PRAYER_REQUEST: ["pray", "prayer", "intercede", "god help", "need god"],
26
+ UserIntent.EMOTIONAL_SUPPORT: ["sad", "depressed", "anxious", "lonely", "hurt", "grief", "pain", "crying", "broken", "suffering"],
27
+ UserIntent.BIBLE_STUDY: ["verse", "scripture", "passage", "chapter", "bible", "read", "psalm", "gospel", "book of"],
28
+ UserIntent.LIFE_DECISION: ["decide", "choice", "what should i do", "guidance", "direction", "path", "will for me"],
29
+ UserIntent.CONFESSION: ["sin", "forgive", "confess", "guilt", "mistake", "wrong", "sorry", "repent"],
30
+ UserIntent.QUESTION: ["who", "what", "where", "when", "why", "how", "?"],
31
+ }
32
+
33
+ async def analyze(self, message: str) -> IntentAnalysis:
34
+ msg_lower = message.lower()
35
+
36
+ # Priority 1: High urgency / Specific needs
37
+ if any(kw in msg_lower for kw in self.intent_keywords[UserIntent.PRAYER_REQUEST]):
38
+ return IntentAnalysis(intent=UserIntent.PRAYER_REQUEST, requires_scripture=True, urgency="medium")
39
+
40
+ if any(kw in msg_lower for kw in self.intent_keywords[UserIntent.CONFESSION]):
41
+ return IntentAnalysis(intent=UserIntent.CONFESSION, requires_scripture=True, urgency="medium")
42
+
43
+ if any(kw in msg_lower for kw in self.intent_keywords[UserIntent.EMOTIONAL_SUPPORT]):
44
+ return IntentAnalysis(intent=UserIntent.EMOTIONAL_SUPPORT, requires_scripture=True, urgency="medium")
45
+
46
+ # Priority 2: Study and Guidance
47
+ if any(kw in msg_lower for kw in self.intent_keywords[UserIntent.BIBLE_STUDY]):
48
+ return IntentAnalysis(intent=UserIntent.BIBLE_STUDY, requires_scripture=True)
49
+
50
+ if any(kw in msg_lower for kw in self.intent_keywords[UserIntent.LIFE_DECISION]):
51
+ return IntentAnalysis(intent=UserIntent.LIFE_DECISION, requires_scripture=True, urgency="medium")
52
+
53
+ # Priority 3: General Questions
54
+ if any(kw in msg_lower for kw in self.intent_keywords[UserIntent.QUESTION]):
55
+ return IntentAnalysis(intent=UserIntent.QUESTION, requires_scripture=False)
56
+
57
+ # Fallback
58
+ return IntentAnalysis(intent=UserIntent.UNKNOWN, requires_scripture=False)
59
+
60
+ intent_service = IntentService()
app/services/journal.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import lancedb
2
+ import uuid
3
+ import os
4
+ from typing import List, Dict, Optional
5
+ from datetime import datetime
6
+ from app.services.llm import llm_service
7
+
8
+ class JournalService:
9
+ LANCE_PATH = "data/journal_lancedb"
10
+ TABLE_NAME = "journal_nodes"
11
+
12
+ def __init__(self):
13
+ if not os.path.exists("data"):
14
+ os.makedirs("data")
15
+ self.db = lancedb.connect(self.LANCE_PATH)
16
+
17
+ async def create_entry(self, user_id: str, text: str, verses: List[str] = None, tags: List[str] = None) -> str:
18
+ """
19
+ Creates a new journal node, embeds it, and automatically finds links to previous entries.
20
+ """
21
+ entry_id = str(uuid.uuid4())
22
+ timestamp = datetime.now().isoformat()
23
+
24
+ # 1. Generate Embedding
25
+ vector = await llm_service.get_embedding(text)
26
+ if not vector:
27
+ print("JournalService: Failed to generate embedding.")
28
+ return None
29
+
30
+ # 2. Prepare Data
31
+ data = [{
32
+ "vector": vector,
33
+ "id": entry_id,
34
+ "user_id": user_id,
35
+ "text": text,
36
+ "verses": verses or [],
37
+ "tags": tags or [],
38
+ "timestamp": timestamp
39
+ }]
40
+
41
+ # 3. Store in LanceDB
42
+ if self.TABLE_NAME in self.db.table_names():
43
+ tbl = self.db.open_table(self.TABLE_NAME)
44
+ tbl.add(data)
45
+ else:
46
+ self.db.create_table(self.TABLE_NAME, data=data)
47
+
48
+ print(f"JournalService: Created entry {entry_id} for user {user_id}")
49
+ return entry_id
50
+
51
+ async def get_related_entries(self, user_id: str, entry_text: str, limit: int = 3) -> List[Dict]:
52
+ """
53
+ Finds the 'Zettelkasten links' — other entries that are semantically related.
54
+ """
55
+ if self.TABLE_NAME not in self.db.table_names():
56
+ return []
57
+
58
+ query_vec = await llm_service.get_embedding(entry_text)
59
+ if not query_vec:
60
+ return []
61
+
62
+ tbl = self.db.open_table(self.TABLE_NAME)
63
+
64
+ # Search for similar entries by the same user
65
+ results = (tbl.search(query_vec)
66
+ .where(f"user_id = '{user_id}'", prefilter=True)
67
+ .limit(limit + 1)
68
+ .to_list())
69
+
70
+ # Filter out exact matches (the entry itself)
71
+ filtered = [r for r in results if r['text'] != entry_text]
72
+ return filtered[:limit]
73
+
74
+ async def get_user_entries(self, user_id: str, limit: int = 20) -> List[Dict]:
75
+ """Retrieves a timeline of entries."""
76
+ if self.TABLE_NAME not in self.db.table_names():
77
+ return []
78
+
79
+ tbl = self.db.open_table(self.TABLE_NAME)
80
+ # Using a simple to_list and manual sort for small POC datasets
81
+ results = tbl.search().where(f"user_id = '{user_id}'").to_list()
82
+ results.sort(key=lambda x: x['timestamp'], reverse=True)
83
+ return results[:limit]
84
+
85
+ # Singleton instance
86
+ journal_service = JournalService()
app/services/llm.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openai import AsyncOpenAI
2
+ from app.core.config import settings
3
+
4
+ class LLMService:
5
+ def __init__(self):
6
+ self.client = AsyncOpenAI(
7
+ base_url=settings.LLM_BASE_URL,
8
+ api_key=settings.LLM_API_KEY
9
+ )
10
+ self.is_offline = False # Cache offline status to avoid repeated timeouts
11
+
12
+ async def generate_response(self, message: str, system_prompt: str = settings.SYSTEM_PROMPT, tools: list = None) -> dict:
13
+ if self.is_offline:
14
+ return self._get_mock_swarm_response(message, system_prompt, tools)
15
+
16
+ try:
17
+ messages = [
18
+ {"role": "system", "content": system_prompt},
19
+ {"role": "user", "content": message}
20
+ ]
21
+
22
+ kwargs = {
23
+ "model": settings.MODEL_NAME,
24
+ "messages": messages,
25
+ "temperature": 0.7
26
+ }
27
+
28
+ if tools:
29
+ kwargs["tools"] = tools
30
+ kwargs["tool_choice"] = "auto"
31
+
32
+ completion = await self.client.chat.completions.create(**kwargs)
33
+
34
+ choice = completion.choices[0].message
35
+ return {
36
+ "content": choice.content or "",
37
+ "tool_calls": getattr(choice, "tool_calls", None)
38
+ }
39
+
40
+ except Exception as e:
41
+ self.is_offline = True
42
+ print(f"LLM Connectivity failed: {str(e)}. Switching to MOCK mode.")
43
+ return self._get_mock_swarm_response(message, system_prompt, tools)
44
+
45
+ def _get_mock_swarm_response(self, message: str, system_prompt: str, tools: list) -> dict:
46
+ """
47
+ Simulates agent handoffs and tool calling based on keywords.
48
+ Used for verification when Ollama is offline.
49
+ """
50
+ msg_lower = message.lower()
51
+
52
+ # Check for Episodic Memory in system prompt
53
+ memory_insight = ""
54
+ if "Relevant past insights:" in system_prompt:
55
+ # Extract the first insight for the mock response
56
+ parts = system_prompt.split("Relevant past insights:")
57
+ if len(parts) > 1:
58
+ memory_insight = parts[1].split("\n")[1].strip("- ").strip()
59
+
60
+ # 1. RLM / REPL Mocking
61
+ if "Python script" in system_prompt or "calculate" in msg_lower:
62
+ return {
63
+ "content": "```python\n# Simulated reasoning code\ndate1 = -586\ndate2 = 70\nprint(f'Total span: {date2 - date1} years')\n```",
64
+ "tool_calls": None
65
+ }
66
+
67
+ # 2. Check for Handoff Keywords
68
+ if tools:
69
+ tool_names = [t["function"]["name"] for t in tools]
70
+
71
+ if "transfer_to_theologian" in tool_names and ("bible" in msg_lower or "genesis" in msg_lower or "study" in msg_lower):
72
+ return {
73
+ "content": f"I see we previously talked about {memory_insight}. I will hand you over to our Theologian for a deeper Bible study.",
74
+ "tool_calls": [type('ToolCall', (), {
75
+ "id": "mock_handoff_1",
76
+ "function": type('Func', (), {"name": "transfer_to_theologian", "arguments": "{}"})
77
+ })]
78
+ }
79
+
80
+ if "transfer_to_healer" in tool_names and ("sad" in msg_lower or "prayer" in msg_lower or "help" in msg_lower):
81
+ return {
82
+ "content": f"I remember you were feeling {memory_insight} earlier. I will connect you with our Healer for prayer.",
83
+ "tool_calls": [type('ToolCall', (), {
84
+ "id": "mock_handoff_2",
85
+ "function": type('Func', (), {"name": "transfer_to_healer", "arguments": "{}"})
86
+ })]
87
+ }
88
+
89
+ response_content = f"[MOCK MODE] I am processing your message: '{message}'."
90
+ if memory_insight:
91
+ response_content += f" I remember you mentioned: '{memory_insight}'."
92
+
93
+ return {
94
+ "content": response_content,
95
+ "tool_calls": None
96
+ }
97
+
98
+
99
+ async def get_embedding(self, text: str) -> list[float]:
100
+ if self.is_offline:
101
+ return self._get_mock_embedding(text)
102
+
103
+ try:
104
+ response = await self.client.embeddings.create(
105
+ model=settings.MODEL_NAME,
106
+ input=text
107
+ )
108
+ return response.data[0].embedding
109
+ except Exception as e:
110
+ # First failure sets the flag for this session/instance
111
+ self.is_offline = True
112
+ print(f"Embedding connectivity failed: {str(e)}. Switching to MOCK mode for this session.")
113
+ return self._get_mock_embedding(text)
114
+
115
+ def _get_mock_embedding(self, text: str, dim: int = 1536) -> list[float]:
116
+ """
117
+ Creates a deterministic sparse embedding based on word hashing.
118
+ Allows basic keyword matching to work even without a real LLM.
119
+ """
120
+ vec = [0.0] * dim
121
+ words = text.lower().split()
122
+ for word in words:
123
+ # Simple hash to map word to index
124
+ idx = sum(ord(c) for c in word) % dim
125
+ vec[idx] = 1.0
126
+ return vec
127
+
128
+ llm_service = LLMService()
app/services/memory.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sqlite3
2
+ import json
3
+ import os
4
+ import lancedb
5
+ from typing import List, Dict, Optional
6
+ from datetime import datetime
7
+ from app.services.llm import llm_service
8
+
9
+ class MemoryService:
10
+ DB_PATH = "data/memory.db"
11
+ LANCE_PATH = "data/memory_lancedb"
12
+ EPISODE_TABLE = "episodes"
13
+
14
+ def __init__(self):
15
+ self._init_sqlite()
16
+ self._init_lancedb()
17
+
18
+ def _init_sqlite(self):
19
+ """Initialize the SQLite database with necessary tables."""
20
+ if not os.path.exists("data"):
21
+ os.makedirs("data")
22
+
23
+ conn = sqlite3.connect(self.DB_PATH)
24
+ cursor = conn.cursor()
25
+
26
+ # Table for conversation history (short-term memory)
27
+ cursor.execute('''
28
+ CREATE TABLE IF NOT EXISTS interactions (
29
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
30
+ user_id TEXT NOT NULL,
31
+ role TEXT NOT NULL,
32
+ content TEXT NOT NULL,
33
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
34
+ )
35
+ ''')
36
+
37
+ # Table for facts/long-term memory
38
+ cursor.execute('''
39
+ CREATE TABLE IF NOT EXISTS facts (
40
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
41
+ user_id TEXT NOT NULL,
42
+ fact TEXT NOT NULL,
43
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
44
+ )
45
+ ''')
46
+
47
+ conn.commit()
48
+ conn.close()
49
+
50
+ def _init_lancedb(self):
51
+ """Initialize LanceDB for episodic memory."""
52
+ self.db = lancedb.connect(self.LANCE_PATH)
53
+
54
+ async def add_interaction(self, user_id: str, role: str, content: str):
55
+ """Add a message interactions to the short-term memory."""
56
+ conn = sqlite3.connect(self.DB_PATH)
57
+ cursor = conn.cursor()
58
+ cursor.execute(
59
+ 'INSERT INTO interactions (user_id, role, content) VALUES (?, ?, ?)',
60
+ (user_id, role, content)
61
+ )
62
+ conn.commit()
63
+ conn.close()
64
+
65
+ async def get_short_term_memory(self, user_id: str, limit: int = 10) -> List[Dict[str, str]]:
66
+ """Retrieve recent interactions for a user."""
67
+ conn = sqlite3.connect(self.DB_PATH)
68
+ conn.row_factory = sqlite3.Row
69
+ cursor = conn.cursor()
70
+ cursor.execute(
71
+ 'SELECT role, content FROM interactions WHERE user_id = ? ORDER BY id DESC LIMIT ?',
72
+ (user_id, limit)
73
+ )
74
+ rows = cursor.fetchall()
75
+ conn.close()
76
+ return [{"role": row["role"], "content": row["content"]} for row in reversed(rows)]
77
+
78
+ async def add_fact(self, user_id: str, fact: str):
79
+ """Add a specific fact to long-term memory."""
80
+ conn = sqlite3.connect(self.DB_PATH)
81
+ cursor = conn.cursor()
82
+ cursor.execute(
83
+ 'INSERT INTO facts (user_id, fact) VALUES (?, ?)',
84
+ (user_id, fact)
85
+ )
86
+ conn.commit()
87
+ conn.close()
88
+
89
+ async def get_long_term_memory(self, user_id: str) -> List[str]:
90
+ """Retrieve all stored facts for a user."""
91
+ conn = sqlite3.connect(self.DB_PATH)
92
+ conn.row_factory = sqlite3.Row
93
+ cursor = conn.cursor()
94
+ cursor.execute(
95
+ 'SELECT fact FROM facts WHERE user_id = ? ORDER BY timestamp DESC',
96
+ (user_id,)
97
+ )
98
+ rows = cursor.fetchall()
99
+ conn.close()
100
+ return [row["fact"] for row in rows]
101
+
102
+ # --- EPISODIC MEMORY (Phase 2 Upgrade) ---
103
+
104
+ async def store_episode(self, user_id: str, content: str, insight: str, emotion: str = "neutral"):
105
+ """
106
+ Summarizes a session/interaction into an 'Episode' and stores in LanceDB.
107
+ """
108
+ # 1. Generate Embedding for the insight/content
109
+ combined_text = f"Episode: {content} Insight: {insight} Emotion: {emotion}"
110
+ vector = await llm_service.get_embedding(combined_text)
111
+
112
+ if not vector:
113
+ print("MemoryService: Failed to generate embedding for episode.")
114
+ return
115
+
116
+ # 2. Data to store
117
+ data = [{
118
+ "vector": vector,
119
+ "user_id": user_id,
120
+ "content": content,
121
+ "insight": insight,
122
+ "emotion": emotion,
123
+ "timestamp": datetime.now().isoformat()
124
+ }]
125
+
126
+ # 3. Create or Update Table
127
+ if self.EPISODE_TABLE in self.db.table_names():
128
+ tbl = self.db.open_table(self.EPISODE_TABLE)
129
+ tbl.add(data)
130
+ else:
131
+ self.db.create_table(self.EPISODE_TABLE, data=data)
132
+
133
+ print(f"MemoryService: Episode stored for user {user_id}")
134
+
135
+ async def retrieve_episodes(self, user_id: str, query: str, limit: int = 3) -> List[Dict]:
136
+ """
137
+ Retrieves relevant spiritual episodes using semantic search.
138
+ """
139
+ if self.EPISODE_TABLE not in self.db.table_names():
140
+ return []
141
+
142
+ query_vec = await llm_service.get_embedding(query)
143
+ if not query_vec:
144
+ return []
145
+
146
+ try:
147
+ tbl = self.db.open_table(self.EPISODE_TABLE)
148
+
149
+ # Filter by user_id
150
+ results = (tbl.search(query_vec)
151
+ .where(f"user_id = '{user_id}'", prefilter=True)
152
+ .limit(limit)
153
+ .to_list())
154
+
155
+ return results
156
+ except Exception as e:
157
+ print(f"MemoryService: Search error: {str(e)}")
158
+ return []
159
+
160
+ # --- Legacy Compatibility ---
161
+ async def get_history(self, session_id: str) -> List[Dict[str, str]]:
162
+ return await self.get_short_term_memory(session_id, limit=50)
163
+
164
+ async def add_message(self, session_id: str, role: str, content: str):
165
+ await self.add_interaction(session_id, role, content)
166
+
167
+ # Singleton instance
168
+ memory_service = MemoryService()
169
+
app/services/orchestrator.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.services.memory import memory_service
2
+ from app.services.guardrails import guardrail_service, GuardrailViolation
3
+ from app.core.swarm_client import swarm_client
4
+ from app.agents.gatekeeper import gatekeeper_agent
5
+ from typing import Dict
6
+
7
+ class OrchestratorService:
8
+ async def process_message(self, user_id: str, message: str) -> dict:
9
+ # 1. Safety Check (Input)
10
+ is_safe = await guardrail_service.check_input_safety(message)
11
+ if not is_safe:
12
+ return {
13
+ "role": "assistant",
14
+ "content": "I sense you are in deep distress. Please contact a professional or emergency services immediately. You are valuable and loved."
15
+ }
16
+
17
+ # 2. Retrieve Episodic Context (Phase 2)
18
+ episodes = await memory_service.retrieve_episodes(user_id, message, limit=2)
19
+ episodic_context = ""
20
+ if episodes:
21
+ insights = [f"- {e['timestamp']}: {e['insight']}" for e in episodes]
22
+ episodic_context = "\nRelevant past insights:\n" + "\n".join(insights)
23
+
24
+ # 3. Run Swarm
25
+ messages = [{"role": "user", "content": message}]
26
+
27
+ # Inject episodic memory into context variables for agents to see
28
+ context = {
29
+ "user_id": user_id,
30
+ "episodic_memory": episodic_context
31
+ }
32
+
33
+ swarm_response = await swarm_client.run(
34
+ agent=gatekeeper_agent,
35
+ messages=messages,
36
+ context_variables=context
37
+ )
38
+
39
+ # The final message in the swarm history is the result
40
+ response_text = swarm_response.messages[-1]["content"]
41
+
42
+ # 3. Final Safety Validation of Output
43
+ try:
44
+ response_text = await guardrail_service.validate_response(response_text)
45
+ except GuardrailViolation as e:
46
+ print(f"Safety Violation blocked: {e}")
47
+ response_text = "I apologize, but I cannot provide that response as it violates my safety guidelines regarding authority claims."
48
+ except GuardrailViolation:
49
+ response_text = "I apologize, but I cannot complete that response as it violates my safety guidelines."
50
+
51
+ return {
52
+ "role": "assistant",
53
+ "content": response_text,
54
+ "agent": swarm_response.agent.name if swarm_response.agent else "Unknown",
55
+ "trace": swarm_response.trace
56
+ }
57
+
58
+ orchestrator = OrchestratorService()
app/services/practices.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import List, Optional
3
+
4
+ class SpiritualPractice(BaseModel):
5
+ title: str
6
+ description: str
7
+ steps: List[str]
8
+ duration_minutes: int
9
+ scripture_ref: Optional[str] = None
10
+
11
+ class PracticeGenerator:
12
+ async def generate(self, intent_data: dict, profile_data: dict) -> SpiritualPractice:
13
+ # Generation logic here
14
+ return SpiritualPractice(
15
+ title="Lectio Divina",
16
+ description="A traditional practice of scriptural reading, meditation and prayer.",
17
+ steps=["Read", "Reflect", "Respond", "Rest"],
18
+ duration_minutes=15
19
+ )
app/services/prayer.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.services.llm import llm_service
2
+
3
+ class PrayerService:
4
+ PROMPT = """You are ORA, a prayer companion.
5
+
6
+ Prayer focus: {topic}
7
+ User emotion: {emotion}
8
+ Scripture (optional): {scripture}
9
+
10
+ Write a prayer that:
11
+ - Is gentle, humble, and invitational
12
+ - Avoids absolutes (e.g., "always", "never", "definitely")
13
+ - Does NOT make promises on God's behalf (e.g., "God will heal you")
14
+ - Does NOT use commands or imperatives (e.g., "Pray this", "Do this", "Trust me")
15
+ - Uses natural, conversational language, not sermon tone
16
+ - Expresses hope and longing without guaranteeing outcomes"""
17
+
18
+ async def compose(self, topic: str, emotion: str, scripture: str = "") -> str:
19
+ prompt = self.PROMPT.format(topic=topic, emotion=emotion, scripture=scripture)
20
+ return await llm_service.generate_response(message="Compose a prayer for me.", system_prompt=prompt)
21
+
22
+ prayer_service = PrayerService()
app/services/profile.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import List, Optional
3
+ from enum import Enum
4
+ import sqlite3
5
+ import json
6
+
7
+ class TonePreference(str, Enum):
8
+ PASTORAL = "pastoral"
9
+ SCHOLARLY = "scholarly"
10
+ GENTLE = "gentle"
11
+ DIRECT = "direct"
12
+
13
+ class SpiritualGoal(str, Enum):
14
+ PRAYER = "prayer"
15
+ STUDY = "study"
16
+ HEALING = "healing"
17
+ LEADERSHIP = "leadership"
18
+ DISCERNMENT = "discernment"
19
+
20
+ class UserProfile(BaseModel):
21
+ user_id: str
22
+ denomination: Optional[str] = None
23
+ bible_translations: List[str] = Field(default=["NIV", "ESV"])
24
+ tone_preference: TonePreference = TonePreference.PASTORAL
25
+ spiritual_goals: List[SpiritualGoal] = []
26
+
27
+ class Config:
28
+ json_schema_extra = {
29
+ "example": {
30
+ "user_id": "usr_123",
31
+ "denomination": "non-denominational",
32
+ "bible_translations": ["NIV"],
33
+ "tone_preference": "gentle",
34
+ "spiritual_goals": ["prayer", "healing"]
35
+ }
36
+ }
37
+
38
+ class ProfileService:
39
+ def __init__(self, db_path: str = "memory.db"):
40
+ self.db_path = db_path
41
+ self._init_db()
42
+
43
+ def _init_db(self):
44
+ conn = sqlite3.connect(self.db_path)
45
+ cursor = conn.cursor()
46
+ cursor.execute('''
47
+ CREATE TABLE IF NOT EXISTS user_profiles (
48
+ user_id TEXT PRIMARY KEY,
49
+ denomination TEXT,
50
+ bible_translations TEXT,
51
+ tone_preference TEXT,
52
+ spiritual_goals TEXT
53
+ )
54
+ ''')
55
+ conn.commit()
56
+ conn.close()
57
+
58
+ async def get_profile(self, user_id: str) -> UserProfile:
59
+ conn = sqlite3.connect(self.db_path)
60
+ conn.row_factory = sqlite3.Row
61
+ cursor = conn.cursor()
62
+
63
+ cursor.execute('SELECT * FROM user_profiles WHERE user_id = ?', (user_id,))
64
+ row = cursor.fetchone()
65
+ conn.close()
66
+
67
+ if row:
68
+ return UserProfile(
69
+ user_id=row['user_id'],
70
+ denomination=row['denomination'],
71
+ bible_translations=json.loads(row['bible_translations']) if row['bible_translations'] else ["NIV"],
72
+ tone_preference=row['tone_preference'] or TonePreference.PASTORAL,
73
+ spiritual_goals=json.loads(row['spiritual_goals']) if row['spiritual_goals'] else []
74
+ )
75
+
76
+ # Return default profile if none exists
77
+ return UserProfile(user_id=user_id)
78
+
79
+ async def update_profile(self, user_id: str, data: dict) -> UserProfile:
80
+ # Get current profile to merge (or default)
81
+ current = await self.get_profile(user_id)
82
+
83
+ # Merge data and validate to ensure Enums are correctly parsed
84
+ merged_data = current.model_dump()
85
+ merged_data.update(data)
86
+ updated_model = UserProfile(**merged_data)
87
+
88
+ conn = sqlite3.connect(self.db_path)
89
+ cursor = conn.cursor()
90
+
91
+ cursor.execute('''
92
+ INSERT OR REPLACE INTO user_profiles (user_id, denomination, bible_translations, tone_preference, spiritual_goals)
93
+ VALUES (?, ?, ?, ?, ?)
94
+ ''', (
95
+ updated_model.user_id,
96
+ updated_model.denomination,
97
+ json.dumps(updated_model.bible_translations),
98
+ updated_model.tone_preference.value,
99
+ json.dumps([g.value for g in updated_model.spiritual_goals])
100
+ ))
101
+
102
+ conn.commit()
103
+ conn.close()
104
+
105
+ return updated_model
106
+
107
+ profile_service = ProfileService()
app/services/repl.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import io
3
+ import contextlib
4
+ import multiprocessing
5
+ import traceback
6
+ from typing import Dict, Any, Optional
7
+
8
+ def _restricted_execute(code: str, queue: multiprocessing.Queue):
9
+ """
10
+ Executes code in a completely isolated process environment.
11
+ """
12
+ # Redirect stdout to capture it
13
+ stdout = io.StringIO()
14
+
15
+ # Define restricted globals
16
+ # We remove dangerous modules like 'os', 'sys', 'shutil', etc.
17
+ safe_globals = {
18
+ "__builtins__": __builtins__.copy(),
19
+ "math": __import__("math"),
20
+ "datetime": __import__("datetime"),
21
+ "json": __import__("json"),
22
+ }
23
+ # Remove dangerous builtins
24
+ dangerous_builtins = ["open", "exec", "eval", "getattr", "setattr", "delattr", "help", "input", "compile"]
25
+ for b in dangerous_builtins:
26
+ if b in safe_globals["__builtins__"]:
27
+ del safe_globals["__builtins__"][b]
28
+
29
+ try:
30
+ with contextlib.redirect_stdout(stdout):
31
+ exec(code, safe_globals)
32
+ queue.put({"success": True, "output": stdout.getvalue(), "error": None})
33
+ except Exception:
34
+ queue.put({"success": False, "output": stdout.getvalue(), "error": traceback.format_exc()})
35
+
36
+ class REPLService:
37
+ def execute(self, code: str, timeout: int = 2) -> Dict[str, Any]:
38
+ """
39
+ Executes Python code in a restricted global environment.
40
+ """
41
+ stdout = io.StringIO()
42
+
43
+ # Define restricted globals
44
+ safe_globals = {
45
+ "__builtins__": {
46
+ "print": print,
47
+ "range": range,
48
+ "len": len,
49
+ "list": list,
50
+ "dict": dict,
51
+ "str": str,
52
+ "int": int,
53
+ "float": float,
54
+ "bool": bool,
55
+ "abs": abs,
56
+ "sum": sum,
57
+ "min": min,
58
+ "max": max,
59
+ "reversed": reversed,
60
+ "sorted": sorted,
61
+ "set": set,
62
+ "enumerate": enumerate,
63
+ "zip": zip,
64
+ },
65
+ "math": __import__("math"),
66
+ "datetime": __import__("datetime"),
67
+ "json": __import__("json"),
68
+ }
69
+
70
+ try:
71
+ with contextlib.redirect_stdout(stdout):
72
+ # Using a wrapper to avoid affecting the main thread too much if it hangs
73
+ # Note: This doesn't actually provide timeout protection in same-thread
74
+ # but works around the multiprocess hang in local dev
75
+ exec(code, safe_globals)
76
+
77
+ return {
78
+ "success": True,
79
+ "output": stdout.getvalue(),
80
+ "error": None
81
+ }
82
+ except Exception:
83
+ return {
84
+ "success": False,
85
+ "output": stdout.getvalue(),
86
+ "error": traceback.format_exc()
87
+ }
88
+
89
+ repl_service = REPLService()
app/services/rlm.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.services.repl import repl_service
2
+ from app.services.llm import llm_service
3
+ from typing import Dict, Any
4
+
5
+ class RLMService:
6
+ """
7
+ Reason Synergizing Service.
8
+ Uses the REPL to solve complex logical problems through code generation and execution.
9
+ """
10
+
11
+ async def reason(self, problem: str) -> str:
12
+ """
13
+ Takes a logical problem, generates Python code to solve it, and returns the result.
14
+ """
15
+ system_prompt = """
16
+ You are the ORA Reasoning Engine.
17
+ Your task is to write a Python script to solve the user's logical problem or calculation.
18
+ - Only output the Python code, nothing else.
19
+ - Use print() to output final results.
20
+ - You have access to: math, datetime, json.
21
+ - Do not use dangerous modules (os, subprocess, etc).
22
+ - Keep the script efficient.
23
+ """
24
+
25
+ # 1. Generate the code
26
+ prompt = f"Problem: {problem}\n\nWrite a Python script to solve this. Only output code."
27
+ llm_res = await llm_service.generate_response(prompt, system_prompt=system_prompt)
28
+ code = llm_res.get("content", "").strip()
29
+
30
+ # Clean up markdown if LLM adds it
31
+ if code.startswith("```python"):
32
+ code = code.split("```python")[1].split("```")[0].strip()
33
+ elif code.startswith("```"):
34
+ code = code.split("```")[1].split("```")[0].strip()
35
+
36
+ if not code:
37
+ return "Error: Could not generate reasoning code."
38
+
39
+ # 2. Execute in REPL
40
+ print(f"RLM: Executing reasoning code...")
41
+ result = repl_service.execute(code)
42
+
43
+ if not result["success"]:
44
+ return f"Reasoning Fallacy (Code Error): {result['error']}"
45
+
46
+ return result["output"]
47
+
48
+ rlm_service = RLMService()
app/services/simplify.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.services.llm import llm_service
2
+
3
+ class SimplifyService:
4
+ PROMPT = """Rewrite the following spiritual explanation
5
+ in clear, everyday language.
6
+
7
+ Text:
8
+ {text}
9
+
10
+ Rules:
11
+ - No jargon
12
+ - No preaching
13
+ - Keep meaning intact"""
14
+
15
+ async def simplify(self, text: str) -> str:
16
+ prompt = self.PROMPT.format(text=text)
17
+ return await llm_service.generate_response(message="Simplify this.", system_prompt=prompt)
18
+
19
+ simplify_service = SimplifyService()
app/services/stillness.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.services.llm import llm_service
2
+
3
+ class StillnessService:
4
+ PROMPT = """You are guiding the user into silence and stillness.
5
+
6
+ Create:
7
+ - A short breathing or grounding exercise
8
+ - One reflective sentence
9
+ - An invitation to pause without pressure
10
+
11
+ Do not quote scripture unless requested.
12
+ Keep it under 150 words."""
13
+
14
+ async def guide(self) -> str:
15
+ return await llm_service.generate_response(message="Help me find stillness.", system_prompt=self.PROMPT)
16
+
17
+ stillness_service = StillnessService()
app/services/trace.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from datetime import datetime
4
+ from typing import Dict, Any
5
+
6
+ class TraceService:
7
+ TRACE_DIR = "important/reasoning_traces"
8
+
9
+ def __init__(self):
10
+ if not os.path.exists(self.TRACE_DIR):
11
+ os.makedirs(self.TRACE_DIR, exist_ok=True)
12
+
13
+ def save_trace(self, trace_data: Dict[str, Any]):
14
+ """
15
+ Appends a reasoning trace to a daily .jsonl file.
16
+ """
17
+ today = datetime.now().strftime("%Y-%m-%d")
18
+ file_path = os.path.join(self.TRACE_DIR, f"ora_traces_{today}.jsonl")
19
+
20
+ # Add timestamp if missing
21
+ if "timestamp" not in trace_data:
22
+ trace_data["timestamp"] = datetime.now().isoformat()
23
+
24
+ try:
25
+ with open(file_path, "a", encoding="utf-8") as f:
26
+ f.write(json.dumps(trace_data) + "\n")
27
+ # print(f"TraceService: Trace saved to {file_path}")
28
+ except Exception as e:
29
+ print(f"TraceService Error: Failed to save trace: {str(e)}")
30
+
31
+ trace_service = TraceService()
app/services/translation.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.services.llm import llm_service
2
+ from typing import List
3
+
4
+ class TranslationService:
5
+ PROMPT = """Compare the following Bible translations:
6
+
7
+ Verse reference: {reference}
8
+ Translations:
9
+ {translations}
10
+
11
+ Explain:
12
+ - Key wording differences
13
+ - What meaning is shared
14
+ - What remains ambiguous
15
+
16
+ Do not claim one translation is superior."""
17
+
18
+ async def compare(self, reference: str, translations: List[str]) -> str:
19
+ # In a real app, we would fetch the texts first, here we mock passing them
20
+ translations_text = "\n".join(translations) # Placeholder
21
+ prompt = self.PROMPT.format(reference=reference, translations=translations_text)
22
+ return await llm_service.generate_response(message="Compare these translations.", system_prompt=prompt)
23
+
24
+ translation_service = TranslationService()
config.yml ADDED
@@ -0,0 +1 @@
 
 
1
+ {}
frontend/.claude/settings.local.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(dir:*)",
5
+ "Bash(npm run build:*)",
6
+ "Bash(npm run dev:*)",
7
+ "Bash(taskkill:*)",
8
+ "Bash(timeout /t 2)",
9
+ "Bash(cat:*)",
10
+ "Bash(if [ -d \".next\" ])",
11
+ "Bash(then rm -rf .next)",
12
+ "Bash(fi)",
13
+ "Bash(git init:*)",
14
+ "Bash(git add:*)"
15
+ ]
16
+ }
17
+ }
frontend/.gitignore ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dependencies
2
+ /node_modules
3
+ /.pnp
4
+ .pnp.js
5
+
6
+ # Testing
7
+ /coverage
8
+
9
+ # Next.js
10
+ /.next/
11
+ /out/
12
+
13
+ # Production
14
+ /build
15
+
16
+ # Misc
17
+ .DS_Store
18
+ *.pem
19
+
20
+ # Debug
21
+ npm-debug.log*
22
+ yarn-debug.log*
23
+ yarn-error.log*
24
+
25
+ # Local env files
26
+ .env*.local
27
+
28
+ # Vercel
29
+ .vercel
30
+
31
+ # TypeScript
32
+ *.tsbuildinfo
33
+ next-env.d.ts
frontend/README.md ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SoapBox Candles - Web App
2
+
3
+ A Next.js web application for SoapBox Super App's Candles feature - spiritual engagement rewards and AI feature credits.
4
+
5
+ ## Getting Started
6
+
7
+ ### Prerequisites
8
+ - Node.js 18+
9
+ - npm or yarn
10
+
11
+ ### Installation
12
+
13
+ ```bash
14
+ cd soapbox-candles
15
+ npm install
16
+ ```
17
+
18
+ ### Development
19
+
20
+ ```bash
21
+ npm run dev
22
+ ```
23
+
24
+ Open [http://localhost:3000](http://localhost:3000) in your browser.
25
+
26
+ ### Build
27
+
28
+ ```bash
29
+ npm run build
30
+ ```
31
+
32
+ ### Production
33
+
34
+ ```bash
35
+ npm start
36
+ ```
37
+
38
+ ## Deploy to Vercel
39
+
40
+ ### Option 1: Vercel CLI
41
+
42
+ ```bash
43
+ npm i -g vercel
44
+ vercel
45
+ ```
46
+
47
+ ### Option 2: GitHub Integration
48
+
49
+ 1. Push this project to a GitHub repository
50
+ 2. Go to [vercel.com](https://vercel.com)
51
+ 3. Import your GitHub repository
52
+ 4. Vercel will auto-detect Next.js and deploy
53
+
54
+ ## Project Structure
55
+
56
+ ```
57
+ soapbox-candles/
58
+ ├── app/
59
+ │ ├── globals.css # Global styles & animations
60
+ │ ├── layout.tsx # Root layout with Navbar/Footer
61
+ │ ├── page.tsx # Home page (Candles landing)
62
+ │ ├── features/ # Features page
63
+ │ ├── pricing/ # Pricing page
64
+ │ ├── about/ # About page
65
+ │ ├── contact/ # Contact page
66
+ │ ├── signin/ # Sign in page
67
+ │ └── get-started/ # Sign up page
68
+ ├── components/
69
+ │ ├── Navbar.tsx # Navigation component
70
+ │ ├── Footer.tsx # Footer component
71
+ │ ├── SpotlightCard.tsx # Interactive card component
72
+ │ └── ShinyButton.tsx # Animated CTA button
73
+ ├── public/ # Static assets
74
+ ├── package.json
75
+ ├── tailwind.config.js
76
+ ├── tsconfig.json
77
+ └── next.config.js
78
+ ```
79
+
80
+ ## Features
81
+
82
+ - **Responsive Design** - Works on all devices
83
+ - **Animations** - Sonar, beam, float, flicker effects
84
+ - **Interactive Cards** - Spotlight effect on hover
85
+ - **Shiny CTA Buttons** - Animated gradient borders
86
+ - **Dark Theme** - Warm amber/gold color scheme
87
+ - **TypeScript** - Full type safety
88
+ - **Tailwind CSS** - Utility-first styling
89
+
90
+ ## Pages
91
+
92
+ - `/` - Candles landing page
93
+ - `/features` - Platform features
94
+ - `/pricing` - Pricing plans
95
+ - `/about` - About SoapBox
96
+ - `/contact` - Contact form
97
+ - `/signin` - Sign in
98
+ - `/get-started` - Sign up
frontend/app/about/page.tsx ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { Sparkles, Brain, Heart, BookOpen, Shield, Users, Zap, Target } from 'lucide-react';
4
+ import SpotlightCard from '@/components/SpotlightCard';
5
+ import Link from 'next/link';
6
+ import ShinyButton from '@/components/ShinyButton';
7
+
8
+ const values = [
9
+ {
10
+ icon: Shield,
11
+ title: 'Faith-First Design',
12
+ description: 'Every feature is built with theological accuracy and spiritual sensitivity at its core.',
13
+ },
14
+ {
15
+ icon: Heart,
16
+ title: 'Compassionate AI',
17
+ description: 'Our agents are trained to provide empathetic, pastoral care in every interaction.',
18
+ },
19
+ {
20
+ icon: Brain,
21
+ title: 'Intelligent Guidance',
22
+ description: 'Multi-agent architecture ensures you receive specialized expertise for every question.',
23
+ },
24
+ {
25
+ icon: Users,
26
+ title: 'Privacy Respected',
27
+ description: 'Your spiritual journey is sacred. We never sell data or compromise your privacy.',
28
+ },
29
+ ];
30
+
31
+ const agents = [
32
+ {
33
+ name: 'Gatekeeper',
34
+ icon: Shield,
35
+ color: 'purple',
36
+ description: 'The intelligent router that understands your intent and connects you with the right specialist.',
37
+ },
38
+ {
39
+ name: 'Theologian',
40
+ icon: BookOpen,
41
+ color: 'blue',
42
+ description: 'Deep Scripture analysis with cross-references, historical context, and doctrinal insights.',
43
+ },
44
+ {
45
+ name: 'Healer',
46
+ icon: Heart,
47
+ color: 'rose',
48
+ description: 'Compassionate pastoral care with prayer guidance and emotional support.',
49
+ },
50
+ ];
51
+
52
+ const colorClasses: Record<string, { bg: string; border: string; text: string }> = {
53
+ purple: { bg: 'bg-purple-500/10', border: 'border-purple-500/30', text: 'text-purple-400' },
54
+ blue: { bg: 'bg-blue-500/10', border: 'border-blue-500/30', text: 'text-blue-400' },
55
+ rose: { bg: 'bg-rose-500/10', border: 'border-rose-500/30', text: 'text-rose-400' },
56
+ };
57
+
58
+ export default function AboutPage() {
59
+ return (
60
+ <>
61
+ {/* Hero */}
62
+ <section className="relative pt-32 pb-20 overflow-hidden">
63
+ <div className="absolute inset-0 ora-grid-bg pointer-events-none z-0" />
64
+ <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[800px] h-[600px] bg-purple-600/20 rounded-full blur-[150px] pointer-events-none -z-10" />
65
+
66
+ <div className="max-w-4xl mx-auto px-6 text-center">
67
+ <div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-purple-500/30 bg-purple-500/10 mb-8">
68
+ <Sparkles className="w-4 h-4 text-purple-400" />
69
+ <span className="text-xs font-mono text-purple-300 uppercase tracking-wider">About ORA</span>
70
+ </div>
71
+
72
+ <h1 className="text-4xl md:text-5xl font-bold tracking-tight mb-6 text-white">
73
+ AI Spiritual Guidance,{' '}
74
+ <span className="text-purple-400">Reimagined</span>
75
+ </h1>
76
+
77
+ <p className="text-lg text-neutral-400 max-w-2xl mx-auto">
78
+ ORA is a sovereign AI spiritual companion built with a multi-agent architecture
79
+ designed to provide personalized, theologically-grounded guidance for your faith journey.
80
+ </p>
81
+ </div>
82
+ </section>
83
+
84
+ {/* Mission */}
85
+ <section className="py-20 border-t border-white/5">
86
+ <div className="max-w-6xl mx-auto px-6">
87
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
88
+ <div>
89
+ <h2 className="text-3xl font-bold text-white mb-6">Our Mission</h2>
90
+ <p className="text-neutral-400 leading-relaxed mb-4">
91
+ We believe technology can serve faith. ORA was created to bridge the gap between
92
+ ancient wisdom and modern AI, providing believers with an intelligent companion
93
+ that understands Scripture, respects tradition, and meets you where you are.
94
+ </p>
95
+ <p className="text-neutral-400 leading-relaxed">
96
+ Our name embodies our method: <strong className="text-purple-400">O</strong>bserve,
97
+ <strong className="text-purple-400"> R</strong>eflect, <strong className="text-purple-400">A</strong>ct.
98
+ This framework guides every interaction, helping you engage more deeply with your faith.
99
+ </p>
100
+ </div>
101
+ <div className="grid grid-cols-2 gap-4">
102
+ {values.map((value) => (
103
+ <div
104
+ key={value.title}
105
+ className="p-5 rounded-xl bg-white/[0.02] border border-white/10 hover:border-purple-500/30 transition-all"
106
+ >
107
+ <value.icon className="w-8 h-8 text-purple-400 mb-3" />
108
+ <h3 className="text-white font-medium mb-1">{value.title}</h3>
109
+ <p className="text-neutral-500 text-sm">{value.description}</p>
110
+ </div>
111
+ ))}
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </section>
116
+
117
+ {/* Agent Architecture */}
118
+ <section className="py-20 border-t border-white/5 bg-[#030303]">
119
+ <div className="max-w-6xl mx-auto px-6">
120
+ <div className="text-center mb-12">
121
+ <h2 className="text-3xl font-bold text-white mb-4">Multi-Agent Architecture</h2>
122
+ <p className="text-neutral-400 max-w-2xl mx-auto">
123
+ ORA uses specialized AI agents that work together, each bringing unique expertise
124
+ to serve your spiritual needs.
125
+ </p>
126
+ </div>
127
+
128
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
129
+ {agents.map((agent) => {
130
+ const colors = colorClasses[agent.color];
131
+ return (
132
+ <SpotlightCard
133
+ key={agent.name}
134
+ className={`p-6 rounded-xl border ${colors.border} bg-white/[0.02]`}
135
+ spotlightColor="purple"
136
+ >
137
+ <div className={`w-14 h-14 rounded-xl ${colors.bg} flex items-center justify-center border ${colors.border} mb-4`}>
138
+ <agent.icon className={`w-7 h-7 ${colors.text}`} />
139
+ </div>
140
+ <h3 className="text-xl font-semibold text-white mb-2">{agent.name}</h3>
141
+ <p className="text-neutral-400 text-sm">{agent.description}</p>
142
+ </SpotlightCard>
143
+ );
144
+ })}
145
+ </div>
146
+ </div>
147
+ </section>
148
+
149
+ {/* Technology */}
150
+ <section className="py-20 border-t border-white/5">
151
+ <div className="max-w-4xl mx-auto px-6 text-center">
152
+ <h2 className="text-3xl font-bold text-white mb-6">Built with Purpose</h2>
153
+ <p className="text-neutral-400 mb-8 max-w-2xl mx-auto">
154
+ ORA combines cutting-edge AI with deep respect for theological tradition.
155
+ Our system includes episodic memory, reasoning traces, and safety guardrails
156
+ to ensure every interaction is helpful, accurate, and spiritually sensitive.
157
+ </p>
158
+
159
+ <div className="flex flex-wrap justify-center gap-3 mb-12">
160
+ {['Multi-Agent Swarm', 'Episodic Memory', 'LanceDB Vectors', 'Safety Guardrails', 'Reasoning Traces', 'Bible RAG'].map((tech) => (
161
+ <span
162
+ key={tech}
163
+ className="px-4 py-2 rounded-full bg-purple-500/10 border border-purple-500/20 text-purple-300 text-sm"
164
+ >
165
+ {tech}
166
+ </span>
167
+ ))}
168
+ </div>
169
+
170
+ <Link href="/dashboard">
171
+ <ShinyButton>
172
+ <span className="flex items-center gap-2">
173
+ <Sparkles className="w-5 h-5" />
174
+ Experience ORA
175
+ </span>
176
+ </ShinyButton>
177
+ </Link>
178
+ </div>
179
+ </section>
180
+ </>
181
+ );
182
+ }
frontend/app/candle/page.tsx ADDED
@@ -0,0 +1,635 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { Flame, Sun, Share2, TrendingUp, Gift, Calendar, Check, Users, Church, Star, Zap, ArrowRight, Sparkles, Heart, BookOpen, Video, MessageSquare, Trophy, Target, UserPlus, ThumbsUp } from 'lucide-react';
4
+ import SpotlightCard from '@/components/SpotlightCard';
5
+ import ShinyButton from '@/components/ShinyButton';
6
+ import Link from 'next/link';
7
+
8
+ export default function CandlePage() {
9
+ return (
10
+ <>
11
+ {/* Hero Section - Completely Redesigned */}
12
+ <section className="relative pt-36 pb-24 md:pt-44 md:pb-32 overflow-hidden min-h-[90vh] flex items-center">
13
+ <div className="absolute inset-0 bg-grid-pattern opacity-50 z-0 pointer-events-none" style={{ maskImage: 'radial-gradient(circle at center, black 30%, transparent 80%)', WebkitMaskImage: 'radial-gradient(circle at center, black 30%, transparent 80%)' }} />
14
+
15
+ {/* Animated Glow Backgrounds */}
16
+ <div className="absolute top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[1000px] h-[1000px] bg-amber-600/20 rounded-full blur-[150px] pointer-events-none -z-10 animate-pulse" style={{ animationDuration: '4s' }} />
17
+ <div className="absolute bottom-0 right-0 w-[600px] h-[600px] bg-orange-900/15 rounded-full blur-[100px] pointer-events-none -z-10" />
18
+ <div className="absolute top-1/2 left-0 w-[400px] h-[400px] bg-amber-500/10 rounded-full blur-[80px] pointer-events-none -z-10 animate-pulse" style={{ animationDuration: '6s', animationDelay: '2s' }} />
19
+
20
+ <div className="max-w-7xl mx-auto px-6 w-full">
21
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-20 items-center">
22
+ {/* Left: Content */}
23
+ <div className="max-w-xl z-10">
24
+ <div className="inline-flex items-center gap-2 px-4 py-2 rounded-full border border-amber-500/20 bg-amber-500/5 mb-8 backdrop-blur-sm animate-fade-slide-in">
25
+ <span className="relative flex h-2 w-2">
26
+ <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75" />
27
+ <span className="relative inline-flex rounded-full h-2 w-2 bg-amber-500" />
28
+ </span>
29
+ <span className="text-xs uppercase tracking-widest font-medium text-amber-200/80">
30
+ Spiritual Rewards System
31
+ </span>
32
+ </div>
33
+
34
+ <h1 className="text-5xl sm:text-6xl lg:text-7xl font-semibold tracking-tight leading-[1.05] mb-6 animate-fade-slide-in stagger-1">
35
+ <span className="text-white">Light Your</span>
36
+ <br />
37
+ <span className="text-transparent bg-clip-text bg-gradient-to-r from-amber-400 via-orange-400 to-amber-500">
38
+ Faith Journey
39
+ </span>
40
+ </h1>
41
+
42
+ <p className="text-xl text-neutral-300 mb-8 leading-relaxed animate-fade-slide-in stagger-2">
43
+ Earn <span className="text-amber-400 font-medium">Candles</span> through spiritual engagement. Spend them on AI-powered tools. Watch your faith community grow.
44
+ </p>
45
+
46
+ {/* Stats Row */}
47
+ <div className="flex items-center gap-6 mb-10 animate-fade-slide-in stagger-3">
48
+ <div className="text-center">
49
+ <div className="text-3xl font-bold text-amber-400">250</div>
50
+ <div className="text-xs text-neutral-500 uppercase tracking-wider">Free Start</div>
51
+ </div>
52
+ <div className="w-px h-12 bg-white/10" />
53
+ <div className="text-center">
54
+ <div className="text-3xl font-bold text-amber-400">+250</div>
55
+ <div className="text-xs text-neutral-500 uppercase tracking-wider">Monthly</div>
56
+ </div>
57
+ <div className="w-px h-12 bg-white/10" />
58
+ <div className="text-center">
59
+ <div className="text-3xl font-bold text-emerald-400">∞</div>
60
+ <div className="text-xs text-neutral-500 uppercase tracking-wider">Earn More</div>
61
+ </div>
62
+ </div>
63
+
64
+ <div className="flex flex-col sm:flex-row gap-4 animate-fade-slide-in stagger-4">
65
+ <Link href="/get-started">
66
+ <ShinyButton>
67
+ <span className="flex items-center gap-2">
68
+ <Flame className="w-[18px] h-[18px] candle-glow" />
69
+ Get 250 Free Candles
70
+ </span>
71
+ </ShinyButton>
72
+ </Link>
73
+ <button className="px-6 py-3.5 text-sm font-medium text-neutral-300 border border-white/10 rounded-full hover:bg-white/5 hover:text-white transition-all flex items-center gap-2">
74
+ <span>See How It Works</span>
75
+ <ArrowRight className="w-4 h-4" />
76
+ </button>
77
+ </div>
78
+ </div>
79
+
80
+ {/* Right: Candle Visual System */}
81
+ <div className="relative h-[500px] lg:h-[600px] w-full flex items-center justify-center animate-fade-slide-in stagger-2">
82
+ {/* Central Candle Orb */}
83
+ <div className="relative z-20">
84
+ {/* Outer Glow Ring */}
85
+ <div className="absolute inset-0 w-64 h-64 -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2">
86
+ <div className="absolute inset-0 rounded-full border border-amber-500/20 animate-ping" style={{ animationDuration: '3s' }} />
87
+ <div className="absolute inset-4 rounded-full border border-amber-500/30 animate-ping" style={{ animationDuration: '3s', animationDelay: '1s' }} />
88
+ <div className="absolute inset-8 rounded-full border border-amber-500/40 animate-ping" style={{ animationDuration: '3s', animationDelay: '2s' }} />
89
+ </div>
90
+
91
+ {/* Main Candle Circle */}
92
+ <div className="w-48 h-48 rounded-full bg-gradient-to-br from-amber-500/20 via-orange-500/10 to-transparent border border-amber-500/30 flex items-center justify-center shadow-[0_0_100px_-20px_rgba(251,191,36,0.5)] animate-breathe">
93
+ <div className="w-36 h-36 rounded-full bg-gradient-to-br from-amber-500/30 to-orange-600/20 border border-amber-400/40 flex items-center justify-center">
94
+ <div className="w-24 h-24 rounded-full bg-gradient-to-br from-amber-400/50 to-orange-500/30 border border-amber-300/50 flex items-center justify-center shadow-[inset_0_0_30px_rgba(251,191,36,0.3)]">
95
+ <Flame className="w-12 h-12 text-amber-300 drop-shadow-[0_0_20px_rgba(251,191,36,0.8)] animate-flicker" />
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ {/* Orbiting Elements */}
101
+ <div className="absolute inset-0 w-72 h-72 -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2 animate-spin-slow">
102
+ <div className="absolute top-0 left-1/2 -translate-x-1/2 -translate-y-1/2">
103
+ <div className="w-10 h-10 rounded-full bg-amber-500/20 border border-amber-500/30 flex items-center justify-center">
104
+ <Sun className="w-5 h-5 text-amber-400" />
105
+ </div>
106
+ </div>
107
+ <div className="absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-1/2">
108
+ <div className="w-10 h-10 rounded-full bg-rose-500/20 border border-rose-500/30 flex items-center justify-center">
109
+ <Heart className="w-5 h-5 text-rose-400" />
110
+ </div>
111
+ </div>
112
+ <div className="absolute left-0 top-1/2 -translate-x-1/2 -translate-y-1/2">
113
+ <div className="w-10 h-10 rounded-full bg-blue-500/20 border border-blue-500/30 flex items-center justify-center">
114
+ <Share2 className="w-5 h-5 text-blue-400" />
115
+ </div>
116
+ </div>
117
+ <div className="absolute right-0 top-1/2 translate-x-1/2 -translate-y-1/2">
118
+ <div className="w-10 h-10 rounded-full bg-emerald-500/20 border border-emerald-500/30 flex items-center justify-center">
119
+ <TrendingUp className="w-5 h-5 text-emerald-400" />
120
+ </div>
121
+ </div>
122
+ </div>
123
+
124
+ {/* Inner Orbit */}
125
+ <div className="absolute inset-0 w-56 h-56 -translate-x-1/2 -translate-y-1/2 left-1/2 top-1/2 animate-spin-slow" style={{ animationDirection: 'reverse', animationDuration: '20s' }}>
126
+ <div className="absolute top-0 right-0 translate-x-1/4 -translate-y-1/4">
127
+ <div className="w-8 h-8 rounded-full bg-purple-500/20 border border-purple-500/30 flex items-center justify-center">
128
+ <Sparkles className="w-4 h-4 text-purple-400" />
129
+ </div>
130
+ </div>
131
+ <div className="absolute bottom-0 left-0 -translate-x-1/4 translate-y-1/4">
132
+ <div className="w-8 h-8 rounded-full bg-cyan-500/20 border border-cyan-500/30 flex items-center justify-center">
133
+ <Zap className="w-4 h-4 text-cyan-400" />
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ {/* Floating Cards */}
140
+ <div className="absolute top-8 right-0 p-4 rounded-2xl bg-white/5 border border-white/10 backdrop-blur-xl flex items-center gap-3 animate-float" style={{ animationDuration: '6s' }}>
141
+ <div className="w-10 h-10 rounded-xl bg-amber-500/20 flex items-center justify-center">
142
+ <Gift className="w-5 h-5 text-amber-400" />
143
+ </div>
144
+ <div>
145
+ <div className="text-[10px] text-neutral-500 uppercase tracking-wider">Welcome Bonus</div>
146
+ <div className="text-lg font-bold text-white">+250 <span className="text-amber-400 text-sm">Candles</span></div>
147
+ </div>
148
+ </div>
149
+
150
+ <div className="absolute bottom-16 left-0 p-4 rounded-2xl bg-white/5 border border-white/10 backdrop-blur-xl flex items-center gap-3 animate-float" style={{ animationDuration: '7s', animationDelay: '1s' }}>
151
+ <div className="w-10 h-10 rounded-xl bg-emerald-500/20 flex items-center justify-center">
152
+ <Calendar className="w-5 h-5 text-emerald-400" />
153
+ </div>
154
+ <div>
155
+ <div className="text-[10px] text-neutral-500 uppercase tracking-wider">Monthly Refresh</div>
156
+ <div className="text-lg font-bold text-white">+250 <span className="text-emerald-400 text-sm">Candles</span></div>
157
+ </div>
158
+ </div>
159
+
160
+ <div className="absolute top-1/2 right-8 -translate-y-1/2 p-3 rounded-xl bg-purple-500/10 border border-purple-500/20 backdrop-blur-xl animate-float" style={{ animationDuration: '8s', animationDelay: '2s' }}>
161
+ <div className="flex items-center gap-2">
162
+ <Sparkles className="w-4 h-4 text-purple-400" />
163
+ <span className="text-xs text-purple-300 font-medium">ORA™ Powered</span>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </section>
170
+
171
+ {/* How Candles Work - Interactive Visual */}
172
+ <section className="py-24 relative border-t border-white/5">
173
+ <div className="max-w-7xl mx-auto px-6">
174
+ <div className="text-center mb-16">
175
+ <div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-full border border-amber-500/10 bg-amber-500/5 mb-6">
176
+ <Flame className="w-3.5 h-3.5 text-amber-400" />
177
+ <span className="text-[10px] uppercase tracking-widest font-medium text-amber-200/80">The Candle Economy</span>
178
+ </div>
179
+ <h2 className="text-3xl md:text-4xl font-semibold tracking-tight mb-4 text-white">
180
+ How Candles Work
181
+ </h2>
182
+ <p className="text-neutral-400 max-w-2xl mx-auto">
183
+ A simple three-step cycle that rewards your spiritual growth and empowers your ministry
184
+ </p>
185
+ </div>
186
+
187
+ {/* Three Step Flow */}
188
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-16">
189
+ {[
190
+ {
191
+ step: '01',
192
+ icon: Sun,
193
+ title: 'Earn',
194
+ subtitle: 'Through Engagement',
195
+ description: 'Complete spiritual activities, engage with your community, and grow in faith to earn Candles.',
196
+ color: 'amber',
197
+ examples: ['Daily devotions', 'Prayer requests', 'Community posts'],
198
+ },
199
+ {
200
+ step: '02',
201
+ icon: Zap,
202
+ title: 'Spend',
203
+ subtitle: 'On AI Features',
204
+ description: 'Use your Candles to access powerful AI tools that enhance your worship and ministry.',
205
+ color: 'purple',
206
+ examples: ['ORA™ insights', 'Video editing', 'Sermon prep'],
207
+ },
208
+ {
209
+ step: '03',
210
+ icon: Share2,
211
+ title: 'Share',
212
+ subtitle: 'With Others',
213
+ description: 'Gift Candles to friends, family, or donate to churches and ministries in need.',
214
+ color: 'emerald',
215
+ examples: ['Gift to friends', 'Support churches', 'Fund missions'],
216
+ },
217
+ ].map((item, i) => {
218
+ const colorClasses = {
219
+ amber: { bg: 'bg-amber-500/10', border: 'border-amber-500/20', hoverBorder: 'hover:border-amber-500/40', text: 'text-amber-400', spotlight: 'rgba(245, 158, 11, 0.15)' },
220
+ purple: { bg: 'bg-purple-500/10', border: 'border-purple-500/20', hoverBorder: 'hover:border-purple-500/40', text: 'text-purple-400', spotlight: 'rgba(168, 85, 247, 0.15)' },
221
+ emerald: { bg: 'bg-emerald-500/10', border: 'border-emerald-500/20', hoverBorder: 'hover:border-emerald-500/40', text: 'text-emerald-400', spotlight: 'rgba(16, 185, 129, 0.15)' },
222
+ };
223
+ const colors = colorClasses[item.color as keyof typeof colorClasses];
224
+ const staggerClass = `stagger-${i + 1}`;
225
+
226
+ return (
227
+ <SpotlightCard
228
+ key={i}
229
+ className={`p-8 rounded-3xl border ${colors.border} bg-white/[0.02] ${colors.hoverBorder} transition-all group animate-fade-slide-in ${staggerClass} relative overflow-hidden`}
230
+ spotlightColor={colors.spotlight}
231
+ >
232
+ {/* Step Number */}
233
+ <div className="absolute top-4 right-4 text-6xl font-bold text-white/5">{item.step}</div>
234
+
235
+ <div className={`w-16 h-16 rounded-2xl ${colors.bg} flex items-center justify-center mb-6 border ${colors.border} group-hover:scale-110 transition-transform`}>
236
+ <item.icon className={`w-8 h-8 ${colors.text}`} />
237
+ </div>
238
+
239
+ <div className="mb-2">
240
+ <h3 className="text-2xl font-semibold text-white">{item.title}</h3>
241
+ <p className={`text-sm ${colors.text}`}>{item.subtitle}</p>
242
+ </div>
243
+
244
+ <p className="text-neutral-400 text-sm leading-relaxed mb-6">{item.description}</p>
245
+
246
+ <div className="space-y-2">
247
+ {item.examples.map((ex, j) => (
248
+ <div key={j} className="flex items-center gap-2 text-xs text-neutral-500">
249
+ <Check className={`w-3.5 h-3.5 ${colors.text}`} />
250
+ {ex}
251
+ </div>
252
+ ))}
253
+ </div>
254
+ </SpotlightCard>
255
+ );
256
+ })}
257
+ </div>
258
+
259
+ {/* Connection Line */}
260
+ <div className="hidden md:flex items-center justify-center gap-4 -mt-8 mb-8">
261
+ <div className="flex-1 h-px bg-gradient-to-r from-transparent via-amber-500/30 to-transparent" />
262
+ <div className="w-3 h-3 rounded-full bg-amber-500/30 border border-amber-500/50" />
263
+ <div className="flex-1 h-px bg-gradient-to-r from-transparent via-purple-500/30 to-transparent" />
264
+ <div className="w-3 h-3 rounded-full bg-purple-500/30 border border-purple-500/50" />
265
+ <div className="flex-1 h-px bg-gradient-to-r from-transparent via-emerald-500/30 to-transparent" />
266
+ </div>
267
+ </div>
268
+ </section>
269
+
270
+ {/* Live Candle Counter Demo */}
271
+ <section className="py-24 relative border-t border-white/5 bg-[#080808]">
272
+ <div className="max-w-7xl mx-auto px-6">
273
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
274
+ {/* Left: Demo Interface */}
275
+ <div className="rounded-3xl border border-white/10 bg-[#0a0a0a] overflow-hidden">
276
+ <div className="px-6 py-4 border-b border-white/5 flex items-center justify-between bg-white/[0.02]">
277
+ <div className="flex items-center gap-2">
278
+ <div className="w-3 h-3 rounded-full bg-red-500/50" />
279
+ <div className="w-3 h-3 rounded-full bg-yellow-500/50" />
280
+ <div className="w-3 h-3 rounded-full bg-green-500/50" />
281
+ </div>
282
+ <span className="text-xs text-white/30 font-mono">candle-wallet.app</span>
283
+ </div>
284
+
285
+ <div className="p-8">
286
+ {/* Balance Display */}
287
+ <div className="text-center mb-8">
288
+ <div className="text-xs text-neutral-500 uppercase tracking-wider mb-2">Your Candle Balance</div>
289
+ <div className="flex items-center justify-center gap-3">
290
+ <Flame className="w-10 h-10 text-amber-400 animate-flicker" />
291
+ <span className="text-6xl font-bold text-white">1,247</span>
292
+ </div>
293
+ <div className="text-sm text-emerald-400 mt-2">+127 this week</div>
294
+ </div>
295
+
296
+ {/* Recent Activity */}
297
+ <div className="space-y-3">
298
+ <div className="text-xs text-neutral-500 uppercase tracking-wider mb-3">Recent Activity</div>
299
+ {[
300
+ { action: 'Completed Daily Devotion', amount: '+25', icon: BookOpen, color: 'amber', time: '2h ago' },
301
+ { action: 'Posted Prayer Request', amount: '+10', icon: Heart, color: 'rose', time: '5h ago' },
302
+ { action: 'Used ORA™ for Bible Study', amount: '-15', icon: Sparkles, color: 'purple', time: '1d ago' },
303
+ { action: '7-Day Streak Bonus!', amount: '+50', icon: Trophy, color: 'emerald', time: '1d ago' },
304
+ ].map((item, i) => (
305
+ <div key={i} className="flex items-center justify-between p-3 rounded-xl bg-white/5 border border-white/5">
306
+ <div className="flex items-center gap-3">
307
+ <div className={`w-8 h-8 rounded-lg bg-${item.color}-500/10 flex items-center justify-center`}>
308
+ <item.icon className={`w-4 h-4 text-${item.color}-400`} />
309
+ </div>
310
+ <div>
311
+ <div className="text-sm text-white">{item.action}</div>
312
+ <div className="text-[10px] text-neutral-500">{item.time}</div>
313
+ </div>
314
+ </div>
315
+ <span className={`text-sm font-semibold ${item.amount.startsWith('+') ? 'text-emerald-400' : 'text-amber-400'}`}>
316
+ {item.amount}
317
+ </span>
318
+ </div>
319
+ ))}
320
+ </div>
321
+ </div>
322
+ </div>
323
+
324
+ {/* Right: Content */}
325
+ <div>
326
+ <h2 className="text-3xl md:text-4xl font-semibold tracking-tight mb-6 text-white">
327
+ Watch Your Candles Grow
328
+ </h2>
329
+ <p className="text-neutral-400 leading-relaxed mb-8">
330
+ Every spiritual activity earns you Candles. Track your balance, see your growth over time, and celebrate milestones in your faith journey.
331
+ </p>
332
+
333
+ <div className="space-y-4 mb-8">
334
+ {[
335
+ { label: 'Real-time balance updates', desc: 'See your Candles change as you engage' },
336
+ { label: 'Activity history', desc: 'Track every earn and spend transaction' },
337
+ { label: 'Streak bonuses', desc: 'Earn bonus Candles for consistent engagement' },
338
+ { label: 'Monthly refresh', desc: 'Get 250 free Candles every month' },
339
+ ].map((item, i) => (
340
+ <div key={i} className="flex items-start gap-3">
341
+ <div className="w-6 h-6 rounded-full bg-amber-500/20 flex items-center justify-center shrink-0 mt-0.5">
342
+ <Check className="w-3.5 h-3.5 text-amber-400" />
343
+ </div>
344
+ <div>
345
+ <div className="text-white font-medium">{item.label}</div>
346
+ <div className="text-sm text-neutral-500">{item.desc}</div>
347
+ </div>
348
+ </div>
349
+ ))}
350
+ </div>
351
+
352
+ <Link href="/get-started">
353
+ <ShinyButton>
354
+ <span className="flex items-center gap-2">
355
+ Start Earning Today
356
+ <ArrowRight className="w-4 h-4" />
357
+ </span>
358
+ </ShinyButton>
359
+ </Link>
360
+ </div>
361
+ </div>
362
+ </div>
363
+ </section>
364
+
365
+ {/* Earn Candles Section - Enhanced */}
366
+ <section className="py-24 relative border-t border-white/5">
367
+ <div className="max-w-7xl mx-auto px-6">
368
+ <div className="text-center mb-16">
369
+ <h2 className="text-3xl md:text-4xl font-semibold tracking-tight mb-4 text-white">
370
+ Ways to Earn Candles
371
+ </h2>
372
+ <p className="text-neutral-400 max-w-lg mx-auto">
373
+ Every meaningful interaction with your faith community earns you Candles
374
+ </p>
375
+ </div>
376
+
377
+ {/* Community Engagement */}
378
+ <div className="mb-12">
379
+ <div className="flex items-center gap-3 mb-6">
380
+ <div className="w-10 h-10 rounded-xl bg-blue-500/10 flex items-center justify-center border border-blue-500/20">
381
+ <MessageSquare className="w-5 h-5 text-blue-400" />
382
+ </div>
383
+ <div>
384
+ <h3 className="text-lg font-semibold text-white">Community Engagement</h3>
385
+ <p className="text-xs text-neutral-500">Connect and interact with your faith family</p>
386
+ </div>
387
+ </div>
388
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
389
+ {[
390
+ { icon: UserPlus, title: 'Add A Contact', candles: 25, badge: 'Connector', color: 'blue' },
391
+ { icon: Target, title: 'Attend An Event', candles: 25, badge: 'Active Member', color: 'blue' },
392
+ { icon: MessageSquare, title: 'Post A Discussion', candles: 20, badge: 'Community Builder', color: 'blue' },
393
+ { icon: ThumbsUp, title: 'Like/Amen/Share', candles: 5, badge: 'Encourager', color: 'blue' },
394
+ ].map((item, i) => {
395
+ const staggerClass = `stagger-${(i % 4) + 1}`;
396
+ return (
397
+ <SpotlightCard key={i} className={`p-5 rounded-2xl border border-blue-500/20 bg-blue-500/5 hover:border-blue-500/40 transition-all group animate-fade-slide-in ${staggerClass}`} spotlightColor="rgba(59, 130, 246, 0.15)">
398
+ <div className="flex items-center justify-between mb-3">
399
+ <div className="w-10 h-10 rounded-xl bg-blue-500/20 flex items-center justify-center group-hover:scale-110 transition-transform">
400
+ <item.icon className="w-5 h-5 text-blue-400" />
401
+ </div>
402
+ <span className="text-[10px] px-2 py-0.5 rounded-full bg-blue-500/20 text-blue-300 border border-blue-500/30">{item.badge}</span>
403
+ </div>
404
+ <h4 className="text-sm font-medium text-white mb-1">{item.title}</h4>
405
+ <div className="flex items-center gap-1">
406
+ <Flame className="w-4 h-4 text-amber-400" />
407
+ <span className="text-amber-400 font-bold">{item.candles}</span>
408
+ <span className="text-neutral-500 text-xs">Candles</span>
409
+ </div>
410
+ </SpotlightCard>
411
+ );
412
+ })}
413
+ </div>
414
+ </div>
415
+
416
+ {/* Spiritual Habits */}
417
+ <div className="mb-12">
418
+ <div className="flex items-center gap-3 mb-6">
419
+ <div className="w-10 h-10 rounded-xl bg-rose-500/10 flex items-center justify-center border border-rose-500/20">
420
+ <Heart className="w-5 h-5 text-rose-400" />
421
+ </div>
422
+ <div>
423
+ <h3 className="text-lg font-semibold text-white">Spiritual Habits</h3>
424
+ <p className="text-xs text-neutral-500">Deepen your faith through daily practices</p>
425
+ </div>
426
+ </div>
427
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
428
+ {[
429
+ { icon: Trophy, title: 'Finish A Reading Plan', candles: 50, badge: 'Devoted Reader', color: 'rose' },
430
+ { icon: Flame, title: '7-Day Streak', candles: 50, badge: 'Disciplined', color: 'rose' },
431
+ { icon: BookOpen, title: 'Post S.O.A.P. Journal', candles: 25, badge: 'Reflective', color: 'rose' },
432
+ { icon: Heart, title: 'Request A Prayer', candles: 10, badge: 'Prayer Warrior', color: 'rose' },
433
+ ].map((item, i) => {
434
+ const staggerClass = `stagger-${(i % 4) + 1}`;
435
+ return (
436
+ <SpotlightCard key={i} className={`p-5 rounded-2xl border border-rose-500/20 bg-rose-500/5 hover:border-rose-500/40 transition-all group animate-fade-slide-in ${staggerClass}`} spotlightColor="rgba(244, 63, 94, 0.15)">
437
+ <div className="flex items-center justify-between mb-3">
438
+ <div className="w-10 h-10 rounded-xl bg-rose-500/20 flex items-center justify-center group-hover:scale-110 transition-transform">
439
+ <item.icon className="w-5 h-5 text-rose-400" />
440
+ </div>
441
+ <span className="text-[10px] px-2 py-0.5 rounded-full bg-rose-500/20 text-rose-300 border border-rose-500/30">{item.badge}</span>
442
+ </div>
443
+ <h4 className="text-sm font-medium text-white mb-1">{item.title}</h4>
444
+ <div className="flex items-center gap-1">
445
+ <Flame className="w-4 h-4 text-amber-400" />
446
+ <span className="text-amber-400 font-bold">{item.candles}</span>
447
+ <span className="text-neutral-500 text-xs">Candles</span>
448
+ </div>
449
+ </SpotlightCard>
450
+ );
451
+ })}
452
+ </div>
453
+ </div>
454
+
455
+ {/* Growth & Leadership */}
456
+ <div>
457
+ <div className="flex items-center gap-3 mb-6">
458
+ <div className="w-10 h-10 rounded-xl bg-amber-500/10 flex items-center justify-center border border-amber-500/20">
459
+ <Star className="w-5 h-5 text-amber-400" />
460
+ </div>
461
+ <div>
462
+ <h3 className="text-lg font-semibold text-white">Growth & Leadership</h3>
463
+ <p className="text-xs text-neutral-500">Big rewards for big commitments</p>
464
+ </div>
465
+ </div>
466
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
467
+ {[
468
+ { icon: Users, title: 'Complete Profile', candles: 250, badge: 'Committed', highlight: true },
469
+ { icon: Gift, title: 'Refer A Friend', candles: 250, badge: 'Evangelist', highlight: true },
470
+ { icon: Church, title: 'Create A Church', candles: 100, badge: 'Church Builder', highlight: false },
471
+ { icon: Users, title: 'Create A Ministry', candles: 50, badge: 'Founder', highlight: false },
472
+ ].map((item, i) => {
473
+ const staggerClass = `stagger-${(i % 4) + 1}`;
474
+ return (
475
+ <SpotlightCard
476
+ key={i}
477
+ className={`p-5 rounded-2xl border ${item.highlight ? 'border-amber-500/30 bg-amber-500/10' : 'border-emerald-500/20 bg-emerald-500/5'} ${item.highlight ? 'hover:border-amber-500/50' : 'hover:border-emerald-500/40'} transition-all group animate-fade-slide-in ${staggerClass}`}
478
+ spotlightColor={item.highlight ? "rgba(245, 158, 11, 0.2)" : "rgba(16, 185, 129, 0.15)"}
479
+ >
480
+ {item.highlight && (
481
+ <div className="absolute top-2 right-2">
482
+ <span className="text-[8px] px-1.5 py-0.5 rounded bg-amber-500/30 text-amber-200 font-bold uppercase">Bonus</span>
483
+ </div>
484
+ )}
485
+ <div className="flex items-center justify-between mb-3">
486
+ <div className={`w-10 h-10 rounded-xl ${item.highlight ? 'bg-amber-500/20' : 'bg-emerald-500/20'} flex items-center justify-center group-hover:scale-110 transition-transform`}>
487
+ <item.icon className={`w-5 h-5 ${item.highlight ? 'text-amber-400' : 'text-emerald-400'}`} />
488
+ </div>
489
+ <span className={`text-[10px] px-2 py-0.5 rounded-full ${item.highlight ? 'bg-amber-500/20 text-amber-300 border-amber-500/30' : 'bg-emerald-500/20 text-emerald-300 border-emerald-500/30'} border`}>{item.badge}</span>
490
+ </div>
491
+ <h4 className="text-sm font-medium text-white mb-1">{item.title}</h4>
492
+ <div className="flex items-center gap-1">
493
+ <Flame className="w-4 h-4 text-amber-400" />
494
+ <span className="text-amber-400 font-bold text-lg">{item.candles}</span>
495
+ <span className="text-neutral-500 text-xs">Candles</span>
496
+ </div>
497
+ </SpotlightCard>
498
+ );
499
+ })}
500
+ </div>
501
+ </div>
502
+
503
+ <p className="text-center text-xs text-neutral-500 mt-8">
504
+ *Candles are awarded upon completion. Maximum 5,000 Candles per person per month.
505
+ </p>
506
+ </div>
507
+ </section>
508
+
509
+ {/* Spend Your Candles */}
510
+ <section className="py-24 relative border-t border-white/5 bg-[#080808]">
511
+ <div className="max-w-7xl mx-auto px-6">
512
+ <div className="text-center mb-16">
513
+ <h2 className="text-3xl md:text-4xl font-semibold tracking-tight mb-4 text-white">
514
+ Spend Your Candles
515
+ </h2>
516
+ <p className="text-neutral-400 max-w-lg mx-auto">
517
+ Unlock powerful AI features that enhance your worship, study, and ministry
518
+ </p>
519
+ </div>
520
+
521
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
522
+ {[
523
+ { icon: Sparkles, title: 'ORA™ Insights', desc: 'AI-powered Scripture analysis and spiritual guidance', cost: '5-15', color: 'purple' },
524
+ { icon: Video, title: 'Pro Studio', desc: 'Professional video editing and content creation', cost: '10-50', color: 'amber' },
525
+ { icon: BookOpen, title: 'Study Tools', desc: 'Advanced Bible study and sermon preparation', cost: '5-20', color: 'blue' },
526
+ { icon: MessageSquare, title: 'Content AI', desc: 'Social media posts and ministry content', cost: '5-25', color: 'emerald' },
527
+ ].map((item, i) => {
528
+ const colorClasses = {
529
+ purple: { bg: 'bg-purple-500/10', border: 'border-purple-500/20', hoverBorder: 'hover:border-purple-500/40', text: 'text-purple-400' },
530
+ amber: { bg: 'bg-amber-500/10', border: 'border-amber-500/20', hoverBorder: 'hover:border-amber-500/40', text: 'text-amber-400' },
531
+ blue: { bg: 'bg-blue-500/10', border: 'border-blue-500/20', hoverBorder: 'hover:border-blue-500/40', text: 'text-blue-400' },
532
+ emerald: { bg: 'bg-emerald-500/10', border: 'border-emerald-500/20', hoverBorder: 'hover:border-emerald-500/40', text: 'text-emerald-400' },
533
+ };
534
+ const colors = colorClasses[item.color as keyof typeof colorClasses];
535
+ const staggerClass = `stagger-${(i % 4) + 1}`;
536
+
537
+ return (
538
+ <SpotlightCard key={i} className={`p-6 rounded-2xl border ${colors.border} bg-white/[0.02] ${colors.hoverBorder} transition-all group animate-fade-slide-in ${staggerClass}`}>
539
+ <div className={`w-14 h-14 rounded-xl ${colors.bg} flex items-center justify-center mb-4 border ${colors.border} group-hover:scale-110 transition-transform`}>
540
+ <item.icon className={`w-7 h-7 ${colors.text}`} />
541
+ </div>
542
+ <h3 className="text-lg font-semibold text-white mb-2">{item.title}</h3>
543
+ <p className="text-neutral-400 text-sm leading-relaxed mb-4">{item.desc}</p>
544
+ <div className="flex items-center gap-2 text-sm">
545
+ <Flame className="w-4 h-4 text-amber-400" />
546
+ <span className="text-amber-400 font-semibold">{item.cost}</span>
547
+ <span className="text-neutral-500">Candles per use</span>
548
+ </div>
549
+ </SpotlightCard>
550
+ );
551
+ })}
552
+ </div>
553
+ </div>
554
+ </section>
555
+
556
+ {/* Share the Light */}
557
+ <section className="py-24 relative border-t border-white/5">
558
+ <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[600px] h-[400px] bg-amber-600/5 rounded-full blur-[100px] pointer-events-none -z-10" />
559
+
560
+ <div className="max-w-7xl mx-auto px-6">
561
+ <div className="text-center mb-16">
562
+ <h2 className="text-3xl md:text-4xl font-semibold tracking-tight mb-4 text-white">
563
+ Share the Light
564
+ </h2>
565
+ <p className="text-neutral-400 max-w-lg mx-auto">
566
+ Gift your Candles to bless others in their faith journey
567
+ </p>
568
+ </div>
569
+
570
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
571
+ {[
572
+ { icon: Users, title: 'Gift to Friends', desc: 'Send Candles to friends and family who need AI features for their spiritual growth', color: 'blue' },
573
+ { icon: Church, title: 'Support Churches', desc: 'Donate to churches and help them access powerful ministry tools', color: 'purple' },
574
+ { icon: Heart, title: 'Fund Missions', desc: 'Empower missionaries and ministry teams with collaborative Candle pools', color: 'emerald' },
575
+ ].map((item, i) => {
576
+ const colorClasses = {
577
+ blue: { bg: 'bg-blue-500/10', border: 'border-blue-500/20', hoverBorder: 'hover:border-blue-500/40', text: 'text-blue-400', spotlight: 'rgba(59, 130, 246, 0.15)' },
578
+ purple: { bg: 'bg-purple-500/10', border: 'border-purple-500/20', hoverBorder: 'hover:border-purple-500/40', text: 'text-purple-400', spotlight: 'rgba(168, 85, 247, 0.15)' },
579
+ emerald: { bg: 'bg-emerald-500/10', border: 'border-emerald-500/20', hoverBorder: 'hover:border-emerald-500/40', text: 'text-emerald-400', spotlight: 'rgba(16, 185, 129, 0.15)' },
580
+ };
581
+ const colors = colorClasses[item.color as keyof typeof colorClasses];
582
+ const staggerClass = `stagger-${i + 1}`;
583
+
584
+ return (
585
+ <SpotlightCard key={i} className={`p-8 rounded-3xl border ${colors.border} bg-white/[0.02] ${colors.hoverBorder} transition-all group text-center animate-fade-slide-in ${staggerClass}`} spotlightColor={colors.spotlight}>
586
+ <div className={`w-16 h-16 mx-auto rounded-2xl ${colors.bg} flex items-center justify-center mb-6 border ${colors.border} group-hover:scale-110 transition-transform`}>
587
+ <item.icon className={`w-8 h-8 ${colors.text}`} />
588
+ </div>
589
+ <h3 className="text-lg font-semibold text-white mb-3">{item.title}</h3>
590
+ <p className="text-neutral-400 text-sm leading-relaxed">{item.desc}</p>
591
+ </SpotlightCard>
592
+ );
593
+ })}
594
+ </div>
595
+ </div>
596
+ </section>
597
+
598
+ {/* CTA Section */}
599
+ <section className="py-32 text-center relative overflow-hidden border-t border-white/5 bg-[#080808]">
600
+ <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[800px] h-[400px] bg-amber-600/10 rounded-full blur-[120px] pointer-events-none -z-10" />
601
+
602
+ <div className="max-w-3xl mx-auto px-6 relative z-10">
603
+ <div className="w-24 h-24 mx-auto rounded-full bg-gradient-to-br from-amber-500/20 to-orange-600/10 flex items-center justify-center mb-8 border border-amber-500/30 animate-breathe shadow-[0_0_60px_-15px_rgba(251,191,36,0.5)]">
604
+ <Flame className="w-12 h-12 text-amber-400 drop-shadow-[0_0_20px_rgba(251,191,36,0.8)]" />
605
+ </div>
606
+
607
+ <h2 className="text-4xl sm:text-5xl font-semibold tracking-tight mb-6 text-white">
608
+ Start Your Candle Journey
609
+ </h2>
610
+ <p className="text-neutral-400 text-lg mb-10 max-w-xl mx-auto">
611
+ Join thousands of believers earning and using Candles to enhance their faith journey with AI-powered tools.
612
+ </p>
613
+
614
+ <div className="flex flex-col sm:flex-row justify-center gap-4">
615
+ <Link href="/get-started">
616
+ <ShinyButton>
617
+ <span className="flex items-center gap-2">
618
+ <Flame className="w-[18px] h-[18px] candle-glow" />
619
+ Get 250 Free Candles
620
+ </span>
621
+ </ShinyButton>
622
+ </Link>
623
+ <Link href="/pricing" className="px-8 py-3.5 text-sm font-medium text-neutral-300 border border-white/10 rounded-full hover:bg-white/5 hover:text-white transition-all">
624
+ View Pricing Plans
625
+ </Link>
626
+ </div>
627
+
628
+ <p className="text-xs text-neutral-500 mt-6">
629
+ No credit card required • 250 Candles refresh monthly
630
+ </p>
631
+ </div>
632
+ </section>
633
+ </>
634
+ );
635
+ }
frontend/app/contact/page.tsx ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { Mail, MessageSquare, MapPin, Send, Sparkles } from 'lucide-react';
4
+ import SpotlightCard from '@/components/SpotlightCard';
5
+ import ShinyButton from '@/components/ShinyButton';
6
+ import Link from 'next/link';
7
+
8
+ export default function ContactPage() {
9
+ return (
10
+ <>
11
+ {/* Hero */}
12
+ <section className="relative pt-32 pb-20 overflow-hidden">
13
+ <div className="absolute inset-0 ora-grid-bg pointer-events-none z-0" />
14
+ <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[800px] h-[600px] bg-purple-600/20 rounded-full blur-[150px] pointer-events-none -z-10" />
15
+
16
+ <div className="max-w-4xl mx-auto px-6 text-center">
17
+ <div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-purple-500/30 bg-purple-500/10 mb-8">
18
+ <Mail className="w-4 h-4 text-purple-400" />
19
+ <span className="text-xs font-mono text-purple-300 uppercase tracking-wider">Contact</span>
20
+ </div>
21
+
22
+ <h1 className="text-4xl md:text-5xl font-bold tracking-tight mb-6 text-white">
23
+ Get in <span className="text-purple-400">Touch</span>
24
+ </h1>
25
+
26
+ <p className="text-lg text-neutral-400 max-w-2xl mx-auto">
27
+ Have questions about ORA? We'd love to hear from you. Reach out and we'll respond as soon as we can.
28
+ </p>
29
+ </div>
30
+ </section>
31
+
32
+ {/* Contact Methods */}
33
+ <section className="py-16 border-t border-white/5">
34
+ <div className="max-w-4xl mx-auto px-6">
35
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-16">
36
+ <SpotlightCard className="p-6 rounded-xl border border-purple-500/20 bg-white/[0.02]" spotlightColor="purple">
37
+ <div className="w-12 h-12 rounded-xl bg-purple-500/10 flex items-center justify-center border border-purple-500/20 mb-4">
38
+ <Mail className="w-6 h-6 text-purple-400" />
39
+ </div>
40
+ <h3 className="text-lg font-semibold text-white mb-2">Email</h3>
41
+ <p className="text-neutral-500 text-sm mb-3">Send us an email anytime</p>
42
+ <a href="mailto:support@ora.ai" className="text-purple-400 hover:text-purple-300 transition-colors text-sm">
43
+ support@ora.ai
44
+ </a>
45
+ </SpotlightCard>
46
+
47
+ <SpotlightCard className="p-6 rounded-xl border border-blue-500/20 bg-white/[0.02]" spotlightColor="purple">
48
+ <div className="w-12 h-12 rounded-xl bg-blue-500/10 flex items-center justify-center border border-blue-500/20 mb-4">
49
+ <MessageSquare className="w-6 h-6 text-blue-400" />
50
+ </div>
51
+ <h3 className="text-lg font-semibold text-white mb-2">Chat with ORA</h3>
52
+ <p className="text-neutral-500 text-sm mb-3">Try our AI assistant directly</p>
53
+ <Link href="/dashboard" className="text-blue-400 hover:text-blue-300 transition-colors text-sm">
54
+ Open Dashboard
55
+ </Link>
56
+ </SpotlightCard>
57
+
58
+ <SpotlightCard className="p-6 rounded-xl border border-emerald-500/20 bg-white/[0.02]" spotlightColor="purple">
59
+ <div className="w-12 h-12 rounded-xl bg-emerald-500/10 flex items-center justify-center border border-emerald-500/20 mb-4">
60
+ <Sparkles className="w-6 h-6 text-emerald-400" />
61
+ </div>
62
+ <h3 className="text-lg font-semibold text-white mb-2">Documentation</h3>
63
+ <p className="text-neutral-500 text-sm mb-3">Browse our help resources</p>
64
+ <Link href="/help" className="text-emerald-400 hover:text-emerald-300 transition-colors text-sm">
65
+ View Help Center
66
+ </Link>
67
+ </SpotlightCard>
68
+ </div>
69
+
70
+ {/* Contact Form */}
71
+ <div className="max-w-xl mx-auto">
72
+ <h2 className="text-2xl font-bold text-white text-center mb-8">Send a Message</h2>
73
+ <form className="space-y-4">
74
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
75
+ <div>
76
+ <label className="block text-sm text-neutral-400 mb-2">Name</label>
77
+ <input
78
+ type="text"
79
+ className="w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white placeholder-neutral-500 focus:outline-none focus:border-purple-500/50 transition-colors"
80
+ placeholder="Your name"
81
+ />
82
+ </div>
83
+ <div>
84
+ <label className="block text-sm text-neutral-400 mb-2">Email</label>
85
+ <input
86
+ type="email"
87
+ className="w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white placeholder-neutral-500 focus:outline-none focus:border-purple-500/50 transition-colors"
88
+ placeholder="you@example.com"
89
+ />
90
+ </div>
91
+ </div>
92
+ <div>
93
+ <label className="block text-sm text-neutral-400 mb-2">Subject</label>
94
+ <input
95
+ type="text"
96
+ className="w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white placeholder-neutral-500 focus:outline-none focus:border-purple-500/50 transition-colors"
97
+ placeholder="How can we help?"
98
+ />
99
+ </div>
100
+ <div>
101
+ <label className="block text-sm text-neutral-400 mb-2">Message</label>
102
+ <textarea
103
+ rows={5}
104
+ className="w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white placeholder-neutral-500 focus:outline-none focus:border-purple-500/50 transition-colors resize-none"
105
+ placeholder="Tell us more..."
106
+ />
107
+ </div>
108
+ <button
109
+ type="submit"
110
+ className="w-full py-3 rounded-xl bg-purple-600 hover:bg-purple-500 text-white font-medium transition-colors flex items-center justify-center gap-2"
111
+ >
112
+ <Send className="w-4 h-4" />
113
+ Send Message
114
+ </button>
115
+ </form>
116
+ </div>
117
+ </div>
118
+ </section>
119
+ </>
120
+ );
121
+ }
frontend/app/dashboard/bible/page.tsx ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { BookOpen, Search, Bookmark, ChevronRight, Sparkles, History, ExternalLink } from 'lucide-react';
5
+
6
+ const popularBooks = [
7
+ { name: 'Genesis', chapters: 50, category: 'Torah' },
8
+ { name: 'Psalms', chapters: 150, category: 'Wisdom' },
9
+ { name: 'Proverbs', chapters: 31, category: 'Wisdom' },
10
+ { name: 'Isaiah', chapters: 66, category: 'Prophets' },
11
+ { name: 'Matthew', chapters: 28, category: 'Gospel' },
12
+ { name: 'John', chapters: 21, category: 'Gospel' },
13
+ { name: 'Romans', chapters: 16, category: 'Epistle' },
14
+ { name: 'Revelation', chapters: 22, category: 'Apocalyptic' },
15
+ ];
16
+
17
+ const recentSearches = [
18
+ 'Love one another - John 13:34',
19
+ 'Faith without works - James 2:17',
20
+ 'The Lord is my shepherd - Psalm 23',
21
+ 'For God so loved - John 3:16',
22
+ ];
23
+
24
+ export default function BiblePage() {
25
+ const [searchQuery, setSearchQuery] = useState('');
26
+
27
+ return (
28
+ <div className="flex-1 overflow-y-auto">
29
+ {/* Header */}
30
+ <div className="sticky top-0 z-10 bg-[#0a0a0a]/90 backdrop-blur-xl border-b border-white/5">
31
+ <div className="max-w-4xl mx-auto px-6 py-4">
32
+ <div className="flex items-center gap-3 mb-4">
33
+ <div className="w-10 h-10 rounded-xl bg-blue-500/10 border border-blue-500/30 flex items-center justify-center">
34
+ <BookOpen className="w-5 h-5 text-blue-400" />
35
+ </div>
36
+ <div>
37
+ <h1 className="text-xl font-semibold text-white">Bible Study</h1>
38
+ <p className="text-sm text-neutral-500">Search Scripture with the Theologian Agent</p>
39
+ </div>
40
+ </div>
41
+
42
+ {/* Search Bar */}
43
+ <div className="relative">
44
+ <Search className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-neutral-500" />
45
+ <input
46
+ type="text"
47
+ value={searchQuery}
48
+ onChange={(e) => setSearchQuery(e.target.value)}
49
+ placeholder="Search verses, topics, or ask a theological question..."
50
+ className="w-full pl-12 pr-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white placeholder-neutral-500 focus:outline-none focus:border-blue-500/50 transition-colors"
51
+ />
52
+ <button className="absolute right-2 top-1/2 -translate-y-1/2 px-4 py-1.5 rounded-lg bg-blue-600 hover:bg-blue-500 text-white text-sm font-medium transition-colors">
53
+ Search
54
+ </button>
55
+ </div>
56
+ </div>
57
+ </div>
58
+
59
+ {/* Content */}
60
+ <div className="max-w-4xl mx-auto px-6 py-8">
61
+ {/* Quick Actions */}
62
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
63
+ <button className="p-4 rounded-xl bg-white/[0.02] border border-white/10 hover:border-blue-500/30 transition-all text-left group">
64
+ <div className="flex items-center gap-3 mb-2">
65
+ <Sparkles className="w-5 h-5 text-purple-400" />
66
+ <span className="text-white font-medium">Ask Theologian</span>
67
+ </div>
68
+ <p className="text-sm text-neutral-500">Get deep theological insights</p>
69
+ </button>
70
+ <button className="p-4 rounded-xl bg-white/[0.02] border border-white/10 hover:border-blue-500/30 transition-all text-left group">
71
+ <div className="flex items-center gap-3 mb-2">
72
+ <Bookmark className="w-5 h-5 text-amber-400" />
73
+ <span className="text-white font-medium">My Bookmarks</span>
74
+ </div>
75
+ <p className="text-sm text-neutral-500">View saved verses</p>
76
+ </button>
77
+ <button className="p-4 rounded-xl bg-white/[0.02] border border-white/10 hover:border-blue-500/30 transition-all text-left group">
78
+ <div className="flex items-center gap-3 mb-2">
79
+ <History className="w-5 h-5 text-emerald-400" />
80
+ <span className="text-white font-medium">Study History</span>
81
+ </div>
82
+ <p className="text-sm text-neutral-500">Continue where you left off</p>
83
+ </button>
84
+ </div>
85
+
86
+ {/* Recent Searches */}
87
+ <div className="mb-8">
88
+ <h2 className="text-lg font-semibold text-white mb-4">Recent Searches</h2>
89
+ <div className="space-y-2">
90
+ {recentSearches.map((search, i) => (
91
+ <button
92
+ key={i}
93
+ className="w-full flex items-center gap-3 p-3 rounded-xl bg-white/[0.02] border border-white/5 hover:border-blue-500/30 transition-all text-left"
94
+ >
95
+ <History className="w-4 h-4 text-neutral-500" />
96
+ <span className="text-neutral-300 text-sm">{search}</span>
97
+ <ChevronRight className="w-4 h-4 text-neutral-500 ml-auto" />
98
+ </button>
99
+ ))}
100
+ </div>
101
+ </div>
102
+
103
+ {/* Popular Books */}
104
+ <div>
105
+ <h2 className="text-lg font-semibold text-white mb-4">Browse by Book</h2>
106
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
107
+ {popularBooks.map((book) => (
108
+ <button
109
+ key={book.name}
110
+ className="p-4 rounded-xl bg-white/[0.02] border border-white/10 hover:border-blue-500/30 transition-all text-left group"
111
+ >
112
+ <div className="flex items-center justify-between mb-1">
113
+ <span className="text-white font-medium">{book.name}</span>
114
+ <ExternalLink className="w-3.5 h-3.5 text-neutral-500 opacity-0 group-hover:opacity-100 transition-opacity" />
115
+ </div>
116
+ <div className="flex items-center gap-2">
117
+ <span className="text-xs text-neutral-500">{book.chapters} chapters</span>
118
+ <span className="text-[10px] px-1.5 py-0.5 rounded bg-blue-500/10 text-blue-400">{book.category}</span>
119
+ </div>
120
+ </button>
121
+ ))}
122
+ </div>
123
+ </div>
124
+
125
+ {/* AI Suggestion */}
126
+ <div className="mt-8 p-6 rounded-2xl bg-gradient-to-br from-blue-500/10 to-purple-500/10 border border-blue-500/20">
127
+ <div className="flex items-start gap-4">
128
+ <div className="w-12 h-12 rounded-xl bg-blue-500/20 border border-blue-500/30 flex items-center justify-center shrink-0">
129
+ <Sparkles className="w-6 h-6 text-blue-400" />
130
+ </div>
131
+ <div>
132
+ <h3 className="text-white font-semibold mb-1">Theologian Tip</h3>
133
+ <p className="text-neutral-400 text-sm leading-relaxed">
134
+ Try asking questions like "What does Paul mean by 'justified by faith' in Romans 3:28?"
135
+ or "Compare the creation accounts in Genesis 1 and 2" for deeper theological analysis.
136
+ </p>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ );
143
+ }
frontend/app/dashboard/explore/page.tsx ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Compass, BookOpen, Heart, Users, Sparkles, ChevronRight, Play, Clock, Star } from 'lucide-react';
5
+
6
+ const featuredTopics = [
7
+ {
8
+ title: 'Understanding Grace',
9
+ description: 'Explore the concept of grace throughout Scripture',
10
+ duration: '15 min read',
11
+ category: 'Theology',
12
+ color: 'purple',
13
+ },
14
+ {
15
+ title: 'Prayer 101',
16
+ description: 'Learn different forms of biblical prayer',
17
+ duration: '10 min read',
18
+ category: 'Spiritual Practice',
19
+ color: 'rose',
20
+ },
21
+ {
22
+ title: 'The Beatitudes',
23
+ description: 'Deep dive into Jesus\' Sermon on the Mount',
24
+ duration: '20 min read',
25
+ category: 'Bible Study',
26
+ color: 'blue',
27
+ },
28
+ ];
29
+
30
+ const guidedStudies = [
31
+ {
32
+ title: '7 Days of Psalms',
33
+ description: 'A week-long journey through the Psalms',
34
+ days: 7,
35
+ progress: 3,
36
+ color: 'amber',
37
+ },
38
+ {
39
+ title: 'Foundations of Faith',
40
+ description: 'Core beliefs of Christianity explained',
41
+ days: 14,
42
+ progress: 0,
43
+ color: 'emerald',
44
+ },
45
+ {
46
+ title: 'The Life of Jesus',
47
+ description: 'Walk through the Gospels chronologically',
48
+ days: 30,
49
+ progress: 12,
50
+ color: 'blue',
51
+ },
52
+ ];
53
+
54
+ const quickPrompts = [
55
+ 'What does the Bible say about anxiety?',
56
+ 'Explain the Trinity in simple terms',
57
+ 'How can I strengthen my prayer life?',
58
+ 'What are the fruits of the Spirit?',
59
+ 'Help me understand forgiveness',
60
+ 'What does it mean to love your neighbor?',
61
+ ];
62
+
63
+ const colorClasses: Record<string, { bg: string; border: string; text: string; gradient: string }> = {
64
+ purple: { bg: 'bg-purple-500/10', border: 'border-purple-500/30', text: 'text-purple-400', gradient: 'from-purple-500 to-violet-500' },
65
+ blue: { bg: 'bg-blue-500/10', border: 'border-blue-500/30', text: 'text-blue-400', gradient: 'from-blue-500 to-cyan-500' },
66
+ amber: { bg: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-400', gradient: 'from-amber-500 to-orange-500' },
67
+ rose: { bg: 'bg-rose-500/10', border: 'border-rose-500/30', text: 'text-rose-400', gradient: 'from-rose-500 to-pink-500' },
68
+ emerald: { bg: 'bg-emerald-500/10', border: 'border-emerald-500/30', text: 'text-emerald-400', gradient: 'from-emerald-500 to-teal-500' },
69
+ cyan: { bg: 'bg-cyan-500/10', border: 'border-cyan-500/30', text: 'text-cyan-400', gradient: 'from-cyan-500 to-blue-500' },
70
+ };
71
+
72
+ export default function ExplorePage() {
73
+ return (
74
+ <div className="flex-1 overflow-y-auto">
75
+ {/* Header */}
76
+ <div className="sticky top-0 z-10 bg-[#0a0a0a]/90 backdrop-blur-xl border-b border-white/5">
77
+ <div className="max-w-5xl mx-auto px-6 py-4">
78
+ <div className="flex items-center gap-3">
79
+ <div className="w-10 h-10 rounded-xl bg-cyan-500/10 border border-cyan-500/30 flex items-center justify-center">
80
+ <Compass className="w-5 h-5 text-cyan-400" />
81
+ </div>
82
+ <div>
83
+ <h1 className="text-xl font-semibold text-white">Explore</h1>
84
+ <p className="text-sm text-neutral-500">Discover new spiritual topics and guided studies</p>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <div className="max-w-5xl mx-auto px-6 py-8">
91
+ {/* Quick Prompts */}
92
+ <div className="mb-8">
93
+ <h2 className="text-lg font-semibold text-white mb-4">Quick Questions</h2>
94
+ <div className="flex flex-wrap gap-2">
95
+ {quickPrompts.map((prompt) => (
96
+ <button
97
+ key={prompt}
98
+ className="px-4 py-2 rounded-full bg-white/[0.02] border border-white/10 text-neutral-300 text-sm hover:border-cyan-500/30 hover:text-white transition-all"
99
+ >
100
+ {prompt}
101
+ </button>
102
+ ))}
103
+ </div>
104
+ </div>
105
+
106
+ {/* Featured Topics */}
107
+ <div className="mb-8">
108
+ <div className="flex items-center justify-between mb-4">
109
+ <h2 className="text-lg font-semibold text-white">Featured Topics</h2>
110
+ <button className="text-sm text-cyan-400 hover:text-cyan-300 transition-colors">
111
+ View All
112
+ </button>
113
+ </div>
114
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
115
+ {featuredTopics.map((topic) => {
116
+ const colors = colorClasses[topic.color];
117
+ return (
118
+ <button
119
+ key={topic.title}
120
+ className={`p-5 rounded-xl ${colors.bg} border ${colors.border} hover:brightness-110 transition-all text-left group`}
121
+ >
122
+ <div className="flex items-center gap-2 mb-2">
123
+ <span className={`text-xs px-2 py-0.5 rounded-full ${colors.bg} ${colors.text} border ${colors.border}`}>
124
+ {topic.category}
125
+ </span>
126
+ <span className="text-xs text-neutral-500 flex items-center gap-1">
127
+ <Clock className="w-3 h-3" />
128
+ {topic.duration}
129
+ </span>
130
+ </div>
131
+ <h3 className="text-white font-semibold mb-1">{topic.title}</h3>
132
+ <p className="text-neutral-400 text-sm">{topic.description}</p>
133
+ <div className={`mt-3 flex items-center gap-1 ${colors.text} text-sm opacity-0 group-hover:opacity-100 transition-opacity`}>
134
+ <span>Start reading</span>
135
+ <ChevronRight className="w-4 h-4" />
136
+ </div>
137
+ </button>
138
+ );
139
+ })}
140
+ </div>
141
+ </div>
142
+
143
+ {/* Guided Studies */}
144
+ <div className="mb-8">
145
+ <div className="flex items-center justify-between mb-4">
146
+ <h2 className="text-lg font-semibold text-white">Guided Studies</h2>
147
+ <button className="text-sm text-cyan-400 hover:text-cyan-300 transition-colors">
148
+ Browse All
149
+ </button>
150
+ </div>
151
+ <div className="space-y-3">
152
+ {guidedStudies.map((study) => {
153
+ const colors = colorClasses[study.color];
154
+ const progressPercent = (study.progress / study.days) * 100;
155
+ return (
156
+ <div
157
+ key={study.title}
158
+ className="p-4 rounded-xl bg-white/[0.02] border border-white/10 hover:border-cyan-500/30 transition-all"
159
+ >
160
+ <div className="flex items-start justify-between mb-3">
161
+ <div>
162
+ <h3 className="text-white font-medium">{study.title}</h3>
163
+ <p className="text-neutral-500 text-sm">{study.description}</p>
164
+ </div>
165
+ <button className={`p-2 rounded-lg ${colors.bg} border ${colors.border} ${colors.text} hover:brightness-110 transition-all`}>
166
+ <Play className="w-4 h-4" />
167
+ </button>
168
+ </div>
169
+ <div className="flex items-center gap-3">
170
+ <div className="flex-1 h-2 bg-white/5 rounded-full overflow-hidden">
171
+ <div
172
+ className={`h-full rounded-full bg-gradient-to-r ${colors.gradient}`}
173
+ style={{ width: `${progressPercent}%` }}
174
+ />
175
+ </div>
176
+ <span className="text-xs text-neutral-500">
177
+ {study.progress} / {study.days} days
178
+ </span>
179
+ </div>
180
+ </div>
181
+ );
182
+ })}
183
+ </div>
184
+ </div>
185
+
186
+ {/* Categories */}
187
+ <div>
188
+ <h2 className="text-lg font-semibold text-white mb-4">Browse by Category</h2>
189
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
190
+ {[
191
+ { icon: BookOpen, label: 'Bible Study', count: 24, color: 'blue' },
192
+ { icon: Heart, label: 'Spiritual Growth', count: 18, color: 'rose' },
193
+ { icon: Users, label: 'Relationships', count: 12, color: 'amber' },
194
+ { icon: Sparkles, label: 'Theology', count: 15, color: 'purple' },
195
+ ].map((category) => {
196
+ const colors = colorClasses[category.color];
197
+ return (
198
+ <button
199
+ key={category.label}
200
+ className={`p-4 rounded-xl ${colors.bg} border ${colors.border} hover:brightness-110 transition-all text-left`}
201
+ >
202
+ <category.icon className={`w-6 h-6 ${colors.text} mb-2`} />
203
+ <div className="text-white font-medium">{category.label}</div>
204
+ <div className="text-neutral-500 text-sm">{category.count} topics</div>
205
+ </button>
206
+ );
207
+ })}
208
+ </div>
209
+ </div>
210
+
211
+ {/* AI Suggestion */}
212
+ <div className="mt-8 p-6 rounded-2xl bg-gradient-to-br from-cyan-500/10 to-purple-500/10 border border-cyan-500/20">
213
+ <div className="flex items-start gap-4">
214
+ <div className="w-12 h-12 rounded-xl bg-cyan-500/20 border border-cyan-500/30 flex items-center justify-center shrink-0">
215
+ <Sparkles className="w-6 h-6 text-cyan-400" />
216
+ </div>
217
+ <div>
218
+ <h3 className="text-white font-semibold mb-1">Personalized for You</h3>
219
+ <p className="text-neutral-400 text-sm leading-relaxed">
220
+ Based on your recent conversations about faith and trust, you might enjoy exploring
221
+ "The Life of Abraham" study - it beautifully illustrates unwavering faith in God's promises.
222
+ </p>
223
+ <button className="mt-3 text-cyan-400 hover:text-cyan-300 text-sm font-medium transition-colors flex items-center gap-1">
224
+ Start Study
225
+ <ChevronRight className="w-4 h-4" />
226
+ </button>
227
+ </div>
228
+ </div>
229
+ </div>
230
+ </div>
231
+ </div>
232
+ );
233
+ }
frontend/app/dashboard/insights/page.tsx ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Brain, TrendingUp, Calendar, BookOpen, Heart, Sparkles, BarChart3, PieChart, ArrowUp, ArrowDown } from 'lucide-react';
5
+
6
+ const weeklyStats = [
7
+ { label: 'Conversations', value: 12, change: +3, color: 'purple' },
8
+ { label: 'Bible Searches', value: 28, change: +7, color: 'blue' },
9
+ { label: 'Journal Entries', value: 5, change: +2, color: 'amber' },
10
+ { label: 'Prayers', value: 8, change: -1, color: 'rose' },
11
+ ];
12
+
13
+ const topTopics = [
14
+ { topic: 'Faith & Trust', count: 15, percentage: 30 },
15
+ { topic: 'Prayer & Meditation', count: 12, percentage: 24 },
16
+ { topic: 'Scripture Study', count: 10, percentage: 20 },
17
+ { topic: 'Life Guidance', count: 8, percentage: 16 },
18
+ { topic: 'Emotional Support', count: 5, percentage: 10 },
19
+ ];
20
+
21
+ const spiritualGrowth = [
22
+ { month: 'Jul', score: 65 },
23
+ { month: 'Aug', score: 70 },
24
+ { month: 'Sep', score: 68 },
25
+ { month: 'Oct', score: 75 },
26
+ { month: 'Nov', score: 82 },
27
+ { month: 'Dec', score: 88 },
28
+ ];
29
+
30
+ const colorClasses: Record<string, { bg: string; border: string; text: string }> = {
31
+ purple: { bg: 'bg-purple-500/10', border: 'border-purple-500/30', text: 'text-purple-400' },
32
+ blue: { bg: 'bg-blue-500/10', border: 'border-blue-500/30', text: 'text-blue-400' },
33
+ amber: { bg: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-400' },
34
+ rose: { bg: 'bg-rose-500/10', border: 'border-rose-500/30', text: 'text-rose-400' },
35
+ emerald: { bg: 'bg-emerald-500/10', border: 'border-emerald-500/30', text: 'text-emerald-400' },
36
+ };
37
+
38
+ export default function InsightsPage() {
39
+ const [timeRange, setTimeRange] = useState<'week' | 'month' | 'year'>('week');
40
+
41
+ return (
42
+ <div className="flex-1 overflow-y-auto">
43
+ {/* Header */}
44
+ <div className="sticky top-0 z-10 bg-[#0a0a0a]/90 backdrop-blur-xl border-b border-white/5">
45
+ <div className="max-w-5xl mx-auto px-6 py-4">
46
+ <div className="flex items-center justify-between">
47
+ <div className="flex items-center gap-3">
48
+ <div className="w-10 h-10 rounded-xl bg-emerald-500/10 border border-emerald-500/30 flex items-center justify-center">
49
+ <Brain className="w-5 h-5 text-emerald-400" />
50
+ </div>
51
+ <div>
52
+ <h1 className="text-xl font-semibold text-white">Insights</h1>
53
+ <p className="text-sm text-neutral-500">Track your spiritual growth</p>
54
+ </div>
55
+ </div>
56
+ <div className="flex gap-1 p-1 rounded-lg bg-white/5">
57
+ {(['week', 'month', 'year'] as const).map((range) => (
58
+ <button
59
+ key={range}
60
+ onClick={() => setTimeRange(range)}
61
+ className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${
62
+ timeRange === range
63
+ ? 'bg-white/10 text-white'
64
+ : 'text-neutral-400 hover:text-white'
65
+ }`}
66
+ >
67
+ {range.charAt(0).toUpperCase() + range.slice(1)}
68
+ </button>
69
+ ))}
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </div>
74
+
75
+ <div className="max-w-5xl mx-auto px-6 py-8">
76
+ {/* Weekly Stats */}
77
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
78
+ {weeklyStats.map((stat) => {
79
+ const colors = colorClasses[stat.color];
80
+ return (
81
+ <div
82
+ key={stat.label}
83
+ className={`p-4 rounded-xl ${colors.bg} border ${colors.border}`}
84
+ >
85
+ <div className="flex items-center justify-between mb-2">
86
+ <span className="text-neutral-400 text-sm">{stat.label}</span>
87
+ <div className={`flex items-center gap-1 text-xs ${
88
+ stat.change >= 0 ? 'text-emerald-400' : 'text-rose-400'
89
+ }`}>
90
+ {stat.change >= 0 ? <ArrowUp className="w-3 h-3" /> : <ArrowDown className="w-3 h-3" />}
91
+ {Math.abs(stat.change)}
92
+ </div>
93
+ </div>
94
+ <div className={`text-2xl font-bold ${colors.text}`}>{stat.value}</div>
95
+ </div>
96
+ );
97
+ })}
98
+ </div>
99
+
100
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
101
+ {/* Growth Chart */}
102
+ <div className="p-6 rounded-2xl bg-white/[0.02] border border-white/10">
103
+ <div className="flex items-center justify-between mb-6">
104
+ <h2 className="text-lg font-semibold text-white">Spiritual Growth</h2>
105
+ <BarChart3 className="w-5 h-5 text-neutral-500" />
106
+ </div>
107
+ <div className="flex items-end justify-between h-40 gap-2">
108
+ {spiritualGrowth.map((data) => (
109
+ <div key={data.month} className="flex-1 flex flex-col items-center gap-2">
110
+ <div
111
+ className="w-full bg-gradient-to-t from-emerald-500/50 to-emerald-400/30 rounded-t-lg transition-all"
112
+ style={{ height: `${data.score}%` }}
113
+ />
114
+ <span className="text-xs text-neutral-500">{data.month}</span>
115
+ </div>
116
+ ))}
117
+ </div>
118
+ <div className="mt-4 pt-4 border-t border-white/5 flex items-center justify-between">
119
+ <span className="text-sm text-neutral-400">Engagement Score</span>
120
+ <span className="text-emerald-400 font-semibold">88/100</span>
121
+ </div>
122
+ </div>
123
+
124
+ {/* Top Topics */}
125
+ <div className="p-6 rounded-2xl bg-white/[0.02] border border-white/10">
126
+ <div className="flex items-center justify-between mb-6">
127
+ <h2 className="text-lg font-semibold text-white">Top Topics</h2>
128
+ <PieChart className="w-5 h-5 text-neutral-500" />
129
+ </div>
130
+ <div className="space-y-4">
131
+ {topTopics.map((topic, i) => (
132
+ <div key={topic.topic}>
133
+ <div className="flex items-center justify-between mb-1">
134
+ <span className="text-sm text-neutral-300">{topic.topic}</span>
135
+ <span className="text-sm text-neutral-500">{topic.percentage}%</span>
136
+ </div>
137
+ <div className="h-2 bg-white/5 rounded-full overflow-hidden">
138
+ <div
139
+ className={`h-full rounded-full ${
140
+ i === 0 ? 'bg-purple-500' :
141
+ i === 1 ? 'bg-rose-500' :
142
+ i === 2 ? 'bg-blue-500' :
143
+ i === 3 ? 'bg-amber-500' :
144
+ 'bg-emerald-500'
145
+ }`}
146
+ style={{ width: `${topic.percentage}%` }}
147
+ />
148
+ </div>
149
+ </div>
150
+ ))}
151
+ </div>
152
+ </div>
153
+ </div>
154
+
155
+ {/* AI Insights */}
156
+ <div className="p-6 rounded-2xl bg-gradient-to-br from-emerald-500/10 to-purple-500/10 border border-emerald-500/20">
157
+ <div className="flex items-start gap-4">
158
+ <div className="w-12 h-12 rounded-xl bg-emerald-500/20 border border-emerald-500/30 flex items-center justify-center shrink-0">
159
+ <Sparkles className="w-6 h-6 text-emerald-400" />
160
+ </div>
161
+ <div>
162
+ <h3 className="text-white font-semibold mb-2">ORA's Observation</h3>
163
+ <p className="text-neutral-400 text-sm leading-relaxed mb-4">
164
+ Your spiritual engagement has grown 35% this month! I've noticed you've been particularly
165
+ drawn to topics around faith and trust. Consider exploring the book of Hebrews,
166
+ which offers deep insights on faith in action.
167
+ </p>
168
+ <div className="flex flex-wrap gap-2">
169
+ <span className="px-3 py-1 rounded-full bg-emerald-500/10 text-emerald-400 text-xs">
170
+ +35% engagement
171
+ </span>
172
+ <span className="px-3 py-1 rounded-full bg-purple-500/10 text-purple-400 text-xs">
173
+ 5-day streak
174
+ </span>
175
+ <span className="px-3 py-1 rounded-full bg-blue-500/10 text-blue-400 text-xs">
176
+ 28 verses studied
177
+ </span>
178
+ </div>
179
+ </div>
180
+ </div>
181
+ </div>
182
+
183
+ {/* Goals */}
184
+ <div className="mt-8">
185
+ <h2 className="text-lg font-semibold text-white mb-4">Spiritual Goals</h2>
186
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
187
+ <div className="p-4 rounded-xl bg-white/[0.02] border border-white/10">
188
+ <div className="flex items-center gap-3 mb-3">
189
+ <BookOpen className="w-5 h-5 text-blue-400" />
190
+ <span className="text-white font-medium">Daily Reading</span>
191
+ </div>
192
+ <div className="h-2 bg-white/5 rounded-full overflow-hidden mb-2">
193
+ <div className="h-full bg-blue-500 rounded-full" style={{ width: '70%' }} />
194
+ </div>
195
+ <span className="text-xs text-neutral-500">5 of 7 days this week</span>
196
+ </div>
197
+ <div className="p-4 rounded-xl bg-white/[0.02] border border-white/10">
198
+ <div className="flex items-center gap-3 mb-3">
199
+ <Heart className="w-5 h-5 text-rose-400" />
200
+ <span className="text-white font-medium">Prayer Time</span>
201
+ </div>
202
+ <div className="h-2 bg-white/5 rounded-full overflow-hidden mb-2">
203
+ <div className="h-full bg-rose-500 rounded-full" style={{ width: '85%' }} />
204
+ </div>
205
+ <span className="text-xs text-neutral-500">6 of 7 days this week</span>
206
+ </div>
207
+ <div className="p-4 rounded-xl bg-white/[0.02] border border-white/10">
208
+ <div className="flex items-center gap-3 mb-3">
209
+ <Calendar className="w-5 h-5 text-amber-400" />
210
+ <span className="text-white font-medium">Journaling</span>
211
+ </div>
212
+ <div className="h-2 bg-white/5 rounded-full overflow-hidden mb-2">
213
+ <div className="h-full bg-amber-500 rounded-full" style={{ width: '40%' }} />
214
+ </div>
215
+ <span className="text-xs text-neutral-500">2 of 5 entries this week</span>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ );
222
+ }