feat: Better mix panel analytics
This commit is contained in:
parent
bbb3c835e5
commit
fae796cd85
5 changed files with 163 additions and 32 deletions
|
|
@ -335,7 +335,7 @@ const ImageEditor = ({
|
|||
<p className="text-sm text-gray-500">{promptContent}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="mx-0.5">
|
||||
<h3 className="text-base font-medium mb-2">
|
||||
{isStockImageProvider ? "Image Keyword" : "Image Description"}
|
||||
</h3>
|
||||
|
|
@ -347,7 +347,7 @@ const ImageEditor = ({
|
|||
}
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
className="min-h-[100px]"
|
||||
className="min-h-[100px] "
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -444,7 +444,7 @@ const ImageEditor = ({
|
|||
<div className="space-y-4">
|
||||
<div
|
||||
className={cn(
|
||||
"border-2 border-dashed rounded-lg p-8 text-center transition-colors",
|
||||
"border-2 border-dashed rounded-lg p-8 text-center transition-colors",
|
||||
isUploading
|
||||
? "border-gray-400 bg-gray-50"
|
||||
: "border-gray-300 hover:border-blue-400"
|
||||
|
|
|
|||
|
|
@ -389,9 +389,7 @@ const PresentationHeader = ({
|
|||
) : (
|
||||
titleBlock
|
||||
)}
|
||||
<a href={`/pdf-maker?id=${presentation_id}`}>
|
||||
pdf-maker
|
||||
</a>
|
||||
|
||||
<div className="flex items-center gap-2.5">
|
||||
|
||||
{isPresentationSaving && <div className="flex items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
export default UploadPage;
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue