import { useRef, useState, useEffect } from "react"; import { supabase } from "./supabaseClient"; import ReactMarkdown from "react-markdown"; const getEnv = (key) => { if (typeof window !== "undefined" && window.__ENV__ && window.__ENV__[key]) { return window.__ENV__[key]; } return import.meta.env[key]; }; const API_BASE_URL = getEnv("VITE_API_BASE_URL") || "/api"; const Panel = ({ title, subtitle, children }) => (

{title}

{subtitle ?

{subtitle}

: null}
{children}
); const ProgressStrip = ({ statusText }) => (
    {statusText || "Waiting for run..."}
  
); export default function App() { const [view, setView] = useState("login"); // login | signup | otp | app const [emailDisplay, setEmailDisplay] = useState(""); const [session, setSession] = useState(null); const [status, setStatus] = useState(""); const [forceRefresh, setForceRefresh] = useState(false); const [urlValue, setUrlValue] = useState(""); const [jobResult, setJobResult] = useState(null); const [systemPrompt, setSystemPrompt] = useState(""); const [siteName, setSiteName] = useState("Bot"); const [progressValue, setProgressValue] = useState(0); const [progressText, setProgressText] = useState("Idle"); const [otpEmail, setOtpEmail] = useState(""); const [firstNameDisplay, setFirstNameDisplay] = useState(""); const [resetStatus, setResetStatus] = useState(""); const [resetEmail, setResetEmail] = useState(""); const [resetSent, setResetSent] = useState(false); const [resetOtpEntered, setResetOtpEntered] = useState(false); const [resetOtpValue, setResetOtpValue] = useState(""); const [isRunning, setIsRunning] = useState(false); const [isAuthLoading, setIsAuthLoading] = useState(false); const resetEmailRef = useRef(null); const resetOtpRef = useRef(null); const resetNewPassRef = useRef(null); const resetNewPassConfirmRef = useRef(null); const [signupPassword, setSignupPassword] = useState(""); const [summaryVisible, setSummaryVisible] = useState(false); const [summaryData, setSummaryData] = useState({ pages: 0, searches: 0 }); const [isSessionChecking, setIsSessionChecking] = useState(true); // [NEW] Loading state for initial session check // [NEW] Check for existing session on mount useEffect(() => { // 1. Get initial session supabase.auth.getSession().then(({ data: { session } }) => { if (session) { setSession(session); setEmailDisplay(session.user?.email || ""); const fn = session.user?.user_metadata?.first_name; setFirstNameDisplay(fn || (session.user?.email ? session.user.email.split("@")[0] : "")); setView("app"); setStatus("Restored session."); } setIsSessionChecking(false); }); // 2. Listen for changes (login, logout, auto-refresh) const { data: { subscription }, } = supabase.auth.onAuthStateChange((_event, session) => { setSession(session); if (session) { setEmailDisplay(session.user?.email || ""); const fn = session.user?.user_metadata?.first_name; setFirstNameDisplay(fn || (session.user?.email ? session.user.email.split("@")[0] : "")); // Only switch to app if we were in a login/signup flow to avoid disrupting user setView((prev) => (prev === "login" || prev === "signup" || prev === "otp" || prev === "reset" ? "app" : prev)); } else { // If logged out setView("login"); setEmailDisplay(""); setFirstNameDisplay(""); } }); return () => subscription.unsubscribe(); }, []); // Refs to avoid re-rendering while typing (prevents cursor jump/blur) const loginEmailRef = useRef(null); const loginPassRef = useRef(null); const signupFirstRef = useRef(null); const signupLastRef = useRef(null); const signupEmailRef = useRef(null); const signupPassRef = useRef(null); const otpEmailRef = useRef(null); const otpPassRef = useRef(null); const otpCodeRef = useRef(null); const urlInputRef = useRef(null); const handleSignup = async () => { const email = signupEmailRef.current?.value?.trim() || ""; const password = signupPassRef.current?.value || ""; const first = signupFirstRef.current?.value?.trim() || ""; const last = signupLastRef.current?.value?.trim() || ""; setStatus("Signing up..."); setSignupPassword(password); const { error } = await supabase.auth.signUp({ email, password, options: { data: { first_name: first, last_name: last } }, }); if (error) { setStatus(`Signup failed: ${error.message}`); } else { setFirstNameDisplay(first || ""); setStatus("Signup initiated. Check your email for OTP."); setOtpEmail(email); setView("otp"); } }; const handleVerifyOtp = async () => { const otp = otpCodeRef.current?.value?.trim() || ""; setStatus("Verifying OTP..."); const { error } = await supabase.auth.verifyOtp({ email: otpEmail, token: otp, type: "signup", }); if (error) { setStatus(`OTP failed: ${error.message}`); } else { const { data: loginData, error: loginError } = await supabase.auth.signInWithPassword({ email: otpEmail, password: signupPassRef.current?.value || signupPassword || "", }); if (loginError) { setStatus(`Verified; now log in. ${loginError.message}`); setView("login"); } else { setSession(loginData.session); setEmailDisplay(loginData.session?.user?.email || otpEmail); const fn = loginData.session?.user?.user_metadata?.first_name; setFirstNameDisplay(fn || firstNameDisplay || ""); setStatus("Account confirmed and logged in."); setView("app"); } } }; const handleLogin = async () => { if (isAuthLoading) return; setIsAuthLoading(true); const email = loginEmailRef.current?.value?.trim() || ""; const password = loginPassRef.current?.value || ""; setStatus("Logging in..."); const { data, error } = await supabase.auth.signInWithPassword({ email, password, }); if (error) { setStatus(`Login failed: ${error.message}`); setIsAuthLoading(false); } else { setView("app"); // jump to app immediately on success setSession(data.session); setEmailDisplay(data.session?.user?.email || email); const fn = data.session?.user?.user_metadata?.first_name; setFirstNameDisplay(fn || firstNameDisplay || (email ? email.split("@")[0] : "")); setStatus("Logged in."); setIsAuthLoading(false); } }; const handleLogout = async () => { await supabase.auth.signOut(); setSession(null); setJobResult(null); setSystemPrompt(""); setEmailDisplay(""); setChatMessages([]); if (chatInputRef.current) chatInputRef.current.value = ""; setProgressValue(0); setProgressText("Idle"); setStatus("Logged out."); setView("login"); }; const handleSendReset = async () => { const email = resetEmailRef.current?.value?.trim() || resetEmail || loginEmailRef.current?.value?.trim() || ""; if (!email) { setResetStatus("Enter an email to reset."); return; } setResetEmail(email); setResetOtpEntered(false); setResetOtpValue(""); setResetStatus("Sending reset OTP..."); const { error } = await supabase.auth.resetPasswordForEmail(email); if (error) setResetStatus(`Failed: ${error.message}`); else { setResetStatus("Reset OTP sent. Check your email."); setResetSent(true); } }; const handleVerifyResetOtp = () => { const otp = resetOtpRef.current?.value?.trim() || ""; if (!otp) { setResetStatus("Enter the OTP you received."); return; } setResetOtpValue(otp); setResetOtpEntered(true); setResetStatus("OTP captured. Enter new password."); }; const handleConfirmReset = async () => { const email = resetEmailRef.current?.value?.trim() || resetEmail || loginEmailRef.current?.value?.trim() || ""; const otp = resetOtpValue; const newPass = resetNewPassRef.current?.value || ""; const newPassConfirm = resetNewPassConfirmRef.current?.value || ""; if (!email || !otp || !newPass) { setResetStatus("Enter OTP and new password."); return; } if (newPass !== newPassConfirm) { setResetStatus("New password and confirm password do not match."); return; } setResetEmail(email); setResetStatus("Resetting password..."); const { data: verifyData, error: verifyError } = await supabase.auth.verifyOtp({ email, token: otp, type: "recovery", }); if (verifyError) { setResetStatus(`Failed: ${verifyError.message}`); return; } // After OTP verification, update the password const { error: updateError } = await supabase.auth.updateUser({ password: newPass, }); if (updateError) { setResetStatus(`Failed: ${updateError.message}`); return; } setResetStatus("Password reset. You can log in now."); setResetSent(false); setResetOtpEntered(false); setResetOtpValue(""); if (resetNewPassRef.current) resetNewPassRef.current.value = ""; if (resetNewPassConfirmRef.current) resetNewPassConfirmRef.current.value = ""; setView("login"); }; const runJob = async () => { const targetUrl = (urlInputRef.current?.value || "").trim(); if (!targetUrl) { setStatus("Please enter a URL."); return; } setIsRunning(true); setStatus("Submitting job..."); setJobResult(null); setSystemPrompt(""); setChatMessages([]); if (chatInputRef.current) chatInputRef.current.value = ""; setProgressValue(10); setProgressText("Starting..."); setSummaryVisible(false); try { const resp = await fetch(`${API_BASE_URL}/jobs/run`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url: targetUrl, force_refresh: forceRefresh, user_id: session?.user?.id }), }); if (!resp.ok) { const msg = await resp.text(); throw new Error(msg || `HTTP ${resp.status}`); } const json = await resp.json(); setJobResult(json); const statusText = json?.stats?.status_text || "Completed."; setStatus("Job completed."); setSystemPrompt(json?.stats?.system_prompt || ""); setSiteName(json?.stats?.name || "Bot"); setJobResult((prev) => ({ ...prev, status_text: statusText })); setProgressText(statusText); const match = statusText.match(/Progress:\s*([0-9]{1,3})%/i); setProgressValue(match ? Math.min(100, Math.max(0, parseInt(match[1], 10))) : 100); setSummaryData({ pages: json?.stats?.pages_scraped ?? 0, searches: json?.stats?.searches_run ?? 0, }); setSummaryVisible(true); setTimeout(() => setSummaryVisible(false), 5000); } catch (err) { console.error("Job failed", err); setStatus(`Job failed: ${err.message} | API: ${API_BASE_URL}`); setProgressText("Failed"); setProgressValue(0); } finally { setIsRunning(false); } }; const renderHeader = () => (

ChatSmith

{firstNameDisplay ? `Welcome, ${firstNameDisplay}` : emailDisplay ? `Welcome, ${emailDisplay.split("@")[0]}` : "Welcome"}

AI-powered chatbot generator for any website.

{status}
); const renderLoginCard = () => (

setView("signup")}> Don’t have an account? Sign up

setView("reset")}> Forgot password?
); const renderSignupCard = () => (

setView("login")}> Back to login

); const renderResetCard = () => ( {!resetSent && ( <> )} {resetSent && !resetOtpEntered && ( <> )} {resetSent && resetOtpEntered && ( <>
OTP captured. Enter new password.
)}
{resetStatus}

setView("login")}> Back to login

); const renderOtpCard = () => (
OTP sent to: {otpEmail || "your email"}

setView("login")}> Back to login

); const renderAppCards = () => (
setUrlValue(e.target.value)} />

Paste a URL and generate a chatbot instantly. Scrape → gap detection → targeted search → knowledge base.

{systemPrompt && ( <>
{summaryVisible ? (

Summary

Pages scraped: {summaryData.pages}

Web searches: {summaryData.searches}

) : ( <>
Chatbot: {siteName}
{chatMessages.length === 0 &&
Ask anything about the scraped site.
} {chatMessages.map((m, idx) => (
{m.role === "user" ? "You" : siteName}:
{m.content}
))}