@agent-native/core 0.98.2 → 0.98.3

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.
Files changed (64) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +10 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +2 -2
  5. package/corpus/core/src/client/index.ts +2 -0
  6. package/corpus/core/src/client/settings/DemoModeSection.tsx +6 -6
  7. package/corpus/core/src/client/settings/SettingsPanel.tsx +31 -211
  8. package/corpus/core/src/client/settings/agent-settings-search.ts +219 -0
  9. package/corpus/core/src/client/settings/index.ts +4 -0
  10. package/corpus/core/src/demo/actions/toggle-demo-mode.ts +2 -2
  11. package/corpus/core/src/demo/config.ts +6 -6
  12. package/corpus/core/src/demo/redact.ts +18 -76
  13. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +21 -4
  14. package/corpus/templates/analytics/AGENTS.md +6 -6
  15. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +429 -244
  16. package/corpus/templates/analytics/app/components/layout/command-palette-search.ts +93 -0
  17. package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +2 -1
  18. package/corpus/templates/analytics/app/lib/demo-chart-trend.ts +46 -33
  19. package/corpus/templates/analytics/app/pages/Settings.tsx +8 -45
  20. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +205 -70
  21. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +32 -16
  22. package/corpus/templates/analytics/app/pages/settings/settings-search.ts +126 -0
  23. package/corpus/templates/analytics/changelog/2026-07-12-command-menu-loading-placeholders-now-stay-below-available-r.md +6 -0
  24. package/corpus/templates/analytics/changelog/2026-07-12-command-menu-search-now-prioritizes-the-best-match.md +6 -0
  25. package/corpus/templates/analytics/changelog/2026-07-12-demo-charts-preserve-source-movement.md +6 -0
  26. package/corpus/templates/analytics/changelog/2026-07-12-replay-storage-settings-stay-readable-at-narrow-widths.md +6 -0
  27. package/corpus/templates/analytics/changelog/2026-07-12-session-replays-preserve-recorded-styling-and-keep-malformed.md +6 -0
  28. package/dist/agent/production-agent.js +2 -2
  29. package/dist/agent/production-agent.js.map +1 -1
  30. package/dist/client/index.d.ts +1 -1
  31. package/dist/client/index.d.ts.map +1 -1
  32. package/dist/client/index.js +1 -1
  33. package/dist/client/index.js.map +1 -1
  34. package/dist/client/settings/DemoModeSection.d.ts +3 -3
  35. package/dist/client/settings/DemoModeSection.js +4 -4
  36. package/dist/client/settings/DemoModeSection.js.map +1 -1
  37. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  38. package/dist/client/settings/SettingsPanel.js +41 -191
  39. package/dist/client/settings/SettingsPanel.js.map +1 -1
  40. package/dist/client/settings/agent-settings-search.d.ts +16 -0
  41. package/dist/client/settings/agent-settings-search.d.ts.map +1 -0
  42. package/dist/client/settings/agent-settings-search.js +178 -0
  43. package/dist/client/settings/agent-settings-search.js.map +1 -0
  44. package/dist/client/settings/index.d.ts +1 -0
  45. package/dist/client/settings/index.d.ts.map +1 -1
  46. package/dist/client/settings/index.js +1 -0
  47. package/dist/client/settings/index.js.map +1 -1
  48. package/dist/collab/routes.d.ts +2 -2
  49. package/dist/collab/struct-routes.d.ts +1 -1
  50. package/dist/demo/actions/toggle-demo-mode.js +2 -2
  51. package/dist/demo/actions/toggle-demo-mode.js.map +1 -1
  52. package/dist/demo/config.js +6 -6
  53. package/dist/demo/config.js.map +1 -1
  54. package/dist/demo/redact.d.ts +7 -5
  55. package/dist/demo/redact.d.ts.map +1 -1
  56. package/dist/demo/redact.js +19 -54
  57. package/dist/demo/redact.js.map +1 -1
  58. package/dist/notifications/routes.d.ts +1 -1
  59. package/dist/progress/routes.d.ts +1 -1
  60. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  61. package/dist/resources/handlers.d.ts +3 -3
  62. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  63. package/dist/server/transcribe-voice.d.ts +1 -1
  64. package/package.json +2 -2
@@ -19,17 +19,30 @@ import {
19
19
  IconHistory,
20
20
  IconLanguage,
21
21
  IconRefresh,
22
+ IconSettings,
22
23
  } from "@tabler/icons-react";
23
24
  import { useQuery } from "@tanstack/react-query";
24
25
  import { useTheme } from "next-themes";
25
- import { useEffect, useState, useCallback } from "react";
26
+ import {
27
+ Children,
28
+ cloneElement,
29
+ Fragment,
30
+ isValidElement,
31
+ useCallback,
32
+ useEffect,
33
+ useMemo,
34
+ useRef,
35
+ useState,
36
+ type ComponentProps,
37
+ type ReactElement,
38
+ type ReactNode,
39
+ } from "react";
26
40
  import { useNavigate } from "react-router";
27
41
 
28
42
  import {
29
43
  CommandDialog,
30
44
  CommandInput,
31
45
  CommandList,
32
- CommandEmpty,
33
46
  CommandGroup,
34
47
  CommandItem,
35
48
  } from "@/components/ui/command";
@@ -41,10 +54,19 @@ import {
41
54
  } from "@/components/ui/dialog";
42
55
  import { Label } from "@/components/ui/label";
43
56
  import { Skeleton } from "@/components/ui/skeleton";
57
+ import { useReplayStorageStatus } from "@/hooks/use-replay-storage-status";
44
58
  import { dashboards } from "@/pages/adhoc/registry";
59
+ import {
60
+ buildAnalyticsGeneralSettingsSearchEntries,
61
+ buildAnalyticsSettingsCommandItems,
62
+ } from "@/pages/settings/settings-search";
45
63
 
46
64
  import changelog from "../../../CHANGELOG.md?raw";
47
- import { commandPaletteKeywords } from "./command-palette-search";
65
+ import {
66
+ commandPaletteKeywords,
67
+ rankCommandPaletteEntries,
68
+ uniqueCommandItems,
69
+ } from "./command-palette-search";
48
70
 
49
71
  interface SavedConfig {
50
72
  id: string;
@@ -108,11 +130,10 @@ function CommandLoadingGroup({
108
130
  return (
109
131
  <CommandGroup heading={heading} forceMount>
110
132
  {Array.from({ length: rows }).map((_, index) => (
111
- <CommandItem
133
+ <div
112
134
  key={`${heading}-loading-${index}`}
113
- disabled
114
- forceMount
115
- value={`${heading} loading ${index + 1}`}
135
+ aria-hidden="true"
136
+ className="flex items-center px-2 py-3"
116
137
  >
117
138
  <Skeleton className="me-2 h-4 w-4 shrink-0 rounded-sm" />
118
139
  <Skeleton
@@ -120,15 +141,76 @@ function CommandLoadingGroup({
120
141
  loadingRowWidths[index % loadingRowWidths.length]
121
142
  }`}
122
143
  />
123
- </CommandItem>
144
+ </div>
124
145
  ))}
125
146
  </CommandGroup>
126
147
  );
127
148
  }
128
149
 
150
+ type CommandGroupElement = ReactElement<ComponentProps<typeof CommandGroup>>;
151
+ type CommandItemElement = ReactElement<ComponentProps<typeof CommandItem>>;
152
+
153
+ function isCommandGroupElement(node: ReactNode): node is CommandGroupElement {
154
+ return isValidElement(node) && node.type === CommandGroup;
155
+ }
156
+
157
+ function isCommandItemElement(node: ReactNode): node is CommandItemElement {
158
+ return isValidElement(node) && node.type === CommandItem;
159
+ }
160
+
161
+ function RankedCommandGroups({
162
+ search,
163
+ emptyLabel,
164
+ showEmpty,
165
+ children,
166
+ }: {
167
+ search: string;
168
+ emptyLabel: string;
169
+ showEmpty: boolean;
170
+ children: ReactNode;
171
+ }) {
172
+ const groups = Children.toArray(children).filter(isCommandGroupElement);
173
+ const query = search.trim();
174
+ if (!query) return <>{groups}</>;
175
+
176
+ const rankedGroups = groups
177
+ .map((group, index) => {
178
+ const items = Children.toArray(group.props.children).filter(
179
+ isCommandItemElement,
180
+ );
181
+ const rankedItems = rankCommandPaletteEntries(items, query, (item) => ({
182
+ value: item.props.value ?? "",
183
+ keywords: item.props.keywords,
184
+ }));
185
+ if (rankedItems.length === 0) return null;
186
+ return {
187
+ index,
188
+ score: rankedItems[0].score,
189
+ group: cloneElement(
190
+ group,
191
+ undefined,
192
+ rankedItems.map(({ entry }) => entry),
193
+ ),
194
+ };
195
+ })
196
+ .filter((group): group is NonNullable<typeof group> => group !== null)
197
+ .sort((a, b) => b.score - a.score || a.index - b.index);
198
+
199
+ return (
200
+ <Fragment>
201
+ {showEmpty && rankedGroups.length === 0 ? (
202
+ <div role="presentation" className="py-6 text-center text-sm">
203
+ {emptyLabel}
204
+ </div>
205
+ ) : null}
206
+ {rankedGroups.map(({ group }) => group)}
207
+ </Fragment>
208
+ );
209
+ }
210
+
129
211
  async function fetchSavedConfigs(): Promise<SavedConfig[]> {
130
212
  const rows = await callAction("list-explorer-configs", {}, { method: "GET" });
131
- return (Array.isArray(rows) ? rows : []) as SavedConfig[];
213
+ return uniqueCommandItems((Array.isArray(rows) ? rows : []) as SavedConfig[]);
132
214
  }
133
215
 
134
216
  async function fetchExplorerDashboards(): Promise<ExplorerDashboard[]> {
@@ -143,13 +225,15 @@ async function fetchExplorerDashboards(): Promise<ExplorerDashboard[]> {
143
225
  result && typeof result === "object" && "dashboards" in result
144
226
  ? (result as { dashboards: unknown[] }).dashboards
145
227
  : [];
146
- return (Array.isArray(dashboards) ? dashboards : [])
147
- .filter((d: any) => d && d.name)
148
- .map((d: any) => ({
149
- id: d.id,
150
- name: d.name,
151
- hiddenAt: typeof d.hiddenAt === "string" ? d.hiddenAt : null,
152
- }));
228
+ return uniqueCommandItems(
229
+ (Array.isArray(dashboards) ? dashboards : [])
230
+ .filter((d: any) => d && d.name)
231
+ .map((d: any) => ({
232
+ id: d.id,
233
+ name: d.name,
234
+ hiddenAt: typeof d.hiddenAt === "string" ? d.hiddenAt : null,
235
+ })),
236
+ );
153
237
  }
154
238
 
155
239
  async function fetchSqlDashboards(
@@ -160,40 +244,44 @@ async function fetchSqlDashboards(
160
244
  { hidden: "all" },
161
245
  { method: "GET" },
162
246
  );
163
- return (Array.isArray(rows) ? rows : [])
164
- .filter((d: any) => d && typeof d.id === "string" && d.id.length > 0)
165
- .map((d: any) => ({
166
- id: d.id,
167
- name:
168
- typeof d.name === "string" && d.name.trim().length > 0
169
- ? d.name
170
- : t("commandPalette.untitledDashboard"),
171
- hiddenAt: typeof d.hiddenAt === "string" ? d.hiddenAt : null,
172
- }));
247
+ return uniqueCommandItems(
248
+ (Array.isArray(rows) ? rows : [])
249
+ .filter((d: any) => d && typeof d.id === "string" && d.id.length > 0)
250
+ .map((d: any) => ({
251
+ id: d.id,
252
+ name:
253
+ typeof d.name === "string" && d.name.trim().length > 0
254
+ ? d.name
255
+ : t("commandPalette.untitledDashboard"),
256
+ hiddenAt: typeof d.hiddenAt === "string" ? d.hiddenAt : null,
257
+ })),
258
+ );
173
259
  }
174
260
 
175
261
  async function fetchExtensions(): Promise<ExtensionSearchItem[]> {
176
262
  const res = await fetch(agentNativePath("/_agent-native/extensions"));
177
263
  if (!res.ok) throw new Error(`Failed to load extensions (${res.status})`);
178
264
  const data = await res.json();
179
- return (Array.isArray(data) ? data : [])
180
- .filter((extension: any) => {
181
- return (
182
- extension &&
183
- typeof extension.id === "string" &&
184
- extension.id.length > 0 &&
185
- typeof extension.name === "string" &&
186
- extension.name.trim().length > 0
187
- );
188
- })
189
- .map((extension: any) => ({
190
- id: extension.id,
191
- name: extension.name,
192
- description:
193
- typeof extension.description === "string"
194
- ? extension.description
195
- : undefined,
196
- }));
265
+ return uniqueCommandItems(
266
+ (Array.isArray(data) ? data : [])
267
+ .filter((extension: any) => {
268
+ return (
269
+ extension &&
270
+ typeof extension.id === "string" &&
271
+ extension.id.length > 0 &&
272
+ typeof extension.name === "string" &&
273
+ extension.name.trim().length > 0
274
+ );
275
+ })
276
+ .map((extension: any) => ({
277
+ id: extension.id,
278
+ name: extension.name,
279
+ description:
280
+ typeof extension.description === "string"
281
+ ? extension.description
282
+ : undefined,
283
+ })),
284
+ );
197
285
  }
198
286
 
199
287
  function persistThemePreference(theme: "light" | "dark") {
@@ -211,9 +299,19 @@ export function CommandPalette() {
211
299
  const [changelogOpen, setChangelogOpen] = useState(false);
212
300
  const [languageOpen, setLanguageOpen] = useState(false);
213
301
  const [searchQuery, setSearchQuery] = useState("");
302
+ const [selectedCommand, setSelectedCommand] = useState("");
303
+ const commandListRef = useRef<HTMLDivElement>(null);
214
304
  const navigate = useNavigate();
215
305
  const { resolvedTheme, setTheme } = useTheme();
216
306
  const isDark = resolvedTheme === "dark";
307
+ const replayStorageStatus = useReplayStorageStatus({ enabled: open });
308
+ const settingsCommands = useMemo(() => {
309
+ const generalEntries = buildAnalyticsGeneralSettingsSearchEntries(
310
+ t,
311
+ !!replayStorageStatus.data?.configured,
312
+ );
313
+ return buildAnalyticsSettingsCommandItems(t, generalEntries);
314
+ }, [replayStorageStatus.data?.configured, t]);
217
315
 
218
316
  const savedChartsQuery = useQuery({
219
317
  queryKey: ["explorer-configs-palette"],
@@ -252,10 +350,10 @@ export function CommandPalette() {
252
350
  const explorerDashboards = explorerDashboardsQuery.data ?? [];
253
351
  const sqlDashboards = sqlDashboardsQuery.data ?? [];
254
352
  const extensions = extensionsQuery.data ?? [];
255
- const savedChartsFetching = savedChartsQuery.isFetching;
256
- const explorerDashboardsFetching = explorerDashboardsQuery.isFetching;
257
- const sqlDashboardsFetching = sqlDashboardsQuery.isFetching;
258
- const extensionsFetching = extensionsQuery.isFetching;
353
+ const savedChartsLoading = savedChartsQuery.isLoading;
354
+ const explorerDashboardsLoading = explorerDashboardsQuery.isLoading;
355
+ const sqlDashboardsLoading = sqlDashboardsQuery.isLoading;
356
+ const extensionsLoading = extensionsQuery.isLoading;
259
357
  const asyncGroupsErrored =
260
358
  savedChartsQuery.isError ||
261
359
  explorerDashboardsQuery.isError ||
@@ -286,6 +384,13 @@ export function CommandPalette() {
286
384
  };
287
385
  }, []);
288
386
 
387
+ useEffect(() => {
388
+ const frame = requestAnimationFrame(() => {
389
+ if (commandListRef.current) commandListRef.current.scrollTop = 0;
390
+ });
391
+ return () => cancelAnimationFrame(frame);
392
+ }, [searchQuery]);
393
+
289
394
  const go = useCallback(
290
395
  (href: string) => {
291
396
  navigate(href);
@@ -295,10 +400,10 @@ export function CommandPalette() {
295
400
  );
296
401
 
297
402
  const asyncGroupsLoading =
298
- (explorerDashboardsFetching && explorerDashboards.length === 0) ||
299
- (sqlDashboardsFetching && sqlDashboards.length === 0) ||
300
- (extensionsFetching && extensions.length === 0) ||
301
- (savedChartsFetching && savedCharts.length === 0);
403
+ (explorerDashboardsLoading && explorerDashboards.length === 0) ||
404
+ (sqlDashboardsLoading && sqlDashboards.length === 0) ||
405
+ (extensionsLoading && extensions.length === 0) ||
406
+ (savedChartsLoading && savedCharts.length === 0);
302
407
  const showHiddenResults = searchQuery.trim().length > 0;
303
408
  const visibleExplorerDashboards = showHiddenResults
304
409
  ? explorerDashboards
@@ -312,26 +417,37 @@ export function CommandPalette() {
312
417
  <CommandDialog
313
418
  open={open}
314
419
  motion="instant"
420
+ commandProps={{
421
+ shouldFilter: false,
422
+ value: selectedCommand,
423
+ onValueChange: setSelectedCommand,
424
+ }}
315
425
  onOpenChange={(next) => {
316
426
  setOpen(next);
317
- if (!next) setSearchQuery("");
427
+ if (!next) {
428
+ setSearchQuery("");
429
+ setSelectedCommand("");
430
+ }
318
431
  }}
319
432
  >
320
433
  <CommandInput
321
434
  placeholder={t("commandPalette.searchPlaceholder")}
322
435
  value={searchQuery}
323
- onValueChange={setSearchQuery}
436
+ onValueChange={(nextQuery) => {
437
+ setSelectedCommand("");
438
+ setSearchQuery(nextQuery);
439
+ }}
324
440
  />
325
- <CommandList>
326
- {!asyncGroupsLoading && !asyncGroupsErrored && (
327
- <CommandEmpty>{t("commandPalette.noResults")}</CommandEmpty>
328
- )}
329
-
441
+ <CommandList ref={commandListRef}>
330
442
  {asyncGroupsErrored && (
331
- <CommandGroup heading={t("commandPalette.loadFailed")} forceMount>
443
+ <CommandGroup
444
+ key="load-error"
445
+ heading={t("commandPalette.loadFailed")}
446
+ forceMount
447
+ >
332
448
  <CommandItem
333
449
  forceMount
334
- value="retry failed command palette data"
450
+ value="action:retry-command-palette-data"
335
451
  onSelect={retryAsyncGroups}
336
452
  >
337
453
  <IconRefresh className="me-2 h-4 w-4 text-muted-foreground" />
@@ -340,213 +456,282 @@ export function CommandPalette() {
340
456
  </CommandGroup>
341
457
  )}
342
458
 
343
- {explorerDashboardsFetching && explorerDashboards.length === 0 && (
344
- <CommandLoadingGroup
345
- heading={t("commandPalette.groupExplorerDashboards")}
346
- rows={2}
347
- />
348
- )}
349
-
350
- {visibleExplorerDashboards.length > 0 && (
351
- <CommandGroup heading={t("commandPalette.groupExplorerDashboards")}>
352
- {visibleExplorerDashboards.map((d) => (
459
+ <RankedCommandGroups
460
+ search={searchQuery}
461
+ emptyLabel={t("commandPalette.noResults")}
462
+ showEmpty={!asyncGroupsLoading && !asyncGroupsErrored}
463
+ >
464
+ {visibleExplorerDashboards.length > 0 && (
465
+ <CommandGroup
466
+ key="explorer-dashboards"
467
+ heading={t("commandPalette.groupExplorerDashboards")}
468
+ >
469
+ {visibleExplorerDashboards.map((d) => (
470
+ <CommandItem
471
+ key={`ed-${d.id}`}
472
+ value={`explorer-dashboard:${d.id}:${d.name}`}
473
+ onSelect={() =>
474
+ go(`/dashboards/explorer-dashboard?id=${d.id}`)
475
+ }
476
+ keywords={commandPaletteKeywords(
477
+ d.name,
478
+ "explorer dashboard",
479
+ "dashboard",
480
+ )}
481
+ >
482
+ <IconLayoutDashboard className="me-2 h-4 w-4 text-muted-foreground" />
483
+ <span className="truncate">{d.name}</span>
484
+ {d.hiddenAt ? (
485
+ <span className="ms-2 rounded border border-border px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
486
+ {t("commandPalette.hidden")}
487
+ </span>
488
+ ) : null}
489
+ </CommandItem>
490
+ ))}
491
+ </CommandGroup>
492
+ )}
493
+
494
+ {visibleSqlDashboards.length > 0 && (
495
+ <CommandGroup
496
+ key="sql-dashboards"
497
+ heading={t("commandPalette.groupSqlDashboards")}
498
+ >
499
+ {visibleSqlDashboards.map((d) => (
500
+ <CommandItem
501
+ key={`sql-${d.id}`}
502
+ value={`sql-dashboard:${d.id}:${d.name}`}
503
+ onSelect={() => go(`/dashboards/${d.id}`)}
504
+ keywords={commandPaletteKeywords(
505
+ d.name,
506
+ "sql dashboard",
507
+ "dashboard",
508
+ )}
509
+ >
510
+ <IconLayoutDashboard className="me-2 h-4 w-4 text-muted-foreground" />
511
+ <span className="truncate">{d.name}</span>
512
+ {d.hiddenAt ? (
513
+ <span className="ms-2 rounded border border-border px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
514
+ {t("commandPalette.hidden")}
515
+ </span>
516
+ ) : null}
517
+ </CommandItem>
518
+ ))}
519
+ </CommandGroup>
520
+ )}
521
+
522
+ {extensions.length > 0 && (
523
+ <CommandGroup
524
+ key="extensions"
525
+ heading={t("commandPalette.groupExtensions")}
526
+ >
527
+ {extensions.map((extension) => (
528
+ <CommandItem
529
+ key={`extension-${extension.id}`}
530
+ value={`extension:${extension.id}:${extension.name}`}
531
+ onSelect={() =>
532
+ go(extensionPath(extension.id, extension.name))
533
+ }
534
+ keywords={commandPaletteKeywords(
535
+ extension.name,
536
+ extension.description,
537
+ "extension",
538
+ "tool",
539
+ )}
540
+ >
541
+ <IconTool className="me-2 h-4 w-4 text-muted-foreground" />
542
+ {extension.name}
543
+ </CommandItem>
544
+ ))}
545
+ </CommandGroup>
546
+ )}
547
+
548
+ <CommandGroup
549
+ key="dashboards"
550
+ heading={t("commandPalette.groupDashboards")}
551
+ >
552
+ {dashboards.map((d) => (
353
553
  <CommandItem
354
- key={`ed-${d.id}`}
355
- onSelect={() =>
356
- go(`/dashboards/explorer-dashboard?id=${d.id}`)
357
- }
358
- keywords={commandPaletteKeywords(
359
- d.name,
360
- "explorer dashboard",
361
- "dashboard",
362
- )}
554
+ key={`dash-${d.id}`}
555
+ value={`dashboard:${d.id}:${d.name}`}
556
+ onSelect={() => go(`/dashboards/${d.id}`)}
557
+ keywords={commandPaletteKeywords(d.name, "dashboard")}
363
558
  >
364
- <IconLayoutDashboard className="me-2 h-4 w-4 text-muted-foreground" />
365
- <span className="truncate">{d.name}</span>
366
- {d.hiddenAt ? (
367
- <span className="ms-2 rounded border border-border px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
368
- {t("commandPalette.hidden")}
369
- </span>
370
- ) : null}
559
+ <IconFlask className="me-2 h-4 w-4 text-muted-foreground" />
560
+ {d.name}
371
561
  </CommandItem>
372
562
  ))}
373
563
  </CommandGroup>
564
+
565
+ <CommandGroup key="tools" heading={t("commandPalette.groupTools")}>
566
+ {defaultTools
567
+ .filter((tool) => tool.id !== "agents" || canManageOrg)
568
+ .map((tool) => (
569
+ <CommandItem
570
+ key={`tool-${tool.id}`}
571
+ value={`tool:${tool.id}:${t(tool.nameKey)}`}
572
+ onSelect={() => go(tool.href)}
573
+ keywords={commandPaletteKeywords(
574
+ t(tool.nameKey),
575
+ "tool",
576
+ ...tool.keywords,
577
+ )}
578
+ >
579
+ <IconTool className="me-2 h-4 w-4 text-muted-foreground" />
580
+ {t(tool.nameKey)}
581
+ </CommandItem>
582
+ ))}
583
+ </CommandGroup>
584
+
585
+ {showHiddenResults && (
586
+ <CommandGroup key="settings" heading={t("navigation.settings")}>
587
+ {settingsCommands.map((setting) => (
588
+ <CommandItem
589
+ key={`setting-${setting.id}`}
590
+ value={`setting:${setting.id}:${setting.label}`}
591
+ onSelect={() => go(setting.href)}
592
+ keywords={commandPaletteKeywords(
593
+ setting.label,
594
+ setting.keywords,
595
+ "settings",
596
+ )}
597
+ >
598
+ <IconSettings className="me-2 h-4 w-4 text-muted-foreground" />
599
+ <span className="truncate">{setting.label}</span>
600
+ </CommandItem>
601
+ ))}
602
+ </CommandGroup>
603
+ )}
604
+
605
+ <CommandGroup
606
+ key="appearance"
607
+ heading={t("commandPalette.groupAppearance")}
608
+ >
609
+ <CommandItem
610
+ value={`appearance:language:${t("settings.languageTitle")}`}
611
+ onSelect={() => {
612
+ setOpen(false);
613
+ setLanguageOpen(true);
614
+ }}
615
+ keywords={commandPaletteKeywords(
616
+ t("settings.languageTitle"),
617
+ t("settings.languageLabel"),
618
+ "language",
619
+ "locale",
620
+ "translation",
621
+ "internationalization",
622
+ "i18n",
623
+ )}
624
+ >
625
+ <IconLanguage className="me-2 h-4 w-4 text-muted-foreground" />
626
+ {t("settings.languageTitle")}
627
+ </CommandItem>
628
+ <CommandItem
629
+ value={`appearance:theme:${
630
+ isDark
631
+ ? t("commandPalette.toggleLightMode")
632
+ : t("commandPalette.toggleDarkMode")
633
+ }`}
634
+ onSelect={() => {
635
+ const nextTheme = isDark ? "light" : "dark";
636
+ setTheme(nextTheme);
637
+ persistThemePreference(nextTheme);
638
+ }}
639
+ keywords={commandPaletteKeywords(
640
+ isDark
641
+ ? t("commandPalette.toggleLightMode")
642
+ : t("commandPalette.toggleDarkMode"),
643
+ "theme",
644
+ "dark",
645
+ "light",
646
+ "mode",
647
+ )}
648
+ >
649
+ {isDark ? (
650
+ <IconSun className="me-2 h-4 w-4 text-muted-foreground" />
651
+ ) : (
652
+ <IconMoon className="me-2 h-4 w-4 text-muted-foreground" />
653
+ )}
654
+ {isDark
655
+ ? t("commandPalette.toggleLightMode")
656
+ : t("commandPalette.toggleDarkMode")}
657
+ </CommandItem>
658
+ </CommandGroup>
659
+
660
+ <CommandGroup key="help" heading={t("commandPalette.groupHelp")}>
661
+ <CommandItem
662
+ value={`help:changelog:${t("commandPalette.whatsNew")}`}
663
+ onSelect={() => {
664
+ setOpen(false);
665
+ setChangelogOpen(true);
666
+ }}
667
+ keywords={commandPaletteKeywords(
668
+ t("commandPalette.whatsNew"),
669
+ "changelog",
670
+ "updates",
671
+ "release notes",
672
+ "changes",
673
+ )}
674
+ >
675
+ <IconHistory className="me-2 h-4 w-4 text-muted-foreground" />
676
+ {t("commandPalette.whatsNew")}
677
+ </CommandItem>
678
+ </CommandGroup>
679
+
680
+ {savedCharts.length > 0 && (
681
+ <CommandGroup
682
+ key="saved-charts"
683
+ heading={t("commandPalette.groupSavedCharts")}
684
+ >
685
+ {savedCharts.map((c) => (
686
+ <CommandItem
687
+ key={`chart-${c.id}`}
688
+ value={`saved-chart:${c.id}:${c.name}`}
689
+ onSelect={() => go(`/dashboards/explorer?config=${c.id}`)}
690
+ keywords={commandPaletteKeywords(
691
+ c.name,
692
+ "saved chart",
693
+ "chart",
694
+ )}
695
+ >
696
+ <IconChartBar className="me-2 h-4 w-4 text-muted-foreground" />
697
+ {c.name}
698
+ </CommandItem>
699
+ ))}
700
+ </CommandGroup>
701
+ )}
702
+ </RankedCommandGroups>
703
+
704
+ {explorerDashboardsLoading && explorerDashboards.length === 0 && (
705
+ <CommandLoadingGroup
706
+ key="explorer-dashboards-loading"
707
+ heading={t("commandPalette.groupExplorerDashboards")}
708
+ rows={2}
709
+ />
374
710
  )}
375
711
 
376
- {sqlDashboardsFetching && sqlDashboards.length === 0 && (
712
+ {sqlDashboardsLoading && sqlDashboards.length === 0 && (
377
713
  <CommandLoadingGroup
714
+ key="sql-dashboards-loading"
378
715
  heading={t("commandPalette.groupSqlDashboards")}
379
716
  rows={3}
380
717
  />
381
718
  )}
382
719
 
383
- {visibleSqlDashboards.length > 0 && (
384
- <CommandGroup heading={t("commandPalette.groupSqlDashboards")}>
385
- {visibleSqlDashboards.map((d) => (
386
- <CommandItem
387
- key={`sql-${d.id}`}
388
- onSelect={() => go(`/dashboards/${d.id}`)}
389
- keywords={commandPaletteKeywords(
390
- d.name,
391
- "sql dashboard",
392
- "dashboard",
393
- )}
394
- >
395
- <IconLayoutDashboard className="me-2 h-4 w-4 text-muted-foreground" />
396
- <span className="truncate">{d.name}</span>
397
- {d.hiddenAt ? (
398
- <span className="ms-2 rounded border border-border px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
399
- {t("commandPalette.hidden")}
400
- </span>
401
- ) : null}
402
- </CommandItem>
403
- ))}
404
- </CommandGroup>
405
- )}
406
-
407
- {extensionsFetching && extensions.length === 0 && (
720
+ {extensionsLoading && extensions.length === 0 && (
408
721
  <CommandLoadingGroup
722
+ key="extensions-loading"
409
723
  heading={t("commandPalette.groupExtensions")}
410
724
  rows={3}
411
725
  />
412
726
  )}
413
727
 
414
- {extensions.length > 0 && (
415
- <CommandGroup heading={t("commandPalette.groupExtensions")}>
416
- {extensions.map((extension) => (
417
- <CommandItem
418
- key={`extension-${extension.id}`}
419
- onSelect={() =>
420
- go(extensionPath(extension.id, extension.name))
421
- }
422
- keywords={commandPaletteKeywords(
423
- extension.name,
424
- extension.description,
425
- "extension",
426
- "tool",
427
- )}
428
- >
429
- <IconTool className="me-2 h-4 w-4 text-muted-foreground" />
430
- {extension.name}
431
- </CommandItem>
432
- ))}
433
- </CommandGroup>
434
- )}
435
-
436
- <CommandGroup heading={t("commandPalette.groupDashboards")}>
437
- {dashboards.map((d) => (
438
- <CommandItem
439
- key={`dash-${d.id}`}
440
- onSelect={() => go(`/dashboards/${d.id}`)}
441
- keywords={commandPaletteKeywords(d.name, "dashboard")}
442
- >
443
- <IconFlask className="me-2 h-4 w-4 text-muted-foreground" />
444
- {d.name}
445
- </CommandItem>
446
- ))}
447
- </CommandGroup>
448
-
449
- <CommandGroup heading={t("commandPalette.groupTools")}>
450
- {defaultTools
451
- .filter((tool) => tool.id !== "agents" || canManageOrg)
452
- .map((tool) => (
453
- <CommandItem
454
- key={`tool-${tool.id}`}
455
- onSelect={() => go(tool.href)}
456
- keywords={commandPaletteKeywords(
457
- t(tool.nameKey),
458
- "tool",
459
- ...tool.keywords,
460
- )}
461
- >
462
- <IconTool className="me-2 h-4 w-4 text-muted-foreground" />
463
- {t(tool.nameKey)}
464
- </CommandItem>
465
- ))}
466
- </CommandGroup>
467
-
468
- <CommandGroup heading={t("commandPalette.groupAppearance")}>
469
- <CommandItem
470
- onSelect={() => {
471
- setOpen(false);
472
- setLanguageOpen(true);
473
- }}
474
- keywords={commandPaletteKeywords(
475
- t("settings.languageTitle"),
476
- t("settings.languageLabel"),
477
- "language",
478
- "locale",
479
- "translation",
480
- "internationalization",
481
- "i18n",
482
- )}
483
- >
484
- <IconLanguage className="me-2 h-4 w-4 text-muted-foreground" />
485
- {t("settings.languageTitle")}
486
- </CommandItem>
487
- <CommandItem
488
- onSelect={() => {
489
- const nextTheme = isDark ? "light" : "dark";
490
- setTheme(nextTheme);
491
- persistThemePreference(nextTheme);
492
- }}
493
- keywords={["theme", "dark", "light", "mode"]}
494
- >
495
- {isDark ? (
496
- <IconSun className="me-2 h-4 w-4 text-muted-foreground" />
497
- ) : (
498
- <IconMoon className="me-2 h-4 w-4 text-muted-foreground" />
499
- )}
500
- {isDark
501
- ? t("commandPalette.toggleLightMode")
502
- : t("commandPalette.toggleDarkMode")}
503
- </CommandItem>
504
- </CommandGroup>
505
-
506
- <CommandGroup heading={t("commandPalette.groupHelp")}>
507
- <CommandItem
508
- onSelect={() => {
509
- setOpen(false);
510
- setChangelogOpen(true);
511
- }}
512
- keywords={commandPaletteKeywords(
513
- t("commandPalette.whatsNew"),
514
- "changelog",
515
- "updates",
516
- "release notes",
517
- "changes",
518
- )}
519
- >
520
- <IconHistory className="me-2 h-4 w-4 text-muted-foreground" />
521
- {t("commandPalette.whatsNew")}
522
- </CommandItem>
523
- </CommandGroup>
524
-
525
- {savedChartsFetching && savedCharts.length === 0 && (
728
+ {savedChartsLoading && savedCharts.length === 0 && (
526
729
  <CommandLoadingGroup
730
+ key="saved-charts-loading"
527
731
  heading={t("commandPalette.groupSavedCharts")}
528
732
  rows={2}
529
733
  />
530
734
  )}
531
-
532
- {savedCharts.length > 0 && (
533
- <CommandGroup heading={t("commandPalette.groupSavedCharts")}>
534
- {savedCharts.map((c) => (
535
- <CommandItem
536
- key={`chart-${c.id}`}
537
- onSelect={() => go(`/dashboards/explorer?config=${c.id}`)}
538
- keywords={commandPaletteKeywords(
539
- c.name,
540
- "saved chart",
541
- "chart",
542
- )}
543
- >
544
- <IconChartBar className="me-2 h-4 w-4 text-muted-foreground" />
545
- {c.name}
546
- </CommandItem>
547
- ))}
548
- </CommandGroup>
549
- )}
550
735
  </CommandList>
551
736
  </CommandDialog>
552
737
  <ChangelogDialog