@agent-native/core 0.73.0 → 0.74.0
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 +1 -1
- package/corpus/core/CHANGELOG.md +39 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/durable-background.ts +15 -5
- package/corpus/core/src/agent/production-agent.ts +55 -8
- package/corpus/core/src/client/ConnectBuilderCard.tsx +7 -7
- package/corpus/core/src/client/NewWorkspaceAppFlow.tsx +1 -1
- package/corpus/core/src/client/blocks/library/AnnotatedCodeBlock.tsx +33 -18
- package/corpus/core/src/client/blocks/library/annotation-rail.tsx +3 -4
- package/corpus/core/src/client/components/CodeRequiredDialog.tsx +5 -8
- package/corpus/core/src/server/agent-chat-plugin.ts +7 -7
- package/corpus/core/src/server/self-dispatch.ts +8 -1
- package/corpus/templates/analytics/actions/compose-dashboard.ts +34 -0
- package/corpus/templates/analytics/actions/install-dashboard-template.ts +51 -3
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +31 -8
- package/corpus/templates/analytics/changelog/2026-06-24-dashboard-filters-now-apply-consistently-to-first-party-traf.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +97 -28
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +57 -10
- package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -0
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +786 -323
- package/corpus/templates/clips/changelog/2026-06-24-settings-now-puts-what-s-new-in-a-compact-sidebar-prioritize.md +6 -0
- package/corpus/templates/plan/changelog/2026-06-24-annotated-code-callouts-now-use-numbered-gutter-markers-that.md +6 -0
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +15 -5
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +52 -8
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
- package/dist/client/ConnectBuilderCard.js +6 -6
- package/dist/client/ConnectBuilderCard.js.map +1 -1
- package/dist/client/NewWorkspaceAppFlow.js +1 -1
- package/dist/client/NewWorkspaceAppFlow.js.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/AnnotatedCodeBlock.js +14 -10
- package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
- package/dist/client/blocks/library/annotation-rail.d.ts +3 -4
- package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -1
- package/dist/client/blocks/library/annotation-rail.js +3 -4
- package/dist/client/blocks/library/annotation-rail.js.map +1 -1
- package/dist/client/components/CodeRequiredDialog.d.ts.map +1 -1
- package/dist/client/components/CodeRequiredDialog.js +2 -1
- package/dist/client/components/CodeRequiredDialog.js.map +1 -1
- package/dist/server/agent-chat-plugin.js +7 -7
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/self-dispatch.d.ts.map +1 -1
- package/dist/server/self-dispatch.js +5 -1
- package/dist/server/self-dispatch.js.map +1 -1
- package/package.json +1 -1
|
@@ -812,6 +812,24 @@ function formatCell(value: unknown, format: ColumnFormat | undefined): string {
|
|
|
812
812
|
return String(value);
|
|
813
813
|
}
|
|
814
814
|
|
|
815
|
+
export function safeDashboardLinkHref(value: unknown): string | null {
|
|
816
|
+
if (typeof value !== "string") return null;
|
|
817
|
+
const href = value.trim();
|
|
818
|
+
if (!href) return null;
|
|
819
|
+
if (href.startsWith("//")) return null;
|
|
820
|
+
|
|
821
|
+
try {
|
|
822
|
+
const parsed = href.startsWith("/")
|
|
823
|
+
? new URL(href, "https://agent-native.local")
|
|
824
|
+
: new URL(href);
|
|
825
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:"
|
|
826
|
+
? href
|
|
827
|
+
: null;
|
|
828
|
+
} catch {
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
815
833
|
function renderDeltaCell(value: unknown): ReactNode {
|
|
816
834
|
if (value == null || typeof value !== "number" || Number.isNaN(value)) {
|
|
817
835
|
return <span className="text-muted-foreground">-</span>;
|
|
@@ -983,19 +1001,24 @@ function TableRenderer({
|
|
|
983
1001
|
const href = col.linkKey
|
|
984
1002
|
? String(row[col.linkKey] ?? "")
|
|
985
1003
|
: String(raw ?? "");
|
|
1004
|
+
const safeHref = safeDashboardLinkHref(href);
|
|
986
1005
|
return (
|
|
987
1006
|
<td
|
|
988
1007
|
key={col.key}
|
|
989
1008
|
className="py-1.5 px-2 whitespace-nowrap"
|
|
990
1009
|
>
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1010
|
+
{safeHref ? (
|
|
1011
|
+
<a
|
|
1012
|
+
href={safeHref}
|
|
1013
|
+
target="_blank"
|
|
1014
|
+
rel="noopener noreferrer"
|
|
1015
|
+
className="text-primary hover:underline"
|
|
1016
|
+
>
|
|
1017
|
+
{formatted}
|
|
1018
|
+
</a>
|
|
1019
|
+
) : (
|
|
1020
|
+
formatted
|
|
1021
|
+
)}
|
|
999
1022
|
</td>
|
|
1000
1023
|
);
|
|
1001
1024
|
}
|
|
@@ -8,12 +8,30 @@
|
|
|
8
8
|
"label": "Time range",
|
|
9
9
|
"default": "90d",
|
|
10
10
|
"options": [
|
|
11
|
-
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
{
|
|
16
|
-
|
|
11
|
+
{
|
|
12
|
+
"value": "7d",
|
|
13
|
+
"label": "Last 7 days"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"value": "30d",
|
|
17
|
+
"label": "Last 30 days"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"value": "90d",
|
|
21
|
+
"label": "Last 90 days"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"value": "180d",
|
|
25
|
+
"label": "Last 180 days"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"value": "365d",
|
|
29
|
+
"label": "Last 365 days"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"value": "all",
|
|
33
|
+
"label": "All time"
|
|
34
|
+
}
|
|
17
35
|
]
|
|
18
36
|
},
|
|
19
37
|
{
|
|
@@ -22,9 +40,18 @@
|
|
|
22
40
|
"label": "Email filter",
|
|
23
41
|
"default": "all",
|
|
24
42
|
"options": [
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
|
|
43
|
+
{
|
|
44
|
+
"value": "all",
|
|
45
|
+
"label": "All users"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"value": "exclude_builder",
|
|
49
|
+
"label": "Exclude @builder.io"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"value": "only_builder",
|
|
53
|
+
"label": "Only @builder.io"
|
|
54
|
+
}
|
|
28
55
|
]
|
|
29
56
|
}
|
|
30
57
|
],
|
|
@@ -83,7 +110,7 @@
|
|
|
83
110
|
"chartType": "metric",
|
|
84
111
|
"source": "first-party",
|
|
85
112
|
"width": 1,
|
|
86
|
-
"sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template'",
|
|
113
|
+
"sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))",
|
|
87
114
|
"config": {
|
|
88
115
|
"yKey": "count",
|
|
89
116
|
"yFormatter": "number",
|
|
@@ -96,7 +123,7 @@
|
|
|
96
123
|
"chartType": "metric",
|
|
97
124
|
"source": "first-party",
|
|
98
125
|
"width": 1,
|
|
99
|
-
"sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo'",
|
|
126
|
+
"sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))",
|
|
100
127
|
"config": {
|
|
101
128
|
"yKey": "count",
|
|
102
129
|
"yFormatter": "number",
|
|
@@ -109,7 +136,7 @@
|
|
|
109
136
|
"chartType": "metric",
|
|
110
137
|
"source": "first-party",
|
|
111
138
|
"width": 1,
|
|
112
|
-
"sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command'",
|
|
139
|
+
"sql": "SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))",
|
|
113
140
|
"config": {
|
|
114
141
|
"yKey": "count",
|
|
115
142
|
"yFormatter": "number",
|
|
@@ -122,7 +149,7 @@
|
|
|
122
149
|
"chartType": "area",
|
|
123
150
|
"source": "first-party",
|
|
124
151
|
"width": 2,
|
|
125
|
-
"sql": "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
|
|
152
|
+
"sql": "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
|
|
126
153
|
"config": {
|
|
127
154
|
"xKey": "date",
|
|
128
155
|
"yKey": "count",
|
|
@@ -141,7 +168,7 @@
|
|
|
141
168
|
"chartType": "area",
|
|
142
169
|
"source": "first-party",
|
|
143
170
|
"width": 2,
|
|
144
|
-
"sql": "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
|
|
171
|
+
"sql": "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
|
|
145
172
|
"config": {
|
|
146
173
|
"xKey": "date",
|
|
147
174
|
"yKey": "count",
|
|
@@ -160,7 +187,7 @@
|
|
|
160
187
|
"chartType": "area",
|
|
161
188
|
"source": "first-party",
|
|
162
189
|
"width": 2,
|
|
163
|
-
"sql": "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
|
|
190
|
+
"sql": "SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template",
|
|
164
191
|
"config": {
|
|
165
192
|
"xKey": "date",
|
|
166
193
|
"yKey": "count",
|
|
@@ -240,17 +267,37 @@
|
|
|
240
267
|
"chartType": "table",
|
|
241
268
|
"source": "first-party",
|
|
242
269
|
"width": 2,
|
|
243
|
-
"sql": "WITH pageviews AS (SELECT COALESCE(CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') END, NULLIF(url, ''), NULLIF(path, ''), 'unknown') AS url, CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN NULLIF(url, '') LIKE 'http
|
|
270
|
+
"sql": "WITH pageviews AS (SELECT COALESCE(CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') END, NULLIF(url, ''), NULLIF(path, ''), 'unknown') AS url, CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN lower(COALESCE(NULLIF(url, ''), '')) LIKE 'http://%' OR lower(COALESCE(NULLIF(url, ''), '')) LIKE 'https://%' THEN url WHEN NULLIF(path, '') IS NOT NULL AND substr(path, 1, 1) = '/' AND substr(path, 1, 2) != '//' THEN path ELSE '' END AS href, COUNT(*) AS views, COUNT(DISTINCT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) AS users, MAX(substr(timestamp, 1, 10)) AS last_seen FROM analytics_events WHERE event_name = 'pageview' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) GROUP BY 1, 2) SELECT url, views, users, last_seen, href FROM pageviews ORDER BY views DESC LIMIT 25",
|
|
244
271
|
"config": {
|
|
245
272
|
"description": "Most-viewed URLs in the selected time range. Links open in a new tab.",
|
|
246
273
|
"sortable": true,
|
|
247
274
|
"limit": 25,
|
|
248
275
|
"columns": [
|
|
249
|
-
{
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
276
|
+
{
|
|
277
|
+
"key": "url",
|
|
278
|
+
"label": "URL",
|
|
279
|
+
"format": "link",
|
|
280
|
+
"linkKey": "href"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"key": "views",
|
|
284
|
+
"label": "Views",
|
|
285
|
+
"format": "number"
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"key": "users",
|
|
289
|
+
"label": "Users",
|
|
290
|
+
"format": "number"
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"key": "last_seen",
|
|
294
|
+
"label": "Last seen",
|
|
295
|
+
"format": "date"
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"key": "href",
|
|
299
|
+
"hidden": true
|
|
300
|
+
}
|
|
254
301
|
]
|
|
255
302
|
}
|
|
256
303
|
},
|
|
@@ -260,7 +307,7 @@
|
|
|
260
307
|
"chartType": "table",
|
|
261
308
|
"source": "first-party",
|
|
262
309
|
"width": 2,
|
|
263
|
-
"sql": "WITH clip_events AS (SELECT COALESCE(NULLIF(properties::jsonb ->> 'recording_id', ''), NULLIF(path, ''), NULLIF(url, ''), 'unknown') AS clip_key, CASE WHEN NULLIF(url, '') LIKE 'http
|
|
310
|
+
"sql": "WITH clip_events AS (SELECT COALESCE(NULLIF(properties::jsonb ->> 'recording_id', ''), NULLIF(path, ''), NULLIF(url, ''), 'unknown') AS clip_key, CASE WHEN lower(COALESCE(NULLIF(url, ''), '')) LIKE 'http://%' OR lower(COALESCE(NULLIF(url, ''), '')) LIKE 'https://%' THEN url WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN NULLIF(path, '') LIKE '/%' THEN 'https://clips.agent-native.com' || path ELSE '' END AS href, user_id, anonymous_id, timestamp FROM analytics_events WHERE event_name = 'share_view' AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))) AND ('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io')) AND COALESCE(NULLIF(properties::jsonb ->> 'surface', ''), 'clip') = 'clip'), clip_views AS (SELECT clip_key, COALESCE(MAX(NULLIF(href, '')), clip_key) AS href, COUNT(*) AS views, COUNT(DISTINCT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) AS users, MAX(substr(timestamp, 1, 10)) AS last_seen FROM clip_events GROUP BY clip_key) SELECT CASE WHEN lower(COALESCE(href, '')) LIKE 'http://%' OR lower(COALESCE(href, '')) LIKE 'https://%' THEN href ELSE clip_key END AS clip, views, users, last_seen, href FROM clip_views ORDER BY views DESC LIMIT 25",
|
|
264
311
|
"config": {
|
|
265
312
|
"description": "Most-viewed shared Clips pages in the selected time range.",
|
|
266
313
|
"sortable": true,
|
|
@@ -272,10 +319,25 @@
|
|
|
272
319
|
"format": "link",
|
|
273
320
|
"linkKey": "href"
|
|
274
321
|
},
|
|
275
|
-
{
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
322
|
+
{
|
|
323
|
+
"key": "views",
|
|
324
|
+
"label": "Views",
|
|
325
|
+
"format": "number"
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"key": "users",
|
|
329
|
+
"label": "Users",
|
|
330
|
+
"format": "number"
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"key": "last_seen",
|
|
334
|
+
"label": "Last seen",
|
|
335
|
+
"format": "date"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"key": "href",
|
|
339
|
+
"hidden": true
|
|
340
|
+
}
|
|
279
341
|
]
|
|
280
342
|
}
|
|
281
343
|
},
|
|
@@ -487,8 +549,15 @@
|
|
|
487
549
|
"config": {
|
|
488
550
|
"description": "Users (by id) driving the most referred signups over the last 90 days.",
|
|
489
551
|
"columns": [
|
|
490
|
-
{
|
|
491
|
-
|
|
552
|
+
{
|
|
553
|
+
"key": "referrer_user",
|
|
554
|
+
"label": "Referrer (user id)"
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
"key": "signups",
|
|
558
|
+
"label": "Signups",
|
|
559
|
+
"format": "number"
|
|
560
|
+
}
|
|
492
561
|
]
|
|
493
562
|
}
|
|
494
563
|
},
|
|
@@ -45,6 +45,14 @@ export interface FirstPartyMetric {
|
|
|
45
45
|
config: Record<string, unknown>;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
export interface FirstPartyDashboardFilter {
|
|
49
|
+
id: string;
|
|
50
|
+
type: "select";
|
|
51
|
+
label: string;
|
|
52
|
+
default: string;
|
|
53
|
+
options: Array<{ value: string; label: string }>;
|
|
54
|
+
}
|
|
55
|
+
|
|
48
56
|
const WINDOW_DAYS: Record<Exclude<MetricWindow, "all">, number> = {
|
|
49
57
|
"30d": 30,
|
|
50
58
|
"90d": 90,
|
|
@@ -94,6 +102,45 @@ const DASHBOARD_TIME_RANGE_FILTER =
|
|
|
94
102
|
"('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND substr(timestamp, 1, 10) >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))";
|
|
95
103
|
const DASHBOARD_EMAIL_FILTER =
|
|
96
104
|
"('{{emailFilter}}' IN ('', 'all') OR ('{{emailFilter}}' = 'exclude_builder' AND lower(coalesce(user_id, '')) NOT LIKE '%@builder.io') OR ('{{emailFilter}}' = 'only_builder' AND lower(coalesce(user_id, '')) LIKE '%@builder.io'))";
|
|
105
|
+
export const FIRST_PARTY_DASHBOARD_FILTERS: FirstPartyDashboardFilter[] = [
|
|
106
|
+
{
|
|
107
|
+
id: "timeRange",
|
|
108
|
+
type: "select",
|
|
109
|
+
label: "Time range",
|
|
110
|
+
default: "90d",
|
|
111
|
+
options: [
|
|
112
|
+
{ value: "7d", label: "Last 7 days" },
|
|
113
|
+
{ value: "30d", label: "Last 30 days" },
|
|
114
|
+
{ value: "90d", label: "Last 90 days" },
|
|
115
|
+
{ value: "180d", label: "Last 180 days" },
|
|
116
|
+
{ value: "365d", label: "Last 365 days" },
|
|
117
|
+
{ value: "all", label: "All time" },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "emailFilter",
|
|
122
|
+
type: "select",
|
|
123
|
+
label: "Email filter",
|
|
124
|
+
default: "all",
|
|
125
|
+
options: [
|
|
126
|
+
{ value: "all", label: "All users" },
|
|
127
|
+
{ value: "exclude_builder", label: "Exclude @builder.io" },
|
|
128
|
+
{ value: "only_builder", label: "Only @builder.io" },
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
export function buildFirstPartyDashboardFilters(): FirstPartyDashboardFilter[] {
|
|
134
|
+
return FIRST_PARTY_DASHBOARD_FILTERS.map((filter) => ({
|
|
135
|
+
...filter,
|
|
136
|
+
options: filter.options.map((option) => ({ ...option })),
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function usesFirstPartyDashboardFilters(sql: string): boolean {
|
|
141
|
+
return sql.includes("{{timeRange}}") || sql.includes("{{emailFilter}}");
|
|
142
|
+
}
|
|
143
|
+
|
|
97
144
|
const TOTAL_SIGNUPS_SQL = `SELECT COUNT(*) AS signups FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}`;
|
|
98
145
|
const SIGNUPS_OVER_TIME_SQL = `WITH offsets AS (SELECT (ROW_NUMBER() OVER (ORDER BY timestamp) - 1)::int AS n FROM analytics_events LIMIT 800), signup_events AS (SELECT substr(timestamp, 1, 10) AS date, ${SIGNUP_TEMPLATE_EXPR} AS template FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), bounds AS (SELECT MIN(date::date) AS start_date, MAX(date::date) AS end_date FROM signup_events), dates AS (SELECT to_char(bounds.start_date + offsets.n, 'YYYY-MM-DD') AS date FROM bounds CROSS JOIN offsets WHERE bounds.start_date IS NOT NULL AND bounds.start_date + offsets.n <= bounds.end_date), templates AS (SELECT DISTINCT template FROM signup_events), daily AS (SELECT date, template, COUNT(*) AS count FROM signup_events GROUP BY date, template) SELECT dates.date, templates.template, COALESCE(daily.count, 0) AS count FROM dates CROSS JOIN templates LEFT JOIN daily ON daily.date = dates.date AND daily.template = templates.template ORDER BY dates.date, templates.template`;
|
|
99
146
|
const ONE_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) AS user_key, ${SIGNUP_TEMPLATE_EXPR} AS template, substr(timestamp, 1, 10) AS event_date, user_id FROM analytics_events WHERE COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, '')) IS NOT NULL AND ${DASHBOARD_EMAIL_FILTER}), first_seen AS (SELECT user_key, template, MIN(event_date) AS cohort_date FROM base GROUP BY user_key, template), cohorts AS (SELECT user_key, template, cohort_date FROM first_seen WHERE cohort_date <= to_char(CURRENT_DATE - INTERVAL '1 day', 'YYYY-MM-DD') AND ('{{timeRange}}' IN ('', 'all') OR ('{{timeRange}}' = '7d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '7 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '30d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '30 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '90d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '90 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '180d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '180 days', 'YYYY-MM-DD')) OR ('{{timeRange}}' = '365d' AND cohort_date >= to_char(CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))) SELECT c.cohort_date AS date, c.template, COALESCE(COUNT(DISTINCT c.user_key) FILTER (WHERE b.event_date = to_char(c.cohort_date::date + INTERVAL '1 day', 'YYYY-MM-DD'))::float / NULLIF(COUNT(DISTINCT c.user_key), 0), 0) AS rate FROM cohorts c LEFT JOIN base b ON b.user_key = c.user_key AND b.template = c.template GROUP BY c.cohort_date, c.template ORDER BY c.cohort_date, c.template`;
|
|
@@ -217,7 +264,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
217
264
|
width: 1,
|
|
218
265
|
windowed: false,
|
|
219
266
|
buildSql: fixed(
|
|
220
|
-
|
|
267
|
+
`SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}`,
|
|
221
268
|
),
|
|
222
269
|
config: {
|
|
223
270
|
yKey: "count",
|
|
@@ -233,7 +280,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
233
280
|
width: 1,
|
|
234
281
|
windowed: false,
|
|
235
282
|
buildSql: fixed(
|
|
236
|
-
|
|
283
|
+
`SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}`,
|
|
237
284
|
),
|
|
238
285
|
config: {
|
|
239
286
|
yKey: "count",
|
|
@@ -249,7 +296,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
249
296
|
width: 1,
|
|
250
297
|
windowed: false,
|
|
251
298
|
buildSql: fixed(
|
|
252
|
-
|
|
299
|
+
`SELECT COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}`,
|
|
253
300
|
),
|
|
254
301
|
config: {
|
|
255
302
|
yKey: "count",
|
|
@@ -265,7 +312,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
265
312
|
width: 2,
|
|
266
313
|
windowed: false,
|
|
267
314
|
buildSql: fixed(
|
|
268
|
-
|
|
315
|
+
`SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
|
|
269
316
|
),
|
|
270
317
|
config: {
|
|
271
318
|
xKey: "date",
|
|
@@ -287,7 +334,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
287
334
|
width: 2,
|
|
288
335
|
windowed: false,
|
|
289
336
|
buildSql: fixed(
|
|
290
|
-
|
|
337
|
+
`SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click template' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
|
|
291
338
|
),
|
|
292
339
|
config: {
|
|
293
340
|
xKey: "date",
|
|
@@ -309,7 +356,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
309
356
|
width: 2,
|
|
310
357
|
windowed: false,
|
|
311
358
|
buildSql: fixed(
|
|
312
|
-
|
|
359
|
+
`SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'click try demo' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
|
|
313
360
|
),
|
|
314
361
|
config: {
|
|
315
362
|
xKey: "date",
|
|
@@ -331,7 +378,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
331
378
|
width: 2,
|
|
332
379
|
windowed: false,
|
|
333
380
|
buildSql: fixed(
|
|
334
|
-
|
|
381
|
+
`SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
|
|
335
382
|
),
|
|
336
383
|
config: {
|
|
337
384
|
xKey: "date",
|
|
@@ -353,7 +400,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
353
400
|
width: 2,
|
|
354
401
|
windowed: false,
|
|
355
402
|
buildSql: fixed(
|
|
356
|
-
|
|
403
|
+
`SELECT substr(timestamp, 1, 10) AS date, COALESCE(NULLIF(template, ''), 'unknown') AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'copy cli command' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY substr(timestamp, 1, 10), COALESCE(NULLIF(template, ''), 'unknown') ORDER BY date, template`,
|
|
357
404
|
),
|
|
358
405
|
config: {
|
|
359
406
|
xKey: "date",
|
|
@@ -402,7 +449,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
402
449
|
width: 2,
|
|
403
450
|
windowed: false,
|
|
404
451
|
buildSql: fixed(
|
|
405
|
-
`WITH pageviews AS (SELECT COALESCE(CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') END, NULLIF(url, ''), NULLIF(path, ''), 'unknown') AS url, CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN NULLIF(url, '') LIKE 'http
|
|
452
|
+
`WITH pageviews AS (SELECT COALESCE(CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') END, NULLIF(url, ''), NULLIF(path, ''), 'unknown') AS url, CASE WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN lower(COALESCE(NULLIF(url, ''), '')) LIKE 'http://%' OR lower(COALESCE(NULLIF(url, ''), '')) LIKE 'https://%' THEN url WHEN NULLIF(path, '') IS NOT NULL AND substr(path, 1, 1) = '/' AND substr(path, 1, 2) != '//' THEN path ELSE '' END AS href, COUNT(*) AS views, COUNT(DISTINCT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) AS users, MAX(substr(timestamp, 1, 10)) AS last_seen FROM analytics_events WHERE event_name = 'pageview' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY 1, 2) SELECT url, views, users, last_seen, href FROM pageviews ORDER BY views DESC LIMIT 25`,
|
|
406
453
|
),
|
|
407
454
|
config: {
|
|
408
455
|
description:
|
|
@@ -426,7 +473,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
426
473
|
width: 2,
|
|
427
474
|
windowed: false,
|
|
428
475
|
buildSql: fixed(
|
|
429
|
-
`WITH clip_events AS (SELECT COALESCE(NULLIF(properties::jsonb ->> 'recording_id', ''), NULLIF(path, ''), NULLIF(url, ''), 'unknown') AS clip_key, CASE WHEN NULLIF(url, '') LIKE 'http
|
|
476
|
+
`WITH clip_events AS (SELECT COALESCE(NULLIF(properties::jsonb ->> 'recording_id', ''), NULLIF(path, ''), NULLIF(url, ''), 'unknown') AS clip_key, CASE WHEN lower(COALESCE(NULLIF(url, ''), '')) LIKE 'http://%' OR lower(COALESCE(NULLIF(url, ''), '')) LIKE 'https://%' THEN url WHEN NULLIF(hostname, '') IS NOT NULL THEN 'https://' || hostname || COALESCE(NULLIF(path, ''), '/') WHEN NULLIF(path, '') LIKE '/%' THEN 'https://clips.agent-native.com' || path ELSE '' END AS href, user_id, anonymous_id, timestamp FROM analytics_events WHERE event_name = 'share_view' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND COALESCE(NULLIF(properties::jsonb ->> 'surface', ''), 'clip') = 'clip'), clip_views AS (SELECT clip_key, COALESCE(MAX(NULLIF(href, '')), clip_key) AS href, COUNT(*) AS views, COUNT(DISTINCT COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))) AS users, MAX(substr(timestamp, 1, 10)) AS last_seen FROM clip_events GROUP BY clip_key) SELECT CASE WHEN lower(COALESCE(href, '')) LIKE 'http://%' OR lower(COALESCE(href, '')) LIKE 'https://%' THEN href ELSE clip_key END AS clip, views, users, last_seen, href FROM clip_views ORDER BY views DESC LIMIT 25`,
|
|
430
477
|
),
|
|
431
478
|
config: {
|
|
432
479
|
description: "Most-viewed shared Clips pages in the selected time range.",
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
IconLayoutSidebarLeftCollapse,
|
|
16
16
|
IconLayoutSidebarLeftExpand,
|
|
17
17
|
IconPlus,
|
|
18
|
+
IconSettings,
|
|
18
19
|
} from "@tabler/icons-react";
|
|
19
20
|
import {
|
|
20
21
|
AgentSidebar,
|
|
@@ -212,6 +213,12 @@ export function LibraryLayout({ children }: LibraryLayoutProps) {
|
|
|
212
213
|
icon: IconTrash,
|
|
213
214
|
match: (p) => p.startsWith("/trash"),
|
|
214
215
|
},
|
|
216
|
+
{
|
|
217
|
+
to: "/settings",
|
|
218
|
+
label: "Settings",
|
|
219
|
+
icon: IconSettings,
|
|
220
|
+
match: (p) => p.startsWith("/settings"),
|
|
221
|
+
},
|
|
215
222
|
];
|
|
216
223
|
|
|
217
224
|
return (
|