@agent-native/toolkit 0.214.0 → 0.217.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/agent-native.eject.json +5 -0
- package/dist/app/agentkit/react/chat.d.ts.map +1 -1
- package/dist/app/agentkit/react/chat.js +2 -2
- package/dist/app/agentkit/react/chat.js.map +1 -1
- package/dist/app/agentkit/react/components.d.ts +4 -1
- package/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +69 -15
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/agentkit/react/context.d.ts +7 -1
- package/dist/app/agentkit/react/context.d.ts.map +1 -1
- package/dist/app/agentkit/react/context.js +5 -1
- package/dist/app/agentkit/react/context.js.map +1 -1
- package/dist/app/agentkit/react/index.d.ts +1 -0
- package/dist/app/agentkit/react/index.d.ts.map +1 -1
- package/dist/app/agentkit/react/index.js +1 -0
- package/dist/app/agentkit/react/index.js.map +1 -1
- package/dist/app/agentkit/react/root.d.ts +4 -1
- package/dist/app/agentkit/react/root.d.ts.map +1 -1
- package/dist/app/agentkit/react/root.js +2 -2
- package/dist/app/agentkit/react/root.js.map +1 -1
- package/dist/app/agentkit/react/styles.css +43 -6
- package/dist/app/agentkit/react/transcript-layout.d.ts +51 -0
- package/dist/app/agentkit/react/transcript-layout.d.ts.map +1 -0
- package/dist/app/agentkit/react/transcript-layout.js +34 -0
- package/dist/app/agentkit/react/transcript-layout.js.map +1 -0
- package/dist/app/auth/AuthPage.d.ts.map +1 -1
- package/dist/app/auth/AuthPage.js +33 -44
- package/dist/app/auth/AuthPage.js.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +3 -3
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/app/chat/AgentPanel.d.ts +2 -0
- package/dist/app/chat/AgentPanel.d.ts.map +1 -1
- package/dist/app/chat/AgentPanel.js +6 -2
- package/dist/app/chat/AgentPanel.js.map +1 -1
- package/dist/app/chat/chat/index.d.ts +2 -0
- package/dist/app/chat/chat/index.d.ts.map +1 -1
- package/dist/app/chat/chat/index.js.map +1 -1
- package/dist/app/chat/chat/surface-types.d.ts +7 -0
- package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
- package/dist/app/chat/chat/surface-types.js.map +1 -1
- package/dist/app/chat/chat/tool-call-display.d.ts +7 -2
- package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
- package/dist/app/chat/chat/tool-call-display.js +10 -9
- package/dist/app/chat/chat/tool-call-display.js.map +1 -1
- package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts.map +1 -1
- package/dist/app/chat/chat/widgets/DataChartRenderer.js +196 -26
- package/dist/app/chat/chat/widgets/DataChartRenderer.js.map +1 -1
- package/dist/app/chat/chat/widgets/DataChartWidget.d.ts.map +1 -1
- package/dist/app/chat/chat/widgets/DataChartWidget.js +5 -1
- package/dist/app/chat/chat/widgets/DataChartWidget.js.map +1 -1
- package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts +2 -2
- package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts.map +1 -1
- package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js +6 -4
- package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.js +1 -0
- package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.js +1 -0
- package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
- package/dist/app/i18n/catalogs/en-US.d.ts +1 -0
- package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/en-US.js +1 -0
- package/dist/app/i18n/catalogs/en-US.js.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.js +1 -0
- package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.js +1 -0
- package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.js +1 -0
- package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.js +1 -0
- package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.js +1 -0
- package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.js +1 -0
- package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.js +1 -0
- package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.js +1 -0
- package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
- package/dist/app/onboarding/GettingStartedCard.d.ts +35 -0
- package/dist/app/onboarding/GettingStartedCard.d.ts.map +1 -0
- package/dist/app/onboarding/GettingStartedCard.js +38 -0
- package/dist/app/onboarding/GettingStartedCard.js.map +1 -0
- package/dist/app/onboarding/index.d.ts +1 -0
- package/dist/app/onboarding/index.d.ts.map +1 -1
- package/dist/app/onboarding/index.js +1 -0
- package/dist/app/onboarding/index.js.map +1 -1
- package/dist/app/providers/AppProviders.d.ts.map +1 -1
- package/dist/app/providers/AppProviders.js +3 -1
- package/dist/app/providers/AppProviders.js.map +1 -1
- package/dist/app/settings/AccountSettingsCard.d.ts.map +1 -1
- package/dist/app/settings/AccountSettingsCard.js +2 -1
- package/dist/app/settings/AccountSettingsCard.js.map +1 -1
- package/dist/app/settings/index.d.ts +1 -0
- package/dist/app/settings/index.d.ts.map +1 -1
- package/dist/app/settings/index.js +1 -0
- package/dist/app/settings/index.js.map +1 -1
- package/dist/app/settings/shell/SettingsShell.d.ts +40 -1
- package/dist/app/settings/shell/SettingsShell.d.ts.map +1 -1
- package/dist/app/settings/shell/SettingsShell.js +87 -20
- package/dist/app/settings/shell/SettingsShell.js.map +1 -1
- package/dist/app/settings/shell/bridge.d.ts +5 -3
- package/dist/app/settings/shell/bridge.d.ts.map +1 -1
- package/dist/app/settings/shell/bridge.js +8 -5
- package/dist/app/settings/shell/bridge.js.map +1 -1
- package/dist/app/settings/shell/index.d.ts +2 -2
- package/dist/app/settings/shell/index.d.ts.map +1 -1
- package/dist/app/settings/shell/index.js.map +1 -1
- package/dist/app/settings/shell/registry.d.ts +9 -1
- package/dist/app/settings/shell/registry.d.ts.map +1 -1
- package/dist/app/settings/shell/registry.js +16 -0
- package/dist/app/settings/shell/registry.js.map +1 -1
- package/dist/app/settings/shell/routing.d.ts +5 -4
- package/dist/app/settings/shell/routing.d.ts.map +1 -1
- package/dist/app/settings/shell/routing.js +38 -10
- package/dist/app/settings/shell/routing.js.map +1 -1
- package/dist/app/shared/CommandMenu.d.ts +86 -6
- package/dist/app/shared/CommandMenu.d.ts.map +1 -1
- package/dist/app/shared/CommandMenu.js +303 -41
- package/dist/app/shared/CommandMenu.js.map +1 -1
- package/dist/app/shared/ConsentBanner.d.ts +9 -0
- package/dist/app/shared/ConsentBanner.d.ts.map +1 -0
- package/dist/app/shared/ConsentBanner.js +35 -0
- package/dist/app/shared/ConsentBanner.js.map +1 -0
- package/dist/composer/ComposerContextBanner.d.ts +3 -1
- package/dist/composer/ComposerContextBanner.d.ts.map +1 -1
- package/dist/composer/ComposerContextBanner.js +2 -2
- package/dist/composer/ComposerContextBanner.js.map +1 -1
- package/dist/ui/sonner.d.ts +2 -2
- package/dist/ui/sonner.d.ts.map +1 -1
- package/dist/ui/sonner.js +2 -2
- package/dist/ui/sonner.js.map +1 -1
- package/package.json +3 -3
- package/src/app/agentkit/react/chat.tsx +2 -0
- package/src/app/agentkit/react/components.interaction.spec.tsx +107 -0
- package/src/app/agentkit/react/components.spec.tsx +28 -1
- package/src/app/agentkit/react/components.tsx +153 -17
- package/src/app/agentkit/react/context.tsx +12 -0
- package/src/app/agentkit/react/index.ts +1 -0
- package/src/app/agentkit/react/lifecycle.spec.tsx +186 -16
- package/src/app/agentkit/react/root.tsx +7 -0
- package/src/app/agentkit/react/styles.css +43 -6
- package/src/app/agentkit/react/transcript-layout.spec.ts +72 -0
- package/src/app/agentkit/react/transcript-layout.ts +97 -0
- package/src/app/auth/AuthPage.attribution.spec.tsx +6 -0
- package/src/app/auth/AuthPage.interaction.spec.tsx +74 -0
- package/src/app/auth/AuthPage.tsx +32 -39
- package/src/app/chat/AgentKitAssistantChat.spec.tsx +39 -0
- package/src/app/chat/AgentKitAssistantChat.tsx +5 -0
- package/src/app/chat/AgentPanel.page-header-share.spec.tsx +101 -0
- package/src/app/chat/AgentPanel.tsx +14 -2
- package/src/app/chat/chat/index.ts +2 -0
- package/src/app/chat/chat/surface-types.ts +10 -0
- package/src/app/chat/chat/tool-call-display.spec.tsx +83 -0
- package/src/app/chat/chat/tool-call-display.tsx +40 -8
- package/src/app/chat/chat/widgets/DataChartRenderer.spec.tsx +632 -0
- package/src/app/chat/chat/widgets/DataChartRenderer.tsx +308 -51
- package/src/app/chat/chat/widgets/DataChartWidget.spec.tsx +126 -23
- package/src/app/chat/chat/widgets/DataChartWidget.tsx +10 -0
- package/src/app/chat/chat/widgets/DataWidgetDisplayControls.tsx +26 -6
- package/src/app/i18n/catalogs/ar-SA.ts +1 -0
- package/src/app/i18n/catalogs/de-DE.ts +2 -0
- package/src/app/i18n/catalogs/en-US.ts +2 -0
- package/src/app/i18n/catalogs/es-ES.ts +2 -0
- package/src/app/i18n/catalogs/fr-FR.ts +2 -0
- package/src/app/i18n/catalogs/hi-IN.ts +1 -0
- package/src/app/i18n/catalogs/ja-JP.ts +2 -0
- package/src/app/i18n/catalogs/ko-KR.ts +1 -0
- package/src/app/i18n/catalogs/pt-BR.ts +2 -0
- package/src/app/i18n/catalogs/zh-CN.ts +1 -0
- package/src/app/i18n/catalogs/zh-TW.ts +1 -0
- package/src/app/onboarding/GettingStartedCard.tsx +203 -0
- package/src/app/onboarding/index.ts +1 -0
- package/src/app/providers/AppProviders.tsx +7 -1
- package/src/app/settings/AccountSettingsCard.spec.tsx +31 -0
- package/src/app/settings/AccountSettingsCard.tsx +8 -0
- package/src/app/settings/index.ts +1 -0
- package/src/app/settings/shell/SettingsShell.spec.tsx +472 -1
- package/src/app/settings/shell/SettingsShell.tsx +245 -31
- package/src/app/settings/shell/bridge.spec.ts +12 -0
- package/src/app/settings/shell/bridge.ts +17 -4
- package/src/app/settings/shell/index.ts +6 -1
- package/src/app/settings/shell/registry.ts +33 -1
- package/src/app/settings/shell/routing.spec.ts +92 -1
- package/src/app/settings/shell/routing.ts +52 -9
- package/src/app/shared/CommandMenu.spec.tsx +1662 -1
- package/src/app/shared/CommandMenu.tsx +763 -164
- package/src/app/shared/ConsentBanner.spec.tsx +141 -0
- package/src/app/shared/ConsentBanner.tsx +141 -0
- package/src/composer/ComposerContextBanner.spec.tsx +31 -5
- package/src/composer/ComposerContextBanner.tsx +7 -2
- package/src/ui/sonner.spec.ts +10 -0
- package/src/ui/sonner.tsx +2 -2
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
useState,
|
|
48
48
|
useSyncExternalStore,
|
|
49
49
|
type MouseEvent,
|
|
50
|
+
type ReactNode,
|
|
50
51
|
} from "react";
|
|
51
52
|
import { useInRouterContext, useLocation, useNavigate } from "react-router";
|
|
52
53
|
|
|
@@ -74,8 +75,10 @@ import {
|
|
|
74
75
|
SETTINGS_PAGE_GROUPS,
|
|
75
76
|
getSettingsPages,
|
|
76
77
|
isSettingsPageVisible,
|
|
78
|
+
mergeAppSettingsPages,
|
|
77
79
|
sortSettingsPages,
|
|
78
80
|
subscribeSettingsPages,
|
|
81
|
+
type AppSettingsPageDefinition,
|
|
79
82
|
type SettingsPageContext,
|
|
80
83
|
type SettingsPageDefinition,
|
|
81
84
|
type SettingsPageGroup,
|
|
@@ -104,6 +107,14 @@ export interface SettingsShellProps extends SettingsBridgeInput {
|
|
|
104
107
|
appIcon?: SettingsPageIcon;
|
|
105
108
|
/** Scopes the app's changelog unread dot and usage. Defaults to the template id. */
|
|
106
109
|
appId?: string | null;
|
|
110
|
+
/** App-owned pages and controls shown in this shell's app group. */
|
|
111
|
+
appSettingsPages?: readonly AppSettingsPageDefinition[];
|
|
112
|
+
/** Replaces the default page groups with ordered app-owned sections. */
|
|
113
|
+
appNavigationSections?: readonly AppSettingsNavigationSection[];
|
|
114
|
+
/** Optional app chat surface rendered beside the settings shell. */
|
|
115
|
+
agentPanel?: ReactNode;
|
|
116
|
+
/** Replaces the shared agent toggle when an app owns its chat state. */
|
|
117
|
+
agentToggle?: ReactNode;
|
|
107
118
|
/** Raw CHANGELOG.md; enables the What's new unread dot. */
|
|
108
119
|
whatsNewMarkdown?: string;
|
|
109
120
|
enableSearch?: boolean;
|
|
@@ -123,6 +134,41 @@ export interface SettingsShellProps extends SettingsBridgeInput {
|
|
|
123
134
|
onValueChange?: (tabId: string) => void;
|
|
124
135
|
}
|
|
125
136
|
|
|
137
|
+
interface AppSettingsNavigationItemBase {
|
|
138
|
+
id: string;
|
|
139
|
+
visibleWhen?: (context: SettingsPageContext) => boolean;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export type AppSettingsNavigationItem =
|
|
143
|
+
| (AppSettingsNavigationItemBase & {
|
|
144
|
+
type: "page";
|
|
145
|
+
pageId: string;
|
|
146
|
+
sub?: string;
|
|
147
|
+
label?: string;
|
|
148
|
+
icon?: SettingsPageIcon;
|
|
149
|
+
})
|
|
150
|
+
| (AppSettingsNavigationItemBase & {
|
|
151
|
+
type: "link";
|
|
152
|
+
label: string;
|
|
153
|
+
href: string;
|
|
154
|
+
icon?: SettingsPageIcon;
|
|
155
|
+
disabled?: boolean;
|
|
156
|
+
})
|
|
157
|
+
| (AppSettingsNavigationItemBase & {
|
|
158
|
+
type: "action";
|
|
159
|
+
label: string;
|
|
160
|
+
onSelect: (context: SettingsPageContext) => void;
|
|
161
|
+
icon?: SettingsPageIcon;
|
|
162
|
+
disabled?: boolean;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
export interface AppSettingsNavigationSection {
|
|
166
|
+
id: string;
|
|
167
|
+
label: string;
|
|
168
|
+
items: readonly AppSettingsNavigationItem[];
|
|
169
|
+
visibleWhen?: (context: SettingsPageContext) => boolean;
|
|
170
|
+
}
|
|
171
|
+
|
|
126
172
|
// The agent panel module is heavy and already loaded by the app layout; keep
|
|
127
173
|
// it out of the shell's own chunk.
|
|
128
174
|
const AgentToggleButton = lazy(() =>
|
|
@@ -339,6 +385,10 @@ function SettingsShellContent({
|
|
|
339
385
|
className,
|
|
340
386
|
navClassName,
|
|
341
387
|
contentClassName,
|
|
388
|
+
appSettingsPages,
|
|
389
|
+
appNavigationSections,
|
|
390
|
+
agentPanel,
|
|
391
|
+
agentToggle,
|
|
342
392
|
value,
|
|
343
393
|
initialValue,
|
|
344
394
|
onValueChange,
|
|
@@ -413,9 +463,15 @@ function SettingsShellContent({
|
|
|
413
463
|
extraTabs ?? [],
|
|
414
464
|
declared,
|
|
415
465
|
BridgedTabSettingsPage,
|
|
466
|
+
appSettingsPages,
|
|
416
467
|
);
|
|
417
468
|
return {
|
|
418
|
-
pages: sortSettingsPages(
|
|
469
|
+
pages: sortSettingsPages(
|
|
470
|
+
mergeAppSettingsPages(
|
|
471
|
+
[...declared, ...derived.pages],
|
|
472
|
+
appSettingsPages,
|
|
473
|
+
),
|
|
474
|
+
),
|
|
419
475
|
bridge: createSettingsBridge(
|
|
420
476
|
{
|
|
421
477
|
extraTabs,
|
|
@@ -445,6 +501,7 @@ function SettingsShellContent({
|
|
|
445
501
|
appAreas,
|
|
446
502
|
appGroupLabel,
|
|
447
503
|
appLabs,
|
|
504
|
+
appSettingsPages,
|
|
448
505
|
extraTabs,
|
|
449
506
|
general,
|
|
450
507
|
generalGroups,
|
|
@@ -470,12 +527,27 @@ function SettingsShellContent({
|
|
|
470
527
|
() => bridge.appAreas.map((area) => area.id),
|
|
471
528
|
[bridge.appAreas],
|
|
472
529
|
);
|
|
530
|
+
const templateTabPageIds = useMemo(
|
|
531
|
+
() =>
|
|
532
|
+
new Map(
|
|
533
|
+
visiblePages.flatMap((page) => {
|
|
534
|
+
const tab = bridge.tabForPage(page.id);
|
|
535
|
+
return tab ? [[tab.id, page.id] as const] : [];
|
|
536
|
+
}),
|
|
537
|
+
),
|
|
538
|
+
[bridge, visiblePages],
|
|
539
|
+
);
|
|
473
540
|
// The template's first `value` is its default, not a choice (Mail starts
|
|
474
541
|
// on "integrations"), so only a later one names a page for bare /settings.
|
|
475
542
|
const tabValue = value !== undefined && value !== initialValue ? value : null;
|
|
476
543
|
const resolved = useMemo(
|
|
477
|
-
() =>
|
|
478
|
-
|
|
544
|
+
() =>
|
|
545
|
+
resolveSettingsRoute(location, pages, {
|
|
546
|
+
appAreaIds,
|
|
547
|
+
tabValue,
|
|
548
|
+
templateTabPageIds,
|
|
549
|
+
}),
|
|
550
|
+
[appAreaIds, location, pages, tabValue, templateTabPageIds],
|
|
479
551
|
);
|
|
480
552
|
const activePage =
|
|
481
553
|
visiblePages.find((page) => page.id === resolved.page) ??
|
|
@@ -605,14 +677,26 @@ function SettingsShellContent({
|
|
|
605
677
|
seenValueRef.current = value;
|
|
606
678
|
// The template echoing the tab the shell just reported.
|
|
607
679
|
if (value === reportedValueRef.current) return;
|
|
608
|
-
const target = resolveSettingsTabValue(
|
|
680
|
+
const target = resolveSettingsTabValue(
|
|
681
|
+
value,
|
|
682
|
+
visiblePages,
|
|
683
|
+
appAreaIds,
|
|
684
|
+
templateTabPageIds,
|
|
685
|
+
);
|
|
609
686
|
if (target.page && target.page !== routePage) {
|
|
610
687
|
navigate(target.page, target.sub, {
|
|
611
688
|
replace: true,
|
|
612
689
|
anchor: target.anchor ?? undefined,
|
|
613
690
|
});
|
|
614
691
|
}
|
|
615
|
-
}, [
|
|
692
|
+
}, [
|
|
693
|
+
appAreaIds,
|
|
694
|
+
navigate,
|
|
695
|
+
routePage,
|
|
696
|
+
templateTabPageIds,
|
|
697
|
+
value,
|
|
698
|
+
visiblePages,
|
|
699
|
+
]);
|
|
616
700
|
|
|
617
701
|
const latestChangelogId = getChangelogLatestId(
|
|
618
702
|
bridge.whatsNewMarkdown ?? undefined,
|
|
@@ -726,12 +810,16 @@ function SettingsShellContent({
|
|
|
726
810
|
pageLabel={pageLabel}
|
|
727
811
|
pageIcon={pageIcon}
|
|
728
812
|
appName={appGroupLabel}
|
|
813
|
+
allPages={pages}
|
|
729
814
|
returnHref={appMountedPath(returnPath, SETTINGS_ROUTE)}
|
|
730
815
|
onReturn={() => go(returnPath, false)}
|
|
731
816
|
enableSearch={enableSearch}
|
|
732
817
|
searchIndex={searchIndex}
|
|
733
818
|
unreadPageIds={changelog.unseen ? ["whats-new"] : []}
|
|
734
819
|
onNavigate={navigate}
|
|
820
|
+
activeSub={routeSub}
|
|
821
|
+
context={context}
|
|
822
|
+
appNavigationSections={appNavigationSections}
|
|
735
823
|
/>
|
|
736
824
|
);
|
|
737
825
|
|
|
@@ -853,9 +941,13 @@ function SettingsShellContent({
|
|
|
853
941
|
</div>
|
|
854
942
|
</div>
|
|
855
943
|
<div className="absolute end-3.5 top-3.5">
|
|
856
|
-
|
|
857
|
-
<
|
|
858
|
-
|
|
944
|
+
{agentToggle === undefined ? (
|
|
945
|
+
<Suspense fallback={null}>
|
|
946
|
+
<AgentToggleButton showWhenOpen />
|
|
947
|
+
</Suspense>
|
|
948
|
+
) : (
|
|
949
|
+
agentToggle
|
|
950
|
+
)}
|
|
859
951
|
</div>
|
|
860
952
|
</header>
|
|
861
953
|
<div className="mx-auto w-full max-w-[824px] px-4 pb-28 pt-1 min-[760px]:px-8">
|
|
@@ -864,8 +956,12 @@ function SettingsShellContent({
|
|
|
864
956
|
<Page key={activePage.id} {...pageProps} />
|
|
865
957
|
</Suspense>
|
|
866
958
|
) : null}
|
|
959
|
+
{activePage?.group === "app" && activePage.widgetSlot != null ? (
|
|
960
|
+
<div data-settings-widget-slot="">{activePage.widgetSlot}</div>
|
|
961
|
+
) : null}
|
|
867
962
|
</div>
|
|
868
963
|
</div>
|
|
964
|
+
{agentPanel}
|
|
869
965
|
</div>
|
|
870
966
|
</SettingsShellProvider>
|
|
871
967
|
);
|
|
@@ -873,6 +969,7 @@ function SettingsShellContent({
|
|
|
873
969
|
|
|
874
970
|
interface SettingsNavProps {
|
|
875
971
|
pages: readonly SettingsPageDefinition[];
|
|
972
|
+
allPages: readonly SettingsPageDefinition[];
|
|
876
973
|
activePageId: string | null;
|
|
877
974
|
groupLabel: (group: SettingsPageGroup) => string;
|
|
878
975
|
pageLabel: (page: SettingsPageDefinition) => string;
|
|
@@ -884,10 +981,14 @@ interface SettingsNavProps {
|
|
|
884
981
|
searchIndex: readonly SettingsSearchResult[];
|
|
885
982
|
unreadPageIds: readonly string[];
|
|
886
983
|
onNavigate: SettingsShellContextValue["navigate"];
|
|
984
|
+
activeSub: string | null;
|
|
985
|
+
context: SettingsPageContext;
|
|
986
|
+
appNavigationSections?: readonly AppSettingsNavigationSection[];
|
|
887
987
|
}
|
|
888
988
|
|
|
889
989
|
function SettingsNav({
|
|
890
990
|
pages,
|
|
991
|
+
allPages,
|
|
891
992
|
activePageId,
|
|
892
993
|
groupLabel,
|
|
893
994
|
pageLabel,
|
|
@@ -899,6 +1000,9 @@ function SettingsNav({
|
|
|
899
1000
|
searchIndex,
|
|
900
1001
|
unreadPageIds,
|
|
901
1002
|
onNavigate,
|
|
1003
|
+
activeSub,
|
|
1004
|
+
context,
|
|
1005
|
+
appNavigationSections,
|
|
902
1006
|
}: SettingsNavProps) {
|
|
903
1007
|
const t = useT();
|
|
904
1008
|
const [query, setQuery] = useState("");
|
|
@@ -943,22 +1047,46 @@ function SettingsNav({
|
|
|
943
1047
|
return byGroup;
|
|
944
1048
|
}, [pages]);
|
|
945
1049
|
|
|
946
|
-
const renderItem = (
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1050
|
+
const renderItem = (
|
|
1051
|
+
page: SettingsPageDefinition,
|
|
1052
|
+
item?: Extract<AppSettingsNavigationItem, { type: "page" }>,
|
|
1053
|
+
) => {
|
|
1054
|
+
const Icon = item?.icon ?? pageIcon(page);
|
|
1055
|
+
const hasVisibleSubpageEntry = Boolean(
|
|
1056
|
+
activeSub &&
|
|
1057
|
+
appNavigationSections?.some(
|
|
1058
|
+
(section) =>
|
|
1059
|
+
(section.visibleWhen?.(context) ?? true) &&
|
|
1060
|
+
section.items.some(
|
|
1061
|
+
(candidate) =>
|
|
1062
|
+
candidate.type === "page" &&
|
|
1063
|
+
candidate.pageId === page.id &&
|
|
1064
|
+
candidate.sub === activeSub &&
|
|
1065
|
+
(candidate.visibleWhen?.(context) ?? true),
|
|
1066
|
+
),
|
|
1067
|
+
),
|
|
1068
|
+
);
|
|
1069
|
+
const selected =
|
|
1070
|
+
page.id === activePageId &&
|
|
1071
|
+
(item?.sub !== undefined
|
|
1072
|
+
? item.sub === activeSub
|
|
1073
|
+
: activeSub === null || !hasVisibleSubpageEntry);
|
|
1074
|
+
const href = item?.sub
|
|
1075
|
+
? settingsPageHref(page.id, item.sub)
|
|
1076
|
+
: page.href
|
|
1077
|
+
? appMountedPath(page.href, SETTINGS_ROUTE)
|
|
1078
|
+
: settingsPageHref(page.id);
|
|
952
1079
|
return (
|
|
953
1080
|
<a
|
|
954
|
-
key={page.id}
|
|
1081
|
+
key={item?.id ?? page.id}
|
|
955
1082
|
href={href}
|
|
956
1083
|
data-settings-page={page.id}
|
|
1084
|
+
data-settings-nav-item={item?.id}
|
|
957
1085
|
aria-current={selected ? "page" : undefined}
|
|
958
1086
|
onClick={(event) => {
|
|
959
1087
|
if (isModifiedClick(event)) return;
|
|
960
1088
|
event.preventDefault();
|
|
961
|
-
onNavigate(page.id);
|
|
1089
|
+
onNavigate(page.id, item?.sub ?? null);
|
|
962
1090
|
}}
|
|
963
1091
|
className={cn(
|
|
964
1092
|
"flex h-[30px] items-center gap-2 rounded-md px-2 text-[13px] font-medium transition-colors",
|
|
@@ -973,7 +1101,7 @@ function SettingsNav({
|
|
|
973
1101
|
selected ? "text-foreground" : "text-muted-foreground",
|
|
974
1102
|
)}
|
|
975
1103
|
/>
|
|
976
|
-
<span className="truncate">{pageLabel(page)}</span>
|
|
1104
|
+
<span className="truncate">{item?.label ?? pageLabel(page)}</span>
|
|
977
1105
|
{unreadPageIds.includes(page.id) ? (
|
|
978
1106
|
<span
|
|
979
1107
|
role="img"
|
|
@@ -985,6 +1113,90 @@ function SettingsNav({
|
|
|
985
1113
|
);
|
|
986
1114
|
};
|
|
987
1115
|
|
|
1116
|
+
const renderNavigationItem = (item: AppSettingsNavigationItem) => {
|
|
1117
|
+
if (item.visibleWhen && !item.visibleWhen(context)) return null;
|
|
1118
|
+
if (item.type === "page") {
|
|
1119
|
+
const page = pages.find((candidate) => candidate.id === item.pageId);
|
|
1120
|
+
if (!page) {
|
|
1121
|
+
if (allPages.some((candidate) => candidate.id === item.pageId)) {
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
throw new Error(
|
|
1125
|
+
`Settings navigation item "${item.id}" references unknown page "${item.pageId}".`,
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
return renderItem(page, item);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
const Icon = item.icon;
|
|
1132
|
+
const icon = Icon ? (
|
|
1133
|
+
<Icon
|
|
1134
|
+
aria-hidden="true"
|
|
1135
|
+
className="size-4 shrink-0 text-muted-foreground"
|
|
1136
|
+
/>
|
|
1137
|
+
) : null;
|
|
1138
|
+
const className = cn(
|
|
1139
|
+
"flex h-[30px] w-full items-center gap-2 rounded-md px-2 text-start text-[13px] font-medium text-muted-foreground transition-colors hover:bg-sidebar-accent/60 hover:text-foreground",
|
|
1140
|
+
item.disabled && "pointer-events-none opacity-50",
|
|
1141
|
+
);
|
|
1142
|
+
if (item.type === "link") {
|
|
1143
|
+
return (
|
|
1144
|
+
<a
|
|
1145
|
+
key={item.id}
|
|
1146
|
+
href={appMountedPath(item.href, SETTINGS_ROUTE)}
|
|
1147
|
+
data-settings-link={item.id}
|
|
1148
|
+
aria-disabled={item.disabled || undefined}
|
|
1149
|
+
tabIndex={item.disabled ? -1 : undefined}
|
|
1150
|
+
onClick={(event) => {
|
|
1151
|
+
if (item.disabled || isModifiedClick(event)) {
|
|
1152
|
+
if (item.disabled) event.preventDefault();
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
}}
|
|
1156
|
+
className={className}
|
|
1157
|
+
>
|
|
1158
|
+
{icon}
|
|
1159
|
+
<span className="truncate">{item.label}</span>
|
|
1160
|
+
</a>
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
return (
|
|
1165
|
+
<button
|
|
1166
|
+
key={item.id}
|
|
1167
|
+
type="button"
|
|
1168
|
+
data-settings-action={item.id}
|
|
1169
|
+
disabled={item.disabled}
|
|
1170
|
+
onClick={() => item.onSelect(context)}
|
|
1171
|
+
className={className}
|
|
1172
|
+
>
|
|
1173
|
+
{icon}
|
|
1174
|
+
<span className="truncate">{item.label}</span>
|
|
1175
|
+
</button>
|
|
1176
|
+
);
|
|
1177
|
+
};
|
|
1178
|
+
|
|
1179
|
+
const renderSection = (section: AppSettingsNavigationSection) => {
|
|
1180
|
+
if (section.visibleWhen && !section.visibleWhen(context)) return null;
|
|
1181
|
+
const items = section.items.flatMap((item) => {
|
|
1182
|
+
const rendered = renderNavigationItem(item);
|
|
1183
|
+
return rendered ? [rendered] : [];
|
|
1184
|
+
});
|
|
1185
|
+
if (items.length === 0) return null;
|
|
1186
|
+
return (
|
|
1187
|
+
<div
|
|
1188
|
+
key={section.id}
|
|
1189
|
+
data-settings-group={section.id}
|
|
1190
|
+
className="mt-3.5 flex flex-col gap-px"
|
|
1191
|
+
>
|
|
1192
|
+
<div className="truncate px-2 pb-1 text-[11.5px] font-medium text-muted-foreground">
|
|
1193
|
+
{section.label}
|
|
1194
|
+
</div>
|
|
1195
|
+
{items}
|
|
1196
|
+
</div>
|
|
1197
|
+
);
|
|
1198
|
+
};
|
|
1199
|
+
|
|
988
1200
|
const searching = enableSearch && query.trim().length > 0;
|
|
989
1201
|
|
|
990
1202
|
return (
|
|
@@ -1093,20 +1305,22 @@ function SettingsNav({
|
|
|
1093
1305
|
aria-label={t("agentChat.settingsShell.navLabel")}
|
|
1094
1306
|
className="min-h-0 flex-1 overflow-y-auto px-2.5 pb-3"
|
|
1095
1307
|
>
|
|
1096
|
-
{
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1308
|
+
{appNavigationSections
|
|
1309
|
+
? appNavigationSections.map(renderSection)
|
|
1310
|
+
: SETTINGS_PAGE_GROUPS.filter(
|
|
1311
|
+
(group) => (groups.get(group) ?? []).length > 0,
|
|
1312
|
+
).map((group) => (
|
|
1313
|
+
<div
|
|
1314
|
+
key={group}
|
|
1315
|
+
data-settings-group={group}
|
|
1316
|
+
className="mt-3.5 flex flex-col gap-px"
|
|
1317
|
+
>
|
|
1318
|
+
<div className="truncate px-2 pb-1 text-[11.5px] font-medium text-muted-foreground">
|
|
1319
|
+
{groupLabel(group)}
|
|
1320
|
+
</div>
|
|
1321
|
+
{(groups.get(group) ?? []).map((page) => renderItem(page))}
|
|
1322
|
+
</div>
|
|
1323
|
+
))}
|
|
1110
1324
|
</nav>
|
|
1111
1325
|
)}
|
|
1112
1326
|
</div>
|
|
@@ -92,6 +92,18 @@ describe("settings shell bridge", () => {
|
|
|
92
92
|
expect(pages[0]!.legacyTabIds).toEqual(["automations"]);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
+
it("reserves app page ids and legacy tab ids while deriving template pages", () => {
|
|
96
|
+
const { pages, pageTabs } = deriveBridgedAppPages(
|
|
97
|
+
[tab("custom-controls"), tab("workflow-rules")],
|
|
98
|
+
CORE_SETTINGS_PAGES,
|
|
99
|
+
Stub,
|
|
100
|
+
[{ id: "custom-controls", legacyTabIds: ["workflow-rules"] }],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(pages.map((page) => page.id)).toEqual(["app-custom-controls"]);
|
|
104
|
+
expect(pageTabs.get("app-custom-controls")?.id).toBe("custom-controls");
|
|
105
|
+
});
|
|
106
|
+
|
|
95
107
|
it("keeps app-area tabs off the nav", () => {
|
|
96
108
|
const { pages } = deriveBridgedAppPages(
|
|
97
109
|
[tab("recordings", { settingsPlacement: "app-area" })],
|
|
@@ -68,6 +68,8 @@ export interface SettingsBridge {
|
|
|
68
68
|
tab: (...ids: string[]) => SettingsTabItem | undefined;
|
|
69
69
|
/** The template tab behind a page the bridge derived from `extraTabs`. */
|
|
70
70
|
tabForPage: (pageId: string) => SettingsTabItem | undefined;
|
|
71
|
+
/** The bridged page behind a template tab id. */
|
|
72
|
+
pageForTab: (tabId: string) => string | undefined;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
export const EMPTY_SETTINGS_BRIDGE: SettingsBridge = createSettingsBridge({});
|
|
@@ -85,6 +87,9 @@ export function createSettingsBridge(
|
|
|
85
87
|
);
|
|
86
88
|
const labs = input.labs ?? [];
|
|
87
89
|
const byId = new Map(tabs.map((tab) => [tab.id, tab]));
|
|
90
|
+
const pageByTabId = new Map(
|
|
91
|
+
[...pageTabs].map(([pageId, tab]) => [tab.id, pageId]),
|
|
92
|
+
);
|
|
88
93
|
return {
|
|
89
94
|
general: input.generalGroups ?? input.general ?? null,
|
|
90
95
|
account: input.account ?? null,
|
|
@@ -109,6 +114,7 @@ export function createSettingsBridge(
|
|
|
109
114
|
return undefined;
|
|
110
115
|
},
|
|
111
116
|
tabForPage: (pageId) => pageTabs.get(pageId),
|
|
117
|
+
pageForTab: (tabId) => pageByTabId.get(tabId),
|
|
112
118
|
};
|
|
113
119
|
}
|
|
114
120
|
|
|
@@ -162,16 +168,23 @@ export interface BridgedAppPages {
|
|
|
162
168
|
|
|
163
169
|
/**
|
|
164
170
|
* Agent tabs stay in the Agent group; other unclaimed tabs become app pages
|
|
165
|
-
* after General. An id that collides with
|
|
166
|
-
*
|
|
171
|
+
* after General. An id that collides with an existing page gets an `app-`
|
|
172
|
+
* prefix so it never shadows that page.
|
|
167
173
|
*/
|
|
168
174
|
export function deriveBridgedAppPages(
|
|
169
175
|
tabs: readonly SettingsTabItem[],
|
|
170
176
|
corePages: readonly SettingsPageDefinition[],
|
|
171
177
|
component: SettingsPageDefinition["component"],
|
|
178
|
+
reservedPages: readonly Pick<
|
|
179
|
+
SettingsPageDefinition,
|
|
180
|
+
"id" | "legacyTabIds"
|
|
181
|
+
>[] = [],
|
|
172
182
|
): BridgedAppPages {
|
|
173
|
-
const
|
|
174
|
-
const
|
|
183
|
+
const allReservedPages = [...corePages, ...reservedPages];
|
|
184
|
+
const claimed = new Set(
|
|
185
|
+
allReservedPages.flatMap((page) => page.legacyTabIds ?? []),
|
|
186
|
+
);
|
|
187
|
+
const taken = new Set(allReservedPages.map((page) => page.id));
|
|
175
188
|
const pages: SettingsPageDefinition[] = [];
|
|
176
189
|
const pageTabs = new Map<string, SettingsTabItem>();
|
|
177
190
|
tabs.forEach((tab, index) => {
|
|
@@ -8,7 +8,11 @@ export const SettingsShell = lazy(() =>
|
|
|
8
8
|
default: module.SettingsShell,
|
|
9
9
|
})),
|
|
10
10
|
);
|
|
11
|
-
export type {
|
|
11
|
+
export type {
|
|
12
|
+
AppSettingsNavigationItem,
|
|
13
|
+
AppSettingsNavigationSection,
|
|
14
|
+
SettingsShellProps,
|
|
15
|
+
} from "./SettingsShell.js";
|
|
12
16
|
export { SettingsShellSkeleton } from "./SettingsShellSkeleton.js";
|
|
13
17
|
export {
|
|
14
18
|
canManageOrganizationPages,
|
|
@@ -22,6 +26,7 @@ export {
|
|
|
22
26
|
sortSettingsPages,
|
|
23
27
|
subscribeSettingsPages,
|
|
24
28
|
unregisterSettingsPage,
|
|
29
|
+
type AppSettingsPageDefinition,
|
|
25
30
|
type CoreSettingsPageId,
|
|
26
31
|
type SettingsPageContext,
|
|
27
32
|
type SettingsPageDefinition,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { LabStates } from "@agent-native/core/client/labs/use-lab";
|
|
2
2
|
import type { CoreSettingsPageId } from "@agent-native/core/navigation/settings-redirects";
|
|
3
3
|
import type { OrgRole } from "@agent-native/core/org/types";
|
|
4
|
-
import type { ComponentType } from "react";
|
|
4
|
+
import type { ComponentType, ReactNode } from "react";
|
|
5
5
|
|
|
6
6
|
import type { SettingsBridge } from "./bridge.js";
|
|
7
7
|
import type { SettingsPageIcon } from "./types.js";
|
|
@@ -83,6 +83,8 @@ export interface SettingsPageDefinition {
|
|
|
83
83
|
icon: SettingsPageIcon;
|
|
84
84
|
visible?: (context: SettingsPageContext, bridge: SettingsBridge) => boolean;
|
|
85
85
|
component: ComponentType<SettingsPageProps>;
|
|
86
|
+
/** Optional app-owned content rendered after the page on app settings pages. */
|
|
87
|
+
widgetSlot?: ReactNode;
|
|
86
88
|
subpages?: readonly SettingsSubpageDefinition[];
|
|
87
89
|
searchEntries?: readonly SettingsPageSearchEntry[];
|
|
88
90
|
/** Rendered at the right of the sticky header. */
|
|
@@ -99,6 +101,14 @@ export interface SettingsPageDefinition {
|
|
|
99
101
|
keywords?: string;
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
/** An app-owned page shown in the app group without entering the shared registry. */
|
|
105
|
+
export type AppSettingsPageDefinition = Omit<
|
|
106
|
+
SettingsPageDefinition,
|
|
107
|
+
"group"
|
|
108
|
+
> & {
|
|
109
|
+
group?: "app";
|
|
110
|
+
};
|
|
111
|
+
|
|
102
112
|
const PAGE_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
103
113
|
|
|
104
114
|
export function defineSettingsPage(
|
|
@@ -115,6 +125,28 @@ export function defineSettingsPage(
|
|
|
115
125
|
return Object.freeze({ ...definition });
|
|
116
126
|
}
|
|
117
127
|
|
|
128
|
+
/** Adds instance-owned pages after shared and registered pages. */
|
|
129
|
+
export function mergeAppSettingsPages(
|
|
130
|
+
pages: readonly SettingsPageDefinition[],
|
|
131
|
+
appPages: readonly AppSettingsPageDefinition[] = [],
|
|
132
|
+
): SettingsPageDefinition[] {
|
|
133
|
+
const existingIds = new Set(pages.map((page) => page.id));
|
|
134
|
+
const appIds = new Set<string>();
|
|
135
|
+
const additions = appPages.map((page) => {
|
|
136
|
+
if (existingIds.has(page.id)) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`App settings page id "${page.id}" conflicts with an existing page id.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
if (appIds.has(page.id)) {
|
|
142
|
+
throw new Error(`Duplicate app settings page id "${page.id}".`);
|
|
143
|
+
}
|
|
144
|
+
appIds.add(page.id);
|
|
145
|
+
return defineSettingsPage({ ...page, group: "app" });
|
|
146
|
+
});
|
|
147
|
+
return [...pages, ...additions];
|
|
148
|
+
}
|
|
149
|
+
|
|
118
150
|
const registry = new Map<string, SettingsPageDefinition>();
|
|
119
151
|
const listeners = new Set<() => void>();
|
|
120
152
|
let snapshot: readonly SettingsPageDefinition[] = [];
|