Files
assistant-ui--assistant-ui/apps/docs/lib/analytics.ts
T
wehub-resource-sync e30e75b5d4
Code Quality / Oxlint + Oxfmt (push) Waiting to run
Code Quality / Template Sync (push) Waiting to run
Code Quality / Build Changed Packages (push) Waiting to run
Code Quality / Test Changed Packages (push) Waiting to run
Deploy Expo Example / Deploy Production (push) Waiting to run
Deploy Ink Example / Deploy Production (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.12) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Waiting to run
Deploy Shadcn Registry / Deploy Production (push) Waiting to run
Template Metrics / LOC + Bundle Size (push) Waiting to run
Changesets / Create Version PR (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

346 lines
9.3 KiB
TypeScript

declare global {
interface Window {
posthog?: {
capture?: (
event: string,
properties?: Record<string, string | number | boolean>,
) => void;
};
umami?: {
track: (
event: string,
data?: Record<string, string | number | boolean>,
) => void;
};
}
}
let vercelTrackPromise:
| Promise<
| ((
event: string,
properties?: Record<string, string | number | boolean>,
) => void)
| null
>
| undefined;
const getVercelTrack = () => {
if (vercelTrackPromise) return vercelTrackPromise;
vercelTrackPromise = import("@vercel/analytics")
.then(({ track }) => track)
.catch(() => null);
return vercelTrackPromise;
};
export type AnalyticsProperties = Record<string, string | number | boolean>;
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<string, string | number | boolean>,
) => 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),
},
};