@agent-native/core 0.77.24 → 0.78.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +20 -0
- package/corpus/core/docs/content/template-analytics.md +5 -0
- package/corpus/core/docs/content/tracking.md +45 -0
- package/corpus/core/package.json +3 -1
- package/corpus/core/src/agent/types.ts +22 -0
- package/corpus/core/src/client/AgentPanel.tsx +34 -18
- package/corpus/core/src/client/AssistantChat.tsx +40 -0
- package/corpus/core/src/client/agent-chat.ts +173 -0
- package/corpus/core/src/client/analytics.ts +141 -5
- package/corpus/core/src/client/composer/TiptapComposer.tsx +316 -60
- package/corpus/core/src/client/composer/extensions/MentionReference.tsx +3 -0
- package/corpus/core/src/client/composer/types.ts +22 -0
- package/corpus/core/src/client/index.ts +14 -0
- package/corpus/core/src/client/session-replay.ts +800 -0
- package/corpus/core/src/db/schema.ts +12 -0
- package/corpus/core/src/deploy/build.ts +75 -0
- package/corpus/core/src/private-blob/index.ts +18 -0
- package/corpus/core/src/private-blob/registry.ts +241 -0
- package/corpus/core/src/private-blob/types.ts +55 -0
- package/corpus/core/src/scripts/utils.ts +34 -6
- package/corpus/core/src/server/agent-chat-plugin.ts +10 -0
- package/corpus/core/src/server/email.ts +5 -0
- package/corpus/core/src/server/index.ts +1 -0
- package/corpus/core/src/styles/agent-native.css +24 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +1 -1
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +43 -0
- package/corpus/templates/analytics/AGENTS.md +32 -1
- package/corpus/templates/analytics/DEVELOPING.md +9 -1
- package/corpus/templates/analytics/actions/delete-dashboard-report-subscription.ts +24 -0
- package/corpus/templates/analytics/actions/get-session-replay-events.ts +50 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +27 -0
- package/corpus/templates/analytics/actions/list-dashboard-report-subscriptions.ts +24 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +59 -0
- package/corpus/templates/analytics/actions/navigate.ts +15 -5
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -2
- package/corpus/templates/analytics/actions/save-dashboard-report-subscription.ts +36 -0
- package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +60 -0
- package/corpus/templates/analytics/actions/view-screen.ts +50 -2
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +51 -2
- package/corpus/templates/analytics/app/components/layout/Header.tsx +4 -1
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +14 -2
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +6 -6
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +35 -6
- package/corpus/templates/analytics/app/i18n-data.ts +1119 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +437 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +11 -12
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +43 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +136 -43
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-filters.ts +26 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +450 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +419 -0
- package/corpus/templates/analytics/app/routes/overview.tsx +13 -6
- package/corpus/templates/analytics/app/routes/sessions.$recordingId.tsx +10 -0
- package/corpus/templates/analytics/app/routes/sessions._index.tsx +10 -0
- package/corpus/templates/analytics/changelog/2026-06-25-analytics-now-opens-directly-to-ask-instead-of-the-old-overv.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-25-dashboard-charts-now-show-reliable-blue-placement-lines-whil.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboards-can-now-send-scheduled-daily-email-reports-from-t.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-session-replay-browser-sessions-can-now-be-reviewed-from.md +6 -0
- package/corpus/templates/analytics/package.json +5 -2
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +131 -0
- package/corpus/templates/analytics/scripts/seed-session-replay.ts +198 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +140 -27
- package/corpus/templates/analytics/server/db/index.ts +12 -0
- package/corpus/templates/analytics/server/db/schema.ts +131 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +281 -0
- package/corpus/templates/analytics/server/handlers/sql-query.ts +8 -348
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +66 -0
- package/corpus/templates/analytics/server/jobs/session-replay-retention.ts +29 -0
- package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +365 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +473 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +405 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +63 -18
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +98 -26
- package/corpus/templates/analytics/server/lib/session-replay.ts +1645 -0
- package/corpus/templates/analytics/server/plugins/auth.ts +1 -1
- package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +47 -0
- package/corpus/templates/analytics/server/plugins/db.ts +200 -0
- package/corpus/templates/analytics/server/plugins/session-replay-retention-jobs.ts +41 -0
- package/corpus/templates/analytics/server/routes/api/analytics/replay.options.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/analytics/replay.post.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/dashboard-reports/run.post.ts +61 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/chunks/[seq].get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/events.get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/manifest.get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId].get.ts +1 -0
- package/corpus/templates/analytics/server/routes/api/session-replay/recordings.get.ts +1 -0
- package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +10 -2
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +14 -7
- package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +12 -0
- package/corpus/templates/assets/AGENTS.md +18 -3
- package/corpus/templates/assets/actions/_image-model-default.ts +24 -0
- package/corpus/templates/assets/actions/dismiss-variant-slots.ts +15 -22
- package/corpus/templates/assets/actions/generate-asset.ts +30 -23
- package/corpus/templates/assets/actions/generate-image-batch.ts +76 -29
- package/corpus/templates/assets/actions/generate-image.ts +46 -30
- package/corpus/templates/assets/actions/generate-video.ts +18 -3
- package/corpus/templates/assets/actions/list-assets.ts +39 -8
- package/corpus/templates/assets/actions/list-libraries.ts +24 -2
- package/corpus/templates/assets/actions/navigate.ts +1 -1
- package/corpus/templates/assets/actions/open-asset-picker.ts +38 -1
- package/corpus/templates/assets/actions/refresh-generation-run.ts +10 -0
- package/corpus/templates/assets/actions/save-generated-image.ts +21 -19
- package/corpus/templates/assets/actions/variant-slots.ts +131 -28
- package/corpus/templates/assets/actions/view-screen.ts +9 -0
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +425 -0
- package/corpus/templates/assets/app/components/layout/Header.tsx +3 -1
- package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -6
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +47 -21
- package/corpus/templates/assets/app/i18n-data.ts +1815 -0
- package/corpus/templates/assets/app/lib/libraries.ts +6 -0
- package/corpus/templates/assets/app/lib/picker-chat-handoff.ts +71 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
- package/corpus/templates/assets/app/routes/audit.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1557 -2164
- package/corpus/templates/assets/app/routes/brand-kits._index.tsx +7 -191
- package/corpus/templates/assets/app/routes/brand-kits.tsx +1 -5
- package/corpus/templates/assets/app/routes/libraries.tsx +4 -4
- package/corpus/templates/assets/app/routes/library.$id.tsx +9 -7
- package/corpus/templates/assets/app/routes/library.tsx +1454 -175
- package/corpus/templates/assets/server/db/index.ts +1 -1
- package/corpus/templates/assets/server/lib/generation.ts +86 -15
- package/corpus/templates/assets/server/plugins/agent-chat.ts +196 -0
- package/corpus/templates/assets/server/plugins/db.ts +5 -0
- package/corpus/templates/assets/shared/api.ts +6 -3
- package/dist/agent/types.d.ts +21 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +6 -2
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +10 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +6 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +15 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +32 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +113 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/analytics.d.ts +22 -3
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +96 -4
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +2 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +228 -56
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/composer/extensions/MentionReference.d.ts.map +1 -1
- package/dist/client/composer/extensions/MentionReference.js +3 -0
- package/dist/client/composer/extensions/MentionReference.js.map +1 -1
- package/dist/client/composer/types.d.ts +21 -0
- package/dist/client/composer/types.d.ts.map +1 -1
- package/dist/client/composer/types.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/session-replay.d.ts +41 -0
- package/dist/client/session-replay.d.ts.map +1 -0
- package/dist/client/session-replay.js +595 -0
- package/dist/client/session-replay.js.map +1 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/schema.d.ts +3 -1
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +6 -2
- package/dist/db/schema.js.map +1 -1
- package/dist/deploy/build.d.ts +8 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +66 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +8 -8
- package/dist/private-blob/index.d.ts +3 -0
- package/dist/private-blob/index.d.ts.map +1 -0
- package/dist/private-blob/index.js +2 -0
- package/dist/private-blob/index.js.map +1 -0
- package/dist/private-blob/registry.d.ts +10 -0
- package/dist/private-blob/registry.d.ts.map +1 -0
- package/dist/private-blob/registry.js +152 -0
- package/dist/private-blob/registry.js.map +1 -0
- package/dist/private-blob/types.d.ts +51 -0
- package/dist/private-blob/types.d.ts.map +1 -0
- package/dist/private-blob/types.js +2 -0
- package/dist/private-blob/types.js.map +1 -0
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/scripts/utils.d.ts +4 -4
- package/dist/scripts/utils.d.ts.map +1 -1
- package/dist/scripts/utils.js +30 -6
- package/dist/scripts/utils.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +5 -0
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/email.d.ts +2 -0
- package/dist/server/email.d.ts.map +1 -1
- package/dist/server/email.js +3 -0
- package/dist/server/email.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +24 -0
- package/docs/content/template-analytics.md +5 -0
- package/docs/content/tracking.md +45 -0
- package/package.json +3 -1
- package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +0 -209
|
@@ -72,15 +72,46 @@ details live in `.agents/skills/`.
|
|
|
72
72
|
it as an extension instead and tell the user why.
|
|
73
73
|
- Use framework sharing and access helpers for dashboards, analyses, and saved
|
|
74
74
|
resources.
|
|
75
|
+
- Dashboard email reports live in SQL via the
|
|
76
|
+
`dashboard-report-subscriptions` actions. They send daily snapshots scoped to
|
|
77
|
+
the exact user/org context that created the subscription with saved URL
|
|
78
|
+
filters; do not hand-wire custom email routes around that action surface.
|
|
79
|
+
Report PNGs are Playwright captures of the real dashboard route in
|
|
80
|
+
`reportScreenshot=1` mode, authenticated by a short-lived embed-session token
|
|
81
|
+
and embedded inline in email with a CID image. Netlify builds emit a scheduled
|
|
82
|
+
trigger plus a background worker from
|
|
83
|
+
`scripts/emit-netlify-dashboard-report-cron.ts`, using a per-deploy internal
|
|
84
|
+
token and disabling the in-process interval scheduler on Netlify to avoid
|
|
85
|
+
duplicate sends. External cron callers can still sweep due reports by POSTing
|
|
86
|
+
`/api/dashboard-reports/run` with
|
|
87
|
+
`Authorization: Bearer $DASHBOARD_REPORTS_CRON_SECRET`. PNG rendering uses
|
|
88
|
+
local Chrome in development and `playwright-core` plus
|
|
89
|
+
`@sparticuz/chromium-min` in serverless runtimes; set
|
|
90
|
+
`DASHBOARD_REPORT_CHROMIUM_PACK_URL` only when overriding the default
|
|
91
|
+
Chromium pack location.
|
|
75
92
|
|
|
76
93
|
## Application State
|
|
77
94
|
|
|
78
95
|
- `navigation` exposes current dashboard, analysis, source, chart, and selected
|
|
79
96
|
context.
|
|
80
97
|
- `navigate` moves the user to the relevant analytics view, including
|
|
81
|
-
`view="catalog"` for the template catalog
|
|
98
|
+
`view="catalog"` for the template catalog and `view="sessions"` for session
|
|
99
|
+
replay.
|
|
82
100
|
- Use `view-screen` when the active dashboard/chart context is unclear.
|
|
83
101
|
|
|
102
|
+
## Session Replay
|
|
103
|
+
|
|
104
|
+
- `/sessions` lists scoped first-party session recordings. Filters live in the
|
|
105
|
+
URL (`range`, `app`, `q`) and are mirrored through application state so
|
|
106
|
+
the agent sees the same list the user sees.
|
|
107
|
+
- `/sessions/:recordingId` loads summary/timeline data through
|
|
108
|
+
`get-session-replay-summary` and fetches rrweb payloads only through scoped
|
|
109
|
+
chunk routes. Do not expose storage/provider URLs, raw chunk table access, or
|
|
110
|
+
unscoped replay blobs in UI, actions, dashboards, docs, or prompts.
|
|
111
|
+
- Dashboard rows that include `recording_id` should link to
|
|
112
|
+
`/sessions/:recordingId`; rows that only include `session_id` can link to a
|
|
113
|
+
filtered `/sessions` search.
|
|
114
|
+
|
|
84
115
|
## Dashboard Template Catalog
|
|
85
116
|
|
|
86
117
|
- To build or extend a LARGE first-party dashboard, prefer `compose-dashboard`:
|
|
@@ -148,12 +148,20 @@ When a user asks for a **chart, metrics view, or data breakdown** → add it to
|
|
|
148
148
|
## Build & Dev Commands
|
|
149
149
|
|
|
150
150
|
```bash
|
|
151
|
-
pnpm dev # Start dev server (frontend + backend, port 8080)
|
|
151
|
+
pnpm dev # Start dev server with local SQLite (frontend + backend, port 8080)
|
|
152
152
|
pnpm build # Production build
|
|
153
153
|
pnpm typecheck # TypeScript validation
|
|
154
154
|
pnpm test # Run Vitest tests
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
+
`pnpm dev` pins `DATABASE_URL` to `file:./data/app.db` so local work does not
|
|
158
|
+
accidentally touch a production database from `.env`. To intentionally test
|
|
159
|
+
against another local database, set `ANALYTICS_DATABASE_URL` for that command:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
ANALYTICS_DATABASE_URL=postgres://localhost/analytics_dev pnpm dev
|
|
163
|
+
```
|
|
164
|
+
|
|
157
165
|
## Creating Scripts
|
|
158
166
|
|
|
159
167
|
```typescript
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
import { deleteDashboardReportSubscription } from "../server/lib/dashboard-report-subscriptions";
|
|
9
|
+
|
|
10
|
+
export default defineAction({
|
|
11
|
+
description:
|
|
12
|
+
"Delete a daily email report subscription for an analytics dashboard.",
|
|
13
|
+
schema: z.object({
|
|
14
|
+
id: z.string().describe("Subscription ID to delete"),
|
|
15
|
+
}),
|
|
16
|
+
http: { method: "DELETE" },
|
|
17
|
+
run: async (args) => {
|
|
18
|
+
const email = getRequestUserEmail();
|
|
19
|
+
if (!email) throw new Error("no authenticated user");
|
|
20
|
+
const orgId = getRequestOrgId() || null;
|
|
21
|
+
await deleteDashboardReportSubscription(args.id, { email, orgId });
|
|
22
|
+
return { id: args.id, success: true };
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
import { getSessionReplayEvents } from "../server/lib/session-replay.js";
|
|
9
|
+
|
|
10
|
+
function resolveScope() {
|
|
11
|
+
const userEmail = getRequestUserEmail();
|
|
12
|
+
if (!userEmail) throw new Error("no authenticated user");
|
|
13
|
+
return { userEmail, orgId: getRequestOrgId() || null };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default defineAction({
|
|
17
|
+
description:
|
|
18
|
+
"Read sanitized replay events for a first-party Analytics session recording. Results are capped and never expose storage provider URLs or raw chunk table access.",
|
|
19
|
+
schema: z.object({
|
|
20
|
+
recordingId: z.string().describe("The session_recordings id"),
|
|
21
|
+
startSeq: z.coerce
|
|
22
|
+
.number()
|
|
23
|
+
.int()
|
|
24
|
+
.min(0)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("Optional first chunk sequence to include"),
|
|
27
|
+
endSeq: z.coerce
|
|
28
|
+
.number()
|
|
29
|
+
.int()
|
|
30
|
+
.min(0)
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Optional last chunk sequence to include"),
|
|
33
|
+
limit: z.coerce
|
|
34
|
+
.number()
|
|
35
|
+
.int()
|
|
36
|
+
.min(1)
|
|
37
|
+
.max(10000)
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Maximum replay events to return"),
|
|
40
|
+
}),
|
|
41
|
+
http: { method: "GET" },
|
|
42
|
+
readOnly: true,
|
|
43
|
+
run: async (args) => {
|
|
44
|
+
return getSessionReplayEvents(args.recordingId, resolveScope(), {
|
|
45
|
+
startSeq: args.startSeq,
|
|
46
|
+
endSeq: args.endSeq,
|
|
47
|
+
limit: args.limit,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
import { getSessionReplaySummary } from "../server/lib/session-replay.js";
|
|
9
|
+
|
|
10
|
+
function resolveScope() {
|
|
11
|
+
const userEmail = getRequestUserEmail();
|
|
12
|
+
if (!userEmail) throw new Error("no authenticated user");
|
|
13
|
+
return { userEmail, orgId: getRequestOrgId() || null };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default defineAction({
|
|
17
|
+
description:
|
|
18
|
+
"Get a scoped summary for one first-party Analytics session replay recording. Does not return raw chunks or storage references.",
|
|
19
|
+
schema: z.object({
|
|
20
|
+
recordingId: z.string().describe("The session_recordings id"),
|
|
21
|
+
}),
|
|
22
|
+
http: { method: "GET" },
|
|
23
|
+
readOnly: true,
|
|
24
|
+
run: async (args) => {
|
|
25
|
+
return getSessionReplaySummary(args.recordingId, resolveScope());
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
import { listDashboardReportSubscriptions } from "../server/lib/dashboard-report-subscriptions";
|
|
9
|
+
|
|
10
|
+
export default defineAction({
|
|
11
|
+
description:
|
|
12
|
+
"List daily email report subscriptions for an analytics dashboard.",
|
|
13
|
+
schema: z.object({
|
|
14
|
+
dashboardId: z.string().optional().describe("Optional dashboard ID filter"),
|
|
15
|
+
}),
|
|
16
|
+
http: { method: "GET" },
|
|
17
|
+
readOnly: true,
|
|
18
|
+
run: async (args) => {
|
|
19
|
+
const email = getRequestUserEmail();
|
|
20
|
+
if (!email) throw new Error("no authenticated user");
|
|
21
|
+
const orgId = getRequestOrgId() || null;
|
|
22
|
+
return listDashboardReportSubscriptions({ email, orgId }, args.dashboardId);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
import { listSessionRecordings } from "../server/lib/session-replay.js";
|
|
9
|
+
|
|
10
|
+
function resolveScope() {
|
|
11
|
+
const userEmail = getRequestUserEmail();
|
|
12
|
+
if (!userEmail) throw new Error("no authenticated user");
|
|
13
|
+
return { userEmail, orgId: getRequestOrgId() || null };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default defineAction({
|
|
17
|
+
description:
|
|
18
|
+
"List first-party Analytics session replay recordings accessible to the current user/org. Returns scoped recording summaries only, not raw replay chunks.",
|
|
19
|
+
schema: z.object({
|
|
20
|
+
query: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe(
|
|
24
|
+
"Optional broad search across recording, session, visitor, URL, app, and template fields",
|
|
25
|
+
),
|
|
26
|
+
app: z.string().optional().describe("Optional app filter"),
|
|
27
|
+
template: z.string().optional().describe("Optional template filter"),
|
|
28
|
+
sessionId: z.string().optional().describe("Optional analytics session id"),
|
|
29
|
+
userId: z.string().optional().describe("Optional signed-in user id"),
|
|
30
|
+
anonymousId: z.string().optional().describe("Optional anonymous user id"),
|
|
31
|
+
path: z.string().optional().describe("Optional exact path filter"),
|
|
32
|
+
from: z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Inclusive started_at lower bound as an ISO timestamp"),
|
|
36
|
+
to: z
|
|
37
|
+
.string()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Inclusive started_at upper bound as an ISO timestamp"),
|
|
40
|
+
minDurationMs: z.coerce
|
|
41
|
+
.number()
|
|
42
|
+
.int()
|
|
43
|
+
.min(0)
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Only include recordings at least this long"),
|
|
46
|
+
hasErrors: z.boolean().optional().describe("Only recordings with errors"),
|
|
47
|
+
hasRageClicks: z
|
|
48
|
+
.boolean()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Only recordings with detected rage clicks"),
|
|
51
|
+
status: z.enum(["active", "completed"]).optional(),
|
|
52
|
+
limit: z.coerce.number().int().min(1).max(100).optional().default(50),
|
|
53
|
+
}),
|
|
54
|
+
http: { method: "GET" },
|
|
55
|
+
readOnly: true,
|
|
56
|
+
run: async (args) => {
|
|
57
|
+
return listSessionRecordings(resolveScope(), args);
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -4,13 +4,13 @@ import { z } from "zod";
|
|
|
4
4
|
|
|
5
5
|
export default defineAction({
|
|
6
6
|
description:
|
|
7
|
-
"Navigate the UI to a specific view or
|
|
7
|
+
"Navigate the UI to a specific view, dashboard, analysis, extension, or Analytics session recording. For filter changes (dashboard filter query params like ?f_date=... or session filters like ?range=30d&q=signup), use the framework-level `set-search-params` tool instead of this action.",
|
|
8
8
|
schema: z.object({
|
|
9
9
|
view: z
|
|
10
10
|
.string()
|
|
11
11
|
.optional()
|
|
12
12
|
.describe(
|
|
13
|
-
"View to navigate to (
|
|
13
|
+
"View to navigate to (ask, adhoc, analyses, extensions, sessions, catalog, data-dictionary, data-sources, settings)",
|
|
14
14
|
),
|
|
15
15
|
dashboardId: z
|
|
16
16
|
.string()
|
|
@@ -24,6 +24,10 @@ export default defineAction({
|
|
|
24
24
|
.string()
|
|
25
25
|
.optional()
|
|
26
26
|
.describe("Extension ID to open (used with view=extensions)"),
|
|
27
|
+
recordingId: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Session recording id to open (used with view=sessions)"),
|
|
27
31
|
}),
|
|
28
32
|
http: false,
|
|
29
33
|
run: async (args) => {
|
|
@@ -31,14 +35,15 @@ export default defineAction({
|
|
|
31
35
|
!args.view &&
|
|
32
36
|
!args.dashboardId &&
|
|
33
37
|
!args.analysisId &&
|
|
34
|
-
!args.extensionId
|
|
38
|
+
!args.extensionId &&
|
|
39
|
+
!args.recordingId
|
|
35
40
|
) {
|
|
36
41
|
throw new Error(
|
|
37
|
-
"At least --view, --dashboardId, --analysisId, or --
|
|
42
|
+
"At least --view, --dashboardId, --analysisId, --extensionId, or --recordingId is required.",
|
|
38
43
|
);
|
|
39
44
|
}
|
|
40
45
|
const nav: Record<string, string> = {};
|
|
41
|
-
if (args.view) nav.view = args.view;
|
|
46
|
+
if (args.view) nav.view = args.view === "overview" ? "ask" : args.view;
|
|
42
47
|
if (args.dashboardId) {
|
|
43
48
|
nav.dashboardId = args.dashboardId;
|
|
44
49
|
if (!args.view) nav.view = "adhoc";
|
|
@@ -51,6 +56,10 @@ export default defineAction({
|
|
|
51
56
|
nav.extensionId = args.extensionId;
|
|
52
57
|
if (!args.view) nav.view = "extensions";
|
|
53
58
|
}
|
|
59
|
+
if (args.recordingId) {
|
|
60
|
+
nav.recordingId = args.recordingId;
|
|
61
|
+
if (!args.view) nav.view = "sessions";
|
|
62
|
+
}
|
|
54
63
|
await writeAppState("navigate", nav);
|
|
55
64
|
|
|
56
65
|
const parts: string[] = [];
|
|
@@ -58,6 +67,7 @@ export default defineAction({
|
|
|
58
67
|
if (nav.dashboardId) parts.push(`dashboard:${nav.dashboardId}`);
|
|
59
68
|
if (nav.analysisId) parts.push(`analysis:${nav.analysisId}`);
|
|
60
69
|
if (nav.extensionId) parts.push(`extension:${nav.extensionId}`);
|
|
70
|
+
if (nav.recordingId) parts.push(`recording:${nav.recordingId}`);
|
|
61
71
|
return `Navigating to ${parts.join(" ")}`;
|
|
62
72
|
},
|
|
63
73
|
});
|
|
@@ -15,12 +15,12 @@ function resolveScope() {
|
|
|
15
15
|
|
|
16
16
|
export default defineAction({
|
|
17
17
|
description:
|
|
18
|
-
"Query first-party analytics events recorded through this app's analytics collector endpoint (/track). Use this for questions about app/site traffic, product events, template/app usage, conversions, and other first-party
|
|
18
|
+
"Query first-party analytics events recorded through this app's analytics collector endpoint (/track) and session replay summaries recorded through /api/analytics/replay. Use this for questions about app/site traffic, product events, template/app usage, conversions, session recordings, and other first-party data collected by this analytics app. Use source-specific actions such as BigQuery, GA4, Mixpanel, PostHog, or Amplitude when the user asks for those sources or the relevant data lives there. SQL may read analytics_events and session_recordings only; session_replay_chunks is intentionally unavailable, and reads are automatically scoped to the current user/org.",
|
|
19
19
|
schema: z.object({
|
|
20
20
|
sql: z
|
|
21
21
|
.string()
|
|
22
22
|
.describe(
|
|
23
|
-
"Read-only SQL over analytics_events, e.g. SELECT event_name, COUNT(*) AS events FROM analytics_events WHERE timestamp >= '2026-05-01T04:00:00Z' AND timestamp < '2026-05-02T04:00:00Z' GROUP BY event_name ORDER BY events DESC",
|
|
23
|
+
"Read-only SQL over analytics_events and session_recordings, e.g. SELECT event_name, COUNT(*) AS events FROM analytics_events WHERE timestamp >= '2026-05-01T04:00:00Z' AND timestamp < '2026-05-02T04:00:00Z' GROUP BY event_name ORDER BY events DESC",
|
|
24
24
|
),
|
|
25
25
|
}),
|
|
26
26
|
http: false,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
import { saveDashboardReportSubscription } from "../server/lib/dashboard-report-subscriptions";
|
|
9
|
+
|
|
10
|
+
export default defineAction({
|
|
11
|
+
description:
|
|
12
|
+
"Create or update a daily email report subscription for an analytics dashboard.",
|
|
13
|
+
schema: z.object({
|
|
14
|
+
id: z.string().optional().describe("Existing subscription ID to update"),
|
|
15
|
+
dashboardId: z.string().describe("Dashboard ID to email"),
|
|
16
|
+
name: z.string().optional().describe("Human-readable subscription name"),
|
|
17
|
+
recipients: z.array(z.string().email()).min(1).describe("Email recipients"),
|
|
18
|
+
filters: z
|
|
19
|
+
.record(z.string(), z.string())
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Dashboard URL filters to apply, including f_ keys"),
|
|
22
|
+
timeOfDay: z
|
|
23
|
+
.string()
|
|
24
|
+
.regex(/^([01]\d|2[0-3]):([0-5]\d)$/)
|
|
25
|
+
.describe("Local send time in HH:mm"),
|
|
26
|
+
timezone: z.string().describe("IANA timezone for the send time"),
|
|
27
|
+
enabled: z.boolean().default(true),
|
|
28
|
+
}),
|
|
29
|
+
http: { method: "POST" },
|
|
30
|
+
run: async (args) => {
|
|
31
|
+
const email = getRequestUserEmail();
|
|
32
|
+
if (!email) throw new Error("no authenticated user");
|
|
33
|
+
const orgId = getRequestOrgId() || null;
|
|
34
|
+
return saveDashboardReportSubscription(args, { email, orgId });
|
|
35
|
+
},
|
|
36
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
getRequestOrgId,
|
|
4
|
+
getRequestUserEmail,
|
|
5
|
+
} from "@agent-native/core/server";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
import { sendDashboardReportSubscription } from "../server/lib/dashboard-report";
|
|
9
|
+
import {
|
|
10
|
+
claimDashboardReportSubscription,
|
|
11
|
+
getDashboardReportSubscription,
|
|
12
|
+
markDashboardReportResult,
|
|
13
|
+
} from "../server/lib/dashboard-report-subscriptions";
|
|
14
|
+
|
|
15
|
+
export default defineAction({
|
|
16
|
+
description:
|
|
17
|
+
"Send a dashboard email report subscription immediately to its saved recipients.",
|
|
18
|
+
schema: z.object({
|
|
19
|
+
id: z.string().describe("Subscription ID to send now"),
|
|
20
|
+
}),
|
|
21
|
+
http: { method: "POST" },
|
|
22
|
+
needsApproval: true,
|
|
23
|
+
run: async (args) => {
|
|
24
|
+
const email = getRequestUserEmail();
|
|
25
|
+
if (!email) throw new Error("no authenticated user");
|
|
26
|
+
const orgId = getRequestOrgId() || null;
|
|
27
|
+
const sub = await getDashboardReportSubscription(args.id, {
|
|
28
|
+
email,
|
|
29
|
+
orgId,
|
|
30
|
+
});
|
|
31
|
+
if (!sub) {
|
|
32
|
+
throw Object.assign(new Error("Report subscription not found"), {
|
|
33
|
+
statusCode: 404,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const claimed = await claimDashboardReportSubscription(sub.id, {
|
|
38
|
+
email,
|
|
39
|
+
orgId,
|
|
40
|
+
});
|
|
41
|
+
if (!claimed) {
|
|
42
|
+
throw Object.assign(new Error("Report subscription is already sending"), {
|
|
43
|
+
statusCode: 409,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const result = await sendDashboardReportSubscription(claimed);
|
|
49
|
+
await markDashboardReportResult(claimed, "success");
|
|
50
|
+
return { id: claimed.id, success: true, ...result };
|
|
51
|
+
} catch (err: any) {
|
|
52
|
+
await markDashboardReportResult(
|
|
53
|
+
claimed,
|
|
54
|
+
"error",
|
|
55
|
+
err?.message ?? String(err),
|
|
56
|
+
);
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
});
|
|
@@ -12,10 +12,19 @@ import { z } from "zod";
|
|
|
12
12
|
import { listDashboardCatalog } from "../server/lib/dashboard-catalog";
|
|
13
13
|
import { getAnalysis, getDashboard } from "../server/lib/dashboards-store";
|
|
14
14
|
import { listAnalyticsPublicKeys } from "../server/lib/first-party-analytics.js";
|
|
15
|
+
import {
|
|
16
|
+
getSessionReplaySummary,
|
|
17
|
+
listSessionRecordings,
|
|
18
|
+
replayRangeToIso,
|
|
19
|
+
type ReplayRange,
|
|
20
|
+
} from "../server/lib/session-replay.js";
|
|
21
|
+
|
|
22
|
+
const SESSION_FILTER_KEYS = new Set(["range", "app", "q"]);
|
|
23
|
+
const REPLAY_RANGES = new Set(["24h", "7d", "30d", "90d", "all"]);
|
|
15
24
|
|
|
16
25
|
export default defineAction({
|
|
17
26
|
description:
|
|
18
|
-
"See what the user is currently looking at on screen. Returns the current view, dashboard config (if on a dashboard), analysis details (if on an analysis), and any active URL filter params. Prefer the auto-included <current-screen> block; call this only when you need a refreshed snapshot.",
|
|
27
|
+
"See what the user is currently looking at on screen. Returns the current view, dashboard config (if on a dashboard), analysis details (if on an analysis), Analytics session replay context, and any active URL filter params. Prefer the auto-included <current-screen> block; call this only when you need a refreshed snapshot.",
|
|
19
28
|
schema: z.object({}),
|
|
20
29
|
http: false,
|
|
21
30
|
readOnly: true,
|
|
@@ -38,6 +47,13 @@ export default defineAction({
|
|
|
38
47
|
const activeFilters: Record<string, string> = {};
|
|
39
48
|
for (const [k, v] of Object.entries(url.searchParams)) {
|
|
40
49
|
if (k.startsWith("f_") && v) activeFilters[k] = v;
|
|
50
|
+
if (
|
|
51
|
+
url.pathname?.startsWith("/sessions") &&
|
|
52
|
+
SESSION_FILTER_KEYS.has(k) &&
|
|
53
|
+
v
|
|
54
|
+
) {
|
|
55
|
+
activeFilters[k] = v;
|
|
56
|
+
}
|
|
41
57
|
}
|
|
42
58
|
if (Object.keys(activeFilters).length > 0) {
|
|
43
59
|
screen.activeFilters = activeFilters;
|
|
@@ -107,8 +123,34 @@ export default defineAction({
|
|
|
107
123
|
if (nav?.extensionId) {
|
|
108
124
|
screen.extensionId = nav.extensionId;
|
|
109
125
|
}
|
|
126
|
+
} else if (nav?.view === "sessions") {
|
|
127
|
+
screen.page = nav?.recordingId ? "session-replay-detail" : "sessions";
|
|
128
|
+
const email = getRequestUserEmail();
|
|
129
|
+
if (email) {
|
|
130
|
+
const scope = { userEmail: email, orgId: getRequestOrgId() || null };
|
|
131
|
+
try {
|
|
132
|
+
if (nav?.recordingId) {
|
|
133
|
+
screen.sessionReplay = await getSessionReplaySummary(
|
|
134
|
+
nav.recordingId,
|
|
135
|
+
scope,
|
|
136
|
+
);
|
|
137
|
+
} else {
|
|
138
|
+
const params = url?.searchParams ?? {};
|
|
139
|
+
const sessions = await listSessionRecordings(scope, {
|
|
140
|
+
from:
|
|
141
|
+
replayRangeToIso(readReplayRange(params.range)) ?? undefined,
|
|
142
|
+
app: params.app,
|
|
143
|
+
query: params.q,
|
|
144
|
+
limit: 25,
|
|
145
|
+
});
|
|
146
|
+
screen.sessionReplays = sessions;
|
|
147
|
+
}
|
|
148
|
+
} catch (error: any) {
|
|
149
|
+
screen.sessionReplayError = error?.message || String(error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
110
152
|
} else if (nav?.view === "overview" || nav?.view === "home" || !nav?.view) {
|
|
111
|
-
screen.page = "
|
|
153
|
+
screen.page = "ask";
|
|
112
154
|
} else if (nav?.view === "ask") {
|
|
113
155
|
screen.page = "ask";
|
|
114
156
|
} else if (nav?.view === "query") {
|
|
@@ -161,3 +203,9 @@ export default defineAction({
|
|
|
161
203
|
return JSON.stringify(screen, null, 2);
|
|
162
204
|
},
|
|
163
205
|
});
|
|
206
|
+
|
|
207
|
+
function readReplayRange(value: unknown): ReplayRange {
|
|
208
|
+
return typeof value === "string" && REPLAY_RANGES.has(value)
|
|
209
|
+
? (value as ReplayRange)
|
|
210
|
+
: "30d";
|
|
211
|
+
}
|
|
@@ -906,7 +906,12 @@ export function SqlChart({
|
|
|
906
906
|
const placeholderPadY = isMetric ? "py-2" : "py-8";
|
|
907
907
|
|
|
908
908
|
if (!loadData || isLoading) {
|
|
909
|
-
return
|
|
909
|
+
return (
|
|
910
|
+
<Skeleton
|
|
911
|
+
data-dashboard-report-loading="true"
|
|
912
|
+
className={`w-full flex-1 ${placeholderMinH}`}
|
|
913
|
+
/>
|
|
914
|
+
);
|
|
910
915
|
}
|
|
911
916
|
|
|
912
917
|
if (error) {
|
|
@@ -1131,6 +1136,35 @@ function renderDeltaCell(value: unknown): ReactNode {
|
|
|
1131
1136
|
);
|
|
1132
1137
|
}
|
|
1133
1138
|
|
|
1139
|
+
function rowString(
|
|
1140
|
+
row: Record<string, unknown>,
|
|
1141
|
+
keys: string[],
|
|
1142
|
+
): string | null {
|
|
1143
|
+
for (const key of keys) {
|
|
1144
|
+
const value = row[key];
|
|
1145
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
1146
|
+
}
|
|
1147
|
+
return null;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
export function sessionReplayHref(row: Record<string, unknown>): string | null {
|
|
1151
|
+
const recordingId = rowString(row, [
|
|
1152
|
+
"recording_id",
|
|
1153
|
+
"session_recording_id",
|
|
1154
|
+
"sessionRecordingId",
|
|
1155
|
+
]);
|
|
1156
|
+
if (recordingId) return `/sessions/${encodeURIComponent(recordingId)}`;
|
|
1157
|
+
|
|
1158
|
+
const sessionId = rowString(row, ["session_id", "sessionId"]);
|
|
1159
|
+
if (!sessionId) return null;
|
|
1160
|
+
const params = new URLSearchParams({ range: "all", q: sessionId });
|
|
1161
|
+
return `/sessions?${params.toString()}`;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function isSessionColumn(key: string): boolean {
|
|
1165
|
+
return key === "session_id" || key === "sessionId";
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1134
1168
|
function TableRenderer({
|
|
1135
1169
|
rows,
|
|
1136
1170
|
panel,
|
|
@@ -1303,6 +1337,9 @@ function TableRenderer({
|
|
|
1303
1337
|
col.format === "delta"
|
|
1304
1338
|
? renderDeltaCell(raw)
|
|
1305
1339
|
: formatCell(raw, col.format);
|
|
1340
|
+
const replayHref = isSessionColumn(col.key)
|
|
1341
|
+
? sessionReplayHref(row)
|
|
1342
|
+
: null;
|
|
1306
1343
|
return (
|
|
1307
1344
|
<td
|
|
1308
1345
|
key={col.key}
|
|
@@ -1310,7 +1347,19 @@ function TableRenderer({
|
|
|
1310
1347
|
numeric ? "text-right tabular-nums" : ""
|
|
1311
1348
|
}`}
|
|
1312
1349
|
>
|
|
1313
|
-
{
|
|
1350
|
+
{replayHref ? (
|
|
1351
|
+
<span className="inline-flex flex-col gap-0.5">
|
|
1352
|
+
<span>{content}</span>
|
|
1353
|
+
<a
|
|
1354
|
+
href={replayHref}
|
|
1355
|
+
className="text-xs font-medium text-primary hover:underline"
|
|
1356
|
+
>
|
|
1357
|
+
{t("sessions.watchReplay")}
|
|
1358
|
+
</a>
|
|
1359
|
+
</span>
|
|
1360
|
+
) : (
|
|
1361
|
+
content
|
|
1362
|
+
)}
|
|
1314
1363
|
</td>
|
|
1315
1364
|
);
|
|
1316
1365
|
})}
|
|
@@ -13,11 +13,13 @@ import {
|
|
|
13
13
|
} from "./HeaderActions";
|
|
14
14
|
|
|
15
15
|
const pageTitleKeys: Record<string, string> = {
|
|
16
|
-
"/": "navigation.
|
|
16
|
+
"/": "navigation.ask",
|
|
17
|
+
"/ask": "navigation.ask",
|
|
17
18
|
"/data-sources": "navigation.dataSources",
|
|
18
19
|
"/data-dictionary": "navigation.dataDictionary",
|
|
19
20
|
"/catalog": "navigation.templateCatalog",
|
|
20
21
|
"/analyses": "navigation.analyses",
|
|
22
|
+
"/sessions": "navigation.sessions",
|
|
21
23
|
"/dashboards/explorer": "navigation.explorer",
|
|
22
24
|
"/team": "navigation.team",
|
|
23
25
|
"/settings": "navigation.settings",
|
|
@@ -34,6 +36,7 @@ function resolveTitle(pathname: string, t: (key: string) => string): ReactNode {
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
if (pathname.startsWith("/analyses/")) return t("navigation.analyses");
|
|
39
|
+
if (pathname.startsWith("/sessions/")) return t("navigation.sessions");
|
|
37
40
|
|
|
38
41
|
return t("navigation.brand");
|
|
39
42
|
}
|
|
@@ -31,12 +31,14 @@ export function Layout({ children }: LayoutProps) {
|
|
|
31
31
|
const location = useLocation();
|
|
32
32
|
const navigate = useNavigate();
|
|
33
33
|
const t = useT();
|
|
34
|
+
const reportScreenshot =
|
|
35
|
+
new URLSearchParams(location.search).get("reportScreenshot") === "1";
|
|
34
36
|
|
|
35
37
|
// Analytics has two distinct "primary resources" — dashboards
|
|
36
38
|
// (`/dashboards/:id`, legacy `/adhoc/:id`) and ad-hoc analyses
|
|
37
39
|
// (`/analyses/:id`). Each binds the chat to that artifact so a dashboard
|
|
38
40
|
// chat doesn't leak into a different analysis (and vice versa). The list
|
|
39
|
-
// pages and
|
|
41
|
+
// pages and Ask leave scope null so general data questions still work.
|
|
40
42
|
const analyticsScope = useMemo(() => {
|
|
41
43
|
const dashMatch = location.pathname.match(
|
|
42
44
|
/^\/(?:adhoc|dashboards)\/([^/]+)/,
|
|
@@ -84,7 +86,7 @@ export function Layout({ children }: LayoutProps) {
|
|
|
84
86
|
const chatHomeHandoffActive = useAgentChatHomeHandoff({
|
|
85
87
|
storageKey: "analytics",
|
|
86
88
|
activePath: location.pathname,
|
|
87
|
-
enabled: !isAskRoute,
|
|
89
|
+
enabled: !isAskRoute && !reportScreenshot,
|
|
88
90
|
});
|
|
89
91
|
useAgentChatHomeHandoffLinks({
|
|
90
92
|
storageKey: "analytics",
|
|
@@ -101,6 +103,16 @@ export function Layout({ children }: LayoutProps) {
|
|
|
101
103
|
return <>{children}</>;
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
if (reportScreenshot) {
|
|
107
|
+
return (
|
|
108
|
+
<HeaderActionsProvider>
|
|
109
|
+
<main className="min-h-screen bg-background p-6 text-foreground md:p-8">
|
|
110
|
+
{children}
|
|
111
|
+
</main>
|
|
112
|
+
</HeaderActionsProvider>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
104
116
|
const contentFrame = (
|
|
105
117
|
<div className="flex h-full flex-1 flex-col overflow-hidden">
|
|
106
118
|
<MobileNav />
|