import React, { useState, useRef, useEffect } from "react"; import { createRoot } from "react-dom/client"; import { GoogleGenAI, Chat, GenerateContentResponse } from "@google/genai"; // System instructions derived from the website content const SYSTEM_INSTRUCTION = `You are the official AI Assistant for 'Dubai UAE Desert Safari', the #1 rated desert tour operator in Dubai. Your goal is to assist customers with inquiries about safari packages, prices, and booking, and encourage them to book via WhatsApp. Key Information: - Contact/WhatsApp: +971 56 629 0104 - Location: Dubai Desert Conservation Reserve. Pickup available from any hotel in Dubai. - Cancellation: Free up to 24 hours before. Packages & Prices (Approx): 1. Evening Desert Safari (Best Seller): ~150 AED. Includes Dune Bashing, Camel Ride, Sandboarding, BBQ Buffet, Belly Dance, Fire Show. Pickup 2:30-3:30 PM. 2. Morning Desert Safari: ~120 AED. Adventure focused (Dune Bashing, Sandboarding). No Dinner. Pickup 8:00-8:30 AM. 3. VIP Desert Safari: ~450 AED. Luxury experience. Private Land Cruiser pickup, Table service dinner, VIP seating, No queuing. 4. Overnight Safari: ~250 AED. Includes Evening Safari + Camping in tents, Bonfire, Breakfast. 5. Private Car: 700-800 AED per car (up to 6-7 pax). Flexible timing, customizable dune bashing intensity. 6. Sunrise Safari: ~700-900 AED (Private car). 4:30 AM pickup. Tone: Professional, enthusiastic, warm, and helpful. formatting: Use Markdown for bolding key terms. Keep responses concise (under 3 sentences) unless asked for details. Call to Action: Always suggest booking via WhatsApp for the best response time.`; const ChatWidget = () => { const [isOpen, setIsOpen] = useState(false); const [messages, setMessages] = useState<{ role: "user" | "model"; text: string }[]>([ { role: "model", text: "Marhaba! 👋 Welcome to Dubai Desert Safari. How can I help you plan your adventure today?" } ]); const [input, setInput] = useState(""); const [isLoading, setIsLoading] = useState(false); const [isTyping, setIsTyping] = useState(false); const chatSessionRef = useRef(null); const messagesEndRef = useRef(null); // Initialize Gemini Chat Session useEffect(() => { if (process.env.API_KEY) { try { const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); chatSessionRef.current = ai.chats.create({ model: "gemini-3-flash-preview", config: { systemInstruction: SYSTEM_INSTRUCTION, temperature: 0.7, }, }); } catch (error) { console.error("Failed to initialize AI:", error); } } }, []); // Auto-scroll to bottom useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages, isTyping]); const handleSend = async (e?: React.FormEvent) => { e?.preventDefault(); if (!input.trim() || isLoading || !chatSessionRef.current) return; const userMessage = input.trim(); setInput(""); setMessages((prev) => [...prev, { role: "user", text: userMessage }]); setIsLoading(true); setIsTyping(true); try { const resultStream = await chatSessionRef.current.sendMessageStream({ message: userMessage, }); let fullResponse = ""; // Add a placeholder message for the model that we will update setMessages((prev) => [...prev, { role: "model", text: "" }]); for await (const chunk of resultStream) { const c = chunk as GenerateContentResponse; if (c.text) { fullResponse += c.text; // Update the last message with the accumulated text setMessages((prev) => { const newMessages = [...prev]; const lastMsg = newMessages[newMessages.length - 1]; if (lastMsg.role === "model") { lastMsg.text = fullResponse; } return newMessages; }); } } } catch (error) { console.error("Chat error:", error); setMessages((prev) => [ ...prev, { role: "model", text: "I apologize, I'm having trouble connecting to the dunes right now. Please check your internet or contact us on WhatsApp." }, ]); } finally { setIsLoading(false); setIsTyping(false); } }; return (
{/* Toggle Button */} {/* Chat Window */}
🤖

Safari Assistant

Online
{messages.map((msg, idx) => (
{/* Minimal markdown-like parsing for bold text */} {msg.text.split(/(\*\*.*?\*\*)/).map((part, i) => part.startsWith('**') && part.endsWith('**') ? {part.slice(2, -2)} : part )}
))} {isTyping && isLoading && messages[messages.length - 1].text === "" && (
)}
setInput(e.target.value)} placeholder="Ask about prices, timings..." disabled={isLoading} />
); }; const rootElement = document.getElementById("ai-chat-root"); if (rootElement) { const root = createRoot(rootElement); root.render(); }