diff --git a/apps/sim/app/workspace/[workspaceId]/home/home.tsx b/apps/sim/app/workspace/[workspaceId]/home/home.tsx index 8bd266a8724..b3c4cc48519 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/home.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/home.tsx @@ -143,12 +143,21 @@ export function Home({ chatId }: HomeProps = {}) { editQueuedMessage, previewSession, genericResourceData, + getCurrentRequestId, } = useChat( workspaceId, chatId, getMothershipUseChatOptions({ onResourceEvent: handleResourceEvent, initialActiveResourceId: initialResourceId, + onRequestStarted: ({ requestId, userMessageId }) => { + captureEvent(posthogRef.current, 'task_request_started', { + workspace_id: workspaceId, + view: 'mothership', + request_id: requestId, + user_message_id: userMessageId, + }) + }, }) ) @@ -198,6 +207,7 @@ export function Home({ chatId }: HomeProps = {}) { captureEvent(posthogRef.current, 'task_generation_aborted', { workspace_id: workspaceId, view: 'mothership', + request_id: getCurrentRequestId(), }) void stopGeneration().catch(() => {}) } diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts index 6d2c7527a6b..f320d44d46a 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts @@ -167,6 +167,7 @@ export interface UseChatReturn { editQueuedMessage: (id: string) => QueuedMessage | undefined previewSession: FilePreviewSession | null genericResourceData: GenericResourceData | null + getCurrentRequestId: () => string | undefined } const DEPLOY_TOOL_NAMES: Set = new Set([ @@ -1278,6 +1279,8 @@ export interface UseChatOptions { onTitleUpdate?: () => void onStreamEnd?: (chatId: string, messages: ChatMessage[]) => void initialActiveResourceId?: string | null + /** Fired when the server's `traceparent` response header arrives, before any stream content. */ + onRequestStarted?: (info: { requestId: string; userMessageId: string }) => void } interface ActiveStreamRecovery { @@ -1293,7 +1296,10 @@ interface StopGenerationOptions { } export function getMothershipUseChatOptions( - options: Pick = {} + options: Pick< + UseChatOptions, + 'onResourceEvent' | 'onStreamEnd' | 'initialActiveResourceId' | 'onRequestStarted' + > = {} ): UseChatOptions { return { apiPath: MOTHERSHIP_CHAT_API_PATH, @@ -1305,7 +1311,7 @@ export function getMothershipUseChatOptions( export function getWorkflowCopilotUseChatOptions( options: Pick< UseChatOptions, - 'workflowId' | 'onToolResult' | 'onTitleUpdate' | 'onStreamEnd' + 'workflowId' | 'onToolResult' | 'onTitleUpdate' | 'onStreamEnd' | 'onRequestStarted' > = {} ): UseChatOptions { return { @@ -1351,6 +1357,13 @@ export function useChat( onTitleUpdateRef.current = options?.onTitleUpdate const onStreamEndRef = useRef(options?.onStreamEnd) onStreamEndRef.current = options?.onStreamEnd + const onRequestStartedRef = useRef(options?.onRequestStarted) + onRequestStartedRef.current = options?.onRequestStarted + + const getCurrentRequestId = useCallback(() => { + const traceId = streamTraceparentRef.current?.split('-')[1] ?? '' + return /^[0-9a-f]{32}$/.test(traceId) ? traceId : undefined + }, []) const clearQueueDispatchState = useCallback(() => { queueDispatchEpochRef.current++ @@ -4209,6 +4222,14 @@ export function useChat( if (traceparent) { streamTraceparentRef.current = traceparent setCurrentChatTraceparent(traceparent) + const traceId = traceparent.split('-')[1] ?? '' + if (/^[0-9a-f]{32}$/.test(traceId)) { + try { + onRequestStartedRef.current?.({ requestId: traceId, userMessageId }) + } catch (callbackError) { + logger.warn('onRequestStarted callback threw', { error: callbackError }) + } + } } if (!response.ok) { @@ -5103,5 +5124,6 @@ export function useChat( editQueuedMessage, previewSession, genericResourceData, + getCurrentRequestId, } } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx index 64acc3d2a27..de69bbd886c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx @@ -346,6 +346,7 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel removeFromQueue: copilotRemoveFromQueue, sendNow: copilotSendNow, editQueuedMessage: copilotEditQueuedMessage, + getCurrentRequestId: getCopilotCurrentRequestId, } = useChat( workspaceId, copilotChatId, @@ -353,6 +354,14 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel workflowId: activeWorkflowId || undefined, onTitleUpdate: loadCopilotChats, onToolResult: handleCopilotToolResult, + onRequestStarted: ({ requestId, userMessageId }) => { + captureEvent(posthogRef.current, 'task_request_started', { + workspace_id: workspaceId, + view: 'copilot', + request_id: requestId, + user_message_id: userMessageId, + }) + }, }) ) @@ -413,9 +422,10 @@ export const Panel = memo(function Panel({ workspaceId: propWorkspaceId }: Panel captureEvent(posthogRef.current, 'task_generation_aborted', { workspace_id: workspaceId, view: 'copilot', + request_id: getCopilotCurrentRequestId(), }) copilotStopGeneration() - }, [copilotStopGeneration, workspaceId]) + }, [copilotStopGeneration, getCopilotCurrentRequestId, workspaceId]) const handleCopilotSubmit = useCallback( (text: string, fileAttachments?: FileAttachmentForApi[], contexts?: ChatContext[]) => { diff --git a/apps/sim/lib/posthog/events.ts b/apps/sim/lib/posthog/events.ts index 41f26124919..7f928d7f70a 100644 --- a/apps/sim/lib/posthog/events.ts +++ b/apps/sim/lib/posthog/events.ts @@ -418,6 +418,7 @@ export interface PostHogEventMap { task_generation_aborted: { workspace_id: string view: 'mothership' | 'copilot' + request_id?: string } task_message_sent: { @@ -427,6 +428,14 @@ export interface PostHogEventMap { is_new_task: boolean } + /** Pairs with `task_message_sent` via `request_id` for correlation with server-side logs. */ + task_request_started: { + workspace_id: string + view: 'mothership' | 'copilot' + request_id: string + user_message_id: string + } + tour_started: { tour_type: 'nav' | 'workflow' } diff --git a/apps/sim/lib/posthog/server.ts b/apps/sim/lib/posthog/server.ts index 99a76a402cc..c81349e2e79 100644 --- a/apps/sim/lib/posthog/server.ts +++ b/apps/sim/lib/posthog/server.ts @@ -1,4 +1,4 @@ -import { createLogger } from '@sim/logger' +import { createLogger, getRequestContext } from '@sim/logger' import type { PostHog } from 'posthog-node' import type { PostHogEventMap, PostHogEventName } from '@/lib/posthog/events' @@ -71,11 +71,14 @@ export function captureServerEvent( const client = getClient() if (!client) return + const contextRequestId = getRequestContext()?.requestId + const props = properties as Record client.capture({ distinctId, event, properties: { ...properties, + ...(contextRequestId && !('request_id' in props) ? { request_id: contextRequestId } : {}), ...(options?.groups ? { $groups: options.groups } : {}), ...(options?.set ? { $set: options.set } : {}), ...(options?.setOnce ? { $set_once: options.setOnce } : {}),