"use client"; import { useState, useEffect, useMemo } from "react"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { Loader2, Download, CheckCircle } from "lucide-react"; import { useSelector } from "react-redux"; import { RootState } from "@/store/store"; import { handleSaveLLMConfig } from "@/utils/storeHelpers"; import LLMProviderSelection from "./LLMSelection"; import { checkIfSelectedOllamaModelIsPulled, pullOllamaModel, } from "@/utils/providerUtils"; import { LLMConfig } from "@/types/llm_config"; import { trackEvent, MixpanelEvent } from "@/utils/mixpanel"; import { usePathname } from "next/navigation"; import OnBoardingSlidebar from "./OnBoarding/OnBoardingSlidebar"; import OnBoardingHeader from "./OnBoarding/OnBoardingHeader"; import ModeSelectStep from "./OnBoarding/ModeSelectStep"; import PresentonMode from "./OnBoarding/PresentonMode"; import GenerationWithImage from "./OnBoarding/GenerationWithImage"; import FinalStep from "./OnBoarding/FinalStep"; // Button state interface interface ButtonState { isLoading: boolean; isDisabled: boolean; text: string; showProgress: boolean; progressPercentage?: number; status?: string; } const FINAL_STEP_CONFETTI_PIECES = [ // left: denser at top { side: "left", offset: 1, top: 3, width: 28, height: 10, color: "#F59E0B", rotate: 12 }, { side: "left", offset: 7, top: 5, width: 18, height: 7, color: "#7C3AED", rotate: -10 }, { side: "left", offset: 12, top: 7, width: 20, height: 7, color: "#14B8A6", rotate: 22 }, { side: "left", offset: 3, top: 10, width: 22, height: 8, color: "#22C55E", rotate: -18 }, { side: "left", offset: 9, top: 12, width: 24, height: 8, color: "#E11D48", rotate: 18 }, { side: "left", offset: 14, top: 15, width: 18, height: 7, color: "#F43F5E", rotate: 23 }, { side: "left", offset: 5, top: 18, width: 20, height: 7, color: "#0EA5E9", rotate: -12 }, { side: "left", offset: 11, top: 21, width: 26, height: 9, color: "#2563EB", rotate: 20 }, { side: "left", offset: 2, top: 24, width: 19, height: 7, color: "#14B8A6", rotate: -16 }, { side: "left", offset: 8, top: 28, width: 21, height: 8, color: "#FB7185", rotate: 27 }, { side: "left", offset: 13, top: 32, width: 20, height: 7, color: "#06B6D4", rotate: 16 }, { side: "left", offset: 3, top: 36, width: 24, height: 9, color: "#EAB308", rotate: -22 }, { side: "left", offset: 10, top: 41, width: 18, height: 7, color: "#A855F7", rotate: -14 }, { side: "left", offset: 2, top: 50, width: 30, height: 10, color: "#EC4899", rotate: -28 }, { side: "left", offset: 13, top: 58, width: 19, height: 7, color: "#22C55E", rotate: 17 }, { side: "left", offset: 5, top: 66, width: 24, height: 8, color: "#8B5CF6", rotate: 14 }, { side: "left", offset: 11, top: 74, width: 18, height: 7, color: "#3B82F6", rotate: 12 }, { side: "left", offset: 4, top: 82, width: 20, height: 7, color: "#14B8A6", rotate: 21 }, { side: "left", offset: 7, top: 90, width: 24, height: 8, color: "#D946EF", rotate: -26 }, // right: denser at top { side: "right", offset: 1, top: 4, width: 30, height: 10, color: "#F97316", rotate: -14 }, { side: "right", offset: 8, top: 6, width: 19, height: 7, color: "#0EA5E9", rotate: 12 }, { side: "right", offset: 13, top: 9, width: 20, height: 7, color: "#22C55E", rotate: -20 }, { side: "right", offset: 4, top: 12, width: 24, height: 8, color: "#EC4899", rotate: 20 }, { side: "right", offset: 10, top: 15, width: 22, height: 8, color: "#06B6D4", rotate: -18 }, { side: "right", offset: 15, top: 18, width: 20, height: 7, color: "#22C55E", rotate: -25 }, { side: "right", offset: 5, top: 21, width: 18, height: 7, color: "#8B5CF6", rotate: 19 }, { side: "right", offset: 12, top: 24, width: 21, height: 8, color: "#F43F5E", rotate: 14 }, { side: "right", offset: 2, top: 28, width: 26, height: 9, color: "#84CC16", rotate: 15 }, { side: "right", offset: 9, top: 33, width: 21, height: 8, color: "#F97316", rotate: -11 }, { side: "right", offset: 14, top: 38, width: 20, height: 7, color: "#A855F7", rotate: -19 }, { side: "right", offset: 4, top: 44, width: 19, height: 7, color: "#F43F5E", rotate: 20 }, { side: "right", offset: 2, top: 52, width: 28, height: 10, color: "#FACC15", rotate: 25 }, { side: "right", offset: 12, top: 60, width: 18, height: 7, color: "#14B8A6", rotate: -15 }, { side: "right", offset: 6, top: 68, width: 24, height: 8, color: "#22C55E", rotate: -17 }, { side: "right", offset: 1, top: 76, width: 20, height: 7, color: "#A855F7", rotate: 14 }, { side: "right", offset: 13, top: 84, width: 20, height: 7, color: "#3B82F6", rotate: -24 }, { side: "right", offset: 5, top: 92, width: 26, height: 9, color: "#EAB308", rotate: 18 }, ] as const; const getTaperedSideOffset = (offset: number, top: number) => { const taperMultiplier = Math.max(0.72, 1.85 - top * 0.012); return Math.min(29, Number((offset * taperMultiplier).toFixed(2))); }; export default function Home() { const router = useRouter(); const pathname = usePathname(); const [step, setStep] = useState(1) const [selectedMode, setSelectedMode] = useState("presenton") const config = useSelector((state: RootState) => state.userConfig); const [llmConfig, setLlmConfig] = useState(config.llm_config); const [downloadingModel, setDownloadingModel] = useState<{ name: string; size: number | null; downloaded: number | null; status: string; done: boolean; } | null>(null); const [showDownloadModal, setShowDownloadModal] = useState(false); const [buttonState, setButtonState] = useState({ isLoading: false, isDisabled: false, text: "Save Configuration", showProgress: false }); const canChangeKeys = config.can_change_keys; const downloadProgress = useMemo(() => { if (downloadingModel && downloadingModel.downloaded !== null && downloadingModel.size !== null) { return Math.round((downloadingModel.downloaded / downloadingModel.size) * 100); } return 0; }, [downloadingModel?.downloaded, downloadingModel?.size]); const handleSaveConfig = async () => { trackEvent(MixpanelEvent.Home_SaveConfiguration_Button_Clicked, { pathname }); try { setButtonState(prev => ({ ...prev, isLoading: true, isDisabled: true, text: "Saving Configuration..." })); // API: save config trackEvent(MixpanelEvent.Home_SaveConfiguration_API_Call); // API CALL: save config await handleSaveLLMConfig(llmConfig); if (llmConfig.LLM === "ollama" && llmConfig.OLLAMA_MODEL) { // API: check model pulled trackEvent(MixpanelEvent.Home_CheckOllamaModelPulled_API_Call); const isPulled = await checkIfSelectedOllamaModelIsPulled(llmConfig.OLLAMA_MODEL); if (!isPulled) { setShowDownloadModal(true); // API: download model trackEvent(MixpanelEvent.Home_DownloadOllamaModel_API_Call); await handleModelDownload(); } } toast.info("Configuration saved successfully"); setButtonState(prev => ({ ...prev, isLoading: false, isDisabled: false, text: "Save Configuration" })); // Track navigation from -> to trackEvent(MixpanelEvent.Navigation, { from: pathname, to: "/upload" }); router.push("/upload"); } catch (error) { toast.info(error instanceof Error ? error.message : "Failed to save configuration"); setButtonState(prev => ({ ...prev, isLoading: false, isDisabled: false, text: "Save Configuration" })); } }; const handleModelDownload = async () => { try { await pullOllamaModel(llmConfig.OLLAMA_MODEL!, setDownloadingModel); } finally { setDownloadingModel(null); setShowDownloadModal(false); } }; useEffect(() => { if (downloadingModel && downloadingModel.downloaded !== null && downloadingModel.size !== null) { const percentage = Math.round(((downloadingModel.downloaded / downloadingModel.size) * 100)); setButtonState({ isLoading: true, isDisabled: true, text: `Downloading Model (${percentage}%)`, showProgress: true, progressPercentage: percentage, status: downloadingModel.status }); } if (downloadingModel && downloadingModel.done) { setTimeout(() => { setShowDownloadModal(false); setDownloadingModel(null); toast.info("Model downloaded successfully!"); }, 2000); } }, [downloadingModel]); useEffect(() => { if (!canChangeKeys) { router.push("/upload"); } }, [canChangeKeys, router]); if (!canChangeKeys) { return null; } return ( //
//
// {/* Branding Header */} //
//
// Presenton Logo //
//

// Open-source AI presentation generator //

//
// {/* Main Configuration Card */} //
// //
//
// {/* Download Progress Modal */} // {showDownloadModal && downloadingModel && ( //
//
// {/* Modal Content */} //
// {/* Icon */} //
// {downloadingModel.done ? ( // // ) : ( // // )} //
// {/* Title */} //

// {downloadingModel.done ? "Download Complete!" : "Downloading Model"} //

// {/* Model Name */} //

// {llmConfig.OLLAMA_MODEL} //

// {/* Progress Bar */} // {downloadProgress > 0 && ( //
//
//
//
//

// {downloadProgress}% Complete //

//
// )} // {/* Status */} // {downloadingModel.status && ( //
// // // {downloadingModel.status} // //
// )} // {/* Status Message */} // {downloadingModel.status && downloadingModel.status !== "pulled" && ( //
// {downloadingModel.status === "downloading" && "Downloading model files..."} // {downloadingModel.status === "verifying" && "Verifying model integrity..."} // {downloadingModel.status === "pulling" && "Pulling model from registry..."} //
// )} // {/* Download Info */} // {downloadingModel.downloaded && downloadingModel.size && ( //
//
// Downloaded: {(downloadingModel.downloaded / 1024 / 1024).toFixed(1)} MB // Total: {(downloadingModel.size / 1024 / 1024).toFixed(1)} MB //
//
// )} //
//
//
// )} // {/* Fixed Bottom Button */} //
//
// //
//
//
{step === 3 && (
{FINAL_STEP_CONFETTI_PIECES.map((piece, index) => ( ))}
)} {step === 1 && } {step === 2 && selectedMode === "presenton" && } {step === 2 && selectedMode === "image" && } {step === 3 && }
); }