From fae796cd85915afb04a1babdd95d336f14453570 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Mon, 13 Apr 2026 21:04:09 +0545 Subject: [PATCH] feat: Better mix panel analytics --- .../components/ImageEditor.tsx | 6 +- .../components/PresentationHeader.tsx | 4 +- .../upload/components/UploadPage.tsx | 110 +++++++++++++++++- .../components/OnBoarding/PresentonMode.tsx | 66 ++++++++--- electron/servers/nextjs/utils/mixpanel.ts | 9 +- 5 files changed, 163 insertions(+), 32 deletions(-) diff --git a/electron/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx b/electron/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx index 29aee4e6..341e6ecf 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx @@ -335,7 +335,7 @@ const ImageEditor = ({

{promptContent}

-
+

{isStockImageProvider ? "Image Keyword" : "Image Description"}

@@ -347,7 +347,7 @@ const ImageEditor = ({ } value={prompt} onChange={(e) => setPrompt(e.target.value)} - className="min-h-[100px]" + className="min-h-[100px] " />
@@ -444,7 +444,7 @@ const ImageEditor = ({
- pdf-maker - +
{isPresentationSaving &&
diff --git a/electron/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx b/electron/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx index 4664fca5..1caff074 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx @@ -29,8 +29,15 @@ import { ConfigurationSelects } from "./ConfigurationSelects"; import { RootState } from "@/store/store"; import { ImagesApi } from "../../services/api/images"; import CurrentConfig from "./CurrentConfig"; +import { LLMConfig } from "@/types/llm_config"; const STOCK_IMAGE_PROVIDERS = new Set(["pexels", "pixabay"]); +const FILE_TYPE_WORD = new Set([".doc", ".docx", ".docm", ".odt", ".rtf"]); +const FILE_TYPE_PRESENTATION = new Set([".ppt", ".pptx", ".pptm", ".odp"]); +const FILE_TYPE_SPREADSHEET = new Set([".xls", ".xlsx", ".xlsm", ".ods", ".csv", ".tsv"]); +const FILE_TYPE_IMAGE = new Set([".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".webp", ".svg"]); +const FILE_TYPE_PDF = new Set([".pdf"]); +const FILE_TYPE_TEXT = new Set([".txt"]); // Types for loading state interface LoadingState { @@ -41,6 +48,50 @@ interface LoadingState { extra_info?: string; } +const getFileExtension = (fileName: string): string => { + const index = fileName.lastIndexOf("."); + if (index < 0) return ""; + return fileName.slice(index).toLowerCase(); +}; + +const getFileCategory = (file: File): string => { + const extension = getFileExtension(file.name || ""); + if (FILE_TYPE_WORD.has(extension)) return "word"; + if (FILE_TYPE_PRESENTATION.has(extension)) return "presentation"; + if (FILE_TYPE_SPREADSHEET.has(extension)) return "spreadsheet"; + if (FILE_TYPE_IMAGE.has(extension) || (file.type || "").startsWith("image/")) return "image"; + if (FILE_TYPE_PDF.has(extension) || file.type === "application/pdf") return "pdf"; + if (FILE_TYPE_TEXT.has(extension) || file.type === "text/plain") return "text"; + return "other"; +}; + +const getSelectedTextModel = (config?: LLMConfig): string => { + if (!config) return ""; + switch (config.LLM) { + case "openai": + return config.OPENAI_MODEL || ""; + case "google": + return config.GOOGLE_MODEL || ""; + case "anthropic": + return config.ANTHROPIC_MODEL || ""; + case "ollama": + return config.OLLAMA_MODEL || ""; + case "custom": + return config.CUSTOM_MODEL || ""; + case "codex": + return config.CODEX_MODEL || ""; + default: + return ""; + } +}; + +const getSelectedImageQuality = (config?: LLMConfig): string => { + if (!config) return ""; + if (config.IMAGE_PROVIDER === "dall-e-3") return config.DALL_E_3_QUALITY || ""; + if (config.IMAGE_PROVIDER === "gpt-image-1.5") return config.GPT_IMAGE_1_5_QUALITY || ""; + return ""; +}; + const UploadPage = () => { const router = useRouter(); const pathname = usePathname(); @@ -68,6 +119,41 @@ const UploadPage = () => { extra_info: "", }); + const getUploadSnapshotProps = () => { + const trimmedPrompt = config.prompt.trim(); + const trimmedInstructions = (config.instructions || "").trim(); + const attachmentCategories = Array.from(new Set(files.map(getFileCategory))).sort(); + const imageGenerationEnabled = !llmConfig?.DISABLE_IMAGE_GENERATION; + const parsedSlides = + config.slides && /^\d+$/.test(config.slides) ? Number(config.slides) : null; + + return { + pathname, + generation_path: files.length > 0 ? "documents" : "prompt_only", + slides_selected: parsedSlides, + slides_mode: config.slides ? "selected" : "auto", + language: config.language || "", + tone: config.tone, + verbosity: config.verbosity, + include_table_of_contents: !!config.includeTableOfContents, + include_title_slide: !!config.includeTitleSlide, + web_search: !!config.webSearch, + has_prompt: Boolean(trimmedPrompt), + prompt_char_count: trimmedPrompt.length, + prompt_word_count: trimmedPrompt ? trimmedPrompt.split(/\s+/).filter(Boolean).length : 0, + has_instructions: Boolean(trimmedInstructions), + instructions_char_count: trimmedInstructions.length, + has_attachments: files.length > 0, + attachments_count: files.length, + attachment_categories: attachmentCategories.join(","), + text_provider: llmConfig?.LLM || "", + text_model: getSelectedTextModel(llmConfig), + image_generation_enabled: imageGenerationEnabled, + image_provider: imageGenerationEnabled ? (llmConfig?.IMAGE_PROVIDER || "") : "disabled", + image_quality: imageGenerationEnabled ? getSelectedImageQuality(llmConfig) : "", + }; + }; + const handleConfigChange = (key: keyof PresentationConfig, value: unknown) => { setConfig((prev) => ({ ...prev, [key]: value } as PresentationConfig)); }; @@ -108,16 +194,28 @@ const UploadPage = () => { */ const validateConfiguration = (): boolean => { if (!config.language) { + trackEvent(MixpanelEvent.Upload_Validation_Failed, { + ...getUploadSnapshotProps(), + reason: "language_missing", + }); toast.error("Please select language"); return false; } if (files.length > 0 && config.language === LanguageType.Auto) { + trackEvent(MixpanelEvent.Upload_Validation_Failed, { + ...getUploadSnapshotProps(), + reason: "language_auto_with_documents", + }); toast.error("Please choose a language before processing uploaded documents"); return false; } if (!config.prompt.trim() && files.length === 0) { + trackEvent(MixpanelEvent.Upload_Validation_Failed, { + ...getUploadSnapshotProps(), + reason: "prompt_or_document_missing", + }); toast.error("No Prompt or Document Provided"); return false; } @@ -130,8 +228,16 @@ const UploadPage = () => { const handleGeneratePresentation = async () => { if (!validateConfiguration()) return; + trackEvent(MixpanelEvent.Upload_GetStarted_Button_Clicked, getUploadSnapshotProps()); + const isStockProviderReady = await ensureStockImageProviderReady(); - if (!isStockProviderReady) return; + if (!isStockProviderReady) { + trackEvent(MixpanelEvent.Upload_Validation_Failed, { + ...getUploadSnapshotProps(), + reason: "stock_image_provider_unreachable", + }); + return; + } try { const hasUploadedAssets = files.length > 0; @@ -293,4 +399,4 @@ const UploadPage = () => { ); }; -export default UploadPage; \ No newline at end of file +export default UploadPage; diff --git a/electron/servers/nextjs/components/OnBoarding/PresentonMode.tsx b/electron/servers/nextjs/components/OnBoarding/PresentonMode.tsx index 95a98ac5..f26fd80c 100644 --- a/electron/servers/nextjs/components/OnBoarding/PresentonMode.tsx +++ b/electron/servers/nextjs/components/OnBoarding/PresentonMode.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useState } from 'react' import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; import { Button } from '../ui/button'; -import { Check, CheckCircle, ChevronLeft, ChevronRight, ChevronUp, Download, Eye, EyeOff, Loader2 } from 'lucide-react'; +import { Check, CheckCircle, ChevronLeft, ChevronUp, Download, Eye, EyeOff, Loader2 } from 'lucide-react'; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '../ui/command'; import { DALLE_3_QUALITY_OPTIONS, GPT_IMAGE_1_5_QUALITY_OPTIONS, IMAGE_PROVIDERS, LLM_PROVIDERS } from '@/utils/providerConstants'; import { cn } from '@/lib/utils'; @@ -13,7 +13,7 @@ import ToolTip from '../ToolTip'; import { Switch } from '../ui/switch'; import { Select, SelectItem, SelectContent, SelectValue, SelectTrigger } from '../ui/select'; import { MixpanelEvent, trackEvent } from '@/utils/mixpanel'; -import { usePathname, useRouter } from 'next/navigation'; +import { usePathname } from 'next/navigation'; import { handleSaveLLMConfig } from '@/utils/storeHelpers'; import { checkIfSelectedOllamaModelIsPulled, pullOllamaModel } from '@/utils/providerUtils'; import { getApiUrl } from '@/utils/api'; @@ -21,7 +21,6 @@ import CodexConfig, { CHATGPT_MODELS } from '../CodexConfig'; const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep: (step: number) => void }) => { const pathname = usePathname(); - const router = useRouter(); const [openProviderSelect, setOpenProviderSelect] = useState(false); const [openImageProviderSelect, setOpenImageProviderSelect] = useState(false); const userConfigState = useSelector((state: RootState) => state.userConfig); @@ -45,7 +44,6 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep: } | null>(null); const handleProviderChange = (provider: string) => { - setLlmConfig(prev => ({ ...prev, LLM: provider @@ -104,12 +102,36 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep: const currentOllamaUrl = llmConfig.OLLAMA_URL || ''; const useCustomOllamaUrl = !!llmConfig.USE_CUSTOM_URL; + const getSelectedTextModel = (config: LLMConfig): string => { + switch (config.LLM) { + case 'openai': + return config.OPENAI_MODEL || ''; + case 'google': + return config.GOOGLE_MODEL || ''; + case 'anthropic': + return config.ANTHROPIC_MODEL || ''; + case 'ollama': + return config.OLLAMA_MODEL || ''; + case 'custom': + return config.CUSTOM_MODEL || ''; + case 'codex': + return config.CODEX_MODEL || ''; + default: + return ''; + } + }; + + const getSelectedImageQuality = (config: LLMConfig): string => { + if (config.IMAGE_PROVIDER === 'dall-e-3') return config.DALL_E_3_QUALITY || ''; + if (config.IMAGE_PROVIDER === 'gpt-image-1.5') return config.GPT_IMAGE_1_5_QUALITY || ''; + return ''; + }; + const fetchAvailableModels = async () => { if (llmConfig.LLM === 'openai' && !currentApiKey) return; if (llmConfig.LLM === 'google' && !currentApiKey) return; if (llmConfig.LLM === 'anthropic' && !currentApiKey) return; if (llmConfig.LLM === 'custom' && !llmConfig.CUSTOM_LLM_URL) return; - setModelsLoading(true); try { let response: Response; @@ -270,31 +292,39 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep: setShowDownloadModal(false); } }; - - const handleSaveConfig = async () => { - trackEvent(MixpanelEvent.Home_SaveConfiguration_Button_Clicked, { pathname }); try { setSavingConfig(true); - // 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(); } } + + const textProvider = llmConfig.LLM || ''; + const textModel = getSelectedTextModel(llmConfig); + const imageGenerationEnabled = !llmConfig.DISABLE_IMAGE_GENERATION; + const imageProvider = imageGenerationEnabled ? (llmConfig.IMAGE_PROVIDER || '') : 'disabled'; + + trackEvent(MixpanelEvent.Onboarding_Providers_Models_Selected, { + pathname, + text_provider: textProvider, + text_provider_label: LLM_PROVIDERS[textProvider]?.label || textProvider || '', + text_model: textModel, + uses_chatgpt_login: textProvider === 'codex', + image_generation_enabled: imageGenerationEnabled, + image_provider: imageProvider, + image_provider_label: imageGenerationEnabled + ? (IMAGE_PROVIDERS[imageProvider]?.label || imageProvider || '') + : 'Image generation disabled', + image_quality: imageGenerationEnabled ? getSelectedImageQuality(llmConfig) : '' + }); + toast.info("Configuration saved successfully"); - // Track navigation from -> to - trackEvent(MixpanelEvent.Navigation, { from: pathname, to: "/final onboarding step" }); setStep(3) // router.push("/upload"); } catch (error) { @@ -315,7 +345,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep: useEffect(() => { if (llmConfig.LLM === 'ollama' && !modelsChecked && !modelsLoading) { - fetchAvailableModels(); + void fetchAvailableModels(); } }, [llmConfig.LLM, modelsChecked, modelsLoading]); diff --git a/electron/servers/nextjs/utils/mixpanel.ts b/electron/servers/nextjs/utils/mixpanel.ts index ee456a6e..7acf42be 100644 --- a/electron/servers/nextjs/utils/mixpanel.ts +++ b/electron/servers/nextjs/utils/mixpanel.ts @@ -7,10 +7,7 @@ const MIXPANEL_TOKEN = '4ebfc788c739c72a9565c489a7cc2eac'; export enum MixpanelEvent { PageView = 'Page View', Navigation = 'Navigation', - Home_SaveConfiguration_Button_Clicked = 'Home Save Configuration Button Clicked', - Home_SaveConfiguration_API_Call = 'Home Save Configuration API Call', - Home_CheckOllamaModelPulled_API_Call = 'Home Check Ollama Model Pulled API Call', - Home_DownloadOllamaModel_API_Call = 'Home Download Ollama Model API Call', + Onboarding_Providers_Models_Selected = 'Onboarding Providers Models Selected', Codex_SignIn_API_Call = 'Codex Sign In API Call', Outline_Generate_Presentation_Button_Clicked = 'Outline Generate Presentation Button Clicked', Outline_Select_Template_Button_Clicked = 'Outline Select Template Button Clicked', @@ -39,6 +36,8 @@ export enum MixpanelEvent { Upload_Upload_Documents_API_Call = 'Upload Upload Documents API Call', Upload_Decompose_Documents_API_Call = 'Upload Decompose Documents API Call', Upload_Create_Presentation_API_Call = 'Upload Create Presentation API Call', + Upload_GetStarted_Button_Clicked = 'Upload Get Started Button Clicked', + Upload_Validation_Failed = 'Upload Validation Failed', DocumentsPreview_Create_Presentation_API_Call = 'Documents Preview Create Presentation API Call', DocumentsPreview_Next_Button_Clicked = 'Documents Preview Next Button Clicked', Settings_SaveConfiguration_Button_Clicked = 'Settings Save Configuration Button Clicked', @@ -209,5 +208,3 @@ export default { resetTelemetryCache, setTelemetryEnabled, }; - -