import React, { useState, useEffect, useRef } from 'react'; import { StorageService } from '../services/storageService'; import { Recipe, ChatMessage, Review, UserProfile, RecipeCategory } from '../types'; import { getCookingChatSession } from '../services/geminiService'; import { Button } from '../components/ui/Button'; import { Heart, Plus, Loader2, Upload, Image as ImageIcon, X, History, ChefHat, Send, Star, Clock, Utensils, Search, ChevronRight, User, Sparkles } from 'lucide-react'; import { Chat, GenerateContentResponse } from '@google/genai'; import { useLanguage } from '../contexts/LanguageContext'; // Helper to parse dual-language JSON messages const parseMessage = (rawText: string) => { try { const json = JSON.parse(rawText); if (json.en || json.ne) return json; return { en: rawText, ne: rawText }; } catch { return { en: rawText, ne: rawText }; } }; const Recipes: React.FC = () => { const { t, language } = useLanguage(); const [recipes, setRecipes] = useState([]); const [filteredRecipes, setFilteredRecipes] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [showForm, setShowForm] = useState(false); const [loading, setLoading] = useState(true); const [selectedRecipe, setSelectedRecipe] = useState(null); const [currentUser, setCurrentUser] = useState(null); const [likedRecipes, setLikedRecipes] = useState>(new Set()); // Reviews const [reviews, setReviews] = useState([]); const [rating, setRating] = useState(0); const [comment, setComment] = useState(''); // AI Chef State const [isChatOpen, setIsChatOpen] = useState(false); const [chatMessages, setChatMessages] = useState([]); const [chatInput, setChatInput] = useState(''); const [isChatTyping, setIsChatTyping] = useState(false); const chatSessionRef = useRef(null); const messagesEndRef = useRef(null); // New/Edit Recipe Form State const [newRecipe, setNewRecipe] = useState>({ title: '', description: '', ingredients: [], instructions: '', imageUrl: '', prepTime: 20, tags: ['daily'] }); const [ingredientsText, setIngredientsText] = useState(''); const loadRecipes = async () => { setLoading(true); const [data, profile] = await Promise.all([ StorageService.getRecipes('recent'), StorageService.getProfile() ]); setRecipes(data); setCurrentUser(profile); setLoading(false); }; useEffect(() => { loadRecipes(); }, []); const sendMessage = async (text: string) => { if (!text.trim()) return; if (!chatSessionRef.current) { chatSessionRef.current = getCookingChatSession(); } const userMsg: ChatMessage = { id: Date.now().toString(), role: 'user', text: JSON.stringify({ en: text, ne: text }), timestamp: Date.now() }; setChatMessages(prev => [...prev, userMsg]); setChatInput(''); setIsChatTyping(true); try { const resultStream = await chatSessionRef.current.sendMessageStream({ message: text }); let fullResponse = ''; const modelMsgId = (Date.now() + 1).toString(); setChatMessages(prev => [...prev, { id: modelMsgId, role: 'model', text: '', timestamp: Date.now() }]); for await (const chunk of resultStream) { const textChunk = (chunk as GenerateContentResponse).text || ''; fullResponse += textChunk; setChatMessages(prev => prev.map(msg => msg.id === modelMsgId ? { ...msg, text: fullResponse } : msg )); } } catch { setChatMessages(prev => [...prev, { id: Date.now().toString(), role: 'model', text: JSON.stringify({ en: "The kitchen is a bit smoky, try again!", ne: "भान्सामा अलिकति धुवाँ छ, फेरि प्रयास गर्नुहोस्!" }), timestamp: Date.now() }]); } finally { setIsChatTyping(false); } }; useEffect(() => { const handleOpenRecipe = (e: any) => { const { recipeId } = e.detail; if (recipes.length > 0) { const found = recipes.find(r => r.id === recipeId); if (found) setSelectedRecipe(found); } }; const handleDraftRecipe = (e: any) => { const { title, description } = e.detail; setNewRecipe(prev => ({ ...prev, title: title || '', description: description || '' })); setShowForm(true); }; const handleConsultChef = (e: any) => { const { query } = e.detail; setIsChatOpen(true); if (!chatSessionRef.current) { const session = getCookingChatSession(); chatSessionRef.current = session; setChatMessages([{ id: 'init', role: 'model', text: JSON.stringify({ en: "Namaste! I am Bhanse Dai. I am here to share the secrets of Nepali heritage cooking. What are we cooking today?", ne: "नमस्ते! म भान्से दाइ हुँ। म यहाँ नेपाली मौलिक खानाका रहस्यहरू बाँड्न आएको छु। आज हामी के पकाउने?" }), timestamp: Date.now() }]); } setTimeout(() => { sendMessage(`Please teach me how to cook ${query}. Provide a list of ingredients and step-by-step instructions.`); }, 600); }; window.addEventListener('rudraksha-open-recipe', handleOpenRecipe); window.addEventListener('rudraksha-draft-recipe', handleDraftRecipe); window.addEventListener('rudraksha-consult-chef', handleConsultChef); return () => { window.removeEventListener('rudraksha-open-recipe', handleOpenRecipe); window.removeEventListener('rudraksha-draft-recipe', handleDraftRecipe); window.removeEventListener('rudraksha-consult-chef', handleConsultChef); }; }, [recipes, t]); useEffect(() => { let filtered = recipes; if (searchQuery.trim()) { filtered = filtered.filter(r => r.title.toLowerCase().includes(searchQuery.toLowerCase()) || r.description.toLowerCase().includes(searchQuery.toLowerCase()) ); } setFilteredRecipes(filtered); }, [recipes, searchQuery]); const handleLike = async (e: React.MouseEvent, recipe: Recipe) => { e.stopPropagation(); const isLiked = likedRecipes.has(recipe.id); const newLiked = new Set(likedRecipes); if (isLiked) { newLiked.delete(recipe.id); recipe.likes = Math.max(0, recipe.likes - 1); } else { newLiked.add(recipe.id); recipe.likes += 1; } setLikedRecipes(newLiked); await StorageService.saveRecipe(recipe); setRecipes([...recipes]); }; useEffect(() => { if (selectedRecipe) { loadReviews(selectedRecipe.id); } }, [selectedRecipe]); const loadReviews = async (recipeId: string) => { const r = await StorageService.getReviews(recipeId); setReviews(r); }; const handleStarClick = (selectedRating: number) => { if (rating === selectedRating) { setRating(0); } else { setRating(selectedRating); } }; const submitReview = async (e: React.FormEvent) => { e.preventDefault(); if (!selectedRecipe || rating === 0 || !comment.trim()) return; const profile = await StorageService.getProfile(); const review: Review = { id: Date.now().toString(), targetId: selectedRecipe.id, userId: profile?.id || 'anon', userName: profile?.name || 'Anonymous', rating, comment, timestamp: Date.now() }; await StorageService.addReview(review); setReviews([review, ...reviews]); setRating(0); setComment(''); }; useEffect(() => { if (isChatOpen) { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); } }, [chatMessages, isChatOpen]); const handleFileChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { const reader = new FileReader(); reader.onloadend = () => setNewRecipe(prev => ({ ...prev, imageUrl: reader.result as string })); reader.readAsDataURL(file); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const profile = await StorageService.getProfile(); if (!profile) return; const recipeToSave: Recipe = { id: Date.now().toString(), title: newRecipe.title!, author: profile.name, description: newRecipe.description!, ingredients: ingredientsText.split('\n').filter(s => s.trim()), instructions: newRecipe.instructions!, isPublic: true, likes: newRecipe.likes || 0, imageUrl: newRecipe.imageUrl, history: newRecipe.history, prepTime: newRecipe.prepTime, tags: newRecipe.tags }; await StorageService.saveRecipe(recipeToSave); await loadRecipes(); setShowForm(false); }; const toggleChat = () => { if (!isChatOpen) { if (!chatSessionRef.current) { const session = getCookingChatSession(); chatSessionRef.current = session; if (chatMessages.length === 0) { setChatMessages([{ id: 'init', role: 'model', text: JSON.stringify({ en: "Namaste! I am Bhanse Dai. I am here to share the secrets of Nepali heritage cooking. What are we cooking today?", ne: "नमस्ते! म भान्से दाइ हुँ। म यहाँ नेपाली मौलिक खानाका रहस्यहरू बाँड्न आएको छु। आज हामी के पकाउने?" }), timestamp: Date.now() }]); } } } setIsChatOpen(!isChatOpen); }; const openNewForm = () => { setNewRecipe({ title: '', description: '', ingredients: [], instructions: '', imageUrl: '', prepTime: 20, tags: ['daily'] }); setIngredientsText(''); setShowForm(true); }; if (loading) return
; return (
Culinary Rituals

BHANSE
KITCHEN

{t("From daily rituals to ancient ethnic delicacies, explore the flavors that define Nepal.", "From daily rituals to ancient ethnic delicacies, explore the flavors that define Nepal.")}

Dal Bhat
{/* Main Flashcard Grid */}
setSearchQuery(e.target.value)} placeholder="Find a dish..." className="w-full pl-12 pr-4 py-3 bg-white dark:bg-gray-800 border-2 border-gray-100 dark:border-gray-700 rounded-2xl outline-none focus:border-orange-500 transition-all" />
{filteredRecipes.map((r) => (
setSelectedRecipe(r)} className="bg-white dark:bg-gray-800 rounded-[2.5rem] overflow-hidden border-2 border-gray-100 dark:border-gray-700 shadow-sm cursor-pointer relative transition-transform hover:-translate-y-1" >
{r.imageUrl ? ( {r.title} ) : (
)}
{r.prepTime} MIN

{r.title}

by {r.author}

"{r.description}"

{[1,2,3].map(i =>
U
)}
View Recipe
))} {filteredRecipes.length === 0 && (

No recipes matched your search

)}
{/* Floating Chat System */} {isChatOpen && (
setIsChatOpen(false)}>

Bhanse Dai

Heritage Specialist
{chatMessages.map((msg) => { const content = parseMessage(msg.text); const displayText = language === 'ne' ? (content.ne || content.en) : content.en; const isUser = msg.role === 'user'; return (

{displayText}

{new Date(msg.timestamp).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
); })} {isChatTyping && (
)}
{ e.preventDefault(); sendMessage(chatInput); }} className="p-6 bg-white dark:bg-gray-900 border-t border-gray-100 dark:border-gray-800 flex gap-4"> setChatInput(e.target.value)} placeholder={t("Ask for heritage facts or basic tips...", "Ask for heritage facts or basic tips...")} className="flex-1 px-6 py-4 bg-gray-50 dark:bg-gray-800 rounded-2xl text-sm font-medium border-2 border-transparent focus:border-orange-500 outline-none transition-all dark:text-white" />
)} {selectedRecipe && (
setSelectedRecipe(null)}>
{selectedRecipe.title}
{selectedRecipe.tags?.map(t => ( {t} ))}

{selectedRecipe.title}

{selectedRecipe.prepTime} Min {selectedRecipe.author}
{selectedRecipe.history && (

Heritage Protocol

"{selectedRecipe.history}"

)}

Ingredients

    {selectedRecipe.ingredients.map((ing, i) => (
  • {ing}
  • ))}

Preparation

{selectedRecipe.instructions.split('\n').filter(s => s.trim()).map((step, i) => ( // Regex handles numbering if present, otherwise just renders text
{i+1}

{step.replace(/^\d+\.\s*/, '')}

))}

Community Feedback

{reviews.length > 0 ? (reviews.reduce((a,b) => a+b.rating, 0)/reviews.length).toFixed(1) : "NEW"}
{reviews.map(r => (

{r.userName}

{[...Array(r.rating)].map((_, i) => )}

"{r.comment}"

))}

Share Your Thoughts

{[1,2,3,4,5].map(s => ( ))}