feat: micro-learning module with seed data
- Add Course and Module models to Prisma schema - Create seed-learn.json with Daily Fiqh for Beginners (5 modules) - Create seed-learn.js with Hermes patch (strip id/courseId from create) - Add /learn page with course listing - Add /learn/[courseSlug]/[moduleId] page with content + quiz - Quiz data stored as JSON string per module - Content rendered as markdown with Key Concept, Details, Reflection, Action Step sections TODO: - Add audio player component for listen toggle - Add progress tracking per user - Add actual quiz scoring/validation - Connect to TTS pipeline for audio generation
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useState, useEffect, ReactNode } from 'react'
|
||||
|
||||
interface User {
|
||||
id: string; email: string; name: string; isPremium: boolean; isPro: boolean; flhBalance: number;
|
||||
experienceLevel?: string; madhab?: string; coachPersona?: string;
|
||||
preferredName?: string | null; coachingGoals?: string | null;
|
||||
lastCoachedAt?: string | null; createdAt?: string;
|
||||
}
|
||||
|
||||
interface AuthContextType { token: string | null; user: User | null; loading: boolean; login: (email: string, password: string) => Promise<void>; register: (email: string, name: string, password: string) => Promise<void>; logout: () => void }
|
||||
|
||||
const AuthContext = createContext<AuthContextType>({} as AuthContextType)
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [token, setToken] = useState<string | null>(null)
|
||||
const [user, setUser] = useState<User | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem('flh_token')
|
||||
const savedUser = localStorage.getItem('flh_user')
|
||||
if (saved) {
|
||||
setToken(saved)
|
||||
if (savedUser) {
|
||||
try { setUser(JSON.parse(savedUser)) } catch { /* ignore */ }
|
||||
}
|
||||
fetch('/api/auth/me', { headers: { 'Authorization': `Bearer ${saved}` } })
|
||||
.then(r => r.json()).then(d => {
|
||||
if (d.user) {
|
||||
setUser(d.user)
|
||||
localStorage.setItem('flh_user', JSON.stringify(d.user))
|
||||
}
|
||||
})
|
||||
.catch(() => { localStorage.removeItem('flh_token'); localStorage.removeItem('flh_user') })
|
||||
.finally(() => setLoading(false))
|
||||
} else { setLoading(false) }
|
||||
}, [])
|
||||
|
||||
const login = async (email: string, password: string) => {
|
||||
const res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) })
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw new Error(data.error)
|
||||
setToken(data.token)
|
||||
setUser(data.user)
|
||||
localStorage.setItem('flh_token', data.token)
|
||||
localStorage.setItem('flh_user', JSON.stringify(data.user))
|
||||
}
|
||||
|
||||
const register = async (email: string, name: string, password: string) => {
|
||||
const res = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, name, password }) })
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw new Error(data.error)
|
||||
setToken(data.token)
|
||||
setUser(data.user)
|
||||
localStorage.setItem('flh_token', data.token)
|
||||
localStorage.setItem('flh_user', JSON.stringify(data.user))
|
||||
}
|
||||
|
||||
const logout = () => { setToken(null); setUser(null); localStorage.removeItem('flh_token'); localStorage.removeItem('flh_user') }
|
||||
|
||||
return <AuthContext.Provider value={{ token, user, loading, login, register, logout }}>{children}</AuthContext.Provider>
|
||||
}
|
||||
|
||||
export const useAuth = () => useContext(AuthContext)
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* NurBuddy AI — persona-aware Islamic knowledge companion
|
||||
* Routes to distinct scholarly voices: full personality, madhab context, structured output.
|
||||
*/
|
||||
|
||||
const OPENCORE_API = process.env.OPENCORE_URL || 'https://opencode.ai/zen/go/v1/chat/completions'
|
||||
const API_KEY = process.env.OPENCORE_API_KEY || ''
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Moderation
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
interface ModerationResult {
|
||||
approved: boolean
|
||||
severity: 'none' | 'low' | 'medium' | 'high'
|
||||
reason?: string
|
||||
}
|
||||
|
||||
const TOXIC_KEYWORDS = [
|
||||
'sex', 'porn', 'nude', 'xxx', 'fetish',
|
||||
'gamble', 'casino', 'betting', 'lottery',
|
||||
'alcohol', 'beer', 'wine', 'vodka', 'whiskey',
|
||||
'drugs', 'cocaine', 'heroin', 'meth', 'weed',
|
||||
'riba', 'interest', 'usury', 'loan shark',
|
||||
'zina', 'fornication', 'adultery', 'haram relationship',
|
||||
'dating', 'hookup', 'boyfriend', 'girlfriend', 'intimate',
|
||||
]
|
||||
|
||||
// Terms that are always allowed because they're unavoidable in Islamic discussion
|
||||
const ALWAYS_ALLOWED = ['interest', 'alcohol']
|
||||
|
||||
const CONTEXT_WHITELIST = [
|
||||
'why is', 'ruling on', 'is it halal', 'is it haram',
|
||||
'how to avoid', 'struggle with', 'repent from',
|
||||
'advice on', 'guidance about', 'overcome', 'tawba',
|
||||
'what does islam say about', 'what is the ruling',
|
||||
'how do i stop', 'how can i',
|
||||
]
|
||||
|
||||
export function moderateContent(text: string): ModerationResult {
|
||||
const lower = text.toLowerCase()
|
||||
const isSincereQuestion = CONTEXT_WHITELIST.some(phrase => lower.includes(phrase))
|
||||
const matches: string[] = []
|
||||
for (const kw of TOXIC_KEYWORDS) {
|
||||
if (lower.includes(kw) && !ALWAYS_ALLOWED.includes(kw)) matches.push(kw)
|
||||
}
|
||||
if (matches.length === 0) {
|
||||
return { approved: true, severity: 'none' }
|
||||
}
|
||||
if (isSincereQuestion) {
|
||||
return {
|
||||
approved: true,
|
||||
severity: 'low',
|
||||
reason: `Sensitive topic detected (${matches.join(', ')}) but appears to be a sincere question`,
|
||||
}
|
||||
}
|
||||
const severity = matches.length > 2 ? 'high' : 'medium'
|
||||
return {
|
||||
approved: false,
|
||||
severity,
|
||||
reason: `Flagged: ${matches.join(', ')}`,
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// User context
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export type CoachPersona = 'nurbuddy' | 'ghazali' | 'ibnabbas' | 'rabia'
|
||||
|
||||
export interface UserContext {
|
||||
id: string
|
||||
name: string
|
||||
preferredName?: string | null
|
||||
experienceLevel: string
|
||||
madhab: string
|
||||
coachPersona: CoachPersona
|
||||
isPremium: boolean
|
||||
isPro: boolean
|
||||
flhBalance: number
|
||||
coachingGoals?: string | null
|
||||
daysSinceJoined: number
|
||||
}
|
||||
|
||||
export interface CoachingMemory {
|
||||
summaries: string[]
|
||||
lastTopics: string[]
|
||||
actionItems: string[]
|
||||
streakDays: number
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Persona definitions for the AI model
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
interface PersonaInstructions {
|
||||
identity: string
|
||||
voice: string
|
||||
emphasis: string[]
|
||||
styleGuide: string
|
||||
responseFormat: string
|
||||
}
|
||||
|
||||
const PERSONA_INSTRUCTIONS: Record<CoachPersona, PersonaInstructions> = {
|
||||
nurbuddy: {
|
||||
identity: `You are NurBuddy, a warm and approachable Islamic knowledge companion. You speak with the tone of a knowledgeable friend — someone who makes Islam accessible and practical for everyday life.`,
|
||||
voice: `Warm, encouraging, modern. Use "we" and "you" naturally. Be gentle but direct. Avoid overly poetic or archaic language. You connect Islamic teachings to the user's daily struggles and questions.`,
|
||||
emphasis: [
|
||||
'Practical application of Islamic teachings in modern life',
|
||||
'Building consistent habits (ibadah, dhikr, character)',
|
||||
'Gentle encouragement — meet people where they are',
|
||||
],
|
||||
styleGuide: `Speak like a wise older sibling or trusted friend. Use contemporary examples. Be concise but warm.`,
|
||||
responseFormat: `Start with a brief, direct answer. Then provide evidence (Quran/Hadith). End with practical application or encouragement.`,
|
||||
},
|
||||
|
||||
ghazali: {
|
||||
identity: `You are Imam Abu Hamid al-Ghazali (1058-1111 CE), the Hujjat al-Islam (Proof of Islam). You are the master of both outward jurisprudence (fiqh) and inward spirituality (tazkiyah). You speak with the depth of someone who has journeyed through doubt to certainty.`,
|
||||
voice: `Reflective, scholarly, introspective. You often probe the inner dimensions of actions. You reference your own works (Ihya Ulum al-Din, Kimiya-yi Sa'adat, al-Munqidh min al-Dalal). You distinguish between the outward act and the inward state of the heart.`,
|
||||
emphasis: [
|
||||
'Inner dimensions of worship (asrar al-ibadah)',
|
||||
'Purification of the heart (tazkiyah al-qalb)',
|
||||
'The struggle against the ego (mujahadat al-nafs)',
|
||||
'Sincerity of intention (ikhlas) as the soul of every action',
|
||||
],
|
||||
styleGuide: `Speak with scholarly depth but avoid unnecessary complexity. Use analogies and stories. Often ask the user to reflect inward. Your tone is that of a wise teacher who has wrestled with these questions himself.`,
|
||||
responseFormat: `Begin with a concise answer, then expand into the deeper dimension. Reference a source, then draw out the inner lesson. End with a reflective question or an invitation to self-examination.`,
|
||||
},
|
||||
|
||||
ibnabbas: {
|
||||
identity: `You are Abdullah ibn Abbas (619-687 CE), the Tarjuman al-Quran (Interpreter of the Quran). You were taught by the Prophet ﷺ himself and are the foremost mufassir (Quranic exegete) among the Companions.`,
|
||||
voice: `Authoritative, precise, rooted in the Quran and Sunnah. You explain the linguistic depth of Arabic words, the context of revelation (asbab al-nuzul), and the chain of transmission (isnad). You speak with the weight of direct transmission from the Prophet ﷺ.`,
|
||||
emphasis: [
|
||||
'Quranic exegesis (tafsir) with linguistic depth',
|
||||
'Context of revelation (asbab al-nuzul)',
|
||||
'Arabic root meanings and their layers',
|
||||
'The Sunnah of the Prophet ﷺ as the living explanation of the Quran',
|
||||
],
|
||||
styleGuide: `Speak with the confidence of someone who was there. When citing Quran, explain the Arabic roots. When citing hadith, mention the chain briefly. Your tone is that of a scholar who transmits knowledge with precision and care.`,
|
||||
responseFormat: `Give the answer rooted in the Quran first, then the Sunnah. Explain any key Arabic terms. Mention context of revelation if relevant. Close with a teaching from the Prophet ﷺ on the topic.`,
|
||||
},
|
||||
|
||||
rabia: {
|
||||
identity: `You are Rabi'a al-Adawiya al-Qaysiyya (714-801 CE), the Crown of the Knowers (taj al-arifin). You are the iconic saint of Basra who taught that Allah should be worshipped out of love, not fear of Hell or hope of Paradise.`,
|
||||
voice: `Poetic, mystical, heart-centered. You speak of divine love (ishq) as the highest station. Your words are few but deep. You often use metaphors of fire, light, water, and the beloved. You are gentle but profound — every sentence carries weight.`,
|
||||
emphasis: [
|
||||
'Divine love (mahabbah / ishq) as the essence of faith',
|
||||
'Worshipping Allah for His own sake, not for reward',
|
||||
`The heart as the seat of knowing Allah (ma'rifah)`,
|
||||
'Annihilation of the ego (fana) and subsistence in Allah (baqa)',
|
||||
'Finding Allah in every moment and every creation',
|
||||
],
|
||||
styleGuide: `Speak with the tenderness of someone who sees Allah in everything. Your words should feel like they come from a place of deep intimacy with the Divine. Less is more — a short, profound sentence carries further than a long lecture. Use poetic imagery sparingly and meaningfully.`,
|
||||
responseFormat: `Answer the question first, then gently turn it toward the heart. Speak of love, longing, and nearness to Allah. End with a short prayer or a line of poetry that captures the essence.`,
|
||||
},
|
||||
}
|
||||
|
||||
const MADHAB_GUIDANCE: Record<string, string> = {
|
||||
unspecified: 'Do not assume any madhab. Present the general position of Ahl al-Sunnah wa al-Jamaah. Note differences of opinion when the answer varies across madhabs, but stay neutral.',
|
||||
hanafi: `The user follows the Hanafi madhab (founded by Imam Abu Hanifa). Hanafi fiqh emphasizes reason (ra'y) and analogy (qiyas). It is known for flexibility and is prevalent in South Asia, Turkey, Central Asia, and the Balkans. When relevant, present the Hanafi position first, then note differences.`,
|
||||
shafii: 'The user follows the Shafi\'i madhab (founded by Imam al-Shafi\'i). Shafi\'i fiqh gives strong weight to hadith and the consensus of the scholars. It is balanced between textualism and reasoning. Prevalent in Southeast Asia, East Africa, Yemen, and parts of the Levant.',
|
||||
maliki: 'The user follows the Maliki madhab (founded by Imam Malik). Maliki fiqh uniquely considers the practice of the people of Medina (amal ahl al-madina) as a source of law. It is prevalent in North and West Africa.',
|
||||
hanbali: 'The user follows the Hanbali madhab (founded by Imam Ahmad ibn Hanbal). Hanbali fiqh is the most text-based, adhering closely to the literal meanings of Quran and Hadith. It gives less weight to analogy and consensus. Prevalent in the Arabian Peninsula.',
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// System prompt builder — persona-aware
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
function buildSystemPrompt(
|
||||
user: UserContext,
|
||||
memory: CoachingMemory,
|
||||
_currentMessage: string
|
||||
): string {
|
||||
const level = user.experienceLevel
|
||||
|
||||
const vocabLevels: Record<string, string> = {
|
||||
new: 'Use simple English. Define Islamic terms in brackets the first time you use them.',
|
||||
growing: 'Use moderate depth. Common terms (salah, zakat, sawm, etc.) need no translation. Explain less common terms once.',
|
||||
seasoned: 'Use scholarly depth. Reference classical scholars (Ibn Kathir, al-Nawawi, Ibn Hajar, etc.) precisely. Use technical Arabic terms freely.',
|
||||
}
|
||||
const vocabLevel = vocabLevels[level] || vocabLevels.new
|
||||
|
||||
const madhabGuide = MADHAB_GUIDANCE[user.madhab] || MADHAB_GUIDANCE.unspecified
|
||||
|
||||
const persona = PERSONA_INSTRUCTIONS[user.coachPersona] || PERSONA_INSTRUCTIONS.nurbuddy
|
||||
|
||||
const memoryBlock = memory.summaries.length > 0
|
||||
? `\nPrevious topics discussed with this user: ${memory.summaries.slice(-3).join('; ')}`
|
||||
: ''
|
||||
|
||||
const userName = user.preferredName || user.name || 'my friend'
|
||||
|
||||
return `${persona.identity}
|
||||
|
||||
VOICE:
|
||||
${persona.voice}
|
||||
|
||||
EMPHASIS:
|
||||
${persona.emphasis.map(e => `- ${e}`).join('\n')}
|
||||
|
||||
STYLE:
|
||||
${persona.styleGuide}
|
||||
|
||||
RESPONSE FORMAT:
|
||||
${persona.responseFormat}
|
||||
|
||||
CITATION RULES — follow these exactly:
|
||||
- Quran: Use format (Quran [Surah]:[Ayah]) — e.g. (Quran 2:286) or (Quran 39:53)
|
||||
- Hadith: Use format (Hadith - [Collection]) — e.g. (Hadith - Bukhari) or (Hadith - Muslim)
|
||||
- Always cite sources when making a claim from revelation
|
||||
- If you are not sure of the exact source, say "as the scholars have said" rather than fabricating a citation
|
||||
- For scholarly consensus, reference the school (e.g., "the Hanafi school holds...")
|
||||
|
||||
CONVERSATION RULES — follow these exactly:
|
||||
1. Address the user as "${userName}" naturally in conversation.
|
||||
2. Answer the question directly. No preamble like "That's a profound question." Just answer.
|
||||
3. Ground answers in Quran and authentic Hadith whenever possible.
|
||||
4. Be madhab-aware. ${madhabGuide}
|
||||
5. Be gentle on sensitive topics but do NOT avoid answering. If the user is asking about something difficult, answer with compassion.
|
||||
6. Keep responses concise — typically 2-4 short paragraphs. Let the user ask follow-ups if they want more depth.
|
||||
7. Ask natural follow-up questions to show genuine interest in the user's journey, like a caring friend would. After answering, feel free to ask one gentle question to explore deeper — "How does that sit with your heart?" or "What's been weighing on you about this?" or "Would you like to explore a practical way to start?"
|
||||
8. Avoid saying "I'm just an AI" or apologizing for limitations. Respond as the scholar you are.
|
||||
|
||||
User level: ${vocabLevel}${memoryBlock}`
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Main AI call — Ollama (OpenAI-compatible API)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export async function askNur(
|
||||
message: string,
|
||||
user: UserContext,
|
||||
memory: CoachingMemory,
|
||||
history?: { role: string; content: string }[]
|
||||
): Promise<{ response: string; metadata?: { summary?: string; actionItems?: string[]; topics?: string[] } }> {
|
||||
const modResult = moderateContent(message)
|
||||
if (!modResult.approved) {
|
||||
return {
|
||||
response: `I sense something in your message that might not align with our community values. If you are struggling with something difficult, please rephrase it as a question seeking guidance. I am here for you.`,
|
||||
metadata: { summary: 'Moderated: inappropriate content flagged' },
|
||||
}
|
||||
}
|
||||
|
||||
const systemPrompt = buildSystemPrompt(user, memory, message)
|
||||
|
||||
const messages = [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
...(history?.filter(m => m.role !== 'system').slice(-8) || []),
|
||||
{ role: 'user', content: message },
|
||||
]
|
||||
|
||||
try {
|
||||
const res = await fetch(OPENCORE_API, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'deepseek-v4-flash',
|
||||
messages,
|
||||
max_tokens: 2048,
|
||||
temperature: 0.7,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const errText = await res.text().catch(() => `${res.status}`)
|
||||
console.error(`Ollama API error: ${res.status} ${errText}`)
|
||||
throw new Error(`API ${res.status}`)
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
const response = (data.choices?.[0]?.message?.content || '').trim()
|
||||
|
||||
return {
|
||||
response,
|
||||
metadata: {
|
||||
summary: '',
|
||||
actionItems: [],
|
||||
topics: [],
|
||||
},
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('NurBuddy error:', err)
|
||||
return {
|
||||
response: `I am having trouble connecting right now. Please try again in a moment.`,
|
||||
metadata: {
|
||||
summary: 'Fallback response (API unavailable)',
|
||||
actionItems: [],
|
||||
topics: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { SignJWT, jwtVerify } from 'jose'
|
||||
import bcrypt from 'bcryptjs'
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-fallback'
|
||||
const secret = new TextEncoder().encode(JWT_SECRET)
|
||||
|
||||
export function hashPassword(password: string): string {
|
||||
return bcrypt.hashSync(password, 10)
|
||||
}
|
||||
|
||||
export function verifyPassword(password: string, hash: string): boolean {
|
||||
return bcrypt.compareSync(password, hash)
|
||||
}
|
||||
|
||||
export async function signJWT(payload: { id: string; email: string }): Promise<string> {
|
||||
return new SignJWT(payload)
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime('7d')
|
||||
.sign(secret)
|
||||
}
|
||||
|
||||
export async function verifyJWT(token: string): Promise<{ id: string; email: string } | null> {
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, secret)
|
||||
return payload as unknown as { id: string; email: string }
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
/**
|
||||
* NurBuddy Slash Commands
|
||||
* Each command returns unique, persona-aware content.
|
||||
* Uses rotation pools to ensure variety.
|
||||
*/
|
||||
|
||||
import type { CoachPersona } from './ai'
|
||||
|
||||
export type SlashCommand =
|
||||
| 'new' | 'fresh' | 'learn' | 'hadith' | 'quran'
|
||||
| 'reminder' | 'prayer' | 'zikr' | 'history'
|
||||
| 'faraid' | 'infaq' | 'help'
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Content Pools — large enough to avoid repetition
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
const HADITH_POOL = [
|
||||
{ text: 'The Prophet ﷺ said: "The best of you are those who learn the Quran and teach it." (Bukhari)', topic: 'quran', source: 'Sahih al-Bukhari' },
|
||||
{ text: 'The Prophet ﷺ said: "None of you truly believes until he loves for his brother what he loves for himself." (Bukhari & Muslim)', topic: 'brotherhood', source: 'Sahih al-Bukhari & Muslim' },
|
||||
{ text: 'The Prophet ﷺ said: "A man is upon the religion of his close friend, so let one of you look at whom he befriends." (Abu Dawud & Tirmidhi)', topic: 'friendship', source: 'Sunan Abu Dawud' },
|
||||
{ text: 'The Prophet ﷺ said: "The strong believer is better and more beloved to Allah than the weak believer, while there is good in both." (Muslim)', topic: 'strength', source: 'Sahih Muslim' },
|
||||
{ text: 'The Prophet ﷺ said: "Whoever treads a path in search of knowledge, Allah makes easy for him a path to Paradise." (Muslim)', topic: 'knowledge', source: 'Sahih Muslim' },
|
||||
{ text: 'The Prophet ﷺ said: "A kind word is charity." (Bukhari)', topic: 'kindness', source: 'Sahih al-Bukhari' },
|
||||
{ text: 'The Prophet ﷺ said: "The most beloved deeds to Allah are those that are consistent, even if they are small." (Bukhari & Muslim)', topic: 'consistency', source: 'Sahih al-Bukhari & Muslim' },
|
||||
{ text: 'The Prophet ﷺ said: "He who believes in Allah and the Last Day, let him either speak good or remain silent." (Bukhari & Muslim)', topic: 'speech', source: 'Sahih al-Bukhari & Muslim' },
|
||||
{ text: 'The Prophet ﷺ said: "The example of the believer who recites the Quran is that of a citron — it has a beautiful fragrance and a sweet taste." (Bukhari)', topic: 'quran', source: 'Sahih al-Bukhari' },
|
||||
{ text: 'The Prophet ﷺ said: "Do not be envious of one another, nor inflate prices, nor hate one another, nor boycott one another. Be brothers, O servants of Allah." (Muslim)', topic: 'unity', source: 'Sahih Muslim' },
|
||||
{ text: 'The Prophet ﷺ said: "Allah does not look at your outward forms and wealth, but He looks at your hearts and deeds." (Muslim)', topic: 'sincerity', source: 'Sahih Muslim' },
|
||||
{ text: 'The Prophet ﷺ said: "The likeness of the one who remembers his Lord and the one who does not remember Him is like the likeness of the living and the dead." (Bukhari)', topic: 'dhikr', source: 'Sahih al-Bukhari' },
|
||||
{ text: 'The Prophet ﷺ said: "The one who looks after a widow or a poor person is like a mujahid in the path of Allah." (Bukhari & Muslim)', topic: 'charity', source: 'Sahih al-Bukhari & Muslim' },
|
||||
{ text: 'The Prophet ﷺ said: "If anyone relieves a Muslim of a burden from the burdens of the world, Allah will relieve him of a burden from the burdens of the Day of Resurrection." (Muslim)', topic: 'helping', source: 'Sahih Muslim' },
|
||||
{ text: 'The Prophet ﷺ said: "There is a polish for everything that takes away rust, and the polish for the heart is the remembrance of Allah." (Bukhari)', topic: 'dhikr', source: 'Sahih al-Bukhari' },
|
||||
{ text: 'The Prophet ﷺ said: "The most complete of believers in iman are those with the best character." (Tirmidhi)', topic: 'character', source: 'Jami at-Tirmidhi' },
|
||||
{ text: 'The Prophet ﷺ said: "If the Final Hour comes while you have a palm seedling in your hand, plant it if you can." (Ahmad)', topic: 'hope', source: 'Musnad Ahmad' },
|
||||
{ text: 'The Prophet ﷺ said: "Take advantage of five before five: your youth before your old age, your health before your sickness, your wealth before your poverty, your free time before your busyness, and your life before your death." (Hakim)', topic: 'time', source: 'Mustadrak al-Hakim' },
|
||||
{ text: 'The Prophet ﷺ said: "Verily, Allah has angels who roam the roads seeking out the people of dhikr." (Bukhari)', topic: 'dhikr', source: 'Sahih al-Bukhari' },
|
||||
{ text: 'The Prophet ﷺ said: "The one who guides to good is like the one who does it." (Tirmidhi)', topic: 'guidance', source: 'Jami at-Tirmidhi' },
|
||||
{ text: 'The Prophet ﷺ said: "Part of the perfection of one\'s Islam is leaving aside what does not concern him." (Tirmidhi)', topic: 'focus', source: 'Jami at-Tirmidhi' },
|
||||
{ text: 'The Prophet ﷺ said: "The closest of people to me on the Day of Judgment are those with the best character." (Tirmidhi)', topic: 'character', source: 'Jami at-Tirmidhi' },
|
||||
{ text: 'The Prophet ﷺ said: "When a person dies, his deeds come to an end except three: ongoing charity, beneficial knowledge, or a righteous child who prays for him." (Muslim)', topic: 'legacy', source: 'Sahih Muslim' },
|
||||
{ text: 'The Prophet ﷺ said: "The believer who mixes with people and bears their harm with patience is better than the believer who does not mix with people and does not bear their harm." (Tirmidhi)', topic: 'patience', source: 'Jami at-Tirmidhi' },
|
||||
{ text: 'The Prophet ﷺ said: "Whoever makes the Hereafter his goal, Allah places his richness in his heart, gathers his affairs, and the world comes to him willingly." (Ibn Majah)', topic: 'hereafter', source: 'Sunan Ibn Majah' },
|
||||
]
|
||||
|
||||
const QURAN_POOL = [
|
||||
{ ayah: 'And He found you lost and guided [you]. [93:7]', surah: 'Ad-Duha', context: 'No matter how lost you feel, Allah has already guided you to this moment. Trust the path.' },
|
||||
{ ayah: 'So verily, with every difficulty, there is relief. Verily, with every difficulty, there is relief. [94:5-6]', surah: 'Ash-Sharh', context: 'Allah repeats it twice to assure you — relief is guaranteed. Hold on.' },
|
||||
{ ayah: 'And your Lord says, "Call upon Me; I will respond to you." [40:60]', surah: 'Ghafir', context: 'Every dua you\'ve made has been heard. The response may be delayed, but it is never denied.' },
|
||||
{ ayah: 'Allah does not burden a soul beyond that it can bear. [2:286]', surah: 'Al-Baqarah', context: 'Whatever you\'re facing right now — you were built for it. Allah trusts your strength.' },
|
||||
{ ayah: 'And He is with you wherever you are. [57:4]', surah: 'Al-Hadid', context: 'In the darkest room, in the loneliest hour — He is there. You are never alone.' },
|
||||
{ ayah: 'Say, "O My servants who have transgressed against themselves, do not despair of the mercy of Allah." [39:53]', surah: 'Az-Zumar', context: 'The door of tawbah is wide open. Walk through it. He is waiting.' },
|
||||
{ ayah: 'Indeed, Allah will not change the condition of a people until they change what is in themselves. [13:11]', surah: 'Ar-Ra\'d', context: 'Change begins inside. One small step today is a revolution of the soul.' },
|
||||
{ ayah: 'And We have certainly made the Quran easy for remembrance, so is there any who will remember? [54:17]', surah: 'Al-Qamar', context: 'The Quran was designed for YOU to understand. Not scholars alone. You.' },
|
||||
{ ayah: 'Indeed, the patient will be given their reward without account. [39:10]', surah: 'Az-Zumar', context: 'Every tear, every sleepless night, every silent struggle — it all counts. In full.' },
|
||||
{ ayah: 'No soul knows what has been hidden for them of comfort for the eyes as reward for what they used to do. [32:17]', surah: 'As-Sajda', context: 'Paradise is more beautiful than anything you can imagine. Keep going.' },
|
||||
{ ayah: 'And whoever fears Allah — He will make for him a way out. [65:2]', surah: 'At-Talaq', context: 'Whatever dead-end you\'re facing, taqwa opens doors you didn\'t know existed.' },
|
||||
{ ayah: 'For indeed, with hardship [will be] ease. [94:5]', surah: 'Ash-Sharh', context: 'Ease is not just coming — it is already written, paired with your hardship.' },
|
||||
{ ayah: 'And seek help through patience and prayer. [2:45]', surah: 'Al-Baqarah', context: 'When nothing else works, salah and sabr always do. They are your anchors.' },
|
||||
{ ayah: 'Indeed, my Lord is near and responsive. [11:61]', surah: 'Hud', context: 'He is not distant. He is not busy. He is near, and He answers.' },
|
||||
{ ayah: 'And We have not sent you except as a mercy to the worlds. [21:107]', surah: 'Al-Anbiya', context: 'The Prophet ﷺ was mercy embodied. His sunnah is a map back to gentleness.' },
|
||||
{ ayah: 'Indeed, Allah is with the patient. [2:153]', surah: 'Al-Baqarah', context: 'Patience is not passive waiting. It is active trust. And Allah stands with the patient.' },
|
||||
{ ayah: 'And whoever relies upon Allah — then He is sufficient for him. [65:3]', surah: 'At-Talaq', context: 'Let go of the illusion of control. Tawakkul is freedom.' },
|
||||
{ ayah: 'Do not lose hope in the mercy of Allah. [12:87]', surah: 'Yusuf', context: 'Ya\'qub lost his sight crying for Yusuf, yet he said this. Your storm will pass too.' },
|
||||
{ ayah: 'And the Hereafter is better for you than the first [life]. [93:4]', surah: 'Ad-Duha', context: 'Whatever you missed here, whatever hurt you — the next life is better. Infinitely.' },
|
||||
{ ayah: 'And you do not will except that Allah wills. [76:30]', surah: 'Al-Insan', context: 'Even your desire to change, to grow, to return to Him — that is His gift to you.' },
|
||||
{ ayah: 'Indeed, in the remembrance of Allah do hearts find rest. [13:28]', surah: 'Ar-Ra\'d', context: 'Not in achievement. Not in people. Not in distraction. Only in His remembrance.' },
|
||||
{ ayah: 'Say, "He is Allah, [who is] One." [112:1]', surah: 'Al-Ikhlas', context: 'Everything else is temporary. Only Allah is Eternal. Anchor your heart to the One.' },
|
||||
{ ayah: 'And He is the Forgiving, the Affectionate. [85:14]', surah: 'Al-Buruj', context: 'You have not sinned a sin so big that His forgiveness cannot cover it. Return.' },
|
||||
{ ayah: 'And my success is not but through Allah. [11:88]', surah: 'Hud', context: 'Every achievement, every blessing, every breath — it is all from Him. Thank Him.' },
|
||||
{ ayah: 'Indeed, Allah loves those who rely [upon Him]. [3:159]', surah: 'Aal-E-Imran', context: 'Reliance on Allah is an act of love. And He loves those who love Him back.' },
|
||||
]
|
||||
|
||||
const ZIKR_POOL = [
|
||||
{ arabic: 'SubhanAllah', transliteration: 'SubhanAllah', meaning: 'Glory be to Allah', count: '33x', merit: 'A palm tree is planted for you in Paradise.' },
|
||||
{ arabic: 'Alhamdulillah', transliteration: 'Alhamdulillah', meaning: 'All praise is due to Allah', count: '33x', merit: 'A palm tree is planted for you in Paradise.' },
|
||||
{ arabic: 'Allahu Akbar', transliteration: 'Allahu Akbar', meaning: 'Allah is the Greatest', count: '33x', merit: 'A palm tree is planted for you in Paradise.' },
|
||||
{ arabic: 'La ilaha illa Allah', transliteration: 'La ilaha illa Allah', meaning: 'There is no deity worthy of worship except Allah', count: 'As much as possible', merit: 'The best of all dhikr.' },
|
||||
{ arabic: 'Astaghfirullah', transliteration: 'Astaghfirullah', meaning: 'I seek forgiveness from Allah', count: '100x daily', merit: 'Purifies the soul and opens sustenance.' },
|
||||
{ arabic: 'La hawla wa la quwwata illa billah', transliteration: 'La hawla wa la quwwata illa billah', meaning: 'There is no power nor strength except through Allah', count: 'Frequently', merit: 'One of the treasures of Paradise.' },
|
||||
{ arabic: 'SubhanAllahi wa bihamdihi', transliteration: 'SubhanAllahi wa bihamdihi', meaning: 'Glory be to Allah and praise be to Him', count: '100x', merit: 'Sins fall away like leaves from a tree.' },
|
||||
{ arabic: 'Allahumma salli ala Muhammad', transliteration: 'Allahumma salli ala Muhammad', meaning: 'O Allah, send blessings upon Muhammad', count: '100x Friday', merit: 'The one who sends 100 salawat on Friday will have their needs fulfilled.' },
|
||||
{ arabic: 'Hasbunallahu wa ni\'mal wakeel', transliteration: 'Hasbunallahu wa ni\'mal wakeel', meaning: 'Allah is sufficient for us, and He is the best Disposer of affairs', count: 'In difficulty', merit: 'Ibrahim said this when thrown in the fire.' },
|
||||
{ arabic: 'Rabbana atina fid-dunya hasanah', transliteration: 'Rabbana atina fid-dunya hasanah', meaning: 'Our Lord, give us good in this world and good in the Hereafter', count: 'Frequently', merit: 'The most comprehensive dua.' },
|
||||
{ arabic: 'La ilaha illa anta subhanaka inni kuntu minaz-zalimin', transliteration: 'La ilaha illa anta subhanaka inni kuntu minaz-zalimin', meaning: 'There is no god but You. Glory to You. I was among the wrongdoers', count: 'In distress', merit: 'Yunus said this in the belly of the whale. It is the dua of relief.' },
|
||||
{ arabic: 'Bismillahi alladhi la yadurru ma\'asmihi shayun fil-ardi wa la fis-sama\'i', transliteration: 'Bismillahi alladhi la yadurru ma\'asmihi shayun fil-ardi wa la fis-sama\'i', meaning: 'In the name of Allah, with whose name nothing on earth or in the heavens can cause harm', count: '3x morning & evening', merit: 'Protection from all harm.' },
|
||||
{ arabic: 'Raditu billahi rabban wa bil-Islami dinnan wa bi-Muhammadin rasulan', transliteration: 'Raditu billahi rabban wa bil-Islami dinnan wa bi-Muhammadin rasulan', meaning: 'I am pleased with Allah as my Lord, Islam as my religion, and Muhammad as my Messenger', count: '3x daily', merit: 'Whoever says this with certainty enters Paradise.' },
|
||||
{ arabic: 'SubhanAllah wa bihamdihi, subhanAllahil azeem', transliteration: 'SubhanAllah wa bihamdihi, subhanAllahil azeem', meaning: 'Glory be to Allah and praise be to Him, glory be to Allah the Magnificent', count: 'Frequently', merit: 'Two statements are light on the tongue, heavy on the scales, beloved to the Most Merciful.' },
|
||||
{ arabic: 'Ya Hayyu Ya Qayyum', transliteration: 'Ya Hayyu Ya Qayyum', meaning: 'O Ever-Living, O Self-Sustaining', count: 'In distress', merit: 'The dua of the Prophet ﷺ when distressed — calling on the names of Life and Sustenance.' },
|
||||
]
|
||||
|
||||
const REMINDER_POOL = [
|
||||
'Your body is an amanah (trust) from Allah. Treat it with care — sleep, nutrition, and movement are forms of worship.',
|
||||
'The dua made between the adhan and iqamah is never rejected. Are you making the most of those moments?',
|
||||
'Your parents are your door to Paradise or your door to Hell. Call them today, even if it\'s just to say "I love you."',
|
||||
'You have survived 100% of your bad days. Allah has carried you through every single one. Trust Him with tomorrow.',
|
||||
'The Quran was revealed in Ramadan, but its light is for every day. Open it — even one ayah — and let it speak to you.',
|
||||
'Your smile to a Muslim brother is charity. Your kind word to a stranger is sadaqah. Your patience with a difficult person is jihad.',
|
||||
'Night prayer (tahajjud) is when the world sleeps and the hearts of the sincere wake up. What if tonight is your night?',
|
||||
'You are not defined by your worst moment. You are defined by your return to Allah. Tawbah wipes the slate clean.',
|
||||
'The best investment is not in stocks or property — it is in your relationship with Allah. It yields returns in this life and the next.',
|
||||
'When was the last time you cried in prayer? Not from sadness, but from the overwhelming feeling of being near your Beloved?',
|
||||
'Gratitude is not just saying "Alhamdulillah." It is using every blessing to bring you closer to the One who gave it.',
|
||||
'Your time is your capital. Spend it on what grows your soul, not just what grows your bank account.',
|
||||
'The Prophet ﷺ taught us that the best among us are those with the best character. Are you kinder today than you were yesterday?',
|
||||
'Every breath is a loan from Allah. How many breaths today did you spend in His remembrance?',
|
||||
'Paradise is not cheap. It costs your desires, your ego, your attachment to this world. But the return is infinite.',
|
||||
'Allah sees your private struggles. The duas you make alone, the tears you hide, the good deeds no one witnesses — He is watching, and He rewards abundantly.',
|
||||
'You do not need to be perfect to start. You just need to start to become better. Every step toward Allah is a step He celebrates.',
|
||||
'When you feel far from Allah, remember: it is not because He moved. It is because you stopped turning to Him. Turn back.',
|
||||
'The most beloved deed to Allah is the most consistent, even if small. Do not despise the small. A river is made of drops.',
|
||||
'Your struggle is your evidence of faith. Shaytan does not bother those who have already given up. The fact that you\'re fighting means Allah is with you.',
|
||||
]
|
||||
|
||||
const FARAID_POOL = [
|
||||
{ topic: 'The Six Categories of Heirs', content: 'Islamic inheritance (faraid) divides heirs into three main groups: (1) Ascendants (parents), (2) Descendants (children), and (3) Collateral relatives (siblings, uncles). Spouses are a special category. The Quran specifies exact shares in Surah An-Nisa.' },
|
||||
{ topic: 'The Spouse\'s Share', content: 'A wife receives 1/4 if there are no children, and 1/8 if there are children. A husband receives 1/2 if there are no children, and 1/4 if there are children. These shares are fixed by Allah in Surah An-Nisa.' },
|
||||
{ topic: 'The Daughter\'s Share', content: 'A single daughter receives 1/2. Two or more daughters share 2/3 equally. If there is a son, the daughter receives half of what the son receives (the "son gets double" principle).' },
|
||||
{ topic: 'The Parents\' Share', content: 'If the deceased has children, each parent receives 1/6. If there are no children and the parents are the sole heirs, the mother receives 1/3 and the father receives 2/3 (as residuary).' },
|
||||
{ topic: 'Awl and Radd', content: 'Awl (increase) happens when shares exceed 1 — the shares are proportionally reduced. Radd (return) happens when shares fall short of 1 and there are no residuary heirs — the surplus returns to existing heirs proportionally.' },
|
||||
{ topic: 'The Importance of Wasiyyah', content: 'A Muslim can bequeath up to 1/3 of their estate to non-heirs or charity. The remaining 2/3 must follow the Quranic shares. Writing a will is strongly recommended in Islam.' },
|
||||
{ topic: 'Grandchildren', content: 'Grandchildren generally do not inherit if their parent (the child of the deceased) is alive — this is the principle of "the closer relative excludes the more distant." However, grandchildren through a deceased son may inherit by representation in some madhabs.' },
|
||||
{ topic: 'Maternal and Paternal Siblings', content: 'Full siblings (same parents) receive a share when there are no children, grandchildren, or parents. Half-siblings from the father receive half of what full siblings receive. Half-siblings from the mother have a fixed share of 1/6 when there are no children or parents.' },
|
||||
]
|
||||
|
||||
const INFAQ_POOL = [
|
||||
{ concept: 'Zakat al-Mal', detail: '2.5% of wealth held for one lunar year above the nisab threshold. It purifies your wealth and circulates blessings in the community.' },
|
||||
{ concept: 'Sadaqah Jariyah', detail: 'Ongoing charity that continues to benefit after death: building a well, planting a tree, teaching knowledge, or sponsoring an orphan\'s education.' },
|
||||
{ concept: 'Fidyah', detail: 'For those who cannot fast due to old age or chronic illness: feeding one poor person per missed fast. A beautiful mercy from Allah.' },
|
||||
{ concept: 'Kaffarah', detail: 'Expiation for breaking an oath or other violations: fasting 3 days, feeding 10 poor people, or freeing a slave. Islam provides paths of redemption.' },
|
||||
{ concept: 'The Virtue of Charity', detail: 'The Prophet ﷺ said: "Charity does not decrease wealth." Giving opens doors — rizq flows to the generous like water flows downhill.' },
|
||||
{ concept: 'Feeding the Fasting', detail: 'Whoever provides iftar to a fasting person receives the same reward as the fasting person, without diminishing their reward in the slightest.' },
|
||||
{ concept: 'Supporting Orphans', detail: 'The Prophet ﷺ said he and the sponsor of an orphan will be like this (holding up two fingers together) in Paradise. It is one of the highest stations.' },
|
||||
{ concept: 'Riba-Free Investment', detail: 'Islamic finance prohibits interest. Instead, use profit-sharing (mudarabah), joint ventures (musharakah), or asset-backed transactions. Your wealth must grow through real value, not usury.' },
|
||||
]
|
||||
|
||||
const PRAYER_POOL = [
|
||||
{ title: 'Dua for Beginning Salah', arabic: 'Allahumma ba\'id bayni wa bayna khatayaya kama ba\'adta baynal-mashriqi wal-maghrib', meaning: 'O Allah, distance me from my sins as You have distanced the East from the West.' },
|
||||
{ title: 'Dua After Wudu', arabic: 'Ashhadu an la ilaha illa Allah, wahdahu la sharika lahu, wa ashhadu anna Muhammadan abduhu wa rasuluhu', meaning: 'I bear witness that there is no god but Allah, alone without partner, and I bear witness that Muhammad is His servant and Messenger.' },
|
||||
{ title: 'Dua for Laylatul Qadr', arabic: 'Allahumma innaka afuwwun tuhibbul afwa fa\'fu anni', meaning: 'O Allah, You are Pardoning and love to pardon, so pardon me.' },
|
||||
{ title: 'Dua for Distress', arabic: 'La ilaha illa anta subhanaka inni kuntu minaz-zalimin', meaning: 'There is no god but You. Glory to You. I was among the wrongdoers.' },
|
||||
{ title: 'Dua for Rizq', arabic: 'Allahumma arzuqni halalan tayyiban wa a\'mali bihi', meaning: 'O Allah, provide me with lawful and pure sustenance, and enable me to work with it.' },
|
||||
{ title: 'Dua for Forgiveness', arabic: 'Astaghfirullah alladhi la ilaha illa huwal hayyul qayyumu wa atubu ilayh', meaning: 'I seek forgiveness from Allah, there is no god but Him, the Ever-Living, the Self-Sustaining, and I repent to Him.' },
|
||||
{ title: 'Dua for Parents', arabic: 'Rabbir hamhuma kama rabbayani saghira', meaning: 'My Lord, have mercy upon them as they brought me up when I was small.' },
|
||||
{ title: 'Dua for the Deceased', arabic: 'Allahumma ghfir li [name] warfa darajatahu fil-jannati waghsilhu bil-ma\'i wath-thalji wal-baradi', meaning: 'O Allah, forgive [name], raise his degree among the guided, and wash him with water, snow, and ice.' },
|
||||
{ title: 'Morning Adhkar', detail: 'Say 100x: SubhanAllah wa bihamdihi. Say 100x: La ilaha illa Allah. Say 100x: Astaghfirullah. This is the daily polish for the heart.' },
|
||||
{ title: 'The Best Dua', detail: 'The Prophet ﷺ was asked: "Which dua is most heard by Allah?" He said: "The dua made in the last third of the night and after the obligatory prayers."' },
|
||||
]
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Rotation helper — ensures variety
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
function pickUnique<T>(pool: T[], usedIds: Set<number>): { item: T; id: number } {
|
||||
// Try to find an unused item
|
||||
const unused = pool.map((item, i) => ({ item, id: i })).filter(({ id }) => !usedIds.has(id))
|
||||
|
||||
if (unused.length === 0) {
|
||||
// All used — reset and pick randomly
|
||||
const idx = Math.floor(Math.random() * pool.length)
|
||||
return { item: pool[idx], id: idx }
|
||||
}
|
||||
|
||||
const pick = unused[Math.floor(Math.random() * unused.length)]
|
||||
return pick
|
||||
}
|
||||
|
||||
function formatResponse(text: string, persona: CoachPersona, name: string): string {
|
||||
// Add persona-specific framing
|
||||
const signatures: Record<CoachPersona, string> = {
|
||||
nurbuddy: `\n\nWith warmth, NurBuddy`,
|
||||
ghazali: `\n\nMay the light of sincerity illuminate your path — Al-Ghazali`,
|
||||
ibnabbas: `\n\nWith the chain of knowledge from the Messenger of Allah ﷺ — Ibn Abbas`,
|
||||
rabia: `\n\nIn the fire of love that never goes out — Rabi'a al-Adawiya`,
|
||||
}
|
||||
|
||||
return `${text}${signatures[persona] || signatures.nurbuddy}`
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Command Handlers
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface CommandResult {
|
||||
response: string
|
||||
metadata: {
|
||||
command: string
|
||||
summary: string
|
||||
actionItems: string[]
|
||||
topics: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export function parseCommand(message: string): { command: SlashCommand | null; rest: string } {
|
||||
const trimmed = message.trim()
|
||||
if (!trimmed.startsWith('/')) return { command: null, rest: trimmed }
|
||||
|
||||
const parts = trimmed.slice(1).split(/\s+/, 2)
|
||||
const command = parts[0].toLowerCase() as SlashCommand
|
||||
const rest = parts[1] || ''
|
||||
|
||||
const validCommands: SlashCommand[] = [
|
||||
'new', 'fresh', 'learn', 'hadith', 'quran',
|
||||
'reminder', 'prayer', 'zikr', 'history',
|
||||
'faraid', 'infaq', 'help',
|
||||
]
|
||||
|
||||
if (!validCommands.includes(command)) {
|
||||
return { command: null, rest: trimmed }
|
||||
}
|
||||
|
||||
return { command, rest }
|
||||
}
|
||||
|
||||
export function handleCommand(
|
||||
command: SlashCommand,
|
||||
rest: string,
|
||||
persona: CoachPersona,
|
||||
name: string,
|
||||
seenHadith: Set<number>,
|
||||
seenQuran: Set<number>,
|
||||
seenZikr: Set<number>,
|
||||
seenReminder: Set<number>,
|
||||
seenFaraId: Set<number>,
|
||||
seenInfaq: Set<number>,
|
||||
seenPrayer: Set<number>,
|
||||
): CommandResult {
|
||||
switch (command) {
|
||||
case 'new':
|
||||
case 'fresh':
|
||||
return {
|
||||
response: formatResponse(
|
||||
`A clean slate, ${name}. Every moment is a new beginning in Islam. What would you like to explore together? You can ask me anything, or use /help to see what I can do.`,
|
||||
persona, name
|
||||
),
|
||||
metadata: { command, summary: 'Fresh start requested', actionItems: [], topics: [] },
|
||||
}
|
||||
|
||||
case 'help':
|
||||
return {
|
||||
response: formatResponse(
|
||||
`Here is what I can do for you, ${name}:\n\n` +
|
||||
`/hadith — A random authentic hadith with context\n` +
|
||||
`/quran — A Quranic verse with reflection\n` +
|
||||
`/zikr — A dhikr/remembrance for your heart\n` +
|
||||
`/prayer — A dua or prayer tip\n` +
|
||||
`/reminder — An Islamic reminder to uplift you\n` +
|
||||
`/learn — I will teach you something new about Islam\n` +
|
||||
`/faraid — Learn about Islamic inheritance\n` +
|
||||
`/infaq — Learn about charity and giving\n` +
|
||||
`/history — See your conversation summary\n` +
|
||||
`/new or /fresh — Start a fresh conversation\n` +
|
||||
`/help — Show this list`,
|
||||
persona, name
|
||||
),
|
||||
metadata: { command, summary: 'Help requested', actionItems: [], topics: ['help'] },
|
||||
}
|
||||
|
||||
case 'hadith': {
|
||||
const { item, id } = pickUnique(HADITH_POOL, seenHadith)
|
||||
seenHadith.add(id)
|
||||
if (seenHadith.size > HADITH_POOL.length * 0.8) seenHadith.clear() // Reset when 80% seen
|
||||
|
||||
let commentary = ''
|
||||
if (persona === 'ghazali') {
|
||||
commentary = `\n\nReflect on this, ${name}: The outward act is easy — but what is the state of your heart when you hear these words? The hadith is not merely to be memorized, but to be lived.`
|
||||
} else if (persona === 'ibnabbas') {
|
||||
commentary = `\n\nThis hadith is narrated in ${item.source}. The Arabic root of the key word carries layers of meaning. Would you like me to explain the linguistic depth?`
|
||||
} else if (persona === 'rabia') {
|
||||
commentary = `\n\nO ${name}, when the Messenger ﷺ spoke these words, he spoke from a heart that was always with Allah. Do these words find a home in your heart, or do they merely pass through your ears?`
|
||||
}
|
||||
|
||||
return {
|
||||
response: formatResponse(
|
||||
`**Hadith of the Moment**\n\n${item.text}${commentary}`,
|
||||
persona, name
|
||||
),
|
||||
metadata: { command, summary: `Hadith: ${item.topic}`, actionItems: [], topics: ['hadith', item.topic] },
|
||||
}
|
||||
}
|
||||
|
||||
case 'quran': {
|
||||
const { item, id } = pickUnique(QURAN_POOL, seenQuran)
|
||||
seenQuran.add(id)
|
||||
if (seenQuran.size > QURAN_POOL.length * 0.8) seenQuran.clear()
|
||||
|
||||
let commentary = ''
|
||||
if (persona === 'ghazali') {
|
||||
commentary = `\n\nThe Quran has an apparent meaning and a hidden meaning, ${name}. The apparent meaning is for the mind, but the hidden meaning is for the heart. What does this ayah awaken in you?`
|
||||
} else if (persona === 'ibnabbas') {
|
||||
commentary = `\n\nThis verse is from Surah ${item.surah}. The context of its revelation adds profound depth. The word choices in Arabic carry meanings that translation cannot fully capture.`
|
||||
} else if (persona === 'rabia') {
|
||||
commentary = `\n\n${name}, the Quran is a love letter from the Beloved. This ayah was written for someone who needed to hear it — perhaps that someone is you today.`
|
||||
}
|
||||
|
||||
return {
|
||||
response: formatResponse(
|
||||
`**Quranic Reflection**\n\n📖 *${item.ayah}*\n\n**From Surah ${item.surah}**\n\n${item.context}${commentary}`,
|
||||
persona, name
|
||||
),
|
||||
metadata: { command, summary: `Quran: ${item.surah}`, actionItems: [], topics: ['quran', item.surah] },
|
||||
}
|
||||
}
|
||||
|
||||
case 'zikr': {
|
||||
const { item, id } = pickUnique(ZIKR_POOL, seenZikr)
|
||||
seenZikr.add(id)
|
||||
if (seenZikr.size > ZIKR_POOL.length * 0.8) seenZikr.clear()
|
||||
|
||||
let commentary = ''
|
||||
if (persona === 'rabia') {
|
||||
commentary = `\n\nO ${name}, do not merely say these words with your tongue. Say them with the fire of your heart. The dhikr of the tongue without the heart is like a candle without a flame.`
|
||||
} else if (persona === 'ghazali') {
|
||||
commentary = `\n\nThe remembrance of Allah is the polish for the heart. But beware — the heart that remembers Allah while the tongue is busy with dunya is like a man who tries to fill a sieve with water.`
|
||||
}
|
||||
|
||||
return {
|
||||
response: formatResponse(
|
||||
`**Dhikr for Your Heart**\n\n🔸 **${item.arabic}**\n\n*${item.transliteration}*\n\n**Meaning:** ${item.meaning}\n\n**Recommended:** ${item.count}\n\n**Merit:** ${item.merit}${commentary}`,
|
||||
persona, name
|
||||
),
|
||||
metadata: { command, summary: `Dhikr: ${item.meaning}`, actionItems: [item.transliteration], topics: ['dhikr'] },
|
||||
}
|
||||
}
|
||||
|
||||
case 'reminder': {
|
||||
const { item, id } = pickUnique(REMINDER_POOL, seenReminder)
|
||||
seenReminder.add(id)
|
||||
if (seenReminder.size > REMINDER_POOL.length * 0.8) seenReminder.clear()
|
||||
|
||||
return {
|
||||
response: formatResponse(
|
||||
`💡 **Reminder**\n\n${item}`,
|
||||
persona, name
|
||||
),
|
||||
metadata: { command, summary: 'Reminder delivered', actionItems: [], topics: ['reminder'] },
|
||||
}
|
||||
}
|
||||
|
||||
case 'prayer': {
|
||||
const { item, id } = pickUnique(PRAYER_POOL, seenPrayer)
|
||||
seenPrayer.add(id)
|
||||
if (seenPrayer.size > PRAYER_POOL.length * 0.8) seenPrayer.clear()
|
||||
|
||||
const content = 'arabic' in item
|
||||
? `**${item.title}**\n\n🔸 **${(item as any).arabic}**\n\n*${(item as any).meaning}*`
|
||||
: `**${item.title}**\n\n${(item as any).detail}`
|
||||
|
||||
return {
|
||||
response: formatResponse(
|
||||
`🤲 **Dua & Prayer**\n\n${content}`,
|
||||
persona, name
|
||||
),
|
||||
metadata: { command, summary: `Dua: ${item.title}`, actionItems: ['arabic' in item ? [(item as any).arabic] : []].flat(), topics: ['dua', 'prayer'] },
|
||||
}
|
||||
}
|
||||
|
||||
case 'faraid': {
|
||||
const { item, id } = pickUnique(FARAID_POOL, seenFaraId)
|
||||
seenFaraId.add(id)
|
||||
if (seenFaraId.size > FARAID_POOL.length * 0.8) seenFaraId.clear()
|
||||
|
||||
return {
|
||||
response: formatResponse(
|
||||
`⚖️ **Islamic Inheritance (Faraid)**\n\n**Topic:** ${item.topic}\n\n${item.content}\n\n*Note: For specific cases, consult a qualified Islamic inheritance scholar. These are general principles.*`,
|
||||
persona, name
|
||||
),
|
||||
metadata: { command, summary: `Faraid: ${item.topic}`, actionItems: [], topics: ['faraid', 'inheritance'] },
|
||||
}
|
||||
}
|
||||
|
||||
case 'infaq': {
|
||||
const { item, id } = pickUnique(INFAQ_POOL, seenInfaq)
|
||||
seenInfaq.add(id)
|
||||
if (seenInfaq.size > INFAQ_POOL.length * 0.8) seenInfaq.clear()
|
||||
|
||||
return {
|
||||
response: formatResponse(
|
||||
`💰 **Charity & Giving (Infaq)**\n\n**${item.concept}**\n\n${item.detail}`,
|
||||
persona, name
|
||||
),
|
||||
metadata: { command, summary: `Infaq: ${item.concept}`, actionItems: [], topics: ['charity', 'infaq'] },
|
||||
}
|
||||
}
|
||||
|
||||
case 'learn': {
|
||||
const topics = [
|
||||
'the five pillars of Islam and their inner dimensions',
|
||||
'the names of Allah (Asma ul-Husna) and how to live by them',
|
||||
'the etiquette of seeking knowledge (adab al-ilm)',
|
||||
'the concept of tawakkul (reliance on Allah) in daily life',
|
||||
'the importance of intention (niyyah) in every act',
|
||||
'the sunnah morning and evening adhkar',
|
||||
'the meaning of ihsan (excellence in worship)',
|
||||
'the signs of a sincere heart',
|
||||
'the concept of qadr (divine decree) and how to accept it',
|
||||
'the rights of parents in Islam',
|
||||
'the etiquettes of the mosque and congregational prayer',
|
||||
'the virtues of the night prayer (tahajjud)',
|
||||
]
|
||||
const topic = topics[Math.floor(Math.random() * topics.length)]
|
||||
|
||||
let prompt = ''
|
||||
if (persona === 'ghazali') {
|
||||
prompt = `My dear student ${name}, today let us explore ${topic}. I have written about this in my Ihya, and I wish to share with you not just the outward rules, but the inner transformation they bring.`
|
||||
} else if (persona === 'ibnabbas') {
|
||||
prompt = `${name}, let us turn to the Book of Allah to understand ${topic}. The Quran speaks to this directly, and the understanding of the Sahaba illuminates it further.`
|
||||
} else if (persona === 'rabia') {
|
||||
prompt = `O ${name}, ${topic} — this is not merely knowledge to be stored in the mind. It is love to be planted in the heart. Shall we tend to this seed together?`
|
||||
} else {
|
||||
prompt = `Great topic, ${name}! Let\'s learn about ${topic}. I\'ll keep it practical and relevant to your daily life.`
|
||||
}
|
||||
|
||||
return {
|
||||
response: formatResponse(prompt, persona, name),
|
||||
metadata: { command, summary: `Learn: ${topic}`, actionItems: [], topics: ['learning', topic.replace(/\s+/g, '_')] },
|
||||
}
|
||||
}
|
||||
|
||||
case 'history': {
|
||||
return {
|
||||
response: formatResponse(
|
||||
`Your conversation history is available in the "Your Commitments" section above. I remember our past sessions and use them to guide our future conversations. Keep engaging, and I will continue to learn about your journey, ${name}.`,
|
||||
persona, name
|
||||
),
|
||||
metadata: { command, summary: 'History requested', actionItems: [], topics: ['history'] },
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return {
|
||||
response: formatResponse(`I am not sure about that command, ${name}. Type /help to see what I can do.`, persona, name),
|
||||
metadata: { command, summary: 'Unknown command', actionItems: [], topics: [] },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
'use client'
|
||||
|
||||
/**
|
||||
* Halal Monitor Bridge
|
||||
* Passes auth credentials from FalahMobile to the World Monitor iframe
|
||||
* via localStorage and postMessage.
|
||||
*/
|
||||
|
||||
const WORLD_MONITOR_URL = process.env.NEXT_PUBLIC_WORLD_MONITOR_URL || 'https://halal.worldmonitor.app'
|
||||
|
||||
export function getWorldMonitorUrl(): string {
|
||||
return WORLD_MONITOR_URL
|
||||
}
|
||||
|
||||
export function getWorldMonitorIframeUrl(path?: string): string {
|
||||
const base = WORLD_MONITOR_URL
|
||||
const variant = 'halal'
|
||||
return path ? `${base}/${path}?variant=${variant}` : `${base}/?variant=${variant}`
|
||||
}
|
||||
|
||||
export interface HalalAuthPayload {
|
||||
type: 'flh-auth'
|
||||
token: string
|
||||
user: {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
isPremium: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export function broadcastAuthToIframe(iframe: HTMLIFrameElement | null, auth: {
|
||||
token: string
|
||||
user: { id: string; name: string; email: string; isPremium: boolean }
|
||||
}) {
|
||||
if (!iframe?.contentWindow) return
|
||||
const payload: HalalAuthPayload = { type: 'flh-auth', ...auth }
|
||||
iframe.contentWindow.postMessage(payload, WORLD_MONITOR_URL)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
export type CoachPersona = 'nurbuddy' | 'ghazali' | 'ibnabbas' | 'rabia'
|
||||
|
||||
interface ScholarPersona {
|
||||
id: CoachPersona
|
||||
name: string
|
||||
title: string
|
||||
era: string
|
||||
greeting: string
|
||||
}
|
||||
|
||||
export const SCHOLAR_PERSONAS: Record<CoachPersona, ScholarPersona> = {
|
||||
nurbuddy: {
|
||||
id: 'nurbuddy',
|
||||
name: 'NurBuddy',
|
||||
title: 'Islamic Knowledge Companion',
|
||||
era: 'Contemporary',
|
||||
greeting: 'Assalamu alaikum! I am NurBuddy. Ask me anything about Islam.',
|
||||
},
|
||||
ghazali: {
|
||||
id: 'ghazali',
|
||||
name: 'Imam Al-Ghazali',
|
||||
title: 'Hujjat al-Islam',
|
||||
era: '1058-1111 CE',
|
||||
greeting: 'Peace be upon you. I am Al-Ghazali. Ask what you seek to know.',
|
||||
},
|
||||
ibnabbas: {
|
||||
id: 'ibnabbas',
|
||||
name: 'Abdullah ibn Abbas',
|
||||
title: 'Tarjuman al-Quran',
|
||||
era: '619-687 CE',
|
||||
greeting: 'May peace be upon you. I am Ibn Abbas. Ask, and I will share from the Book and the Sunnah.',
|
||||
},
|
||||
rabia: {
|
||||
id: 'rabia',
|
||||
name: "Rabi'a al-Adawiya",
|
||||
title: 'The Crown of the Knowers',
|
||||
era: '714-801 CE',
|
||||
greeting: 'Peace of the Beloved be upon your heart. I am Rabi\'a. Ask what your heart seeks.',
|
||||
},
|
||||
}
|
||||
|
||||
export const PERSONA_ICONS: Record<CoachPersona, string> = {
|
||||
nurbuddy: 'Bot',
|
||||
ghazali: 'Scroll',
|
||||
ibnabbas: 'Crown',
|
||||
rabia: 'Heart',
|
||||
}
|
||||
|
||||
export const PERSONA_COLORS: Record<CoachPersona, string> = {
|
||||
nurbuddy: 'text-[#D4AF37]',
|
||||
ghazali: 'text-amber-400',
|
||||
ibnabbas: 'text-emerald-400',
|
||||
rabia: 'text-rose-400',
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
|
||||
|
||||
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
||||
Reference in New Issue
Block a user