/* global __app_id, __firebase_config, __initial_auth_token */ import React, { useState, useEffect, useRef } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, collection, addDoc, query, orderBy, onSnapshot, serverTimestamp } from 'firebase/firestore'; // Global variables provided by the Canvas environment for Firebase setup. // These variables are populated automatically at runtime by the platform. function App() { // State to store chat messages. Each message object will include text, sender, and timestamp. const [messages, setMessages] = useState([]); // State to hold the current message being typed by the user. const [newMessage, setNewMessage] = useState(''); // State to indicate if the bot is currently processing a response. const [isLoading, setIsLoading] = useState(false); // State to store the current user's ID for chat history. const [userId, setUserId] = useState(null); // State to hold the Firebase Firestore database instance. const [db, setDb] = useState(null); // State to hold the Firebase Auth instance. const [auth, setAuth] = useState(null); // State to track if Firebase authentication has been initialized and ready. const [isAuthReady, setIsAuthReady] = useState(false); // State to store any error messages for display. const [error, setError] = useState(null); // Ref to automatically scroll to the latest message in the chat. const messagesEndRef = useRef(null); // useEffect hook for Firebase initialization and authentication. // This runs once when the component mounts. useEffect(() => { try { // Get the application ID from the global variable, or use a default. const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; // Parse the Firebase configuration from the global variable. const firebaseConfig = JSON.parse(typeof __firebase_config !== 'undefined' ? __firebase_config : '{}'); // Log Firebase config for debugging, but don't expose sensitive info directly. // console.log("Firebase Config:", firebaseConfig); // Check if firebase config is valid. if (Object.keys(firebaseConfig).length === 0) { console.error("Firebase config is empty. Cannot initialize Firebase."); setError("Firebase setup incomplete. Please ensure your Firebase project is configured correctly in the Canvas environment."); return; } // Initialize Firebase app with the provided configuration. const app = initializeApp(firebaseConfig); // Get Firestore and Auth instances. const firestoreDb = getFirestore(app); const firebaseAuth = getAuth(app); // Set the Firebase instances in state. setDb(firestoreDb); setAuth(firebaseAuth); // Set up an authentication state change listener. // This ensures that the user is signed in before attempting Firestore operations. const unsubscribeAuth = onAuthStateChanged(firebaseAuth, async (user) => { if (user) { // If a user is already signed in, set their UID. setUserId(user.uid); setIsAuthReady(true); } else { try { // If no user is signed in, try to sign in with a custom token // provided by the environment, or anonymously if no token exists. if (typeof __initial_auth_token !== 'undefined') { await signInWithCustomToken(firebaseAuth, __initial_auth_token); } else { await signInAnonymously(firebaseAuth); } } catch (authError) { // Log and display authentication errors. console.error("Firebase Auth Error:", authError); setError(`Authentication failed: ${authError.message}. Please reload.`); } setIsAuthReady(true); // Mark auth as ready even if anonymous or error occurred } }); // Cleanup function: unsubscribe from the auth listener when the component unmounts. return () => unsubscribeAuth(); } catch (initError) { // Catch and display errors during Firebase initialization. console.error("Error initializing Firebase:", initError); setError(`Failed to initialize application: ${initError.message}. This might be due to incorrect Firebase configuration.`); } }, []); // Empty dependency array means this effect runs only once on mount. // useEffect hook for real-time message fetching from Firestore. // This runs whenever `db` or `isAuthReady` state changes, ensuring Firebase is ready. useEffect(() => { // Proceed only if Firestore database and authentication are ready. if (db && isAuthReady) { // Construct the Firestore collection path for public chat data. const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; // Re-declare appId for scope const chatCollectionRef = collection(db, `artifacts/${appId}/public/data/chats`); // Create a query to order messages by timestamp. // Note: For large datasets or complex queries, Firestore may require composite indexes. // For this simple chat, 'timestamp' ordering should work without explicit index creation. const q = query(chatCollectionRef, orderBy('timestamp')); // Set up a real-time listener (onSnapshot) to get updates whenever messages change. const unsubscribeSnapshot = onSnapshot(q, (snapshot) => { // Map the document snapshots to an array of message objects. const fetchedMessages = snapshot.docs.map(doc => ({ id: doc.id, // Document ID as key ...doc.data() // All other fields from the document })); // Update the messages state with the fetched messages. setMessages(fetchedMessages); }, (snapshotError) => { // Handle errors during real-time fetching. console.error("Error fetching messages:", snapshotError); setError(`Failed to load messages from Firestore: ${snapshotError.message}. Check your database rules.`); }); // Cleanup function: unsubscribe from the snapshot listener when the component unmounts // or when `db` or `isAuthReady` changes (causing re-run of this effect). return () => unsubscribeSnapshot(); } }, [db, isAuthReady]); // Dependencies: runs when 'db' or 'isAuthReady' changes. // useEffect hook to scroll to the bottom of the chat window whenever messages change. useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); // Dependency: runs whenever 'messages' state updates. // Function to handle sending a new message and triggering bot response. const handleSendMessage = async () => { // Prevent sending empty messages or if Firebase is not ready. if (!newMessage.trim() || !db || !userId) return; setIsLoading(true); // Set loading state to true. setError(null); // Clear any previous errors. // --- Step 1: Add the user's message to Firestore --- try { const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; // Re-declare appId for scope await addDoc(collection(db, `artifacts/${appId}/public/data/chats`), { userId: userId, // User ID of the sender. text: newMessage, // The message text. timestamp: serverTimestamp(), // Firestore's server-side timestamp for accurate ordering. sender: 'user', // Mark the sender as 'user'. }); setNewMessage(''); // Clear the input field after sending. } catch (e) { // Log and display any errors during adding the user message. console.error("Error adding user message:", e); setError(`Failed to send message to database: ${e.message}`); setIsLoading(false); // Stop loading. return; // Stop execution if user message failed to send. } // --- Step 2: Generate bot response using Gemini API --- try { // Prepare chat history for the Gemini API call. const chatHistory = [{ role: "user", parts: [{ text: newMessage }] }]; const payload = { contents: chatHistory }; const apiKey = ""; // API key is automatically provided by Canvas runtime if empty. const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`; // Make a POST request to the Gemini API. const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); // Check for HTTP errors from the API. if (!response.ok) { const errorData = await response.json(); throw new Error(`API Error: ${response.status} ${response.statusText} - ${errorData.error?.message || 'Unknown error'}`); } // Parse the JSON response from the API. const result = await response.json(); let botResponseText = "Sorry, I couldn't generate a response. Please try again."; // Extract the bot's response text from the API result. if (result.candidates && result.candidates.length > 0 && result.candidates[0].content && result.candidates[0].content.parts && result.candidates[0].content.parts.length > 0) { botResponseText = result.candidates[0].content.parts[0].text; } else { console.warn("Unexpected API response structure:", result); } // --- Step 3: Add the bot's response message to Firestore --- const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; // Re-declare appId for scope await addDoc(collection(db, `artifacts/${appId}/public/data/chats`), { userId: 'bot', // Mark the sender as 'bot'. text: botResponseText, // The bot's generated response. timestamp: serverTimestamp(), // Server-side timestamp. sender: 'bot', // Mark the sender as 'bot'. }); } catch (apiError) { // Catch and display any errors during API call or adding bot message. console.error("Error generating bot response:", apiError); setError(`Bot response failed: ${apiError.message}. Check your internet or API key.`); } finally { setIsLoading(false); // Always stop loading after the process completes or fails. } }; return ( // Main container for the chat application, styled with Tailwind CSS for responsiveness. // Changed background to a blue gradient reminiscent of the sea/sky.
{/* Tailwind CSS and Inter font import for consistent styling */} {/* Main chat box container - changed shadow and added a subtle border */}
{/* Chat header - changed gradient to a warm orange/red/yellow theme like a sunset/straw hat */}

HuggingChat Grand Line

{/* SVG for a simple straw hat icon, inline for easy integration */} {userId && (

Your Pirate Crew ID: {userId}

)}
{/* Message display area */}
{/* Changed background for contrast */} {messages.length === 0 && !isLoading && !error ? ( // Display a welcome message if no messages and not loading/error.
Ahoy, matey! Start your adventure chat!
) : ( // Map through messages and display them. messages.map((msg) => (

{msg.text}

{/* Display message timestamp, converting Firebase timestamp to Date if available. */} {msg.timestamp?.toDate ? msg.timestamp.toDate().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : 'Setting Sail...'}
)) )} {/* Loading indicator for bot typing */} {isLoading && (
{/* Typing animation */}

Bot is charting a course...

)} {/* Error message display */} {error && (

Bounty on your head!

{error}

If this persists, please ensure your Firebase project details are correctly configured within the Hugging Face Spaces environment, and check your internet connection.

)} {/* Empty div to enable scrolling to the bottom */}
{/* Message input area */}
setNewMessage(e.target.value)} // Allow sending message with Enter key. onKeyPress={(e) => { if (e.key === 'Enter' && !isLoading) { handleSendMessage(); } }} // Disable input while loading or if Firebase is not ready. disabled={isLoading || !isAuthReady || !db} />
); } export default App;