declare global { interface Window { posthog?: { capture?: ( event: string, properties?: Record, ) => void; }; umami?: { track: ( event: string, data?: Record, ) => void; }; } } let vercelTrackPromise: | Promise< | (( event: string, properties?: Record, ) => void) | null > | undefined; const getVercelTrack = () => { if (vercelTrackPromise) return vercelTrackPromise; vercelTrackPromise = import("@vercel/analytics") .then(({ track }) => track) .catch(() => null); return vercelTrackPromise; }; export type AnalyticsProperties = Record; const trackEvent = (event: string, properties?: AnalyticsProperties) => { if (typeof window === "undefined") return; // PostHog window.posthog?.capture?.(event, properties); // Vercel Analytics void getVercelTrack().then((track) => track?.(event, properties)); // Umami window.umami?.track?.(event, properties); }; export const analytics = { cta: { clicked: ( cta: "get_started" | "contact_sales" | "why_us", location: string, ) => trackEvent("cta_clicked", { cta, location }), npmCommandCopied: ( command = "npx assistant-ui init", properties?: AnalyticsProperties, ) => trackEvent("npm_command_copied", { ...properties, command }), promptCopied: (properties?: AnalyticsProperties) => trackEvent("prompt_copied", properties), }, outbound: { linkClicked: ( href: string, label: string, properties?: Record, ) => trackEvent("outbound_link_clicked", { ...properties, href, label }), }, search: { opened: (source: "header" | "sidebar" | "keyboard") => trackEvent("search_opened", { source }), querySubmitted: (query: string, resultsCount: number) => trackEvent("search_query_submitted", { query, results_count: resultsCount, }), resultClicked: (query: string, url: string, position: number) => trackEvent("search_result_clicked", { query, url, position }), noResults: (query: string) => trackEvent("search_no_results", { query }), askAITriggered: (query: string) => trackEvent("search_ask_ai_triggered", { query }), }, code: { blockCopied: (language: string, source: string) => trackEvent("code_block_copied", { language, source }), }, example: { tabSwitched: (example: string) => trackEvent("example_tab_switched", { example }), }, docs: { navigationClicked: (pageName: string, pageUrl: string, depth: number) => trackEvent("doc_navigation_clicked", { page_name: pageName, page_url: pageUrl, depth, }), folderToggled: (folderName: string, isOpen: boolean, depth: number) => trackEvent("doc_folder_toggled", { folder_name: folderName, is_open: isOpen, depth, }), }, builder: { presetSelected: (preset: string) => trackEvent("builder_preset_selected", { preset }), createDialogOpened: () => trackEvent("builder_create_dialog_opened"), commandCopied: ( commandType: "create" | "shadcn" | "manual_init" | "manual_add", ) => trackEvent("builder_command_copied", { command_type: commandType }), codeCopied: () => trackEvent("builder_code_copied"), shareClicked: () => trackEvent("builder_share_clicked"), }, toc: { linkClicked: (headingTitle: string, headingDepth: number) => trackEvent("toc_link_clicked", { heading_title: headingTitle, heading_depth: headingDepth, }), actionClicked: (action: "copy" | "markdown" | "github" | "ask_ai") => trackEvent("toc_action_clicked", { action }), }, install: { packageManagerSelected: (pm: string) => trackEvent("package_manager_selected", { package_manager: pm }), }, assistant: { feedbackShown: (props: { threadId: string; messageId: string; user_question_length: number; assistant_response_length: number; tool_calls_count: number; tool_names?: string; }) => { trackEvent("assistant_feedback_shown", props); }, feedbackClicked: (props: { threadId: string; messageId: string; type: "positive" | "negative"; category?: | "wrong_information" | "outdated" | "didnt_answer" | "too_vague" | "other"; comment_length?: number; user_question_length: number; assistant_response_length: number; tool_calls_count: number; tool_names?: string; }) => { trackEvent("assistant_feedback_clicked", props); }, feedbackSubmitFailed: (props: { threadId: string; messageId: string; type: "positive" | "negative"; category?: | "wrong_information" | "outdated" | "didnt_answer" | "too_vague" | "other"; comment_length?: number; user_question_length: number; assistant_response_length: number; tool_calls_count: number; tool_names?: string; error_name: string; error_message: string; }) => { trackEvent("assistant_feedback_submit_failed", props); }, panelToggled: (props: { open: boolean; source: "trigger" | "toggle" | "header" | "shortcut"; }) => { trackEvent("assistant_panel_toggled", props); }, messageSent: (props: { threadId: string; messageId?: string; source: "composer" | "ask_ai"; message_length: number; attachments_count: number; pathname?: string; model_name?: string; }) => { trackEvent("assistant_message_sent", props); }, responseCompleted: (props: { threadId: string; latency_ms?: number; status_reason?: string; response_length: number; tool_calls_count: number; response_total_tokens?: number; response_input_tokens?: number; response_output_tokens?: number; pathname?: string; model_name?: string; }) => { trackEvent("assistant_response_completed", props); }, responseFailed: (props: { threadId: string; latency_ms?: number; status_reason?: string; response_length: number; tool_calls_count: number; response_total_tokens?: number; response_input_tokens?: number; response_output_tokens?: number; pathname?: string; model_name?: string; }) => { trackEvent("assistant_response_failed", props); }, newThreadClicked: (props: { threadId?: string; previous_message_count: number; context_total_tokens: number; context_usage_percent: number; pathname?: string; model_name?: string; }) => { trackEvent("assistant_new_thread_clicked", props); }, feedbackSubmitted: (props: { threadId: string; messageId: string; type: "positive" | "negative"; category?: | "wrong_information" | "outdated" | "didnt_answer" | "too_vague" | "other"; comment_length?: number; user_question_length: number; assistant_response_length: number; tool_calls_count: number; tool_names?: string; }) => { trackEvent("assistant_feedback_submitted", props); }, }, xulux: { playgroundViewed: (props: { session_id: string; thread_id?: string; pathname?: string; }) => trackEvent("xulux_playground_viewed", props), promptSubmitted: (props: { session_id: string; thread_id?: string; pathname?: string; source: "typed_prompt" | "suggestion" | "composer" | "retry"; message_length: number; suggestion_group?: string; suggestion_label?: string; }) => trackEvent("xulux_prompt_submitted", props), suggestionSelected: (props: { session_id: string; thread_id?: string; pathname?: string; group: "New app" | "Templates" | "Learn" | "Cloud"; label: string; message_length: number; }) => trackEvent("xulux_suggestion_selected", props), templatesOpened: (props: { session_id: string; thread_id?: string; pathname?: string; surface: "landing_carousel" | "header"; }) => trackEvent("xulux_templates_opened", props), templateSelected: (props: { session_id: string; thread_id?: string; pathname?: string; template_id: string; template_kind: "template" | "example"; surface: "landing_carousel" | "templates_modal" | "detail_modal"; action: "open_detail" | "start" | "other_template"; other_template_id?: string; }) => trackEvent("xulux_template_selected", props), previewShown: (props: { session_id: string; thread_id?: string; pathname?: string; source: "template" | "agent_template" | "agent_sandbox"; template_id?: string; }) => trackEvent("xulux_preview_shown", props), converted: (props: { session_id: string; thread_id?: string; pathname?: string; action: "copy_prompt" | "download"; surface: "open_in_card" | "canvas" | "detail_modal"; download_type?: "template" | "sandbox" | "demo"; template_id?: string; }) => trackEvent("xulux_converted", props), }, };