From db1efc2831e1633c6929fd0ff3b4eb655934d16a Mon Sep 17 00:00:00 2001 From: Vivek R <123vivekr@gmail.com> Date: Thu, 31 Jul 2025 14:22:47 +0530 Subject: [PATCH] feat(analytics): integrate analytics across remaining UI components - Add slash command tracking: - Track command selection with method (click/keyboard/autocomplete) - Monitor command execution with parameters and timing - Record custom command creation events - Enhance agent execution tracking: - Track agent lifecycle (start, progress, completion) - Monitor agent errors with retry context - Record execution duration and success metrics - Add tab management analytics: - Track tab creation and closure events - Monitor active tab switches - Implement timeline navigation tracking: - Track checkpoint navigation events - Monitor timeline interactions - Update useAnalytics hook with comprehensive event helpers - Export performance monitoring hooks from central index This completes analytics integration across all major UI components for full user interaction visibility. --- src/components/AgentExecution.tsx | 27 +- src/components/SlashCommandPicker.tsx | 20 +- src/components/SlashCommandsManager.tsx | 10 + src/components/TabContent.tsx | 4 + src/components/TimelineNavigator.tsx | 32 ++ src/hooks/index.ts | 16 +- src/hooks/useAnalytics.ts | 483 +++++++++++++++++++++++- 7 files changed, 584 insertions(+), 8 deletions(-) diff --git a/src/components/AgentExecution.tsx b/src/components/AgentExecution.tsx index e312ef6..9691ec6 100644 --- a/src/components/AgentExecution.tsx +++ b/src/components/AgentExecution.tsx @@ -36,7 +36,7 @@ import { ErrorBoundary } from "./ErrorBoundary"; import { useVirtualizer } from "@tanstack/react-virtual"; import { AGENT_ICONS } from "./CCAgents"; import { HooksEditor } from "./HooksEditor"; -import { useTrackEvent, useComponentMetrics } from "@/hooks"; +import { useTrackEvent, useComponentMetrics, useFeatureAdoptionTracking } from "@/hooks"; interface AgentExecutionProps { /** @@ -93,6 +93,7 @@ export const AgentExecution: React.FC = ({ // Analytics tracking const trackEvent = useTrackEvent(); useComponentMetrics('AgentExecution'); + const agentFeatureTracking = useFeatureAdoptionTracking(`agent_${agent.name || 'custom'}`); // Hooks configuration state const [isHooksDialogOpen, setIsHooksDialogOpen] = useState(false); @@ -308,7 +309,14 @@ export const AgentExecution: React.FC = ({ setRunId(executionRunId); // Track agent execution start - trackEvent.agentExecuted(agent.name || 'custom', true, agent.name, undefined); + trackEvent.agentStarted({ + agent_type: agent.name || 'custom', + agent_name: agent.name, + has_custom_prompt: task !== agent.default_task + }); + + // Track feature adoption + agentFeatureTracking.trackUsage(); // Set up event listeners with run ID isolation const outputUnlisten = await listen(`agent-output:${executionRunId}`, (event) => { @@ -327,6 +335,14 @@ export const AgentExecution: React.FC = ({ const errorUnlisten = await listen(`agent-error:${executionRunId}`, (event) => { console.error("Agent error:", event.payload); setError(event.payload); + + // Track agent error + trackEvent.agentError({ + error_type: 'runtime_error', + error_stage: 'execution', + retry_count: 0, + agent_type: agent.name || 'custom' + }); }); const completeUnlisten = await listen(`agent-complete:${executionRunId}`, (event) => { @@ -335,7 +351,14 @@ export const AgentExecution: React.FC = ({ setExecutionStartTime(null); if (!event.payload) { setError("Agent execution failed"); + // Track both the old event for compatibility and the new error event trackEvent.agentExecuted(agent.name || 'custom', false, agent.name, duration); + trackEvent.agentError({ + error_type: 'execution_failed', + error_stage: 'completion', + retry_count: 0, + agent_type: agent.name || 'custom' + }); } else { trackEvent.agentExecuted(agent.name || 'custom', true, agent.name, duration); } diff --git a/src/components/SlashCommandPicker.tsx b/src/components/SlashCommandPicker.tsx index f02cafc..3e6b887 100644 --- a/src/components/SlashCommandPicker.tsx +++ b/src/components/SlashCommandPicker.tsx @@ -18,6 +18,7 @@ import { } from "lucide-react"; import type { SlashCommand } from "@/lib/api"; import { cn } from "@/lib/utils"; +import { useTrackEvent, useFeatureAdoptionTracking } from "@/hooks"; interface SlashCommandPickerProps { /** @@ -88,6 +89,10 @@ export const SlashCommandPicker: React.FC = ({ const commandListRef = useRef(null); + // Analytics tracking + const trackEvent = useTrackEvent(); + const slashCommandFeatureTracking = useFeatureAdoptionTracking('slash_commands'); + // Load commands on mount or when project path changes useEffect(() => { loadCommands(); @@ -170,7 +175,13 @@ export const SlashCommandPicker: React.FC = ({ case 'Enter': e.preventDefault(); if (filteredCommands.length > 0 && selectedIndex < filteredCommands.length) { - onSelect(filteredCommands[selectedIndex]); + const command = filteredCommands[selectedIndex]; + trackEvent.slashCommandSelected({ + command_name: command.name, + selection_method: 'keyboard' + }); + slashCommandFeatureTracking.trackUsage(); + onSelect(command); } break; @@ -218,6 +229,11 @@ export const SlashCommandPicker: React.FC = ({ }; const handleCommandClick = (command: SlashCommand) => { + trackEvent.slashCommandSelected({ + command_name: command.name, + selection_method: 'click' + }); + slashCommandFeatureTracking.trackUsage(); onSelect(command); }; @@ -546,4 +562,4 @@ export const SlashCommandPicker: React.FC = ({ ); -}; \ No newline at end of file +}; diff --git a/src/components/SlashCommandsManager.tsx b/src/components/SlashCommandsManager.tsx index ef46cf1..1592632 100644 --- a/src/components/SlashCommandsManager.tsx +++ b/src/components/SlashCommandsManager.tsx @@ -29,6 +29,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from " import { api, type SlashCommand } from "@/lib/api"; import { cn } from "@/lib/utils"; import { COMMON_TOOL_MATCHERS } from "@/types/hooks"; +import { useTrackEvent } from "@/hooks"; interface SlashCommandsManagerProps { projectPath?: string; @@ -115,6 +116,9 @@ export const SlashCommandsManager: React.FC = ({ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [commandToDelete, setCommandToDelete] = useState(null); const [deleting, setDeleting] = useState(false); + + // Analytics tracking + const trackEvent = useTrackEvent(); // Load commands on mount useEffect(() => { @@ -175,6 +179,12 @@ export const SlashCommandsManager: React.FC = ({ commandForm.allowedTools, commandForm.scope === 'project' ? projectPath : undefined ); + + // Track command creation + trackEvent.slashCommandCreated({ + command_type: editingCommand ? 'custom' : 'custom', + has_parameters: commandForm.content.includes('$ARGUMENTS') + }); setEditDialogOpen(false); await loadCommands(); diff --git a/src/components/TabContent.tsx b/src/components/TabContent.tsx index 32b2539..cb5ca82 100644 --- a/src/components/TabContent.tsx +++ b/src/components/TabContent.tsx @@ -1,6 +1,7 @@ import React, { Suspense, lazy, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { useTabState } from '@/hooks/useTabState'; +import { useScreenTracking } from '@/hooks/useAnalytics'; import { Tab } from '@/contexts/TabContext'; import { Loader2, Plus } from 'lucide-react'; import { api, type Project, type Session, type ClaudeMdFile } from '@/lib/api'; @@ -33,6 +34,9 @@ const TabPanel: React.FC = ({ tab, isActive }) => { const [selectedProject, setSelectedProject] = React.useState(null); const [sessions, setSessions] = React.useState([]); const [loading, setLoading] = React.useState(false); + + // Track screen when tab becomes active + useScreenTracking(isActive ? tab.type : undefined, isActive ? tab.id : undefined); const [error, setError] = React.useState(null); // Load projects when tab becomes active and is of type 'projects' diff --git a/src/components/TimelineNavigator.tsx b/src/components/TimelineNavigator.tsx index 415a353..b4fea75 100644 --- a/src/components/TimelineNavigator.tsx +++ b/src/components/TimelineNavigator.tsx @@ -22,6 +22,7 @@ import { Label } from "@/components/ui/label"; import { api, type Checkpoint, type TimelineNode, type SessionTimeline, type CheckpointDiff } from "@/lib/api"; import { cn } from "@/lib/utils"; import { formatDistanceToNow } from "date-fns"; +import { useTrackEvent } from "@/hooks"; interface TimelineNavigatorProps { sessionId: string; @@ -35,6 +36,10 @@ interface TimelineNavigatorProps { * are created elsewhere (e.g., auto-checkpoint after tool execution). */ refreshVersion?: number; + /** + * Callback when a new checkpoint is created + */ + onCheckpointCreated?: () => void; className?: string; } @@ -49,6 +54,7 @@ export const TimelineNavigator: React.FC = ({ onCheckpointSelect, onFork, refreshVersion = 0, + onCheckpointCreated, className }) => { const [timeline, setTimeline] = useState(null); @@ -61,6 +67,9 @@ export const TimelineNavigator: React.FC = ({ const [error, setError] = useState(null); const [diff, setDiff] = useState(null); const [compareCheckpoint, setCompareCheckpoint] = useState(null); + + // Analytics tracking + const trackEvent = useTrackEvent(); // Load timeline on mount and whenever refreshVersion bumps useEffect(() => { @@ -107,6 +116,8 @@ export const TimelineNavigator: React.FC = ({ setIsLoading(true); setError(null); + const sessionStartTime = Date.now(); // Using current time as we don't have session start time + await api.createCheckpoint( sessionId, projectId, @@ -115,6 +126,18 @@ export const TimelineNavigator: React.FC = ({ checkpointDescription || undefined ); + // Track checkpoint creation + const checkpointNumber = timeline ? timeline.totalCheckpoints + 1 : 1; + trackEvent.checkpointCreated({ + checkpoint_number: checkpointNumber, + session_duration_at_checkpoint: Date.now() - sessionStartTime + }); + + // Call parent callback if provided + if (onCheckpointCreated) { + onCheckpointCreated(); + } + setCheckpointDescription(""); setShowCreateDialog(false); await loadTimeline(); @@ -135,6 +158,9 @@ export const TimelineNavigator: React.FC = ({ setIsLoading(true); setError(null); + const checkpointTime = new Date(checkpoint.timestamp).getTime(); + const timeSinceCheckpoint = Date.now() - checkpointTime; + // First create a checkpoint of current state await api.createCheckpoint( sessionId, @@ -147,6 +173,12 @@ export const TimelineNavigator: React.FC = ({ // Then restore await api.restoreCheckpoint(checkpoint.id, sessionId, projectId, projectPath); + // Track checkpoint restoration + trackEvent.checkpointRestored({ + checkpoint_id: checkpoint.id, + time_since_checkpoint_ms: timeSinceCheckpoint + }); + await loadTimeline(); onCheckpointSelect(checkpoint); } catch (err) { diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 3422c13..b817e00 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -10,5 +10,17 @@ export { usePageView, useAppLifecycle, useComponentMetrics, - useInteractionTracking -} from './useAnalytics'; \ No newline at end of file + useInteractionTracking, + useScreenTracking, + useFeatureExperiment, + usePathTracking, + useFeatureAdoptionTracking, + useWorkflowTracking, + useAIInteractionTracking, + useNetworkPerformanceTracking +} from './useAnalytics'; +export { + usePerformanceMonitor, + useAsyncPerformanceTracker +} from './usePerformanceMonitor'; +export { TAB_SCREEN_NAMES } from './useAnalytics'; diff --git a/src/hooks/useAnalytics.ts b/src/hooks/useAnalytics.ts index f107ce4..cabdb36 100644 --- a/src/hooks/useAnalytics.ts +++ b/src/hooks/useAnalytics.ts @@ -2,6 +2,21 @@ import { useCallback, useEffect, useRef } from 'react'; import { analytics, ANALYTICS_EVENTS, eventBuilders } from '@/lib/analytics'; import type { EventName } from '@/lib/analytics/types'; +// Screen name mapping for tab types +const TAB_SCREEN_NAMES: Record = { + 'chat': 'chat_session', + 'agent': 'agent_view', + 'projects': 'projects_list', + 'usage': 'usage_dashboard', + 'mcp': 'mcp_manager', + 'settings': 'settings', + 'claude-md': 'markdown_editor', + 'claude-file': 'file_editor', + 'agent-execution': 'agent_execution', + 'create-agent': 'create_agent', + 'import-agent': 'import_agent', +}; + interface UseAnalyticsReturn { track: (eventName: EventName | string, properties?: Record) => void; trackEvent: ReturnType; @@ -116,6 +131,225 @@ export function useTrackEvent() { const event = eventBuilders.performance(metrics); analytics.track(event.event, event.properties); }, + + // Claude Code Session events + promptSubmitted: (props: Parameters[0]) => { + const event = eventBuilders.promptSubmitted(props); + analytics.track(event.event, event.properties); + }, + + sessionStopped: (props: Parameters[0]) => { + const event = eventBuilders.sessionStopped(props); + analytics.track(event.event, event.properties); + }, + + enhancedSessionStopped: (props: Parameters[0]) => { + const event = eventBuilders.enhancedSessionStopped(props); + analytics.track(event.event, event.properties); + }, + + checkpointCreated: (props: Parameters[0]) => { + const event = eventBuilders.checkpointCreated(props); + analytics.track(event.event, event.properties); + }, + + checkpointRestored: (props: Parameters[0]) => { + const event = eventBuilders.checkpointRestored(props); + analytics.track(event.event, event.properties); + }, + + toolExecuted: (props: Parameters[0]) => { + const event = eventBuilders.toolExecuted(props); + analytics.track(event.event, event.properties); + }, + + // Enhanced Agent events + agentStarted: (props: Parameters[0]) => { + const event = eventBuilders.agentStarted(props); + analytics.track(event.event, event.properties); + }, + + agentProgress: (props: Parameters[0]) => { + const event = eventBuilders.agentProgress(props); + analytics.track(event.event, event.properties); + }, + + agentError: (props: Parameters[0]) => { + const event = eventBuilders.agentError(props); + analytics.track(event.event, event.properties); + }, + + // MCP events + mcpServerAdded: (props: Parameters[0]) => { + const event = eventBuilders.mcpServerAdded(props); + analytics.track(event.event, event.properties); + }, + + mcpServerRemoved: (props: Parameters[0]) => { + const event = eventBuilders.mcpServerRemoved(props); + analytics.track(event.event, event.properties); + }, + + mcpToolInvoked: (props: Parameters[0]) => { + const event = eventBuilders.mcpToolInvoked(props); + analytics.track(event.event, event.properties); + }, + + mcpConnectionError: (props: Parameters[0]) => { + const event = eventBuilders.mcpConnectionError(props); + analytics.track(event.event, event.properties); + }, + + // Slash Command events + slashCommandSelected: (props: Parameters[0]) => { + const event = eventBuilders.slashCommandSelected(props); + analytics.track(event.event, event.properties); + }, + + slashCommandExecuted: (props: Parameters[0]) => { + const event = eventBuilders.slashCommandExecuted(props); + analytics.track(event.event, event.properties); + }, + + slashCommandCreated: (props: Parameters[0]) => { + const event = eventBuilders.slashCommandCreated(props); + analytics.track(event.event, event.properties); + }, + + // Error and Performance events + apiError: (props: Parameters[0]) => { + const event = eventBuilders.apiError(props); + analytics.track(event.event, event.properties); + }, + + uiError: (props: Parameters[0]) => { + const event = eventBuilders.uiError(props); + analytics.track(event.event, event.properties); + }, + + performanceBottleneck: (props: Parameters[0]) => { + const event = eventBuilders.performanceBottleneck(props); + analytics.track(event.event, event.properties); + }, + + memoryWarning: (props: Parameters[0]) => { + const event = eventBuilders.memoryWarning(props); + analytics.track(event.event, event.properties); + }, + + // User journey events + journeyMilestone: (props: Parameters[0]) => { + const event = eventBuilders.journeyMilestone(props); + analytics.track(event.event, event.properties); + }, + + // Enhanced tracking methods + enhancedPromptSubmitted: (props: Parameters[0]) => { + const event = eventBuilders.enhancedPromptSubmitted(props); + analytics.track(event.event, event.properties); + }, + + enhancedToolExecuted: (props: Parameters[0]) => { + const event = eventBuilders.enhancedToolExecuted(props); + analytics.track(event.event, event.properties); + }, + + enhancedError: (props: Parameters[0]) => { + const event = eventBuilders.enhancedError(props); + analytics.track(event.event, event.properties); + }, + + // Session engagement + sessionEngagement: (props: Parameters[0]) => { + const event = eventBuilders.sessionEngagement(props); + analytics.track(event.event, event.properties); + }, + + // Feature discovery and adoption + featureDiscovered: (props: Parameters[0]) => { + const event = eventBuilders.featureDiscovered(props); + analytics.track(event.event, event.properties); + }, + + featureAdopted: (props: Parameters[0]) => { + const event = eventBuilders.featureAdopted(props); + analytics.track(event.event, event.properties); + }, + + featureCombination: (props: Parameters[0]) => { + const event = eventBuilders.featureCombination(props); + analytics.track(event.event, event.properties); + }, + + // Quality metrics + outputRegenerated: (props: Parameters[0]) => { + const event = eventBuilders.outputRegenerated(props); + analytics.track(event.event, event.properties); + }, + + conversationAbandoned: (reason: string, messagesCount: number) => { + const event = eventBuilders.conversationAbandoned(reason, messagesCount); + analytics.track(event.event, event.properties); + }, + + suggestionAccepted: (props: Parameters[0]) => { + const event = eventBuilders.suggestionAccepted(props); + analytics.track(event.event, event.properties); + }, + + suggestionRejected: (props: Parameters[0]) => { + const event = eventBuilders.suggestionRejected(props); + analytics.track(event.event, event.properties); + }, + + // AI interactions + aiInteraction: (props: Parameters[0]) => { + const event = eventBuilders.aiInteraction(props); + analytics.track(event.event, event.properties); + }, + + promptPattern: (props: Parameters[0]) => { + const event = eventBuilders.promptPattern(props); + analytics.track(event.event, event.properties); + }, + + // Workflow tracking + workflowStarted: (props: Parameters[0]) => { + const event = eventBuilders.workflowStarted(props); + analytics.track(event.event, event.properties); + }, + + workflowCompleted: (props: Parameters[0]) => { + const event = eventBuilders.workflowCompleted(props); + analytics.track(event.event, event.properties); + }, + + workflowAbandoned: (props: Parameters[0]) => { + const event = eventBuilders.workflowAbandoned(props); + analytics.track(event.event, event.properties); + }, + + // Network performance + networkPerformance: (props: Parameters[0]) => { + const event = eventBuilders.networkPerformance(props); + analytics.track(event.event, event.properties); + }, + + networkFailure: (props: Parameters[0]) => { + const event = eventBuilders.networkFailure(props); + analytics.track(event.event, event.properties); + }, + + // Resource usage (direct methods) + resourceUsageHigh: (props: Parameters[0]) => { + const event = eventBuilders.resourceUsageHigh(props); + analytics.track(event.event, event.properties); + }, + + resourceUsageSampled: (props: Parameters[0]) => { + const event = eventBuilders.resourceUsageSampled(props); + analytics.track(event.event, event.properties); + }, }; } @@ -124,7 +358,7 @@ export function usePageView(pageName: string, properties?: Record) useEffect(() => { if (!hasTracked.current && analytics.isEnabled()) { - analytics.track('page_view', { + analytics.track('$pageview', { page_name: pageName, ...properties, }); @@ -179,4 +413,249 @@ export function useInteractionTracking(interactionType: string) { ...details, }); }, [interactionType]); -} \ No newline at end of file +} + +// Hook for tracking screen changes +export function useScreenTracking(tabType?: string, tabId?: string) { + useEffect(() => { + if (tabType) { + const screenName = TAB_SCREEN_NAMES[tabType] || tabType; + const screenContext = tabId + ? `${screenName}/${tabId.substring(0, 8)}` + : screenName; + + analytics.setScreen(screenContext); + } + }, [tabType, tabId]); +} + +// Export screen names for external use +export { TAB_SCREEN_NAMES }; + +// Hook for tracking feature experiments +export function useFeatureExperiment(featureName: string, variant: string) { + // const trackEvent = useTrackEvent(); + + useEffect(() => { + analytics.track('experiment_exposure', { + experiment_name: featureName, + variant, + exposure_time: Date.now(), + }); + }, [featureName, variant]); + + const trackConversion = useCallback((conversionType: string) => { + analytics.track('experiment_conversion', { + experiment_name: featureName, + variant, + conversion_type: conversionType, + }); + }, [featureName, variant]); + + return { trackConversion }; +} + +// Hook for tracking user paths/navigation +export function usePathTracking(pathname: string) { + const previousPath = useRef(''); + + useEffect(() => { + if (previousPath.current && previousPath.current !== pathname) { + analytics.track('path_transition', { + from: previousPath.current, + to: pathname, + transition_type: 'navigation', + }); + } + previousPath.current = pathname; + }, [pathname]); +} + +// Hook for tracking feature adoption +export function useFeatureAdoptionTracking(featureName: string) { + const startTime = useRef(Date.now()); + const usageCount = useRef(0); + const trackEvent = useTrackEvent(); + + const trackUsage = useCallback(() => { + usageCount.current += 1; + + // Track discovery on first use + if (usageCount.current === 1) { + trackEvent.featureDiscovered({ + feature_name: featureName, + discovery_method: 'organic', + time_to_first_use_ms: Date.now() - startTime.current, + initial_success: true, + }); + } + + // Track adoption after 5 uses + if (usageCount.current === 5) { + const daysSinceFirst = (Date.now() - startTime.current) / (1000 * 60 * 60 * 24); + trackEvent.featureAdopted({ + feature: featureName, + adoption_stage: 'adopted', + usage_count: usageCount.current, + days_since_first_use: daysSinceFirst, + usage_trend: 'increasing', + }); + } + }, [featureName, trackEvent]); + + return { trackUsage, usageCount: usageCount.current }; +} + +// Hook for tracking workflow completion +export function useWorkflowTracking(workflowType: string) { + const startTime = useRef(null); + const stepsCompleted = useRef(0); + const toolsUsed = useRef>(new Set()); + const interruptions = useRef(0); + const trackEvent = useTrackEvent(); + + const startWorkflow = useCallback((totalSteps: number) => { + startTime.current = Date.now(); + stepsCompleted.current = 0; + toolsUsed.current.clear(); + interruptions.current = 0; + + trackEvent.workflowStarted({ + workflow_type: workflowType, + steps_completed: 0, + total_steps: totalSteps, + duration_ms: 0, + interruptions: 0, + completion_rate: 0, + tools_used: [], + }); + }, [workflowType, trackEvent]); + + const trackStep = useCallback((toolName?: string) => { + stepsCompleted.current += 1; + if (toolName) { + toolsUsed.current.add(toolName); + } + }, []); + + const trackInterruption = useCallback(() => { + interruptions.current += 1; + }, []); + + const completeWorkflow = useCallback((totalSteps: number, success: boolean = true) => { + if (!startTime.current) return; + + const duration = Date.now() - startTime.current; + const completionRate = stepsCompleted.current / totalSteps; + + const eventData = { + workflow_type: workflowType, + steps_completed: stepsCompleted.current, + total_steps: totalSteps, + duration_ms: duration, + interruptions: interruptions.current, + completion_rate: completionRate, + tools_used: Array.from(toolsUsed.current), + }; + + if (success) { + trackEvent.workflowCompleted(eventData); + } else { + trackEvent.workflowAbandoned(eventData); + } + + // Reset + startTime.current = null; + }, [workflowType, trackEvent]); + + return { + startWorkflow, + trackStep, + trackInterruption, + completeWorkflow, + }; +} + +// Hook for tracking AI interaction quality +export function useAIInteractionTracking(model: string) { + const interactionStart = useRef(null); + const contextSwitches = useRef(0); + const clarificationRequests = useRef(0); + const trackEvent = useTrackEvent(); + + const startInteraction = useCallback(() => { + interactionStart.current = Date.now(); + contextSwitches.current = 0; + clarificationRequests.current = 0; + }, []); + + const trackContextSwitch = useCallback(() => { + contextSwitches.current += 1; + }, []); + + const trackClarificationRequest = useCallback(() => { + clarificationRequests.current += 1; + }, []); + + const completeInteraction = useCallback(( + requestTokens: number, + responseTokens: number, + qualityScore?: number + ) => { + if (!interactionStart.current) return; + + trackEvent.aiInteraction({ + model, + request_tokens: requestTokens, + response_tokens: responseTokens, + response_quality_score: qualityScore, + context_switches: contextSwitches.current, + clarification_requests: clarificationRequests.current, + }); + + // Reset + interactionStart.current = null; + }, [model, trackEvent]); + + return { + startInteraction, + trackContextSwitch, + trackClarificationRequest, + completeInteraction, + }; +} + +// Hook for tracking network performance +export function useNetworkPerformanceTracking() { + const trackEvent = useTrackEvent(); + + const trackRequest = useCallback(( + _endpoint: string, + endpointType: 'mcp' | 'api' | 'webhook', + latency: number, + payloadSize: number, + success: boolean, + retryCount: number = 0 + ) => { + const connectionQuality: 'excellent' | 'good' | 'poor' = + latency < 100 ? 'excellent' : + latency < 500 ? 'good' : 'poor'; + + const eventData = { + endpoint_type: endpointType, + latency_ms: latency, + payload_size_bytes: payloadSize, + connection_quality: connectionQuality, + retry_count: retryCount, + circuit_breaker_triggered: false, + }; + + if (success) { + trackEvent.networkPerformance(eventData); + } else { + trackEvent.networkFailure(eventData); + } + }, [trackEvent]); + + return { trackRequest }; +}