From 7fd90526a94aea89e4d0b611be0d5a3d4a5b3023 Mon Sep 17 00:00:00 2001 From: Kaiyu Zhou Date: Tue, 16 Jun 2026 21:40:31 +1000 Subject: [PATCH 1/4] Add PostHog storytelling v2 tracking for notebook usage flows. Instrument upload, chat, dashboard navigation, and workspace lifecycle events with shared analytics context, tests, and GA4 alignment docs. Co-authored-by: Cursor --- .env.local.example | 7 + README.md | 39 ++++ docs/ga4-alignment.md | 41 ++++ docs/posthog-notebook-tracking.md | 34 +++ package-lock.json | 182 ++++++--------- package.json | 1 + src/app/layout.tsx | 3 +- src/components/chat-composer.test.ts | 17 ++ src/components/chat-composer.tsx | 19 ++ src/components/chat-panel.tsx | 9 + src/components/chunks-panel.tsx | 13 ++ .../source-upload-dialog-workflow.test.ts | 20 ++ .../source-upload-dialog-workflow.ts | 32 +++ src/components/source-upload-dialog.test.ts | 11 + src/components/source-upload-dialog.tsx | 25 +- src/components/sources-panel.tsx | 13 +- src/components/top-nav.test.ts | 19 ++ src/components/top-nav.tsx | 22 ++ src/components/workspace-chat-workflow.ts | 40 ++++ src/components/workspace-shell-layout.tsx | 23 ++ src/components/workspace-shell.tsx | 54 ++++- src/lib/posthog.test.ts | 66 ++++++ src/lib/posthog.ts | 219 ++++++++++++++++++ src/providers/posthog-provider.tsx | 16 ++ 24 files changed, 811 insertions(+), 114 deletions(-) create mode 100644 docs/ga4-alignment.md create mode 100644 docs/posthog-notebook-tracking.md create mode 100644 src/lib/posthog.test.ts create mode 100644 src/lib/posthog.ts create mode 100644 src/providers/posthog-provider.tsx diff --git a/.env.local.example b/.env.local.example index f75dd2a..9bc20f2 100644 --- a/.env.local.example +++ b/.env.local.example @@ -43,6 +43,13 @@ NOTEBOOK_PUBLIC_URL=http://notebook.local.knowhereto.ai:3001 # to Better Auth's standard cookie names. # SESSION_COOKIE_NAMES=better-auth.session_token,__Secure-better-auth.session_token +# --- Product analytics (client-side) --- +# PostHog Project API key. When unset, Notebook skips analytics initialization. +# NEXT_PUBLIC_POSTHOG_KEY=phc_your_project_api_key +# +# PostHog host. Keep default for PostHog Cloud. +# NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com + # --- Database (server-side only) --- # Neon Postgres connection string (Vercel Marketplace free tier). # Provisioned by the owner; do not commit a real value. diff --git a/README.md b/README.md index 5e4e239..f3e7d29 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Upload documents, explore parsed content, and ask questions about your knowledge 2. Fill in your API keys in `.env.local`: - `AI_GATEWAY_API_KEY` — your Vercel AI Gateway key for chat (optional `CHAT_MODEL` override) - `KNOWHERE_API_KEY` — optional development override that skips Dashboard auth and calls Knowhere directly + - `NEXT_PUBLIC_POSTHOG_KEY` — PostHog Project API key for front-end event tracking + - `NEXT_PUBLIC_POSTHOG_HOST` — PostHog host (default `https://app.posthog.com`) 3. Install dependencies and run: ```bash @@ -21,6 +23,43 @@ Upload documents, explore parsed content, and ask questions about your knowledge 4. Open [http://localhost:3000](http://localhost:3000) +## PostHog Tracking + +Notebook sends these product analytics events when PostHog is configured: + +- `notebook_upload_button_clicked` +- `notebook_document_upload_completed` (`uploaded_count`, `file_types`, `total_size_bytes`) +- `notebook_assistant_question_submitted` (`selected_sources_count`, `message_length`) +- `notebook_dashboard_link_clicked` + +Notebook also calls PostHog `identify` for authenticated users and `reset` for +guest sessions so insights can be grouped by user. + +### Connect Notebook to PostHog + +1. In PostHog, create/select a project. +2. Copy the project API key and host. +3. Set these values in `.env.local`: + ```bash + NEXT_PUBLIC_POSTHOG_KEY=phc_your_project_api_key + NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com + ``` +4. Restart `pnpm dev` (or `npm run dev`), then trigger a few actions in Notebook. +5. Open PostHog `Events` and filter by the event names above to verify ingestion. + +### Where to view the metrics + +Create four Trends insights in PostHog: + +1. Upload button clicks: count of `notebook_upload_button_clicked` +2. Uploaded documents: `sum(uploaded_count)` on `notebook_document_upload_completed` +3. Avg sources per question: `avg(selected_sources_count)` on `notebook_assistant_question_submitted` +4. Users opening dashboard: `Unique users` on `notebook_dashboard_link_clicked` + +Pin those four insights to a `Notebook Tracking` dashboard for team reporting. + +GA4 field and event alignment guidance lives in `docs/ga4-alignment.md`. + ## Tech Stack - **Framework**: [Next.js 16](https://nextjs.org) with App Router and Server Components diff --git a/docs/ga4-alignment.md b/docs/ga4-alignment.md new file mode 100644 index 0000000..aee8443 --- /dev/null +++ b/docs/ga4-alignment.md @@ -0,0 +1,41 @@ +# GA4 Alignment for Notebook Storytelling v2 + +This document maps the Notebook storytelling events to GA4 naming and parameters. + +## Naming policy + +- Keep the same event names in PostHog and GA4. +- Keep the same parameter names and meanings in both systems. + +## Shared user properties + +- `workspace_id` +- `workspace_namespace` +- `is_guest` + +## Event mapping + +- `notebook_upload_button_clicked` + - params: `source_count_snapshot`, `surface` +- `notebook_document_upload_completed` + - params: `uploaded_count`, `file_types`, `total_size_bytes`, `source_count_before`, `source_count_after` +- `notebook_document_upload_failed` + - params: `file_type`, `file_size_bytes`, `error_type`, `error_message` +- `notebook_assistant_question_submitted` + - params: `thread_id`, `selected_sources_count`, `source_count_snapshot`, `message_length` +- `notebook_assistant_answer_completed` + - params: `thread_id`, `latency_ms` +- `notebook_assistant_answer_failed` + - params: `thread_id`, `latency_ms`, `error_type`, `error_message` +- `notebook_dashboard_link_clicked` + - params: `from_page`, `target_url`, `has_sources`, `has_chats` +- `notebook_workspace_first_document_uploaded` + - params: `surface` +- `notebook_workspace_first_question_asked` + - params: `selected_sources_count`, `surface` + +## GA4 implementation notes + +- Send GA4 events from the same trigger points as PostHog. +- Attach `workspace_id`, `workspace_namespace`, and `is_guest` as GA4 user properties after auth state resolves. +- Keep `error_message` truncated to avoid oversized payloads. diff --git a/docs/posthog-notebook-tracking.md b/docs/posthog-notebook-tracking.md new file mode 100644 index 0000000..499a276 --- /dev/null +++ b/docs/posthog-notebook-tracking.md @@ -0,0 +1,34 @@ +# PostHog Notebook Tracking Setup + +This runbook configures the four baseline insights for Knowhere Notebook usage. + +## Events + +- `notebook_upload_button_clicked` +- `notebook_document_upload_completed` +- `notebook_assistant_question_submitted` +- `notebook_dashboard_link_clicked` + +## Create the Insights + +1. Open PostHog `Product Analytics -> Insights`. +2. Create a `Trends` insight named `Notebook - Upload Button Clicks`. + - Series: event `notebook_upload_button_clicked` + - Math: `Total count` +3. Create `Notebook - Uploaded Documents`. + - Series: event `notebook_document_upload_completed` + - Math: `Sum` + - Property: `uploaded_count` +4. Create `Notebook - Avg Sources Per Question`. + - Series: event `notebook_assistant_question_submitted` + - Math: `Average` + - Property: `selected_sources_count` +5. Create `Notebook - Dashboard Click Users`. + - Series: event `notebook_dashboard_link_clicked` + - Math: `Unique users` + +## Dashboard + +1. Open PostHog `Dashboards` and create `Notebook Tracking`. +2. Add all four insights above to this dashboard. +3. Use a rolling `Last 30 days` filter for product reviews. diff --git a/package-lock.json b/package-lock.json index d5a79bb..f543f10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@ai-sdk/react": "^3.0.177", "@effect/platform": "^0.96.1", "@neondatabase/serverless": "^1.1.0", - "@ontos-ai/knowhere-sdk": "^0.4.0", + "@ontos-ai/knowhere-sdk": "^0.6.0", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", @@ -39,6 +39,7 @@ "next-themes": "^0.4.6", "pdfjs-dist": "5.4.296", "postgres": "^3.4.9", + "posthog-js": "^1.386.8", "react": "19.2.4", "react-dom": "19.2.4", "react-markdown": "^10.1.0", @@ -2251,7 +2252,9 @@ } }, "node_modules/@ontos-ai/knowhere-sdk": { - "version": "0.4.0", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@ontos-ai/knowhere-sdk/-/knowhere-sdk-0.6.0.tgz", + "integrity": "sha512-YqDgg+BKwGp7NepD50aquhEOmUgdDAQxpoyttqh0d+4tWjOgkSo74vEGiJVxt8v+LAcA/5CmxPeghX1Hx0v3wQ==", "license": "MIT", "dependencies": { "axios": "^1.15.2", @@ -2310,6 +2313,21 @@ "node": ">=18" } }, + "node_modules/@posthog/core": { + "version": "1.32.5", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.32.5.tgz", + "integrity": "sha512-Vmr6LZms7QEP1ljfTRRBVtLkeEHMrTmLECt0FXPLwUIgPwxOgvBdAYwRMWyP13WuN/XQweo1tJ9J1DxHNAF3qg==", + "license": "MIT", + "dependencies": { + "@posthog/types": "^1.386.4" + } + }, + "node_modules/@posthog/types": { + "version": "1.386.4", + "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.386.4.tgz", + "integrity": "sha512-UgE9+5+wkZg7yc2MpW5G32/6zYW8fAbXJkG8WGnrb4X8OKWzNa9cWeNCd82vKok+TSoRpM4qiYQFtBSpJS1dsw==", + "license": "MIT" + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "license": "MIT" @@ -5114,6 +5132,17 @@ "node": ">=6.6.0" } }, + "node_modules/core-js": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "license": "MIT" @@ -7567,6 +7596,12 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==", + "license": "MIT" + }, "node_modules/figures": { "version": "6.1.0", "dev": true, @@ -11462,6 +11497,22 @@ "url": "https://github.com/sponsors/porsager" } }, + "node_modules/posthog-js": { + "version": "1.386.8", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.386.8.tgz", + "integrity": "sha512-L4cJD1sleUULE7K4mZQI5wpS24NHGtNmFw1rPHruc579unVzHuooHiVDEHp/qyNnhNxtNE6alBTiVORbYnRTSw==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@posthog/core": "^1.32.4", + "@posthog/types": "^1.386.4", + "core-js": "^3.38.1", + "dompurify": "^3.3.2", + "fflate": "^0.4.8", + "preact": "^10.28.2", + "query-selector-shadow-dom": "^1.0.1", + "web-vitals": "^5.1.0" + } + }, "node_modules/powershell-utils": { "version": "0.1.0", "dev": true, @@ -11473,6 +11524,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/preact": { + "version": "10.29.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.2.tgz", + "integrity": "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -11623,6 +11684,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/query-selector-shadow-dom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", + "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "dev": true, @@ -14917,6 +14984,12 @@ "node": ">= 8" } }, + "node_modules/web-vitals": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.3.0.tgz", + "integrity": "sha512-q6LWsLatGYZp5VGBIOvbTj6JBV2nOmC8KvWztXBmwJcfFAzhwKwbOxhUH306XY3CcaZDUlSmSuNPBsCn0bFu+g==", + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "8.0.1", "dev": true, @@ -15268,111 +15341,6 @@ "type": "github", "url": "https://github.com/sponsors/wooorm" } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz", - "integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz", - "integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz", - "integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz", - "integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz", - "integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz", - "integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz", - "integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } } } } diff --git a/package.json b/package.json index a0f56ea..b199855 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "next-themes": "^0.4.6", "pdfjs-dist": "5.4.296", "postgres": "^3.4.9", + "posthog-js": "^1.386.8", "react": "19.2.4", "react-dom": "19.2.4", "react-markdown": "^10.1.0", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index afbda8b..042c71b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import { ThemeProvider } from "@/components/theme-provider"; import { appMetadata } from "@/lib/app-metadata"; +import { PostHogProvider } from "@/providers/posthog-provider"; import "./globals.css"; const geistSans = Geist({ @@ -34,7 +35,7 @@ export default function RootLayout({ enableSystem disableTransitionOnChange > - {children} + {children} diff --git a/src/components/chat-composer.test.ts b/src/components/chat-composer.test.ts index 7e893a1..2afaa47 100644 --- a/src/components/chat-composer.test.ts +++ b/src/components/chat-composer.test.ts @@ -4,11 +4,21 @@ import { cleanup, render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, describe, expect, it, vi } from "vitest"; +const mocks = vi.hoisted(() => ({ + trackNotebookAssistantQuestionSubmitted: vi.fn(), +})); + +vi.mock("@/lib/posthog", () => ({ + trackNotebookAssistantQuestionSubmitted: + mocks.trackNotebookAssistantQuestionSubmitted, +})); + import { ChatComposer } from "./chat-composer"; describe("ChatComposer", () => { afterEach(() => { cleanup(); + vi.clearAllMocks(); }); it("sends trimmed input and clears the composer", async () => { @@ -24,6 +34,13 @@ describe("ChatComposer", () => { await user.click(screen.getByRole("button", { name: "Send message" })); expect(onSend).toHaveBeenCalledWith("Summarize this document"); + expect(mocks.trackNotebookAssistantQuestionSubmitted).toHaveBeenCalledWith({ + context: undefined, + threadId: null, + selectedSourcesCount: 0, + sourceCountSnapshot: 0, + messageLength: "Summarize this document".length, + }); expect((input as HTMLTextAreaElement).value).toBe(""); }); diff --git a/src/components/chat-composer.tsx b/src/components/chat-composer.tsx index 5b75e95..c2ed9ab 100644 --- a/src/components/chat-composer.tsx +++ b/src/components/chat-composer.tsx @@ -11,12 +11,20 @@ import { Send } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/spinner"; import { Textarea } from "@/components/ui/textarea"; +import { + trackNotebookAssistantQuestionSubmitted, + type AnalyticsContext, +} from "@/lib/posthog"; const chatComposerId = "chat-composer"; export type ChatComposerProps = { readonly isDisabled?: boolean; readonly isSending?: boolean; + readonly activeThreadId?: string | null; + readonly analyticsContext?: AnalyticsContext; + readonly sourceCountSnapshot?: number; + readonly selectedSourcesCount?: number; readonly onLoginClick?: () => void; readonly onSend?: (text: string) => void; }; @@ -24,6 +32,10 @@ export type ChatComposerProps = { export function ChatComposer({ isDisabled = false, isSending = false, + activeThreadId = null, + analyticsContext, + sourceCountSnapshot = 0, + selectedSourcesCount = 0, onLoginClick, onSend, }: ChatComposerProps): ReactElement { @@ -44,6 +56,13 @@ export function ChatComposer({ function handleSend(): void { if (!canSend) return; + void trackNotebookAssistantQuestionSubmitted({ + context: analyticsContext, + threadId: activeThreadId, + selectedSourcesCount, + sourceCountSnapshot, + messageLength: trimmedInput.length, + }); onSend?.(trimmedInput); setInput(""); } diff --git a/src/components/chat-panel.tsx b/src/components/chat-panel.tsx index 47c8c5e..49f0865 100644 --- a/src/components/chat-panel.tsx +++ b/src/components/chat-panel.tsx @@ -31,6 +31,7 @@ import type { ChatMessageView, ChatThreadView, } from "@/domains/chat/types"; +import type { AnalyticsContext } from "@/lib/posthog"; export type ChatPanelProps = { messages: ChatMessageView[]; @@ -44,6 +45,8 @@ export type ChatPanelProps = { onLoginClick?: () => void; sourceTitlesByDocumentId?: Readonly>; sourceCount?: number; + selectedSourcesCount?: number; + analyticsContext?: AnalyticsContext; isSending?: boolean; isHistoryLoading?: boolean; isCreatingThread?: boolean; @@ -66,6 +69,8 @@ export function ChatPanel({ onLoginClick, sourceTitlesByDocumentId = {}, sourceCount = 0, + selectedSourcesCount = 0, + analyticsContext, isSending = false, isHistoryLoading = false, isCreatingThread = false, @@ -207,6 +212,10 @@ export function ChatPanel({ diff --git a/src/components/chunks-panel.tsx b/src/components/chunks-panel.tsx index 7d5b853..1e3b280 100644 --- a/src/components/chunks-panel.tsx +++ b/src/components/chunks-panel.tsx @@ -43,6 +43,7 @@ import { useSourceOriginalPreviewWarmup } from "@/components/source-original-pre import { sourceOriginalPreviewModel } from "@/components/source-original-preview-model"; import type { ParsedChunkView } from "@/domains/chunks/types"; import type { SourceOriginalFileView, SourceView } from "@/domains/sources/types"; +import type { AnalyticsContext } from "@/lib/posthog"; import { cn } from "@/lib/utils"; export type ChunksPanelProps = { @@ -60,6 +61,8 @@ export type ChunksPanelProps = { onLoadAllChunks?: () => void; onLoginClick?: () => void; onSourceUploaded?: (source: SourceView) => void; + analyticsContext?: AnalyticsContext; + sourceCountSnapshot?: number; }; type ChunkDisplayMode = "list" | "tree"; @@ -85,6 +88,8 @@ export function ChunksPanel({ onLoadAllChunks, onLoginClick, onSourceUploaded, + analyticsContext, + sourceCountSnapshot = 0, }: Partial = {}) { const originalPreviewCacheKey = selectedSourceFile?.url ?? null; const isOriginalPreviewAvailable = @@ -362,6 +367,8 @@ export function ChunksPanel({ ) ) : isTreeModeVisible ? ( @@ -967,11 +974,15 @@ function truncateTreeLabel(value: string): string { } function EmptySourceUploadState({ + analyticsContext, onLoginClick, onSourceUploaded, + sourceCountSnapshot = 0, }: { + readonly analyticsContext?: import("@/lib/posthog").AnalyticsContext; readonly onLoginClick?: () => void; readonly onSourceUploaded?: (source: SourceView) => void; + readonly sourceCountSnapshot?: number; }): ReactNode { if (onLoginClick) { return ( @@ -999,6 +1010,8 @@ function EmptySourceUploadState({ return ( (