@object-ui/app-shell 6.2.3 → 7.1.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/CHANGELOG.md +1229 -0
- package/README.md +292 -0
- package/dist/assistant/assistantBus.d.ts +72 -0
- package/dist/assistant/assistantBus.js +133 -0
- package/dist/chrome/CommandPalette.d.ts +1 -1
- package/dist/chrome/CommandPalette.js +26 -22
- package/dist/chrome/ConditionalAuthWrapper.d.ts +1 -1
- package/dist/chrome/ConsoleToaster.d.ts +1 -1
- package/dist/chrome/ConsoleToaster.js +3 -1
- package/dist/chrome/ErrorBoundary.d.ts +1 -1
- package/dist/chrome/KeyboardShortcutsDialog.d.ts +1 -1
- package/dist/chrome/KeyboardShortcutsDialog.js +16 -5
- package/dist/chrome/LoadingScreen.d.ts +1 -1
- package/dist/chrome/LoadingScreen.js +22 -26
- package/dist/chrome/RouteFader.d.ts +1 -1
- package/dist/chrome/ThemeProvider.d.ts +1 -1
- package/dist/components/ManagedByBadge.d.ts +1 -1
- package/dist/console/AppContent.d.ts +1 -1
- package/dist/console/AppContent.js +184 -39
- package/dist/console/ConsoleShell.d.ts +7 -7
- package/dist/console/ConsoleShell.js +32 -3
- package/dist/console/ai/AiChatPage.d.ts +88 -1
- package/dist/console/ai/AiChatPage.js +747 -66
- package/dist/console/ai/ConversationsSidebar.d.ts +26 -1
- package/dist/console/ai/ConversationsSidebar.js +149 -34
- package/dist/console/ai/LiveCanvas.d.ts +28 -0
- package/dist/console/ai/LiveCanvas.js +80 -0
- package/dist/console/ai/reconcileTurn.d.ts +8 -0
- package/dist/console/ai/reconcileTurn.js +20 -0
- package/dist/console/auth/AuthPageLayout.d.ts +1 -1
- package/dist/console/auth/ForgotPasswordPage.d.ts +1 -1
- package/dist/console/auth/LoginPage.d.ts +1 -1
- package/dist/console/auth/RegisterPage.d.ts +1 -1
- package/dist/console/auth/RegisterPage.js +23 -3
- package/dist/console/cloud-connection/CloudConnectionPanel.d.ts +1 -0
- package/dist/console/cloud-connection/CloudConnectionPanel.js +169 -0
- package/dist/console/home/AppCard.d.ts +1 -1
- package/dist/console/home/AppCard.js +6 -12
- package/dist/console/home/HomeAppsStrip.d.ts +8 -0
- package/dist/console/home/HomeAppsStrip.js +61 -0
- package/dist/console/home/HomeLayout.d.ts +1 -1
- package/dist/console/home/HomeLayout.js +3 -1
- package/dist/console/home/HomePage.d.ts +1 -2
- package/dist/console/home/HomePage.js +149 -21
- package/dist/console/home/HomeRail.d.ts +22 -0
- package/dist/console/home/HomeRail.js +62 -0
- package/dist/console/home/QuickActions.d.ts +1 -1
- package/dist/console/home/QuickActions.js +3 -11
- package/dist/console/home/RecentApps.d.ts +1 -1
- package/dist/console/home/RecentApps.js +2 -2
- package/dist/console/home/StarredApps.d.ts +1 -1
- package/dist/console/home/StarredApps.js +2 -2
- package/dist/console/marketplace/InstalledListWidget.d.ts +1 -0
- package/dist/console/marketplace/InstalledListWidget.js +93 -0
- package/dist/console/marketplace/MarkdownText.d.ts +1 -1
- package/dist/console/marketplace/MarketplaceAccessDenied.d.ts +1 -1
- package/dist/console/marketplace/MarketplaceInstalledPage.d.ts +8 -14
- package/dist/console/marketplace/MarketplaceInstalledPage.js +14 -66
- package/dist/console/marketplace/MarketplacePackagePage.d.ts +1 -1
- package/dist/console/marketplace/MarketplacePackagePage.js +249 -8
- package/dist/console/marketplace/MarketplacePage.d.ts +1 -1
- package/dist/console/marketplace/MarketplacePage.js +60 -3
- package/dist/console/marketplace/PackageIcon.d.ts +1 -1
- package/dist/console/marketplace/PluginDisclosure.d.ts +14 -0
- package/dist/console/marketplace/PluginDisclosure.js +38 -0
- package/dist/console/marketplace/marketplaceApi.d.ts +123 -0
- package/dist/console/marketplace/marketplaceApi.js +254 -1
- package/dist/console/organizations/CreateWorkspaceDialog.d.ts +1 -1
- package/dist/console/organizations/OrganizationsLayout.d.ts +1 -1
- package/dist/console/organizations/OrganizationsPage.d.ts +1 -1
- package/dist/console/organizations/manage/AcceptInvitationPage.d.ts +1 -1
- package/dist/console/organizations/manage/InvitationsPage.d.ts +1 -1
- package/dist/console/organizations/manage/InviteMemberDialog.d.ts +1 -1
- package/dist/console/organizations/manage/MembersPage.d.ts +1 -1
- package/dist/console/organizations/manage/OrganizationLayout.d.ts +1 -1
- package/dist/console/organizations/manage/SettingsPage.d.ts +1 -1
- package/dist/context/CommandPaletteProvider.d.ts +44 -0
- package/dist/context/CommandPaletteProvider.js +71 -0
- package/dist/context/FavoritesProvider.d.ts +1 -1
- package/dist/context/NavigationContext.d.ts +1 -1
- package/dist/context/RecentItemsProvider.d.ts +2 -2
- package/dist/context/UserStateAdapters.d.ts +1 -1
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.js +1 -0
- package/dist/hooks/index.d.ts +5 -2
- package/dist/hooks/index.js +4 -1
- package/dist/hooks/useActionModal.d.ts +53 -0
- package/dist/hooks/useActionModal.js +111 -0
- package/dist/hooks/useChatConversation.d.ts +137 -4
- package/dist/hooks/useChatConversation.js +316 -25
- package/dist/hooks/useConsoleActionRuntime.d.ts +70 -0
- package/dist/hooks/useConsoleActionRuntime.js +564 -0
- package/dist/hooks/useConversationList.js +61 -3
- package/dist/hooks/useHomeInbox.d.ts +13 -0
- package/dist/hooks/useHomeInbox.js +142 -0
- package/dist/hooks/useNavPins.js +17 -23
- package/dist/hooks/useNavigationSync.d.ts +33 -0
- package/dist/hooks/useNavigationSync.js +98 -12
- package/dist/hooks/useReconcileOnError.d.ts +40 -0
- package/dist/hooks/useReconcileOnError.js +37 -0
- package/dist/hooks/useRecordApprovals.d.ts +18 -19
- package/dist/hooks/useRecordApprovals.js +24 -40
- package/dist/hooks/useResponsiveSidebar.js +14 -5
- package/dist/hooks/useSettleSignal.d.ts +19 -0
- package/dist/hooks/useSettleSignal.js +20 -0
- package/dist/hooks/useTrackRouteAsRecent.js +35 -0
- package/dist/hooks/useUrlOverlay.d.ts +62 -0
- package/dist/hooks/useUrlOverlay.js +88 -0
- package/dist/index.d.ts +18 -8
- package/dist/index.js +17 -5
- package/dist/layout/ActivityFeed.d.ts +1 -1
- package/dist/layout/AppHeader.d.ts +3 -2
- package/dist/layout/AppHeader.js +237 -72
- package/dist/layout/AppSidebar.d.ts +2 -1
- package/dist/layout/AppSidebar.js +26 -46
- package/dist/layout/AppSwitcher.d.ts +2 -1
- package/dist/layout/AppSwitcher.js +9 -5
- package/dist/layout/AuthPageLayout.d.ts +1 -1
- package/dist/layout/ConnectionStatus.d.ts +1 -1
- package/dist/layout/ConnectionStatus.js +9 -6
- package/dist/layout/ConsoleChatbotFab.d.ts +19 -1
- package/dist/layout/ConsoleChatbotFab.js +16 -2
- package/dist/layout/ConsoleFloatingChatbot.d.ts +34 -2
- package/dist/layout/ConsoleFloatingChatbot.js +391 -41
- package/dist/layout/ConsoleLayout.d.ts +1 -1
- package/dist/layout/ConsoleLayout.js +27 -11
- package/dist/layout/ContextSelectors.d.ts +44 -0
- package/dist/layout/ContextSelectors.js +242 -0
- package/dist/layout/InboxPopover.d.ts +6 -1
- package/dist/layout/InboxPopover.js +25 -6
- package/dist/layout/LocaleSwitcher.d.ts +1 -1
- package/dist/layout/LocalizedSidebarTrigger.d.ts +2 -0
- package/dist/layout/LocalizedSidebarTrigger.js +15 -0
- package/dist/layout/MobileViewSwitcherContext.d.ts +1 -1
- package/dist/layout/ModeToggle.d.ts +1 -1
- package/dist/layout/PageHeader.d.ts +1 -1
- package/dist/layout/UnifiedSidebar.d.ts +2 -1
- package/dist/layout/UnifiedSidebar.js +116 -15
- package/dist/layout/agentPicker.d.ts +56 -0
- package/dist/layout/agentPicker.js +40 -0
- package/dist/observability/index.d.ts +1 -0
- package/dist/observability/index.js +1 -0
- package/dist/observability/settleSignal.d.ts +64 -0
- package/dist/observability/settleSignal.js +131 -0
- package/dist/preview/CommitTimeline.d.ts +15 -0
- package/dist/preview/CommitTimeline.js +82 -0
- package/dist/preview/DraftChangesPanel.d.ts +19 -0
- package/dist/preview/DraftChangesPanel.js +114 -0
- package/dist/preview/DraftPreviewBar.d.ts +8 -0
- package/dist/preview/DraftPreviewBar.js +86 -0
- package/dist/preview/PreviewDraftEmptyState.d.ts +16 -0
- package/dist/preview/PreviewDraftEmptyState.js +47 -0
- package/dist/preview/PreviewModeContext.d.ts +57 -0
- package/dist/preview/PreviewModeContext.js +99 -0
- package/dist/preview/UnpublishedAppBar.d.ts +8 -0
- package/dist/preview/UnpublishedAppBar.js +83 -0
- package/dist/preview/commitHistory.d.ts +28 -0
- package/dist/preview/commitHistory.js +48 -0
- package/dist/preview/draftStatus.d.ts +20 -0
- package/dist/preview/draftStatus.js +27 -0
- package/dist/preview/usePublishAllDrafts.d.ts +18 -0
- package/dist/preview/usePublishAllDrafts.js +106 -0
- package/dist/providers/AdapterProvider.d.ts +1 -1
- package/dist/providers/AdapterProvider.js +6 -1
- package/dist/providers/ExpressionProvider.d.ts +1 -1
- package/dist/providers/MetadataProvider.d.ts +17 -2
- package/dist/providers/MetadataProvider.js +192 -12
- package/dist/runtime-config.d.ts +46 -2
- package/dist/runtime-config.js +39 -2
- package/dist/services/builtinComponents.js +68 -59
- package/dist/skeletons/SkeletonDashboard.d.ts +1 -1
- package/dist/skeletons/SkeletonDetail.d.ts +1 -1
- package/dist/skeletons/SkeletonGrid.d.ts +1 -1
- package/dist/utils/appRoute.d.ts +21 -0
- package/dist/utils/appRoute.js +25 -0
- package/dist/utils/deriveRelatedLists.d.ts +54 -0
- package/dist/utils/deriveRelatedLists.js +91 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/managedByEmptyState.d.ts +8 -1
- package/dist/utils/managedByEmptyState.js +13 -7
- package/dist/utils/preferLocal.d.ts +18 -0
- package/dist/utils/preferLocal.js +24 -0
- package/dist/views/ActionConfirmDialog.d.ts +1 -1
- package/dist/views/ActionConfirmDialog.js +3 -1
- package/dist/views/ActionParamDialog.d.ts +6 -1
- package/dist/views/ActionParamDialog.js +9 -3
- package/dist/views/ActionResultDialog.d.ts +13 -0
- package/dist/views/ActionResultDialog.js +134 -0
- package/dist/views/ComponentNavView.d.ts +14 -1
- package/dist/views/CreateViewDialog.d.ts +1 -1
- package/dist/views/DashboardConfigPanel.d.ts +28 -0
- package/dist/views/DashboardConfigPanel.js +81 -0
- package/dist/views/DashboardView.d.ts +4 -3
- package/dist/views/DashboardView.js +38 -239
- package/dist/views/FlowRunner.d.ts +31 -0
- package/dist/views/FlowRunner.js +121 -0
- package/dist/views/InterfaceListPage.d.ts +49 -0
- package/dist/views/InterfaceListPage.js +347 -0
- package/dist/views/MetadataInspector.d.ts +2 -2
- package/dist/views/ObjectView.d.ts +1 -1
- package/dist/views/ObjectView.js +209 -532
- package/dist/views/PageView.d.ts +8 -3
- package/dist/views/PageView.js +45 -32
- package/dist/views/RecordDetailView.d.ts +1 -1
- package/dist/views/RecordDetailView.js +363 -148
- package/dist/views/RecordFormPage.d.ts +1 -1
- package/dist/views/RecordFormPage.js +26 -1
- package/dist/views/ReportConfigPanel.d.ts +37 -0
- package/dist/views/ReportConfigPanel.js +85 -0
- package/dist/views/ReportView.d.ts +1 -1
- package/dist/views/ReportView.js +116 -7
- package/dist/views/RuntimeDraftBar.d.ts +30 -0
- package/dist/views/RuntimeDraftBar.js +112 -0
- package/dist/views/ScreenView.d.ts +70 -0
- package/dist/views/ScreenView.js +73 -0
- package/dist/views/SearchResultsPage.d.ts +1 -1
- package/dist/views/SearchResultsPage.js +8 -18
- package/dist/views/ViewConfigPanel.d.ts +24 -17
- package/dist/views/ViewConfigPanel.js +121 -77
- package/dist/views/index.d.ts +1 -1
- package/dist/views/index.js +1 -1
- package/dist/views/metadata-admin/AuditPanel.d.ts +28 -0
- package/dist/views/metadata-admin/AuditPanel.js +79 -0
- package/dist/views/metadata-admin/DiagnosticsPage.d.ts +20 -0
- package/dist/views/metadata-admin/DiagnosticsPage.js +69 -0
- package/dist/views/metadata-admin/DirectoryPage.d.ts +16 -1
- package/dist/views/metadata-admin/DirectoryPage.js +101 -24
- package/dist/views/metadata-admin/DraftReviewPanel.d.ts +33 -0
- package/dist/views/metadata-admin/DraftReviewPanel.js +77 -0
- package/dist/views/metadata-admin/EmbeddedItemEditor.d.ts +17 -1
- package/dist/views/metadata-admin/EmbeddedItemEditor.js +15 -8
- package/dist/views/metadata-admin/JsonSourceEditor.d.ts +39 -0
- package/dist/views/metadata-admin/JsonSourceEditor.js +196 -0
- package/dist/views/metadata-admin/LayeredDiff.d.ts +39 -1
- package/dist/views/metadata-admin/LayeredDiff.js +171 -5
- package/dist/views/metadata-admin/MetadataDetailDrawer.d.ts +15 -1
- package/dist/views/metadata-admin/MetadataTypeActions.d.ts +48 -0
- package/dist/views/metadata-admin/MetadataTypeActions.js +165 -0
- package/dist/views/metadata-admin/PackagesPage.d.ts +18 -0
- package/dist/views/metadata-admin/PackagesPage.js +403 -0
- package/dist/views/metadata-admin/PageShell.d.ts +1 -1
- package/dist/views/metadata-admin/PageShell.js +9 -4
- package/dist/views/metadata-admin/PermissionMatrixEditor.d.ts +35 -1
- package/dist/views/metadata-admin/QuickFind.d.ts +21 -1
- package/dist/views/metadata-admin/QuickFind.js +6 -3
- package/dist/views/metadata-admin/RelatedPanel.d.ts +24 -1
- package/dist/views/metadata-admin/RelatedPanel.js +20 -18
- package/dist/views/metadata-admin/ResourceEditPage.d.ts +40 -1
- package/dist/views/metadata-admin/ResourceEditPage.js +1250 -60
- package/dist/views/metadata-admin/ResourceHistoryPage.d.ts +39 -1
- package/dist/views/metadata-admin/ResourceHistoryPage.js +66 -16
- package/dist/views/metadata-admin/ResourceListPage.d.ts +13 -1
- package/dist/views/metadata-admin/ResourceListPage.js +258 -30
- package/dist/views/metadata-admin/ResourceRouter.d.ts +23 -1
- package/dist/views/metadata-admin/SchemaForm.d.ts +34 -1
- package/dist/views/metadata-admin/SchemaForm.js +559 -49
- package/dist/views/metadata-admin/StudioHomePage.d.ts +22 -0
- package/dist/views/metadata-admin/StudioHomePage.js +205 -0
- package/dist/views/metadata-admin/anchors.js +255 -24
- package/dist/views/metadata-admin/clientValidation.d.ts +50 -0
- package/dist/views/metadata-admin/clientValidation.js +169 -0
- package/dist/views/metadata-admin/color-variant-field.d.ts +30 -0
- package/dist/views/metadata-admin/color-variant-field.js +38 -0
- package/dist/views/metadata-admin/createDerive.d.ts +75 -0
- package/dist/views/metadata-admin/createDerive.js +179 -0
- package/dist/views/metadata-admin/dashboard-schema.d.ts +12 -0
- package/dist/views/metadata-admin/dashboard-schema.js +80 -0
- package/dist/views/metadata-admin/datasource/DatasourceResourcePage.d.ts +35 -0
- package/dist/views/metadata-admin/datasource/DatasourceResourcePage.js +327 -0
- package/dist/views/metadata-admin/datasource/register.d.ts +1 -0
- package/dist/views/metadata-admin/datasource/register.js +24 -0
- package/dist/views/metadata-admin/default-inspector-registry.d.ts +49 -0
- package/dist/views/metadata-admin/default-inspector-registry.js +8 -0
- package/dist/views/metadata-admin/default-schemas.js +115 -10
- package/dist/views/metadata-admin/external/ExternalDatasourcePanel.d.ts +27 -0
- package/dist/views/metadata-admin/external/ExternalDatasourcePanel.js +69 -0
- package/dist/views/metadata-admin/external/ImportObjectDialog.d.ts +27 -0
- package/dist/views/metadata-admin/external/ImportObjectDialog.js +77 -0
- package/dist/views/metadata-admin/external/SchemaBrowser.d.ts +16 -0
- package/dist/views/metadata-admin/external/SchemaBrowser.js +74 -0
- package/dist/views/metadata-admin/external/ValidationPanel.d.ts +16 -0
- package/dist/views/metadata-admin/external/ValidationPanel.js +68 -0
- package/dist/views/metadata-admin/external/api.d.ts +100 -0
- package/dist/views/metadata-admin/external/api.js +124 -0
- package/dist/views/metadata-admin/i18n.d.ts +1 -0
- package/dist/views/metadata-admin/i18n.js +1252 -2
- package/dist/views/metadata-admin/index.d.ts +8 -5
- package/dist/views/metadata-admin/index.js +12 -2
- package/dist/views/metadata-admin/inspector-registry.d.ts +51 -0
- package/dist/views/metadata-admin/inspector-registry.js +11 -0
- package/dist/views/metadata-admin/inspectors/ActionDefaultInspector.d.ts +30 -0
- package/dist/views/metadata-admin/inspectors/ActionDefaultInspector.js +180 -0
- package/dist/views/metadata-admin/inspectors/AppNavInspector.d.ts +16 -0
- package/dist/views/metadata-admin/inspectors/AppNavInspector.js +110 -0
- package/dist/views/metadata-admin/inspectors/ConditionBuilder.d.ts +29 -0
- package/dist/views/metadata-admin/inspectors/ConditionBuilder.js +154 -0
- package/dist/views/metadata-admin/inspectors/DashboardDefaultInspector.d.ts +28 -0
- package/dist/views/metadata-admin/inspectors/DashboardDefaultInspector.js +110 -0
- package/dist/views/metadata-admin/inspectors/DashboardWidgetInspector.d.ts +18 -0
- package/dist/views/metadata-admin/inspectors/DashboardWidgetInspector.js +139 -0
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +21 -0
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +221 -0
- package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.d.ts +16 -0
- package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +126 -0
- package/dist/views/metadata-admin/inspectors/FlowInspector.d.ts +12 -0
- package/dist/views/metadata-admin/inspectors/FlowInspector.js +9 -0
- package/dist/views/metadata-admin/inspectors/FlowKeyValueField.d.ts +30 -0
- package/dist/views/metadata-admin/inspectors/FlowKeyValueField.js +125 -0
- package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.d.ts +18 -0
- package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.js +40 -0
- package/dist/views/metadata-admin/inspectors/FlowNodeInspector.d.ts +14 -0
- package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +205 -0
- package/dist/views/metadata-admin/inspectors/FlowObjectListField.d.ts +26 -0
- package/dist/views/metadata-admin/inspectors/FlowObjectListField.js +105 -0
- package/dist/views/metadata-admin/inspectors/FlowReferenceField.d.ts +83 -0
- package/dist/views/metadata-admin/inspectors/FlowReferenceField.js +181 -0
- package/dist/views/metadata-admin/inspectors/FlowStringListField.d.ts +21 -0
- package/dist/views/metadata-admin/inspectors/FlowStringListField.js +60 -0
- package/dist/views/metadata-admin/inspectors/InspectorComboField.d.ts +40 -0
- package/dist/views/metadata-admin/inspectors/InspectorComboField.js +61 -0
- package/dist/views/metadata-admin/inspectors/ObjectDefaultInspector.d.ts +21 -0
- package/dist/views/metadata-admin/inspectors/ObjectDefaultInspector.js +55 -0
- package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.d.ts +23 -0
- package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +365 -0
- package/dist/views/metadata-admin/inspectors/PageBlockInspector.d.ts +48 -0
- package/dist/views/metadata-admin/inspectors/PageBlockInspector.js +332 -0
- package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.d.ts +58 -0
- package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.js +218 -0
- package/dist/views/metadata-admin/inspectors/ViewColumnInspector.d.ts +19 -0
- package/dist/views/metadata-admin/inspectors/ViewColumnInspector.js +144 -0
- package/dist/views/metadata-admin/inspectors/ViewInspector.d.ts +19 -0
- package/dist/views/metadata-admin/inspectors/ViewInspector.js +21 -0
- package/dist/views/metadata-admin/inspectors/ViewVariantInspector.d.ts +54 -0
- package/dist/views/metadata-admin/inspectors/ViewVariantInspector.js +191 -0
- package/dist/views/metadata-admin/inspectors/_shared.d.ts +128 -0
- package/dist/views/metadata-admin/inspectors/_shared.js +113 -0
- package/dist/views/metadata-admin/inspectors/datasetFilterCondition.d.ts +24 -0
- package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +97 -0
- package/dist/views/metadata-admin/inspectors/expression-validate.d.ts +26 -0
- package/dist/views/metadata-admin/inspectors/expression-validate.js +66 -0
- package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +143 -0
- package/dist/views/metadata-admin/inspectors/flow-node-config.js +506 -0
- package/dist/views/metadata-admin/inspectors/index.d.ts +1 -0
- package/dist/views/metadata-admin/inspectors/index.js +45 -0
- package/dist/views/metadata-admin/inspectors/json-schema-to-fields.d.ts +40 -0
- package/dist/views/metadata-admin/inspectors/json-schema-to-fields.js +227 -0
- package/dist/views/metadata-admin/inspectors/useDatasetFields.d.ts +72 -0
- package/dist/views/metadata-admin/inspectors/useDatasetFields.js +0 -0
- package/dist/views/metadata-admin/issuePath.d.ts +22 -0
- package/dist/views/metadata-admin/issuePath.js +65 -0
- package/dist/views/metadata-admin/mergeServerFields.d.ts +65 -0
- package/dist/views/metadata-admin/mergeServerFields.js +56 -0
- package/dist/views/metadata-admin/package-scope.d.ts +26 -0
- package/dist/views/metadata-admin/package-scope.js +43 -0
- package/dist/views/metadata-admin/preview-registry.d.ts +55 -0
- package/dist/views/metadata-admin/previews/ActionPreview.d.ts +25 -0
- package/dist/views/metadata-admin/previews/ActionPreview.js +238 -0
- package/dist/views/metadata-admin/previews/AddWidgetPicker.d.ts +12 -0
- package/dist/views/metadata-admin/previews/AddWidgetPicker.js +56 -0
- package/dist/views/metadata-admin/previews/AgentPreview.d.ts +24 -0
- package/dist/views/metadata-admin/previews/AgentPreview.js +100 -0
- package/dist/views/metadata-admin/previews/AppNavCanvas.d.ts +31 -0
- package/dist/views/metadata-admin/previews/AppNavCanvas.js +260 -0
- package/dist/views/metadata-admin/previews/AppPreview.d.ts +16 -1
- package/dist/views/metadata-admin/previews/AppPreview.js +23 -14
- package/dist/views/metadata-admin/previews/BookPreview.d.ts +20 -0
- package/dist/views/metadata-admin/previews/BookPreview.js +132 -0
- package/dist/views/metadata-admin/previews/DashboardPreview.d.ts +16 -1
- package/dist/views/metadata-admin/previews/DashboardPreview.js +110 -8
- package/dist/views/metadata-admin/previews/DatasetPreview.d.ts +18 -0
- package/dist/views/metadata-admin/previews/DatasetPreview.js +105 -0
- package/dist/views/metadata-admin/previews/DatasourcePreview.d.ts +23 -0
- package/dist/views/metadata-admin/previews/DatasourcePreview.js +68 -0
- package/dist/views/metadata-admin/previews/EmailTemplatePreview.d.ts +14 -1
- package/dist/views/metadata-admin/previews/FieldStub.d.ts +30 -0
- package/dist/views/metadata-admin/previews/FieldStub.js +104 -0
- package/dist/views/metadata-admin/previews/FieldsListEditor.d.ts +50 -0
- package/dist/views/metadata-admin/previews/FieldsListEditor.js +97 -0
- package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +49 -0
- package/dist/views/metadata-admin/previews/FlowCanvas.js +416 -0
- package/dist/views/metadata-admin/previews/FlowPreview.d.ts +20 -0
- package/dist/views/metadata-admin/previews/FlowPreview.js +120 -0
- package/dist/views/metadata-admin/previews/FlowRunsPanel.d.ts +46 -0
- package/dist/views/metadata-admin/previews/FlowRunsPanel.js +97 -0
- package/dist/views/metadata-admin/previews/FlowSimulatorPanel.d.ts +25 -0
- package/dist/views/metadata-admin/previews/FlowSimulatorPanel.js +204 -0
- package/dist/views/metadata-admin/previews/JobPreview.d.ts +28 -0
- package/dist/views/metadata-admin/previews/JobPreview.js +290 -0
- package/dist/views/metadata-admin/previews/ObjectFormCanvas.d.ts +30 -0
- package/dist/views/metadata-admin/previews/ObjectFormCanvas.js +547 -0
- package/dist/views/metadata-admin/previews/ObjectPreview.d.ts +14 -1
- package/dist/views/metadata-admin/previews/ObjectPreview.js +5 -30
- package/dist/views/metadata-admin/previews/OutlineStrip.d.ts +32 -0
- package/dist/views/metadata-admin/previews/OutlineStrip.js +8 -0
- package/dist/views/metadata-admin/previews/PageBlockCanvas.d.ts +49 -0
- package/dist/views/metadata-admin/previews/PageBlockCanvas.js +510 -0
- package/dist/views/metadata-admin/previews/PagePreview.d.ts +10 -1
- package/dist/views/metadata-admin/previews/PagePreview.js +200 -5
- package/dist/views/metadata-admin/previews/PermissionPreview.d.ts +27 -0
- package/dist/views/metadata-admin/previews/PermissionPreview.js +115 -0
- package/dist/views/metadata-admin/previews/PreviewShell.d.ts +29 -6
- package/dist/views/metadata-admin/previews/PreviewShell.js +16 -3
- package/dist/views/metadata-admin/previews/ReportPreview.d.ts +18 -1
- package/dist/views/metadata-admin/previews/ReportPreview.js +23 -15
- package/dist/views/metadata-admin/previews/RolePreview.d.ts +19 -0
- package/dist/views/metadata-admin/previews/RolePreview.js +14 -0
- package/dist/views/metadata-admin/previews/ScreenPreview.d.ts +38 -0
- package/dist/views/metadata-admin/previews/ScreenPreview.js +61 -0
- package/dist/views/metadata-admin/previews/SkillPreview.d.ts +22 -0
- package/dist/views/metadata-admin/previews/SkillPreview.js +34 -0
- package/dist/views/metadata-admin/previews/ToolPreview.d.ts +25 -0
- package/dist/views/metadata-admin/previews/ToolPreview.js +122 -0
- package/dist/views/metadata-admin/previews/TranslationPreview.d.ts +25 -0
- package/dist/views/metadata-admin/previews/TranslationPreview.js +52 -0
- package/dist/views/metadata-admin/previews/ValidationPreview.d.ts +27 -0
- package/dist/views/metadata-admin/previews/ValidationPreview.js +110 -0
- package/dist/views/metadata-admin/previews/ViewColumnPanes.d.ts +62 -0
- package/dist/views/metadata-admin/previews/ViewColumnPanes.js +140 -0
- package/dist/views/metadata-admin/previews/ViewPreview.d.ts +23 -1
- package/dist/views/metadata-admin/previews/ViewPreview.js +101 -73
- package/dist/views/metadata-admin/previews/block-config.d.ts +82 -0
- package/dist/views/metadata-admin/previews/block-config.js +324 -0
- package/dist/views/metadata-admin/previews/block-types.d.ts +40 -0
- package/dist/views/metadata-admin/previews/block-types.js +110 -0
- package/dist/views/metadata-admin/previews/field-types.d.ts +53 -0
- package/dist/views/metadata-admin/previews/field-types.js +97 -0
- package/dist/views/metadata-admin/previews/flow-canvas-layout.d.ts +102 -0
- package/dist/views/metadata-admin/previews/flow-canvas-layout.js +227 -0
- package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +96 -0
- package/dist/views/metadata-admin/previews/flow-canvas-parts.js +373 -0
- package/dist/views/metadata-admin/previews/form-preview.d.ts +24 -0
- package/dist/views/metadata-admin/previews/form-preview.js +29 -0
- package/dist/views/metadata-admin/previews/index.js +43 -0
- package/dist/views/metadata-admin/previews/object-fields-bridge.d.ts +66 -0
- package/dist/views/metadata-admin/previews/object-fields-bridge.js +171 -0
- package/dist/views/metadata-admin/previews/object-fields-io.d.ts +130 -0
- package/dist/views/metadata-admin/previews/object-fields-io.js +243 -0
- package/dist/views/metadata-admin/previews/screen-spec.d.ts +43 -0
- package/dist/views/metadata-admin/previews/screen-spec.js +108 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +102 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-types.js +2 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.d.ts +15 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +185 -0
- package/dist/views/metadata-admin/previews/simulator/flow-simulator.d.ts +73 -0
- package/dist/views/metadata-admin/previews/simulator/flow-simulator.js +426 -0
- package/dist/views/metadata-admin/previews/useDatasetCatalog.d.ts +47 -0
- package/dist/views/metadata-admin/previews/useDatasetCatalog.js +133 -0
- package/dist/views/metadata-admin/previews/useFlowNodePalette.d.ts +44 -0
- package/dist/views/metadata-admin/previews/useFlowNodePalette.js +124 -0
- package/dist/views/metadata-admin/previews/useMetaOptions.d.ts +8 -0
- package/dist/views/metadata-admin/previews/useMetaOptions.js +50 -0
- package/dist/views/metadata-admin/previews/useObjectFields.d.ts +23 -0
- package/dist/views/metadata-admin/previews/useObjectFields.js +79 -0
- package/dist/views/metadata-admin/previews/useObjectOptions.d.ts +8 -0
- package/dist/views/metadata-admin/previews/useObjectOptions.js +43 -0
- package/dist/views/metadata-admin/previews/view-column-io.d.ts +42 -0
- package/dist/views/metadata-admin/previews/view-column-io.js +73 -0
- package/dist/views/metadata-admin/previews/widget-types.d.ts +24 -0
- package/dist/views/metadata-admin/previews/widget-types.js +40 -0
- package/dist/views/metadata-admin/registry.d.ts +140 -19
- package/dist/views/metadata-admin/report-schema.d.ts +26 -0
- package/dist/views/metadata-admin/report-schema.js +121 -0
- package/dist/views/metadata-admin/useMetadata.d.ts +100 -2
- package/dist/views/metadata-admin/useMetadata.js +155 -4
- package/dist/views/metadata-admin/view-item-normalize.d.ts +20 -0
- package/dist/views/metadata-admin/view-item-normalize.js +68 -0
- package/dist/views/metadata-admin/view-schema.d.ts +16 -0
- package/dist/views/metadata-admin/view-schema.js +107 -0
- package/dist/views/metadata-admin/view-variant-model.d.ts +23 -0
- package/dist/views/metadata-admin/view-variant-model.js +64 -0
- package/dist/views/metadata-admin/widgets.d.ts +89 -1
- package/dist/views/metadata-admin/widgets.js +491 -17
- package/dist/views/runtime-metadata-persistence.d.ts +78 -0
- package/dist/views/runtime-metadata-persistence.js +89 -0
- package/dist/views/useOpenRecordList.d.ts +18 -0
- package/dist/views/useOpenRecordList.js +36 -0
- package/dist/views/userFilterUrlState.d.ts +15 -0
- package/dist/views/userFilterUrlState.js +53 -0
- package/dist/views/view-config-adapter.d.ts +38 -0
- package/dist/views/view-config-adapter.js +80 -0
- package/package.json +52 -34
- package/dist/views/DesignDrawer.d.ts +0 -28
- package/dist/views/DesignDrawer.js +0 -51
- package/dist/views/metadata-admin/DesignerEditorWrapper.d.ts +0 -68
- package/dist/views/metadata-admin/DesignerEditorWrapper.js +0 -158
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
3
3
|
/**
|
|
4
4
|
* AiChatPage — full-page ChatGPT-style AI surface.
|
|
@@ -13,16 +13,158 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
13
13
|
* turns are appended to `ai_messages` automatically.
|
|
14
14
|
*/
|
|
15
15
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
16
|
-
import { useNavigate, useParams } from 'react-router-dom';
|
|
16
|
+
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
|
17
17
|
import { useAuth } from '@object-ui/auth';
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
18
|
+
import { useObjectTranslation } from '@object-ui/i18n';
|
|
19
|
+
import { toast } from 'sonner';
|
|
20
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Button, ShareDialog, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, cn, } from '@object-ui/components';
|
|
21
|
+
import { PanelLeft, PanelLeftClose, PanelLeftOpen, Share2 } from 'lucide-react';
|
|
22
|
+
import { ChatbotEnhanced, useAgents, useObjectChat, useHitlInChat, resolveDefaultAgentName, PLATFORM_DEFAULT_AGENT, agentRouteName, resolveAgentParam, isBuiltinAgentName, isBuildAgent, isAskAgent, publishHealthFromResponse, detectDraftResult, detectProposedPlan, buildProgressFromDraftReview, } from '@object-ui/plugin-chatbot';
|
|
21
23
|
import { AppHeader } from '../../layout/AppHeader';
|
|
24
|
+
import { fetchPendingDraftCount } from '../../preview/draftStatus';
|
|
25
|
+
import { emitMetadataRefresh } from '../../assistant/assistantBus';
|
|
26
|
+
import { getRuntimeConfig } from '../../runtime-config';
|
|
27
|
+
import { cloudPricingDeepLink } from '../marketplace/marketplaceApi';
|
|
22
28
|
import { useNavigationContext } from '../../context/NavigationContext';
|
|
23
|
-
import { useChatConversation } from '../../hooks/useChatConversation';
|
|
29
|
+
import { fetchConversation, sanitizeChatMessagesForCache, useChatConversation, writeConversationMessagesCache, } from '../../hooks/useChatConversation';
|
|
30
|
+
import { useReconcileOnError } from '../../hooks/useReconcileOnError';
|
|
24
31
|
import { ConversationsSidebar } from './ConversationsSidebar';
|
|
32
|
+
import { LiveCanvas } from './LiveCanvas';
|
|
25
33
|
const DEFAULT_AI_PATH = '/api/v1/ai';
|
|
34
|
+
function partString(part, key) {
|
|
35
|
+
const value = part[key];
|
|
36
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
37
|
+
}
|
|
38
|
+
function partToolState(part) {
|
|
39
|
+
const state = partString(part, 'state');
|
|
40
|
+
switch (state) {
|
|
41
|
+
// Hydrated history is never a live stream: the turn that drove these
|
|
42
|
+
// tools has ENDED, so a dangling mid-stream state means the terminal
|
|
43
|
+
// state was never snapshotted server-side — promote to Completed or a
|
|
44
|
+
// reloaded build conversation shows every tool "Running" forever (the
|
|
45
|
+
// same incident mapMessages fixed for the floating-chat path).
|
|
46
|
+
case 'input-streaming':
|
|
47
|
+
case 'input-available':
|
|
48
|
+
return 'output-available';
|
|
49
|
+
case 'approval-requested':
|
|
50
|
+
case 'approval-responded':
|
|
51
|
+
case 'output-available':
|
|
52
|
+
case 'output-error':
|
|
53
|
+
case 'output-denied':
|
|
54
|
+
return state;
|
|
55
|
+
default:
|
|
56
|
+
// No state at all: server-side conversations persist ModelMessage
|
|
57
|
+
// `tool-call` content entries, which carry no UI state — contentToParts
|
|
58
|
+
// passes them through as `tool-call` parts verbatim. In hydrated
|
|
59
|
+
// history that turn has ended too, so stateless ≡ completed; returning
|
|
60
|
+
// undefined here leaves the invocation state-less and the chip renders
|
|
61
|
+
// "Running" forever (the live-verified gap left by the first fix).
|
|
62
|
+
return 'output-available';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Exported for tests — maps persisted/cached history to renderable messages. */
|
|
66
|
+
export function hydratedMessagesToChatMessages(messages) {
|
|
67
|
+
return messages.map((message) => {
|
|
68
|
+
const toolInvocations = [];
|
|
69
|
+
let buildProgress;
|
|
70
|
+
const content = message.parts
|
|
71
|
+
.filter((part) => part.type === 'text')
|
|
72
|
+
.map((part) => part.text ?? '')
|
|
73
|
+
.join('');
|
|
74
|
+
if (message.role === 'assistant') {
|
|
75
|
+
for (const part of message.parts) {
|
|
76
|
+
if (!part.type.startsWith('tool-'))
|
|
77
|
+
continue;
|
|
78
|
+
const toolName = partString(part, 'toolName') ?? part.type.slice('tool-'.length);
|
|
79
|
+
const toolCallId = partString(part, 'toolCallId') ?? `${message.id}-${toolName}`;
|
|
80
|
+
const state = partToolState(part);
|
|
81
|
+
// The tool RESULT (merged onto the call part by toUIMessages from the
|
|
82
|
+
// separate `tool` row) carries the ADR-0033 draft envelope. Rebuild
|
|
83
|
+
// `draftReview` so the publish / preview / review affordances return,
|
|
84
|
+
// and synthesize the "Built X" panel so the blueprint summary survives
|
|
85
|
+
// a refresh (the live progress bar is transient and not persisted).
|
|
86
|
+
const result = part.output ?? part.result;
|
|
87
|
+
const draftReview = detectDraftResult(result);
|
|
88
|
+
// The pre-build PLAN (propose_blueprint → blueprint_proposed) rides the
|
|
89
|
+
// same merged tool result; lift it so the "Proposed plan" review card
|
|
90
|
+
// survives a reload on this surface, not just in the floating chat.
|
|
91
|
+
const proposedPlan = detectProposedPlan(result);
|
|
92
|
+
toolInvocations.push({
|
|
93
|
+
toolCallId,
|
|
94
|
+
toolName,
|
|
95
|
+
...(state ? { state } : {}),
|
|
96
|
+
...(result !== undefined ? { result } : {}),
|
|
97
|
+
...(draftReview ? { draftReview } : {}),
|
|
98
|
+
...(proposedPlan ? { proposedPlan } : {}),
|
|
99
|
+
...(part.errorText ? { errorText: String(part.errorText) } : {}),
|
|
100
|
+
});
|
|
101
|
+
if (!buildProgress) {
|
|
102
|
+
const synthesized = buildProgressFromDraftReview(draftReview);
|
|
103
|
+
if (synthesized)
|
|
104
|
+
buildProgress = synthesized;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
id: message.id,
|
|
110
|
+
role: message.role,
|
|
111
|
+
content,
|
|
112
|
+
...(toolInvocations.length > 0 ? { toolInvocations } : {}),
|
|
113
|
+
...(buildProgress ? { buildProgress } : {}),
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function firstUserMessageText(messages) {
|
|
118
|
+
const message = messages.find((item) => item.role === 'user');
|
|
119
|
+
const text = message?.parts
|
|
120
|
+
.filter((part) => part.type === 'text')
|
|
121
|
+
.map((part) => part.text ?? '')
|
|
122
|
+
.join('')
|
|
123
|
+
.trim();
|
|
124
|
+
return text || undefined;
|
|
125
|
+
}
|
|
126
|
+
// Keyed by the FRIENDLY agent name (alias-group head) so the new id, the legacy
|
|
127
|
+
// id, and the route segment all localize to the same label.
|
|
128
|
+
const PLATFORM_AGENT_LABEL_KEYS = {
|
|
129
|
+
ask: { key: 'console.ai.agentLabels.ask', defaultValue: 'Ask' },
|
|
130
|
+
build: { key: 'console.ai.agentLabels.build', defaultValue: 'Build' },
|
|
131
|
+
};
|
|
132
|
+
function localizeAgentLabel(t, agentName, fallback) {
|
|
133
|
+
const known = agentName ? PLATFORM_AGENT_LABEL_KEYS[agentRouteName(agentName)] : undefined;
|
|
134
|
+
if (!known)
|
|
135
|
+
return fallback;
|
|
136
|
+
return t(known.key, { defaultValue: known.defaultValue });
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Per-surface empty-state branding. The split gives each assistant its own
|
|
140
|
+
* identity: the Build surface reads as authoring ("describe an app"), the Ask
|
|
141
|
+
* surface as data Q&A ("ask about your records"). Keyed by friendly name; falls
|
|
142
|
+
* back to the generic empty state for custom agents.
|
|
143
|
+
*/
|
|
144
|
+
function agentEmptyState(t, agentName) {
|
|
145
|
+
if (isBuildAgent(agentName)) {
|
|
146
|
+
return {
|
|
147
|
+
title: t('console.ai.empty.build.title', { defaultValue: 'Build with AI' }),
|
|
148
|
+
description: t('console.ai.empty.build.description', {
|
|
149
|
+
defaultValue: 'Describe an app or workflow in plain language — I draft the objects, screens and automations, then you review and publish.',
|
|
150
|
+
}),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (isAskAgent(agentName)) {
|
|
154
|
+
return {
|
|
155
|
+
title: t('console.ai.empty.ask.title', { defaultValue: 'Ask your data' }),
|
|
156
|
+
description: t('console.ai.empty.ask.description', {
|
|
157
|
+
defaultValue: 'Ask questions about your records — counts, lists, and summaries across the data you can access.',
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
title: t('console.ai.emptyTitle', { defaultValue: 'Start a conversation' }),
|
|
163
|
+
description: t('console.ai.emptyDescription', {
|
|
164
|
+
defaultValue: 'Ask anything — the assistant has access to your current app context.',
|
|
165
|
+
}),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
26
168
|
function resolveApiBase(explicit) {
|
|
27
169
|
if (explicit)
|
|
28
170
|
return explicit.replace(/\/$/, '');
|
|
@@ -33,10 +175,194 @@ function resolveApiBase(explicit) {
|
|
|
33
175
|
const serverUrl = env.VITE_SERVER_URL ?? '';
|
|
34
176
|
return `${serverUrl.replace(/\/$/, '')}${DEFAULT_AI_PATH}`;
|
|
35
177
|
}
|
|
178
|
+
const CHATS_COLLAPSED_STORAGE_KEY = 'ai-chats-collapsed';
|
|
179
|
+
/**
|
|
180
|
+
* State for the collapsible desktop conversations list. Exported for tests.
|
|
181
|
+
*
|
|
182
|
+
* Two drivers, one rule — never fight the user:
|
|
183
|
+
* - **Manual** `toggle()` flips it and PERSISTS the preference (localStorage),
|
|
184
|
+
* and marks the user as having taken control.
|
|
185
|
+
* - **Auto** `handleCanvasOpenChange(open)` tucks the list away when the Live
|
|
186
|
+
* Canvas preview opens (the chat + preview split is tight) and restores it on
|
|
187
|
+
* close — but ONLY if the auto-collapse is what hid it. A manual toggle (or a
|
|
188
|
+
* list the user already collapsed) is never overridden, and auto-collapse is
|
|
189
|
+
* transient (not persisted).
|
|
190
|
+
*/
|
|
191
|
+
export function useCollapsibleChatsList() {
|
|
192
|
+
const [collapsed, setCollapsed] = useState(() => {
|
|
193
|
+
try {
|
|
194
|
+
return localStorage.getItem(CHATS_COLLAPSED_STORAGE_KEY) === '1';
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
const autoCollapsedRef = useRef(false);
|
|
201
|
+
const toggle = useCallback(() => {
|
|
202
|
+
autoCollapsedRef.current = false; // an explicit toggle is the user taking control
|
|
203
|
+
setCollapsed((prev) => {
|
|
204
|
+
const next = !prev;
|
|
205
|
+
try {
|
|
206
|
+
localStorage.setItem(CHATS_COLLAPSED_STORAGE_KEY, next ? '1' : '0');
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
/* private mode / disabled storage — preference just won't persist */
|
|
210
|
+
}
|
|
211
|
+
return next;
|
|
212
|
+
});
|
|
213
|
+
}, []);
|
|
214
|
+
const handleCanvasOpenChange = useCallback((open) => {
|
|
215
|
+
if (open) {
|
|
216
|
+
setCollapsed((prev) => {
|
|
217
|
+
if (!prev) {
|
|
218
|
+
autoCollapsedRef.current = true;
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
return prev; // already collapsed (manual) — leave it, don't claim it
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
else if (autoCollapsedRef.current) {
|
|
225
|
+
autoCollapsedRef.current = false;
|
|
226
|
+
setCollapsed(false);
|
|
227
|
+
}
|
|
228
|
+
}, []);
|
|
229
|
+
return { collapsed, toggle, handleCanvasOpenChange };
|
|
230
|
+
}
|
|
231
|
+
const CHAT_PANE_WIDTH_STORAGE_KEY = 'ai-chat-pane-width';
|
|
232
|
+
/** Default chat-column width (px) when the preview opens. */
|
|
233
|
+
const CHAT_PANE_DEFAULT_WIDTH = 480;
|
|
234
|
+
/** Chat column never narrower than this. */
|
|
235
|
+
const CHAT_PANE_MIN_WIDTH = 360;
|
|
236
|
+
/** Preview pane always keeps at least this much room (caps how wide chat can grow). */
|
|
237
|
+
const CHAT_PREVIEW_MIN_WIDTH = 420;
|
|
238
|
+
/** Keyboard resize step (px) when the divider is focused. */
|
|
239
|
+
const CHAT_PANE_KEYBOARD_STEP = 24;
|
|
240
|
+
/**
|
|
241
|
+
* Clamp a desired chat-column width so neither pane collapses: at least
|
|
242
|
+
* `min`, and never so wide that the preview drops below `previewMin`. Pure +
|
|
243
|
+
* exported for tests. `containerWidth <= 0` (unmeasured) skips the upper bound.
|
|
244
|
+
*/
|
|
245
|
+
export function clampChatPaneWidth(desired, opts) {
|
|
246
|
+
const upper = opts.containerWidth > 0 ? Math.max(opts.min, opts.containerWidth - opts.previewMin) : Infinity;
|
|
247
|
+
return Math.min(Math.max(desired, opts.min), upper);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Draggable width for the chat column when the Live Canvas preview is open
|
|
251
|
+
* (ChatGPT/Claude-style split). Width persists; drags and keyboard nudges are
|
|
252
|
+
* clamped against the live container so the preview always keeps room, and a
|
|
253
|
+
* ResizeObserver re-clamps when the window shrinks. All DOM reads happen in
|
|
254
|
+
* handlers/effects, never during render.
|
|
255
|
+
*/
|
|
256
|
+
export function useResizableChatPane(active) {
|
|
257
|
+
const containerRef = useRef(null);
|
|
258
|
+
const [width, setWidth] = useState(() => {
|
|
259
|
+
try {
|
|
260
|
+
const saved = Number(localStorage.getItem(CHAT_PANE_WIDTH_STORAGE_KEY));
|
|
261
|
+
return Number.isFinite(saved) && saved > 0 ? saved : CHAT_PANE_DEFAULT_WIDTH;
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return CHAT_PANE_DEFAULT_WIDTH;
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
const [dragging, setDragging] = useState(false);
|
|
268
|
+
const clampToContainer = useCallback((desired) => clampChatPaneWidth(desired, {
|
|
269
|
+
min: CHAT_PANE_MIN_WIDTH,
|
|
270
|
+
previewMin: CHAT_PREVIEW_MIN_WIDTH,
|
|
271
|
+
containerWidth: containerRef.current?.clientWidth ?? 0,
|
|
272
|
+
}), []);
|
|
273
|
+
const persist = useCallback((w) => {
|
|
274
|
+
try {
|
|
275
|
+
localStorage.setItem(CHAT_PANE_WIDTH_STORAGE_KEY, String(Math.round(w)));
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
/* storage disabled — width just won't persist */
|
|
279
|
+
}
|
|
280
|
+
}, []);
|
|
281
|
+
// Re-clamp when the available width changes (window resize, sidebar collapse),
|
|
282
|
+
// so a previously-saved wide chat can't starve the preview.
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
if (!active)
|
|
285
|
+
return;
|
|
286
|
+
const el = containerRef.current;
|
|
287
|
+
if (!el || typeof ResizeObserver === 'undefined')
|
|
288
|
+
return;
|
|
289
|
+
const ro = new ResizeObserver(() => setWidth((w) => clampToContainer(w)));
|
|
290
|
+
ro.observe(el);
|
|
291
|
+
return () => ro.disconnect();
|
|
292
|
+
}, [active, clampToContainer]);
|
|
293
|
+
const onHandlePointerDown = useCallback((e) => {
|
|
294
|
+
e.preventDefault();
|
|
295
|
+
const startX = e.clientX;
|
|
296
|
+
const startWidth = width; // state is the style source, so it == the rendered width
|
|
297
|
+
setDragging(true);
|
|
298
|
+
const onMove = (ev) => setWidth(clampToContainer(startWidth + (ev.clientX - startX)));
|
|
299
|
+
const onUp = (ev) => {
|
|
300
|
+
const final = clampToContainer(startWidth + (ev.clientX - startX));
|
|
301
|
+
setWidth(final);
|
|
302
|
+
persist(final);
|
|
303
|
+
setDragging(false);
|
|
304
|
+
window.removeEventListener('pointermove', onMove);
|
|
305
|
+
window.removeEventListener('pointerup', onUp);
|
|
306
|
+
};
|
|
307
|
+
window.addEventListener('pointermove', onMove);
|
|
308
|
+
window.addEventListener('pointerup', onUp);
|
|
309
|
+
}, [clampToContainer, persist, width]);
|
|
310
|
+
const onHandleKeyDown = useCallback((e) => {
|
|
311
|
+
const delta = e.key === 'ArrowLeft' ? -CHAT_PANE_KEYBOARD_STEP : e.key === 'ArrowRight' ? CHAT_PANE_KEYBOARD_STEP : 0;
|
|
312
|
+
if (!delta)
|
|
313
|
+
return;
|
|
314
|
+
e.preventDefault();
|
|
315
|
+
setWidth((w) => {
|
|
316
|
+
const next = clampToContainer(w + delta);
|
|
317
|
+
persist(next);
|
|
318
|
+
return next;
|
|
319
|
+
});
|
|
320
|
+
}, [clampToContainer, persist]);
|
|
321
|
+
const reset = useCallback(() => {
|
|
322
|
+
const next = clampToContainer(CHAT_PANE_DEFAULT_WIDTH);
|
|
323
|
+
setWidth(next);
|
|
324
|
+
persist(next);
|
|
325
|
+
}, [clampToContainer, persist]);
|
|
326
|
+
return { width, dragging, containerRef, onHandlePointerDown, onHandleKeyDown, reset };
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Match a keydown to an AI-chat shortcut, mirroring ChatGPT/Claude:
|
|
330
|
+
* - ⌘/Ctrl+Shift+O → new chat
|
|
331
|
+
* - ⌘/Ctrl+Shift+S → toggle the conversations list
|
|
332
|
+
*
|
|
333
|
+
* Both use the ⌘/Ctrl+Shift modifier so they're safe to fire even while the
|
|
334
|
+
* composer is focused (they can't be produced by ordinary typing). Pure +
|
|
335
|
+
* exported for tests.
|
|
336
|
+
*/
|
|
337
|
+
export function matchAiChatShortcut(e) {
|
|
338
|
+
if (!(e.metaKey || e.ctrlKey) || !e.shiftKey || e.altKey)
|
|
339
|
+
return null;
|
|
340
|
+
switch (e.key.toLowerCase()) {
|
|
341
|
+
case 'o':
|
|
342
|
+
return 'new-chat';
|
|
343
|
+
case 's':
|
|
344
|
+
return 'toggle-list';
|
|
345
|
+
default:
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
36
349
|
export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentProp } = {}) {
|
|
37
350
|
const { user } = useAuth();
|
|
351
|
+
const { t } = useObjectTranslation();
|
|
38
352
|
const userId = user?.id;
|
|
39
|
-
|
|
353
|
+
// The agent is BAKED INTO THE ROUTE now: `/ai/:agent[/:conversationId]`.
|
|
354
|
+
// Deriving it from the path param (resolved against the live catalog) removes
|
|
355
|
+
// the old `?agent=` query snapshot, which StrictMode's double-mount + the
|
|
356
|
+
// `/ai`→`/ai/:id` URL rewrite used to drop (the metadata_assistant deep-link
|
|
357
|
+
// bug). The dropdown is now a launcher that navigates between these routes.
|
|
358
|
+
const { agent: agentSegment, conversationId: urlConversationId } = useParams();
|
|
359
|
+
const [searchParams] = useSearchParams();
|
|
360
|
+
const searchString = searchParams.toString();
|
|
361
|
+
// Explicit new-conversation intent (`?new=1`, the sidebar's New button).
|
|
362
|
+
// Read LIVE (not snapshotted): the button can be clicked again later from an
|
|
363
|
+
// existing conversation, and the flag is stripped once the fresh id is
|
|
364
|
+
// mirrored into the URL.
|
|
365
|
+
const forceNewConversation = searchParams.get('new') !== null;
|
|
40
366
|
const navigate = useNavigate();
|
|
41
367
|
const { setContext } = useNavigationContext();
|
|
42
368
|
useEffect(() => {
|
|
@@ -46,33 +372,191 @@ export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentPro
|
|
|
46
372
|
const env = import.meta.env ?? {};
|
|
47
373
|
const envDefaultAgent = env.VITE_AI_DEFAULT_AGENT;
|
|
48
374
|
const { agents, isLoading: agentsLoading, error: agentsError } = useAgents({ apiBase });
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
375
|
+
const catalogNames = useMemo(() => agents.map((a) => a.name), [agents]);
|
|
376
|
+
// Is the first path segment an agent? It is when it resolves to one (friendly
|
|
377
|
+
// alias / new id / legacy id). When it doesn't, it's a legacy bare
|
|
378
|
+
// `/ai/:conversationId` link (redirected below). `undefined` = catalog still
|
|
379
|
+
// loading, so we can't tell yet and redirects must wait.
|
|
380
|
+
const segmentIsAgent = useMemo(() => {
|
|
381
|
+
if (!agentSegment)
|
|
382
|
+
return false;
|
|
383
|
+
if (agents.length === 0)
|
|
384
|
+
return undefined;
|
|
385
|
+
return resolveAgentParam(agentSegment, catalogNames) !== undefined;
|
|
386
|
+
}, [agentSegment, agents.length, catalogNames]);
|
|
387
|
+
// Back-compat: the legacy deep-link `/ai?agent=metadata_assistant` (only
|
|
388
|
+
// meaningful on a bare `/ai`, before the agent moved into the path). Honored
|
|
389
|
+
// here, then stripped as the route is canonicalized to `/ai/:agent`.
|
|
390
|
+
const legacyAgentParam = !agentSegment ? searchParams.get('agent') ?? undefined : undefined;
|
|
391
|
+
// App/platform default — used for a bare `/ai` (respecting the legacy
|
|
392
|
+
// `?agent=`), and as the endpoint while a legacy bare-id link is being
|
|
393
|
+
// redirected to its real agent surface.
|
|
394
|
+
const fallbackAgent = useMemo(() => resolveDefaultAgentName(agents, legacyAgentParam ?? defaultAgentProp ?? envDefaultAgent), [agents, legacyAgentParam, defaultAgentProp, envDefaultAgent]);
|
|
395
|
+
// Resolved backend agent name for this surface (route agent wins; else default).
|
|
396
|
+
const activeAgent = useMemo(() => {
|
|
397
|
+
if (agents.length === 0)
|
|
398
|
+
return undefined;
|
|
399
|
+
if (agentSegment && segmentIsAgent) {
|
|
400
|
+
return resolveAgentParam(agentSegment, catalogNames);
|
|
55
401
|
}
|
|
56
|
-
|
|
402
|
+
return fallbackAgent;
|
|
403
|
+
}, [agents.length, agentSegment, segmentIsAgent, catalogNames, fallbackAgent]);
|
|
404
|
+
const activeAgentRoute = activeAgent ? agentRouteName(activeAgent) : undefined;
|
|
405
|
+
// A KNOWN built-in agent (build/ask/…) that the live catalog doesn't serve —
|
|
406
|
+
// e.g. `/ai/build` on a deployment without the cloud AI Studio plugin. It's
|
|
407
|
+
// an unavailable AGENT, not a conversation id, so we fall back to the default
|
|
408
|
+
// surface instead of treating "build" as a chat to load.
|
|
409
|
+
const unavailableKnownAgent = Boolean(agentSegment && segmentIsAgent === false && isBuiltinAgentName(agentSegment));
|
|
410
|
+
// A first segment that ISN'T an agent and ISN'T a known (unavailable) agent
|
|
411
|
+
// name is a legacy bare conversation id.
|
|
412
|
+
const legacyConversationId = agentSegment && segmentIsAgent === false && !unavailableKnownAgent ? agentSegment : undefined;
|
|
57
413
|
const chatApi = activeAgent
|
|
58
414
|
? `${apiBase}/agents/${encodeURIComponent(activeAgent)}/chat`
|
|
59
415
|
: undefined;
|
|
60
|
-
const { conversationId,
|
|
61
|
-
|
|
416
|
+
const { conversationId, conversationScope, initialMessages } = useChatConversation({
|
|
417
|
+
// Gate resolution on the agent being known: resolving while `activeAgent`
|
|
418
|
+
// is still undefined (catalog loading) would bind a SCOPELESS conversation
|
|
419
|
+
// that the per-(user,scope) guard then sticks with — so the agent surface
|
|
420
|
+
// would resume some other agent's last chat. Waiting one tick keys the
|
|
421
|
+
// conversation to the right agent from the first resolve.
|
|
422
|
+
userId: activeAgent ? userId : undefined,
|
|
62
423
|
scope: activeAgent,
|
|
63
424
|
apiBase,
|
|
64
|
-
activeId: urlConversationId,
|
|
425
|
+
activeId: urlConversationId ?? legacyConversationId,
|
|
426
|
+
forceNew: forceNewConversation,
|
|
65
427
|
});
|
|
428
|
+
// ── Route canonicalization ──────────────────────────────────────────────
|
|
429
|
+
// Back-compat redirects (the agent is now in the path): bare `/ai` → the
|
|
430
|
+
// default agent surface (or the `?agent=` deep-link target); a legacy built-in
|
|
431
|
+
// id in the agent slot (`/ai/metadata_assistant`) → its friendly form
|
|
432
|
+
// (`/ai/build`). Custom agents already route by their own name and no-op here.
|
|
433
|
+
// The `?new=1` intent is preserved; the consumed legacy `agent` param is
|
|
434
|
+
// stripped so it doesn't linger in the canonical URL.
|
|
435
|
+
useEffect(() => {
|
|
436
|
+
if (agents.length === 0 || !activeAgent)
|
|
437
|
+
return;
|
|
438
|
+
const friendly = agentRouteName(activeAgent);
|
|
439
|
+
const preserved = new URLSearchParams(searchString);
|
|
440
|
+
preserved.delete('agent');
|
|
441
|
+
const preservedQuery = preserved.toString() ? `?${preserved.toString()}` : '';
|
|
442
|
+
if (!agentSegment) {
|
|
443
|
+
navigate(`/ai/${friendly}${preservedQuery}`, { replace: true });
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
// A known agent that isn't deployed here (e.g. `/ai/build` with no cloud AI
|
|
447
|
+
// Studio): land cleanly on the default surface rather than treating the
|
|
448
|
+
// segment as a conversation id (which produced the junk `/ai/ask/build`).
|
|
449
|
+
if (unavailableKnownAgent) {
|
|
450
|
+
navigate(`/ai/${friendly}${preservedQuery}`, { replace: true });
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (segmentIsAgent && agentSegment !== friendly) {
|
|
454
|
+
const tail = urlConversationId ? `/${encodeURIComponent(urlConversationId)}` : '';
|
|
455
|
+
navigate(`/ai/${friendly}${tail}${preservedQuery}`, { replace: true });
|
|
456
|
+
}
|
|
457
|
+
}, [agents.length, activeAgent, agentSegment, segmentIsAgent, unavailableKnownAgent, urlConversationId, searchString, navigate]);
|
|
458
|
+
// ── Legacy `/ai/:conversationId` (bare id) ──────────────────────────────
|
|
459
|
+
// Resolve the conversation's own agent and 301 to `/ai/:agent/:conversationId`
|
|
460
|
+
// so old bookmarks keep working under the agent-scoped routes.
|
|
461
|
+
useEffect(() => {
|
|
462
|
+
if (legacyConversationId === undefined)
|
|
463
|
+
return;
|
|
464
|
+
let cancelled = false;
|
|
465
|
+
(async () => {
|
|
466
|
+
let convAgent;
|
|
467
|
+
try {
|
|
468
|
+
const conv = await fetchConversation(apiBase, legacyConversationId);
|
|
469
|
+
convAgent = conv?.agentId ?? undefined;
|
|
470
|
+
}
|
|
471
|
+
catch {
|
|
472
|
+
/* gone / inaccessible — fall back to the default surface below */
|
|
473
|
+
}
|
|
474
|
+
if (cancelled)
|
|
475
|
+
return;
|
|
476
|
+
const resolved = resolveAgentParam(convAgent ?? '', catalogNames);
|
|
477
|
+
const friendly = agentRouteName(resolved ?? activeAgent ?? PLATFORM_DEFAULT_AGENT);
|
|
478
|
+
navigate(`/ai/${friendly}/${encodeURIComponent(legacyConversationId)}`, { replace: true });
|
|
479
|
+
})();
|
|
480
|
+
return () => {
|
|
481
|
+
cancelled = true;
|
|
482
|
+
};
|
|
483
|
+
}, [legacyConversationId, apiBase, catalogNames, activeAgent, navigate]);
|
|
66
484
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
485
|
+
const [titleHints, setTitleHints] = useState({});
|
|
67
486
|
const [shareOpen, setShareOpen] = useState(false);
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
//
|
|
487
|
+
const [mobileChatsOpen, setMobileChatsOpen] = useState(false);
|
|
488
|
+
const { collapsed: chatsCollapsed, toggle: toggleChatsCollapsed, handleCanvasOpenChange, } = useCollapsibleChatsList();
|
|
489
|
+
// Keyboard shortcuts (ChatGPT/Claude parity): ⌘⇧O new chat, ⌘⇧S toggle list.
|
|
71
490
|
useEffect(() => {
|
|
72
|
-
|
|
73
|
-
|
|
491
|
+
const onKeyDown = (e) => {
|
|
492
|
+
const action = matchAiChatShortcut(e);
|
|
493
|
+
if (!action)
|
|
494
|
+
return;
|
|
495
|
+
e.preventDefault();
|
|
496
|
+
if (action === 'toggle-list')
|
|
497
|
+
toggleChatsCollapsed();
|
|
498
|
+
else
|
|
499
|
+
navigate(activeAgentRoute ? `/ai/${activeAgentRoute}?new=1` : '/ai?new=1');
|
|
500
|
+
};
|
|
501
|
+
document.addEventListener('keydown', onKeyDown);
|
|
502
|
+
return () => document.removeEventListener('keydown', onKeyDown);
|
|
503
|
+
}, [toggleChatsCollapsed, navigate, activeAgentRoute]);
|
|
504
|
+
const restApiBase = useMemo(() => apiBase.replace(/\/v1\/ai$/, '').replace(/\/ai$/, '') || '/api', [apiBase]);
|
|
505
|
+
// Public share-link landing base. SharedRecordPage lives UNDER the console
|
|
506
|
+
// SPA basename (e.g. `/_console/s/:token`), so the ShareDialog default of
|
|
507
|
+
// `${origin}/s/:token` 404s for recipients. Derive the base from the SPA's
|
|
508
|
+
// BASE_URL so the copyable link points at the actually-served route.
|
|
509
|
+
const publicShareBase = useMemo(() => {
|
|
510
|
+
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
511
|
+
return undefined;
|
|
512
|
+
// Mirror the console's own basename resolution (App.tsx resolveBasename):
|
|
513
|
+
// the published SPA uses a relative Vite base, so the mount path is carried
|
|
514
|
+
// by the injected `<base href>` tag, NOT import.meta.env.BASE_URL.
|
|
515
|
+
let base = '';
|
|
516
|
+
try {
|
|
517
|
+
const href = document.querySelector('base')?.getAttribute('href');
|
|
518
|
+
if (href)
|
|
519
|
+
base = new URL(href, window.location.origin).pathname.replace(/\/+$/, '');
|
|
74
520
|
}
|
|
75
|
-
|
|
521
|
+
catch { /* no <base> → root-mounted SPA */ }
|
|
522
|
+
return `${window.location.origin}${base}/s`;
|
|
523
|
+
}, []);
|
|
524
|
+
// New-conversation race guard. On an IN-SPA `/ai?new=1` navigation the
|
|
525
|
+
// URL-mirroring effect below fires in the SAME commit as the hook's effect,
|
|
526
|
+
// with this render's (stale) `conversationId` still in its closure — the
|
|
527
|
+
// hook's setConversationId(undefined) hasn't re-rendered yet. Unguarded, it
|
|
528
|
+
// bounced straight back to `/ai/:oldId` and stripped the flag before the
|
|
529
|
+
// fresh conversation existed (the New button looked like a no-op; a full
|
|
530
|
+
// page load on the same URL worked because state starts empty). Snapshot
|
|
531
|
+
// the id visible when the flag appears — a RENDER-phase ref write, so it's
|
|
532
|
+
// set before any effect of this commit runs — and refuse to mirror that
|
|
533
|
+
// exact id while the flag is up. The fresh id differs, mirrors normally,
|
|
534
|
+
// and the navigation strips `?new=1`, which resets the snapshot.
|
|
535
|
+
const staleNewTargetRef = useRef(null);
|
|
536
|
+
if (forceNewConversation) {
|
|
537
|
+
if (staleNewTargetRef.current === null)
|
|
538
|
+
staleNewTargetRef.current = { id: conversationId };
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
staleNewTargetRef.current = null;
|
|
542
|
+
}
|
|
543
|
+
// After the hook resolves a real id for a fresh agent-surface visit, mirror
|
|
544
|
+
// it into the URL (`/ai/:agent/:id`) so the sidebar's active-row + share +
|
|
545
|
+
// refresh all work. Only fires on a real agent surface that has no id yet —
|
|
546
|
+
// not while a bare `/ai` or a legacy bare id is still being redirected.
|
|
547
|
+
useEffect(() => {
|
|
548
|
+
if (!segmentIsAgent || urlConversationId || !conversationId || !activeAgentRoute)
|
|
549
|
+
return;
|
|
550
|
+
if (staleNewTargetRef.current && staleNewTargetRef.current.id === conversationId)
|
|
551
|
+
return;
|
|
552
|
+
// Don't mirror a conversation that belongs to the PREVIOUS agent: right
|
|
553
|
+
// after a launcher switch, `conversationId` still holds the old agent's id
|
|
554
|
+
// until the hook re-resolves under the new scope. Mirroring it would write
|
|
555
|
+
// it onto the new agent's URL and resume the wrong chat.
|
|
556
|
+
if (conversationScope !== activeAgent)
|
|
557
|
+
return;
|
|
558
|
+
navigate(`/ai/${activeAgentRoute}/${conversationId}`, { replace: true });
|
|
559
|
+
}, [segmentIsAgent, urlConversationId, conversationId, conversationScope, activeAgent, activeAgentRoute, navigate]);
|
|
76
560
|
const titledRef = useRef(new Set());
|
|
77
561
|
// A resumed conversation already has history; treat it as already-titled
|
|
78
562
|
// so we don't clobber the original title on the next user turn.
|
|
@@ -81,9 +565,20 @@ export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentPro
|
|
|
81
565
|
titledRef.current.add(conversationId);
|
|
82
566
|
}
|
|
83
567
|
}, [conversationId, initialMessages.length]);
|
|
568
|
+
useEffect(() => {
|
|
569
|
+
if (!conversationId)
|
|
570
|
+
return;
|
|
571
|
+
const hint = firstUserMessageText(initialMessages);
|
|
572
|
+
if (!hint)
|
|
573
|
+
return;
|
|
574
|
+
setTitleHints((current) => current[conversationId] === hint ? current : { ...current, [conversationId]: hint });
|
|
575
|
+
}, [conversationId, initialMessages]);
|
|
84
576
|
const handleSent = useCallback((firstUserMessage) => {
|
|
85
577
|
// New user turn → bump sidebar list so the row's preview/timestamp refreshes.
|
|
86
578
|
setRefreshKey((k) => k + 1);
|
|
579
|
+
if (firstUserMessage && conversationId) {
|
|
580
|
+
setTitleHints((current) => ({ ...current, [conversationId]: firstUserMessage }));
|
|
581
|
+
}
|
|
87
582
|
// Server now generates a concise LLM-summarised title fire-and-forget
|
|
88
583
|
// after the first assistant turn lands (see service-ai
|
|
89
584
|
// `summarizeConversation`). We don't PATCH a truncated preview from the
|
|
@@ -103,32 +598,88 @@ export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentPro
|
|
|
103
598
|
void t1;
|
|
104
599
|
void t2;
|
|
105
600
|
}, [conversationId]);
|
|
106
|
-
return (_jsxs("div", { className: "flex h-svh w-full flex-col bg-background", "data-testid": "ai-chat-page", children: [_jsxs("header", { className: "sticky top-0 z-30 flex h-14 w-full shrink-0 items-center gap-2 border-b bg-background px-2 sm:px-4", children: [_jsx(
|
|
601
|
+
return (_jsxs("div", { className: "flex h-svh w-full flex-col bg-background", "data-testid": "ai-chat-page", children: [_jsxs("header", { className: "sticky top-0 z-30 flex h-14 w-full shrink-0 items-center gap-2 border-b bg-background/95 px-2 backdrop-blur sm:px-4", children: [_jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 shrink-0 md:hidden", onClick: () => setMobileChatsOpen(true), "aria-label": t('console.ai.openChats'), "data-testid": "ai-chat-mobile-sidebar-trigger", children: _jsx(PanelLeft, { className: "h-4 w-4" }) }), _jsx(Button, { variant: "ghost", size: "icon", className: "hidden h-8 w-8 shrink-0 md:inline-flex", onClick: toggleChatsCollapsed, "aria-label": chatsCollapsed
|
|
602
|
+
? t('console.ai.showChats', { defaultValue: 'Show chats' })
|
|
603
|
+
: t('console.ai.hideChats', { defaultValue: 'Hide chats' }), title: chatsCollapsed
|
|
604
|
+
? t('console.ai.showChats', { defaultValue: 'Show chats' })
|
|
605
|
+
: t('console.ai.hideChats', { defaultValue: 'Hide chats' }), "data-testid": "ai-chat-collapse-sidebar-trigger", "aria-pressed": chatsCollapsed, children: chatsCollapsed ? _jsx(PanelLeftOpen, { className: "h-4 w-4" }) : _jsx(PanelLeftClose, { className: "h-4 w-4" }) }), _jsx("div", { className: "min-w-0 flex-1", children: _jsx(AppHeader, { variant: "home" }) })] }), _jsx(Sheet, { open: mobileChatsOpen, onOpenChange: setMobileChatsOpen, children: _jsxs(SheetContent, { side: "left", className: "w-[320px] p-0 sm:max-w-[360px]", "data-testid": "ai-chat-mobile-sidebar", children: [_jsxs(SheetHeader, { className: "sr-only", children: [_jsx(SheetTitle, { children: t('console.ai.chats') }), _jsx(SheetDescription, { children: t('console.ai.chatsDescription') })] }), _jsx(ConversationsSidebar, { userId: userId, apiBase: apiBase, activeAgent: activeAgent, refreshKey: refreshKey, titleHints: titleHints, className: "h-full border-r-0", onNavigate: () => setMobileChatsOpen(false) })] }) }), conversationId && (_jsx(ShareDialog, { open: shareOpen, onOpenChange: setShareOpen, objectName: "ai_conversations", recordId: conversationId, recordLabel: "this conversation", apiBase: restApiBase, publicBaseUrl: publicShareBase })), _jsxs("div", { className: "flex min-h-0 flex-1 w-full bg-muted/20", children: [!chatsCollapsed && (_jsx(ConversationsSidebar, { userId: userId, apiBase: apiBase, activeAgent: activeAgent, refreshKey: refreshKey, titleHints: titleHints, className: "hidden w-72 shrink-0 border-r md:flex" })), _jsx("main", { className: "flex min-w-0 flex-1 flex-col", children: _jsx(ChatPane, { agents: agents, agentsLoading: agentsLoading, agentsError: agentsError, activeAgent: activeAgent, chatApi: chatApi, apiBase: apiBase, conversationId: conversationId, initialMessages: initialMessages, onSent: handleSent, onShare: () => setShareOpen(true), onCanvasOpenChange: handleCanvasOpenChange }, `${chatApi ?? 'local'}:${conversationId ?? 'pending'}`) })] })] }));
|
|
107
606
|
}
|
|
108
|
-
function ChatPane({ agents, agentsLoading, agentsError, activeAgent,
|
|
607
|
+
function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, apiBase, conversationId, initialMessages, onSent, onShare, onCanvasOpenChange, }) {
|
|
608
|
+
const { t } = useObjectTranslation();
|
|
609
|
+
const navigate = useNavigate();
|
|
610
|
+
// The agent dropdown is a LAUNCHER now (not an in-surface mode toggle): it
|
|
611
|
+
// navigates to `/ai/:agent`, so it naturally lists custom agents and can stay
|
|
612
|
+
// always-available. Shown only when there's more than one agent to switch to.
|
|
613
|
+
const showAgentLauncher = agents.length > 1;
|
|
614
|
+
// ── ADR-0037 Live Canvas ────────────────────────────────────────────────
|
|
615
|
+
// When a build session drafts an `app`, open the split-view canvas: the
|
|
616
|
+
// drafted app rendered as-if-published (`?preview=draft`) beside the chat.
|
|
617
|
+
// Per-artifact signals coalesce (800 ms) into one pane refresh so a
|
|
618
|
+
// whole-app build doesn't trigger an invalidation storm.
|
|
619
|
+
const [canvasApp, setCanvasApp] = useState(null);
|
|
620
|
+
const [canvasRefreshKey, setCanvasRefreshKey] = useState(0);
|
|
621
|
+
const canvasTimerRef = useRef(null);
|
|
622
|
+
useEffect(() => () => {
|
|
623
|
+
if (canvasTimerRef.current)
|
|
624
|
+
window.clearTimeout(canvasTimerRef.current);
|
|
625
|
+
}, []);
|
|
626
|
+
// Tell the page when the preview pane opens/closes so it can tuck the chats
|
|
627
|
+
// list away for the (tight) chat + preview split, and restore it after.
|
|
628
|
+
const canvasOpen = canvasApp !== null;
|
|
629
|
+
useEffect(() => {
|
|
630
|
+
onCanvasOpenChange?.(canvasOpen);
|
|
631
|
+
}, [canvasOpen, onCanvasOpenChange]);
|
|
632
|
+
// Draggable chat ↔ preview split (active only while the preview is open).
|
|
633
|
+
const split = useResizableChatPane(canvasOpen);
|
|
634
|
+
const handleDraftArtifacts = useCallback((artifacts, appSegment) => {
|
|
635
|
+
const app = artifacts.find((a) => a.type === 'app');
|
|
636
|
+
// Route the preview on the app's package id (ADR-0048), not its name.
|
|
637
|
+
if (app)
|
|
638
|
+
setCanvasApp((prev) => prev ?? { name: app.name, segment: appSegment, materialized: false });
|
|
639
|
+
if (canvasTimerRef.current)
|
|
640
|
+
window.clearTimeout(canvasTimerRef.current);
|
|
641
|
+
canvasTimerRef.current = window.setTimeout(() => setCanvasRefreshKey((k) => k + 1), 800);
|
|
642
|
+
}, []);
|
|
643
|
+
// ADR-0045: the build finished and was materialized (real tables + data,
|
|
644
|
+
// app unlisted). Switch the open canvas from the draft overlay to the REAL
|
|
645
|
+
// app URL — the reload that follows shows live rows in every list.
|
|
646
|
+
const handleBuildMaterialized = useCallback((appName) => {
|
|
647
|
+
setCanvasApp((prev) => prev && prev.name === appName && !prev.materialized
|
|
648
|
+
? { ...prev, materialized: true } // keep the package-id segment
|
|
649
|
+
: prev ?? { name: appName, materialized: true });
|
|
650
|
+
}, []);
|
|
651
|
+
// A different conversation is a different build session — close the pane.
|
|
652
|
+
useEffect(() => {
|
|
653
|
+
setCanvasApp(null);
|
|
654
|
+
}, [conversationId]);
|
|
109
655
|
const activeAgentLabel = useMemo(() => {
|
|
110
656
|
const found = agents.find((a) => a.name === activeAgent);
|
|
111
|
-
return found?.label ?? activeAgent ?? '
|
|
112
|
-
}, [agents, activeAgent]);
|
|
657
|
+
return localizeAgentLabel(t, activeAgent, found?.label ?? activeAgent ?? t('console.ai.assistant'));
|
|
658
|
+
}, [agents, activeAgent, t]);
|
|
113
659
|
const hydrated = useMemo(() => {
|
|
114
|
-
return initialMessages
|
|
115
|
-
id: m.id,
|
|
116
|
-
role: m.role,
|
|
117
|
-
content: m.parts.map((p) => p.text).join(''),
|
|
118
|
-
}));
|
|
660
|
+
return hydratedMessagesToChatMessages(initialMessages);
|
|
119
661
|
}, [initialMessages]);
|
|
120
662
|
const suggestions = useMemo(() => {
|
|
121
663
|
if (hydrated.length > 0)
|
|
122
664
|
return undefined;
|
|
123
|
-
return buildAgentSuggestions(activeAgent, activeAgentLabel);
|
|
124
|
-
}, [hydrated.length, activeAgent, activeAgentLabel]);
|
|
125
|
-
|
|
665
|
+
return buildAgentSuggestions(activeAgent, activeAgentLabel, t);
|
|
666
|
+
}, [hydrated.length, activeAgent, activeAgentLabel, t]);
|
|
667
|
+
// Per-surface empty-state branding (Build = authoring, Ask = data Q&A).
|
|
668
|
+
const emptyState = useMemo(() => agentEmptyState(t, activeAgent), [t, activeAgent]);
|
|
669
|
+
// ADR-0013 D2: reconcile a stream-transport failure instead of blindly
|
|
670
|
+
// retrying. Shared across chat surfaces — see useReconcileOnError.
|
|
671
|
+
const { errorSuppressed, handleChatError, setMessagesRef, resetSuppression } = useReconcileOnError({ chatApi, conversationId });
|
|
672
|
+
const { messages, isLoading, error, sendMessage, stop, reload, clear, setMessages, } = useObjectChat({
|
|
126
673
|
api: chatApi,
|
|
127
674
|
conversationId,
|
|
675
|
+
onError: handleChatError,
|
|
128
676
|
body: {
|
|
129
677
|
context: {
|
|
130
678
|
activeApp: 'AI',
|
|
131
679
|
agentName: activeAgent,
|
|
680
|
+
// Tell the agent the environment's publish posture so its narration
|
|
681
|
+
// matches reality (an auto-published build is live, not "to publish").
|
|
682
|
+
autoPublishAiBuilds: getRuntimeConfig().features.autoPublishAiBuilds,
|
|
132
683
|
},
|
|
133
684
|
},
|
|
134
685
|
initialMessages: hydrated,
|
|
@@ -136,6 +687,25 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, onAgentChan
|
|
|
136
687
|
autoResponseText: "Thanks for your message! I'm here to help.",
|
|
137
688
|
autoResponseDelay: 600,
|
|
138
689
|
});
|
|
690
|
+
useEffect(() => {
|
|
691
|
+
setMessagesRef.current = setMessages;
|
|
692
|
+
}, [setMessages]);
|
|
693
|
+
useEffect(() => {
|
|
694
|
+
writeConversationMessagesCache(conversationId, sanitizeChatMessagesForCache(messages));
|
|
695
|
+
}, [conversationId, messages]);
|
|
696
|
+
// ADR-0037: refresh the live preview when a turn finishes while the canvas is
|
|
697
|
+
// open. The per-artifact `onDraftArtifacts` signal covers a build streaming in,
|
|
698
|
+
// but an incremental edit (add a field, rename) can land without growing the
|
|
699
|
+
// de-duped artifact set — so its draft never reached the iframe and the pane
|
|
700
|
+
// (and its "Changes (N)" count) went stale until a manual reload. Bumping on
|
|
701
|
+
// the loading falling-edge guarantees the preview reflects every change.
|
|
702
|
+
const prevLoadingRef = useRef(false);
|
|
703
|
+
useEffect(() => {
|
|
704
|
+
if (prevLoadingRef.current && !isLoading && canvasApp) {
|
|
705
|
+
setCanvasRefreshKey((k) => k + 1);
|
|
706
|
+
}
|
|
707
|
+
prevLoadingRef.current = isLoading;
|
|
708
|
+
}, [isLoading, canvasApp]);
|
|
139
709
|
const hitl = useHitlInChat({
|
|
140
710
|
messages: messages,
|
|
141
711
|
apiBase,
|
|
@@ -144,42 +714,153 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, onAgentChan
|
|
|
144
714
|
},
|
|
145
715
|
});
|
|
146
716
|
const handleSend = useCallback((content, files) => {
|
|
717
|
+
resetSuppression();
|
|
147
718
|
sendMessage(content, files);
|
|
148
719
|
onSent(content);
|
|
149
720
|
}, [sendMessage, onSent]);
|
|
150
|
-
const headerSlot =
|
|
151
|
-
return (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
721
|
+
const headerSlot = (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 border-b border-border/50 px-4 pb-2 pt-3 sm:px-6", children: [_jsx("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: showAgentLauncher ? (_jsxs(Select, { value: activeAgent, onValueChange: (name) => navigate(`/ai/${agentRouteName(name)}`), disabled: agentsLoading, children: [_jsx(SelectTrigger, { className: "h-7 w-auto min-w-0 border-0 bg-transparent px-1.5 text-xs shadow-none hover:bg-accent focus:ring-0 focus:ring-offset-0 focus-visible:ring-1 focus-visible:ring-border/80 focus-visible:ring-offset-0 sm:min-w-[160px]", "data-testid": "ai-chat-agent-picker", "aria-label": t('console.ai.switchAssistant', { defaultValue: 'Switch assistant' }), children: _jsx(SelectValue, { placeholder: t('console.ai.chooseAgent', { defaultValue: 'Choose assistant…' }) }) }), _jsx(SelectContent, { align: "start", children: agents.map((agent) => (_jsxs(SelectItem, { value: agent.name, className: "text-xs", children: [_jsx("span", { className: "font-medium", children: localizeAgentLabel(t, agent.name, agent.label) }), agent.description ? (_jsx("span", { className: "block text-muted-foreground text-[10px] truncate max-w-[260px]", children: agent.description })) : null] }, agent.name))) })] })) : (_jsx("span", { className: "truncate text-xs font-medium text-foreground/85", children: activeAgentLabel })) }), _jsx("div", { className: "flex shrink-0 items-center gap-1", children: _jsx(Button, { variant: "ghost", size: "icon", className: "h-7 w-7 text-muted-foreground hover:text-foreground", onClick: onShare, disabled: !conversationId, "aria-label": t('console.ai.share'), "data-testid": "ai-chat-share-button", title: conversationId ? t('console.ai.shareTitle') : t('console.ai.shareDisabledTitle'), children: _jsx(Share2, { className: "h-3.5 w-3.5" }) }) }), agentsError ? (_jsx("span", { className: "basis-full text-[10px] text-amber-700 dark:text-amber-400", title: agentsError.message, children: t('console.ai.offlineDemoMode') })) : null] }));
|
|
722
|
+
return (_jsxs("div", { ref: split.containerRef, className: "relative flex min-h-0 flex-1 px-0", children: [_jsx("div", { "data-chat-column": true, className: canvasApp
|
|
723
|
+
? 'flex min-h-0 shrink-0 justify-center'
|
|
724
|
+
: 'flex min-h-0 flex-1 justify-center', style: canvasApp ? { width: split.width } : undefined, children: _jsx(ChatbotEnhanced, { className: "min-h-0 flex-1 bg-background md:max-w-5xl", onUpgrade: () => window.open(cloudPricingDeepLink(), '_blank', 'noopener,noreferrer'), surface: "plain", maxHeight: "100%", headerSlot: headerSlot, messages: messages, placeholder: activeAgent
|
|
725
|
+
? t('console.ai.askAgent', { agent: activeAgentLabel })
|
|
726
|
+
: agentsLoading
|
|
727
|
+
? t('console.ai.loadingAgents')
|
|
728
|
+
: t('console.ai.askAnything'), labels: {
|
|
729
|
+
emptyTitle: emptyState.title,
|
|
730
|
+
emptyDescription: emptyState.description,
|
|
731
|
+
clear: t('console.ai.clearConversation'),
|
|
732
|
+
sendHint: t('console.ai.sendHint'),
|
|
733
|
+
agentActivity: t('console.ai.agentActivity'),
|
|
734
|
+
toolCompleted: t('console.ai.toolCompleted'),
|
|
735
|
+
toolRunning: t('console.ai.toolRunning'),
|
|
736
|
+
toolAwaitingApproval: t('console.ai.toolAwaitingApproval'),
|
|
737
|
+
toolFailed: t('console.ai.toolFailed'),
|
|
738
|
+
connectionWaiting: t('console.ai.connectionWaiting', { defaultValue: 'Waiting for server…' }),
|
|
739
|
+
connectionStalledLabel: t('console.ai.connectionStalled', { defaultValue: 'Still working…' }),
|
|
740
|
+
connectionOfflineLabel: t('console.ai.connectionOffline', { defaultValue: 'Connection lost — reconnecting…' }),
|
|
741
|
+
toolDetailsHidden: t('console.ai.toolDetailsHidden'),
|
|
742
|
+
copy: t('console.ai.copy'),
|
|
743
|
+
copied: t('console.ai.copied'),
|
|
744
|
+
regenerate: t('console.ai.regenerate'),
|
|
745
|
+
model: t('console.ai.model'),
|
|
746
|
+
submit: t('console.ai.submit'),
|
|
747
|
+
uploadFiles: t('console.ai.uploadFiles'),
|
|
748
|
+
stopResponse: t('console.ai.stopResponse'),
|
|
749
|
+
trace: t('console.ai.trace'),
|
|
750
|
+
viewTrace: t('console.ai.viewTrace'),
|
|
751
|
+
}, suggestions: suggestions, onSendMessage: handleSend, onClear: clear, hideClearBar: true, onStop: isLoading ? stop : undefined, onReload: reload, isLoading: isLoading, error: errorSuppressed ? undefined : error, enableMarkdown: true, onToolApprove: hitl.decide, toolDecisions: hitl.decisions, toolApproveLabel: "Approve & run", toolDenyLabel: "Reject", toolDenyReason: "Operator rejected from chat",
|
|
752
|
+
// Build-tree "Open app": jump straight into the app the agent just built.
|
|
753
|
+
onOpenBuiltApp: (appName, appSegment) => navigate(`/apps/${encodeURIComponent(appSegment ?? appName)}`), openBuiltAppLabel: t('console.ai.openBuiltApp', { defaultValue: 'Open app' }),
|
|
754
|
+
// Live lifecycle truth for draft cards: the server's pending count per
|
|
755
|
+
// package, so reloaded conversations show Published/Publish honestly.
|
|
756
|
+
fetchPendingDraftCount: fetchPendingDraftCount, onPublishDrafts: async (packageId) => {
|
|
757
|
+
// Promote the conversation's staged drafts to live (ADR-0033 gate —
|
|
758
|
+
// the human still clicks). Same call as the floating chat + PackagesPage.
|
|
759
|
+
try {
|
|
760
|
+
const res = await fetch(`/api/v1/packages/${encodeURIComponent(packageId)}/publish-drafts`, {
|
|
761
|
+
method: 'POST',
|
|
762
|
+
credentials: 'include',
|
|
763
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
764
|
+
body: '{}',
|
|
765
|
+
});
|
|
766
|
+
const payload = await res.json().catch(() => null);
|
|
767
|
+
if (!res.ok || payload?.success === false) {
|
|
768
|
+
throw new Error(payload?.error?.message || `HTTP ${res.status}`);
|
|
769
|
+
}
|
|
770
|
+
const failed = payload?.data?.failedCount ?? payload?.failedCount ?? 0;
|
|
771
|
+
if (failed)
|
|
772
|
+
throw new Error(String(failed));
|
|
773
|
+
// Surface a seed-load problem (reported under `seedApplied`, never
|
|
774
|
+
// thrown) so "Published!" can't hide silently empty tables.
|
|
775
|
+
const seedApplied = payload?.data?.seedApplied ?? payload?.seedApplied;
|
|
776
|
+
if (seedApplied && seedApplied.success === false) {
|
|
777
|
+
toast.warning(t('console.ai.seedWarn', { defaultValue: 'Published, but some sample data failed to load.' }), {
|
|
778
|
+
description: seedApplied.error ??
|
|
779
|
+
(Array.isArray(seedApplied.errors) && seedApplied.errors.length
|
|
780
|
+
? String(seedApplied.errors[0])
|
|
781
|
+
: undefined),
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
toast.success(t('console.ai.publishOk', { defaultValue: 'Published — objects are now live.' }));
|
|
786
|
+
}
|
|
787
|
+
// The live registry just changed but the chat-card publish path
|
|
788
|
+
// does not reload the page (unlike DraftPreviewBar). Pulse every
|
|
789
|
+
// mounted MetadataProvider so open forms/views (incl. the canvas
|
|
790
|
+
// preview) refetch the new schema instead of showing stale, empty
|
|
791
|
+
// dropdowns until a manual reload.
|
|
792
|
+
emitMetadataRefresh();
|
|
793
|
+
// ADR-0038 L3 — hand the runtime verification (seedApplied +
|
|
794
|
+
// probes) back to the chat so the Published card grows a
|
|
795
|
+
// build-health line instead of claiming bare success.
|
|
796
|
+
return { ok: true, health: publishHealthFromResponse(payload) };
|
|
797
|
+
}
|
|
798
|
+
catch (e) {
|
|
799
|
+
toast.error(t('console.ai.publishFailed', { defaultValue: 'Publish failed' }), {
|
|
800
|
+
description: e instanceof Error ? e.message : undefined,
|
|
801
|
+
});
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
}, publishDraftsLabel: t('console.ai.publishDrafts', { defaultValue: 'Publish' }), publishedLabel: t('console.ai.published', { defaultValue: 'Published' }), nextStepsLabel: t('console.ai.nextSteps', { defaultValue: "What's next" }), planTitleLabel: t('console.ai.planTitle', { defaultValue: 'Proposed plan' }), planQuestionsLabel: t('console.ai.planQuestions', { defaultValue: 'Confirm before building' }), planAssumptionsLabel: t('console.ai.planAssumptions', { defaultValue: 'Assumptions' }), planApproveHintLabel: t('console.ai.planApproveHint', {
|
|
805
|
+
defaultValue: 'Reply to approve or adjust this plan.',
|
|
806
|
+
}), planApproveLabel: t('console.ai.planApprove', { defaultValue: 'Build it' }), planAdjustLabel: t('console.ai.planAdjust', { defaultValue: 'Adjust' }), planBuiltLabel: t('console.ai.planBuilt', { defaultValue: 'Built' }), planApproveMessage: t('console.ai.planApproveMessage', {
|
|
807
|
+
defaultValue: 'Looks good — build it as proposed.',
|
|
808
|
+
}), planApproveDefaultsMessage: t('console.ai.planApproveDefaultsMessage', {
|
|
809
|
+
defaultValue: 'Build it with your best assumptions; use sensible defaults for the open questions.',
|
|
810
|
+
}), planAnswerMessage: (question, option) => t('console.ai.planAnswerMessage', {
|
|
811
|
+
question,
|
|
812
|
+
option,
|
|
813
|
+
defaultValue: 'For "{{question}}", go with: {{option}}.',
|
|
814
|
+
}),
|
|
815
|
+
// Self-use "magic moment": when the plan enables it, publish the drafted
|
|
816
|
+
// app automatically the moment the agent finishes — no manual click; the
|
|
817
|
+
// user refreshes and sees it live WITH data. Same governed endpoint.
|
|
818
|
+
autoPublishDrafts: getRuntimeConfig().features.autoPublishAiBuilds,
|
|
819
|
+
// ADR-0037 Live Canvas: open/refresh the draft-preview pane as the
|
|
820
|
+
// agent's artifacts land; Preview buttons deep-link the same route.
|
|
821
|
+
onDraftArtifacts: handleDraftArtifacts, onPreviewDraftApp: (appName, opts) => setCanvasApp({ name: appName, segment: opts?.appSegment, materialized: opts?.materialized === true }),
|
|
822
|
+
// ADR-0045: build materialized → canvas leaves the draft overlay for
|
|
823
|
+
// the real (unlisted) app; the reload shows live seed rows.
|
|
824
|
+
onBuildMaterialized: handleBuildMaterialized, previewDraftLabel: t('console.ai.previewDraft', { defaultValue: 'Preview' }), "data-testid": "ai-chat-panel" }) }), canvasApp ? (_jsxs(_Fragment, { children: [_jsxs("div", { role: "separator", "aria-orientation": "vertical", "aria-label": t('console.ai.resizeSplit', { defaultValue: 'Resize chat and preview' }), tabIndex: 0, onPointerDown: split.onHandlePointerDown, onKeyDown: split.onHandleKeyDown, onDoubleClick: split.reset, "data-testid": "ai-chat-split-handle", className: cn('group relative hidden w-1.5 shrink-0 cursor-col-resize touch-none select-none md:block', 'focus:outline-none'), children: [_jsx("span", { "aria-hidden": true, className: "absolute inset-y-0 -left-1.5 -right-1.5" }), _jsx("span", { "aria-hidden": true, className: cn('absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-border transition-colors', 'group-hover:bg-primary/60 group-focus-visible:bg-primary', split.dragging && 'bg-primary') })] }), _jsx(LiveCanvas, { appName: canvasApp.name, appSegment: canvasApp.segment, materialized: canvasApp.materialized, refreshKey: canvasRefreshKey, onClose: () => setCanvasApp(null) }), split.dragging ? _jsx("div", { className: "fixed inset-0 z-50 cursor-col-resize", "data-testid": "ai-chat-split-overlay" }) : null] })) : null] }));
|
|
825
|
+
}
|
|
826
|
+
function dataChatSuggestions(t) {
|
|
827
|
+
return [
|
|
828
|
+
t('console.ai.suggestions.dataChat.userCount', { defaultValue: 'How many users are in the system? List their emails.' }),
|
|
829
|
+
t('console.ai.suggestions.dataChat.recentRecords', { defaultValue: 'List the 5 most recently created records.' }),
|
|
830
|
+
t('console.ai.suggestions.dataChat.recordCounts', { defaultValue: 'Count records for each object.' }),
|
|
831
|
+
];
|
|
832
|
+
}
|
|
833
|
+
function metadataAssistantSuggestions(t) {
|
|
834
|
+
// Creation-first starters: the authoring agent's job is to BUILD from a
|
|
835
|
+
// natural-language description (the magic moment), so the empty-state nudges
|
|
836
|
+
// toward "describe a system" rather than inspecting existing schema.
|
|
837
|
+
return [
|
|
838
|
+
t('console.ai.suggestions.metadataAssistant.buildCrm', { defaultValue: 'Build a sales CRM — customers, contacts, and a deal pipeline I can total by stage.' }),
|
|
839
|
+
t('console.ai.suggestions.metadataAssistant.buildApp', { defaultValue: 'Create a project tracker — projects, tasks with owners and due dates, and a board by status.' }),
|
|
840
|
+
t('console.ai.suggestions.metadataAssistant.buildFlow', { defaultValue: 'Design a support desk — tickets with priority, a status workflow, and customer links.' }),
|
|
841
|
+
t('console.ai.suggestions.metadataAssistant.buildInventory', { defaultValue: 'Build an inventory app — products, stock levels, suppliers, and low-stock visibility.' }),
|
|
842
|
+
t('console.ai.suggestions.metadataAssistant.buildRecruiting', { defaultValue: 'Make an applicant tracker — candidates, open roles, interview stages, and notes.' }),
|
|
843
|
+
];
|
|
844
|
+
}
|
|
845
|
+
function genericSuggestions(t) {
|
|
846
|
+
return [
|
|
847
|
+
t('console.ai.suggestions.generic.help', { defaultValue: 'What can you help me with?' }),
|
|
848
|
+
t('console.ai.suggestions.generic.availableObjects', { defaultValue: 'List the available data objects.' }),
|
|
849
|
+
t('console.ai.suggestions.generic.recentActivity', { defaultValue: 'Summarize my recent activity.' }),
|
|
850
|
+
];
|
|
851
|
+
}
|
|
852
|
+
function buildAgentSuggestions(agentName, agentLabel, t) {
|
|
853
|
+
// Alias-aware: `ask`/`data_chat` → data starters, `build`/`metadata_assistant`
|
|
854
|
+
// → authoring starters. Custom agents fall back to a name/label heuristic.
|
|
855
|
+
if (isAskAgent(agentName))
|
|
856
|
+
return dataChatSuggestions(t);
|
|
857
|
+
if (isBuildAgent(agentName))
|
|
858
|
+
return metadataAssistantSuggestions(t);
|
|
178
859
|
const lower = (agentName ?? agentLabel).toLowerCase();
|
|
179
860
|
if (lower.includes('data'))
|
|
180
|
-
return
|
|
861
|
+
return dataChatSuggestions(t);
|
|
181
862
|
if (lower.includes('metadata'))
|
|
182
|
-
return
|
|
183
|
-
return
|
|
863
|
+
return metadataAssistantSuggestions(t);
|
|
864
|
+
return genericSuggestions(t);
|
|
184
865
|
}
|
|
185
866
|
export default AiChatPage;
|