hololake-system-architecture/product-source/hololake-platform/src/lib/productAnalytics.ts

248 lines
7.7 KiB
TypeScript

import type { AiAgentId } from './aiAgents'
import type { AiAgentPermissionMode } from './aiAgentPermissionMode'
import { trackEvent } from './telemetry'
import type { AllNotesFileVisibility } from '../utils/allNotesFileVisibility'
import type { DateDisplayFormat } from '../utils/dateDisplay'
import type { FilePreviewKind } from '../utils/filePreview'
import type { GitProviderId, NoteWidthMode } from '../types'
import type { CommitMessageDraftSource } from '../utils/commitMessageDraft'
import type { ThemeMode } from './themeMode'
type TrackedPreviewKind = FilePreviewKind | 'unsupported'
type FilePreviewAction = 'copy_deep_link' | 'copy_path' | 'open_external' | 'reveal'
type AgentBlockedReason = 'agent_unavailable' | 'missing_vault'
type AiWorkspaceMode = 'docked' | 'side' | 'window'
type AiWorkspaceTitleSource = 'generated' | 'manual'
type NotePdfExportFailureReason = 'export_unavailable' | 'export_error'
type NotePdfExportSource = 'breadcrumb' | 'app_command' | 'note_list_context_menu'
type AnalyticsBoolean = boolean
type AiAgentResponseText = string
type AiAgentToolCount = number
type AiAgentResponseTextFlag = 'had_text' | 'had_partial_response'
type SheetFormulaFunctionName = string
const ALL_NOTES_VISIBILITY_CATEGORIES: ReadonlyArray<keyof AllNotesFileVisibility> = [
'pdfs',
'images',
'unsupported',
]
function trackedPreviewKind(previewKind: FilePreviewKind | null): TrackedPreviewKind {
return previewKind ?? 'unsupported'
}
function numericFlag(value: AnalyticsBoolean): number {
return value ? 1 : 0
}
function aiAgentResponsePayload(
agent: AiAgentId,
response: AiAgentResponseText,
toolCount: AiAgentToolCount,
textFlag: AiAgentResponseTextFlag,
) {
return {
agent,
[textFlag]: numericFlag(response.trim().length > 0),
tool_count: toolCount,
}
}
export function trackFilePreviewOpened(previewKind: FilePreviewKind | null): void {
trackEvent('file_preview_opened', {
preview_kind: trackedPreviewKind(previewKind),
})
}
export function trackFilePreviewAction(action: FilePreviewAction, previewKind: FilePreviewKind | null): void {
trackEvent('file_preview_action', {
action,
preview_kind: trackedPreviewKind(previewKind),
})
}
export function trackFilePreviewFailed(previewKind: FilePreviewKind): void {
trackEvent('file_preview_failed', { preview_kind: previewKind })
}
export function trackNotePdfExportStarted(source: NotePdfExportSource): void {
trackEvent('note_pdf_export_started', { source })
}
export function trackNotePdfExportFailed(
source: NotePdfExportSource,
reason: NotePdfExportFailureReason,
): void {
trackEvent('note_pdf_export_failed', { reason, source })
}
export function trackAllNotesVisibilityChanged(
previous: AllNotesFileVisibility,
next: AllNotesFileVisibility,
): void {
for (const category of ALL_NOTES_VISIBILITY_CATEGORIES) {
const previousValue = Reflect.get(previous, category) as boolean
const nextValue = Reflect.get(next, category) as boolean
if (previousValue === nextValue) continue
trackEvent('all_notes_visibility_changed', {
category,
enabled: numericFlag(nextValue),
})
}
}
export function trackAiFeaturesEnabledChanged(enabled: AnalyticsBoolean): void {
trackEvent('ai_features_visibility_changed', {
enabled: numericFlag(enabled),
})
}
export function trackGitFeaturesEnabledChanged(enabled: AnalyticsBoolean): void {
trackEvent('git_features_visibility_changed', {
enabled: numericFlag(enabled),
})
}
export function trackGitProviderChanged(provider: GitProviderId): void {
trackEvent('git_provider_changed', { provider })
}
export function trackGitWslDistroChanged(hasDistro: AnalyticsBoolean): void {
trackEvent('git_wsl_distro_changed', {
has_distro: numericFlag(hasDistro),
})
}
export function trackGitProviderTested(provider: GitProviderId, available: AnalyticsBoolean): void {
trackEvent('git_provider_tested', {
available: numericFlag(available),
provider,
})
}
export function trackCommitMessageGenerated(params: {
aiAttempted: AnalyticsBoolean
fileCount: number
source: CommitMessageDraftSource
}): void {
trackEvent('commit_message_generated', {
ai_attempted: numericFlag(params.aiAttempted),
file_count: params.fileCount,
source: params.source,
})
}
export function trackDefaultNoteWidthChanged(mode: NoteWidthMode): void {
trackEvent('note_width_default_changed', { mode })
}
export function trackDateDisplayFormatChanged(format: DateDisplayFormat): void {
trackEvent('date_display_format_changed', { format })
}
export function trackSidebarTypePluralizationChanged(enabled: AnalyticsBoolean): void {
trackEvent('sidebar_type_pluralization_changed', {
enabled: numericFlag(enabled),
})
}
export function trackThemeModeChanged(mode: ThemeMode): void {
trackEvent('theme_mode_changed', { mode })
}
export function trackInlineImageLightboxOpened(): void {
trackEvent('inline_image_lightbox_opened')
}
export function trackDatePropertyDirectEntrySaved(): void {
trackEvent('date_property_direct_entry_saved', { source: 'properties_panel' })
}
export function trackSheetEditorOpened(params: {
columnCount: number
hasMetadata: boolean
rowCount: number
}): void {
trackEvent('sheet_editor_opened', {
column_count: params.columnCount,
has_metadata: numericFlag(params.hasMetadata),
row_count: params.rowCount,
})
}
export function trackSheetFormulaAutocompleteUsed(functionName: SheetFormulaFunctionName): void {
trackEvent('sheet_formula_autocomplete_used', { function_name: functionName })
}
export function trackAiAgentMessageBlocked(agent: AiAgentId, reason: AgentBlockedReason): void {
trackEvent('ai_agent_message_blocked', { agent, reason })
}
export function trackAiAgentMessageSent(params: {
agent: AiAgentId
permissionMode: AiAgentPermissionMode
hasContext: boolean
referenceCount: number
historyMessageCount: number
}): void {
trackEvent('ai_agent_message_sent', {
agent: params.agent,
permission_mode: params.permissionMode,
has_context: numericFlag(params.hasContext),
reference_count: params.referenceCount,
history_message_count: params.historyMessageCount,
})
}
export function trackAiAgentResponseCompleted(
agent: AiAgentId,
response: AiAgentResponseText,
toolCount: AiAgentToolCount,
skipped: AnalyticsBoolean,
): void {
if (skipped) return
trackEvent('ai_agent_response_completed', aiAgentResponsePayload(agent, response, toolCount, 'had_text'))
}
export function trackAiAgentResponseFailed(
agent: AiAgentId,
response: AiAgentResponseText,
toolCount: AiAgentToolCount,
): void {
trackEvent('ai_agent_response_failed', {
...aiAgentResponsePayload(agent, response, toolCount, 'had_partial_response'),
error_kind: 'stream_error',
})
}
export function trackAiAgentResponseStopped(
agent: AiAgentId,
response: AiAgentResponseText,
toolCount: AiAgentToolCount,
): void {
trackEvent('ai_agent_response_stopped', aiAgentResponsePayload(agent, response, toolCount, 'had_partial_response'))
}
export function trackAiAgentPermissionModeChanged(agent: AiAgentId, permissionMode: AiAgentPermissionMode): void {
trackEvent('ai_agent_permission_mode_changed', {
agent,
permission_mode: permissionMode,
})
}
export function trackAiWorkspaceSidebarToggled(collapsed: AnalyticsBoolean, mode: AiWorkspaceMode): void {
trackEvent('ai_workspace_sidebar_toggled', {
collapsed: numericFlag(collapsed),
mode,
})
}
export function trackAiWorkspaceChatTitled(source: AiWorkspaceTitleSource): void {
trackEvent('ai_workspace_chat_titled', { source })
}
export function trackAiWorkspaceConversationDeleted(archived: AnalyticsBoolean): void {
trackEvent('ai_workspace_conversation_deleted', {
archived: numericFlag(archived),
})
}