@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.
Files changed (486) hide show
  1. package/CHANGELOG.md +1229 -0
  2. package/README.md +292 -0
  3. package/dist/assistant/assistantBus.d.ts +72 -0
  4. package/dist/assistant/assistantBus.js +133 -0
  5. package/dist/chrome/CommandPalette.d.ts +1 -1
  6. package/dist/chrome/CommandPalette.js +26 -22
  7. package/dist/chrome/ConditionalAuthWrapper.d.ts +1 -1
  8. package/dist/chrome/ConsoleToaster.d.ts +1 -1
  9. package/dist/chrome/ConsoleToaster.js +3 -1
  10. package/dist/chrome/ErrorBoundary.d.ts +1 -1
  11. package/dist/chrome/KeyboardShortcutsDialog.d.ts +1 -1
  12. package/dist/chrome/KeyboardShortcutsDialog.js +16 -5
  13. package/dist/chrome/LoadingScreen.d.ts +1 -1
  14. package/dist/chrome/LoadingScreen.js +22 -26
  15. package/dist/chrome/RouteFader.d.ts +1 -1
  16. package/dist/chrome/ThemeProvider.d.ts +1 -1
  17. package/dist/components/ManagedByBadge.d.ts +1 -1
  18. package/dist/console/AppContent.d.ts +1 -1
  19. package/dist/console/AppContent.js +184 -39
  20. package/dist/console/ConsoleShell.d.ts +7 -7
  21. package/dist/console/ConsoleShell.js +32 -3
  22. package/dist/console/ai/AiChatPage.d.ts +88 -1
  23. package/dist/console/ai/AiChatPage.js +747 -66
  24. package/dist/console/ai/ConversationsSidebar.d.ts +26 -1
  25. package/dist/console/ai/ConversationsSidebar.js +149 -34
  26. package/dist/console/ai/LiveCanvas.d.ts +28 -0
  27. package/dist/console/ai/LiveCanvas.js +80 -0
  28. package/dist/console/ai/reconcileTurn.d.ts +8 -0
  29. package/dist/console/ai/reconcileTurn.js +20 -0
  30. package/dist/console/auth/AuthPageLayout.d.ts +1 -1
  31. package/dist/console/auth/ForgotPasswordPage.d.ts +1 -1
  32. package/dist/console/auth/LoginPage.d.ts +1 -1
  33. package/dist/console/auth/RegisterPage.d.ts +1 -1
  34. package/dist/console/auth/RegisterPage.js +23 -3
  35. package/dist/console/cloud-connection/CloudConnectionPanel.d.ts +1 -0
  36. package/dist/console/cloud-connection/CloudConnectionPanel.js +169 -0
  37. package/dist/console/home/AppCard.d.ts +1 -1
  38. package/dist/console/home/AppCard.js +6 -12
  39. package/dist/console/home/HomeAppsStrip.d.ts +8 -0
  40. package/dist/console/home/HomeAppsStrip.js +61 -0
  41. package/dist/console/home/HomeLayout.d.ts +1 -1
  42. package/dist/console/home/HomeLayout.js +3 -1
  43. package/dist/console/home/HomePage.d.ts +1 -2
  44. package/dist/console/home/HomePage.js +149 -21
  45. package/dist/console/home/HomeRail.d.ts +22 -0
  46. package/dist/console/home/HomeRail.js +62 -0
  47. package/dist/console/home/QuickActions.d.ts +1 -1
  48. package/dist/console/home/QuickActions.js +3 -11
  49. package/dist/console/home/RecentApps.d.ts +1 -1
  50. package/dist/console/home/RecentApps.js +2 -2
  51. package/dist/console/home/StarredApps.d.ts +1 -1
  52. package/dist/console/home/StarredApps.js +2 -2
  53. package/dist/console/marketplace/InstalledListWidget.d.ts +1 -0
  54. package/dist/console/marketplace/InstalledListWidget.js +93 -0
  55. package/dist/console/marketplace/MarkdownText.d.ts +1 -1
  56. package/dist/console/marketplace/MarketplaceAccessDenied.d.ts +1 -1
  57. package/dist/console/marketplace/MarketplaceInstalledPage.d.ts +8 -14
  58. package/dist/console/marketplace/MarketplaceInstalledPage.js +14 -66
  59. package/dist/console/marketplace/MarketplacePackagePage.d.ts +1 -1
  60. package/dist/console/marketplace/MarketplacePackagePage.js +249 -8
  61. package/dist/console/marketplace/MarketplacePage.d.ts +1 -1
  62. package/dist/console/marketplace/MarketplacePage.js +60 -3
  63. package/dist/console/marketplace/PackageIcon.d.ts +1 -1
  64. package/dist/console/marketplace/PluginDisclosure.d.ts +14 -0
  65. package/dist/console/marketplace/PluginDisclosure.js +38 -0
  66. package/dist/console/marketplace/marketplaceApi.d.ts +123 -0
  67. package/dist/console/marketplace/marketplaceApi.js +254 -1
  68. package/dist/console/organizations/CreateWorkspaceDialog.d.ts +1 -1
  69. package/dist/console/organizations/OrganizationsLayout.d.ts +1 -1
  70. package/dist/console/organizations/OrganizationsPage.d.ts +1 -1
  71. package/dist/console/organizations/manage/AcceptInvitationPage.d.ts +1 -1
  72. package/dist/console/organizations/manage/InvitationsPage.d.ts +1 -1
  73. package/dist/console/organizations/manage/InviteMemberDialog.d.ts +1 -1
  74. package/dist/console/organizations/manage/MembersPage.d.ts +1 -1
  75. package/dist/console/organizations/manage/OrganizationLayout.d.ts +1 -1
  76. package/dist/console/organizations/manage/SettingsPage.d.ts +1 -1
  77. package/dist/context/CommandPaletteProvider.d.ts +44 -0
  78. package/dist/context/CommandPaletteProvider.js +71 -0
  79. package/dist/context/FavoritesProvider.d.ts +1 -1
  80. package/dist/context/NavigationContext.d.ts +1 -1
  81. package/dist/context/RecentItemsProvider.d.ts +2 -2
  82. package/dist/context/UserStateAdapters.d.ts +1 -1
  83. package/dist/context/index.d.ts +2 -0
  84. package/dist/context/index.js +1 -0
  85. package/dist/hooks/index.d.ts +5 -2
  86. package/dist/hooks/index.js +4 -1
  87. package/dist/hooks/useActionModal.d.ts +53 -0
  88. package/dist/hooks/useActionModal.js +111 -0
  89. package/dist/hooks/useChatConversation.d.ts +137 -4
  90. package/dist/hooks/useChatConversation.js +316 -25
  91. package/dist/hooks/useConsoleActionRuntime.d.ts +70 -0
  92. package/dist/hooks/useConsoleActionRuntime.js +564 -0
  93. package/dist/hooks/useConversationList.js +61 -3
  94. package/dist/hooks/useHomeInbox.d.ts +13 -0
  95. package/dist/hooks/useHomeInbox.js +142 -0
  96. package/dist/hooks/useNavPins.js +17 -23
  97. package/dist/hooks/useNavigationSync.d.ts +33 -0
  98. package/dist/hooks/useNavigationSync.js +98 -12
  99. package/dist/hooks/useReconcileOnError.d.ts +40 -0
  100. package/dist/hooks/useReconcileOnError.js +37 -0
  101. package/dist/hooks/useRecordApprovals.d.ts +18 -19
  102. package/dist/hooks/useRecordApprovals.js +24 -40
  103. package/dist/hooks/useResponsiveSidebar.js +14 -5
  104. package/dist/hooks/useSettleSignal.d.ts +19 -0
  105. package/dist/hooks/useSettleSignal.js +20 -0
  106. package/dist/hooks/useTrackRouteAsRecent.js +35 -0
  107. package/dist/hooks/useUrlOverlay.d.ts +62 -0
  108. package/dist/hooks/useUrlOverlay.js +88 -0
  109. package/dist/index.d.ts +18 -8
  110. package/dist/index.js +17 -5
  111. package/dist/layout/ActivityFeed.d.ts +1 -1
  112. package/dist/layout/AppHeader.d.ts +3 -2
  113. package/dist/layout/AppHeader.js +237 -72
  114. package/dist/layout/AppSidebar.d.ts +2 -1
  115. package/dist/layout/AppSidebar.js +26 -46
  116. package/dist/layout/AppSwitcher.d.ts +2 -1
  117. package/dist/layout/AppSwitcher.js +9 -5
  118. package/dist/layout/AuthPageLayout.d.ts +1 -1
  119. package/dist/layout/ConnectionStatus.d.ts +1 -1
  120. package/dist/layout/ConnectionStatus.js +9 -6
  121. package/dist/layout/ConsoleChatbotFab.d.ts +19 -1
  122. package/dist/layout/ConsoleChatbotFab.js +16 -2
  123. package/dist/layout/ConsoleFloatingChatbot.d.ts +34 -2
  124. package/dist/layout/ConsoleFloatingChatbot.js +391 -41
  125. package/dist/layout/ConsoleLayout.d.ts +1 -1
  126. package/dist/layout/ConsoleLayout.js +27 -11
  127. package/dist/layout/ContextSelectors.d.ts +44 -0
  128. package/dist/layout/ContextSelectors.js +242 -0
  129. package/dist/layout/InboxPopover.d.ts +6 -1
  130. package/dist/layout/InboxPopover.js +25 -6
  131. package/dist/layout/LocaleSwitcher.d.ts +1 -1
  132. package/dist/layout/LocalizedSidebarTrigger.d.ts +2 -0
  133. package/dist/layout/LocalizedSidebarTrigger.js +15 -0
  134. package/dist/layout/MobileViewSwitcherContext.d.ts +1 -1
  135. package/dist/layout/ModeToggle.d.ts +1 -1
  136. package/dist/layout/PageHeader.d.ts +1 -1
  137. package/dist/layout/UnifiedSidebar.d.ts +2 -1
  138. package/dist/layout/UnifiedSidebar.js +116 -15
  139. package/dist/layout/agentPicker.d.ts +56 -0
  140. package/dist/layout/agentPicker.js +40 -0
  141. package/dist/observability/index.d.ts +1 -0
  142. package/dist/observability/index.js +1 -0
  143. package/dist/observability/settleSignal.d.ts +64 -0
  144. package/dist/observability/settleSignal.js +131 -0
  145. package/dist/preview/CommitTimeline.d.ts +15 -0
  146. package/dist/preview/CommitTimeline.js +82 -0
  147. package/dist/preview/DraftChangesPanel.d.ts +19 -0
  148. package/dist/preview/DraftChangesPanel.js +114 -0
  149. package/dist/preview/DraftPreviewBar.d.ts +8 -0
  150. package/dist/preview/DraftPreviewBar.js +86 -0
  151. package/dist/preview/PreviewDraftEmptyState.d.ts +16 -0
  152. package/dist/preview/PreviewDraftEmptyState.js +47 -0
  153. package/dist/preview/PreviewModeContext.d.ts +57 -0
  154. package/dist/preview/PreviewModeContext.js +99 -0
  155. package/dist/preview/UnpublishedAppBar.d.ts +8 -0
  156. package/dist/preview/UnpublishedAppBar.js +83 -0
  157. package/dist/preview/commitHistory.d.ts +28 -0
  158. package/dist/preview/commitHistory.js +48 -0
  159. package/dist/preview/draftStatus.d.ts +20 -0
  160. package/dist/preview/draftStatus.js +27 -0
  161. package/dist/preview/usePublishAllDrafts.d.ts +18 -0
  162. package/dist/preview/usePublishAllDrafts.js +106 -0
  163. package/dist/providers/AdapterProvider.d.ts +1 -1
  164. package/dist/providers/AdapterProvider.js +6 -1
  165. package/dist/providers/ExpressionProvider.d.ts +1 -1
  166. package/dist/providers/MetadataProvider.d.ts +17 -2
  167. package/dist/providers/MetadataProvider.js +192 -12
  168. package/dist/runtime-config.d.ts +46 -2
  169. package/dist/runtime-config.js +39 -2
  170. package/dist/services/builtinComponents.js +68 -59
  171. package/dist/skeletons/SkeletonDashboard.d.ts +1 -1
  172. package/dist/skeletons/SkeletonDetail.d.ts +1 -1
  173. package/dist/skeletons/SkeletonGrid.d.ts +1 -1
  174. package/dist/utils/appRoute.d.ts +21 -0
  175. package/dist/utils/appRoute.js +25 -0
  176. package/dist/utils/deriveRelatedLists.d.ts +54 -0
  177. package/dist/utils/deriveRelatedLists.js +91 -0
  178. package/dist/utils/index.d.ts +4 -0
  179. package/dist/utils/index.js +3 -0
  180. package/dist/utils/managedByEmptyState.d.ts +8 -1
  181. package/dist/utils/managedByEmptyState.js +13 -7
  182. package/dist/utils/preferLocal.d.ts +18 -0
  183. package/dist/utils/preferLocal.js +24 -0
  184. package/dist/views/ActionConfirmDialog.d.ts +1 -1
  185. package/dist/views/ActionConfirmDialog.js +3 -1
  186. package/dist/views/ActionParamDialog.d.ts +6 -1
  187. package/dist/views/ActionParamDialog.js +9 -3
  188. package/dist/views/ActionResultDialog.d.ts +13 -0
  189. package/dist/views/ActionResultDialog.js +134 -0
  190. package/dist/views/ComponentNavView.d.ts +14 -1
  191. package/dist/views/CreateViewDialog.d.ts +1 -1
  192. package/dist/views/DashboardConfigPanel.d.ts +28 -0
  193. package/dist/views/DashboardConfigPanel.js +81 -0
  194. package/dist/views/DashboardView.d.ts +4 -3
  195. package/dist/views/DashboardView.js +38 -239
  196. package/dist/views/FlowRunner.d.ts +31 -0
  197. package/dist/views/FlowRunner.js +121 -0
  198. package/dist/views/InterfaceListPage.d.ts +49 -0
  199. package/dist/views/InterfaceListPage.js +347 -0
  200. package/dist/views/MetadataInspector.d.ts +2 -2
  201. package/dist/views/ObjectView.d.ts +1 -1
  202. package/dist/views/ObjectView.js +209 -532
  203. package/dist/views/PageView.d.ts +8 -3
  204. package/dist/views/PageView.js +45 -32
  205. package/dist/views/RecordDetailView.d.ts +1 -1
  206. package/dist/views/RecordDetailView.js +363 -148
  207. package/dist/views/RecordFormPage.d.ts +1 -1
  208. package/dist/views/RecordFormPage.js +26 -1
  209. package/dist/views/ReportConfigPanel.d.ts +37 -0
  210. package/dist/views/ReportConfigPanel.js +85 -0
  211. package/dist/views/ReportView.d.ts +1 -1
  212. package/dist/views/ReportView.js +116 -7
  213. package/dist/views/RuntimeDraftBar.d.ts +30 -0
  214. package/dist/views/RuntimeDraftBar.js +112 -0
  215. package/dist/views/ScreenView.d.ts +70 -0
  216. package/dist/views/ScreenView.js +73 -0
  217. package/dist/views/SearchResultsPage.d.ts +1 -1
  218. package/dist/views/SearchResultsPage.js +8 -18
  219. package/dist/views/ViewConfigPanel.d.ts +24 -17
  220. package/dist/views/ViewConfigPanel.js +121 -77
  221. package/dist/views/index.d.ts +1 -1
  222. package/dist/views/index.js +1 -1
  223. package/dist/views/metadata-admin/AuditPanel.d.ts +28 -0
  224. package/dist/views/metadata-admin/AuditPanel.js +79 -0
  225. package/dist/views/metadata-admin/DiagnosticsPage.d.ts +20 -0
  226. package/dist/views/metadata-admin/DiagnosticsPage.js +69 -0
  227. package/dist/views/metadata-admin/DirectoryPage.d.ts +16 -1
  228. package/dist/views/metadata-admin/DirectoryPage.js +101 -24
  229. package/dist/views/metadata-admin/DraftReviewPanel.d.ts +33 -0
  230. package/dist/views/metadata-admin/DraftReviewPanel.js +77 -0
  231. package/dist/views/metadata-admin/EmbeddedItemEditor.d.ts +17 -1
  232. package/dist/views/metadata-admin/EmbeddedItemEditor.js +15 -8
  233. package/dist/views/metadata-admin/JsonSourceEditor.d.ts +39 -0
  234. package/dist/views/metadata-admin/JsonSourceEditor.js +196 -0
  235. package/dist/views/metadata-admin/LayeredDiff.d.ts +39 -1
  236. package/dist/views/metadata-admin/LayeredDiff.js +171 -5
  237. package/dist/views/metadata-admin/MetadataDetailDrawer.d.ts +15 -1
  238. package/dist/views/metadata-admin/MetadataTypeActions.d.ts +48 -0
  239. package/dist/views/metadata-admin/MetadataTypeActions.js +165 -0
  240. package/dist/views/metadata-admin/PackagesPage.d.ts +18 -0
  241. package/dist/views/metadata-admin/PackagesPage.js +403 -0
  242. package/dist/views/metadata-admin/PageShell.d.ts +1 -1
  243. package/dist/views/metadata-admin/PageShell.js +9 -4
  244. package/dist/views/metadata-admin/PermissionMatrixEditor.d.ts +35 -1
  245. package/dist/views/metadata-admin/QuickFind.d.ts +21 -1
  246. package/dist/views/metadata-admin/QuickFind.js +6 -3
  247. package/dist/views/metadata-admin/RelatedPanel.d.ts +24 -1
  248. package/dist/views/metadata-admin/RelatedPanel.js +20 -18
  249. package/dist/views/metadata-admin/ResourceEditPage.d.ts +40 -1
  250. package/dist/views/metadata-admin/ResourceEditPage.js +1250 -60
  251. package/dist/views/metadata-admin/ResourceHistoryPage.d.ts +39 -1
  252. package/dist/views/metadata-admin/ResourceHistoryPage.js +66 -16
  253. package/dist/views/metadata-admin/ResourceListPage.d.ts +13 -1
  254. package/dist/views/metadata-admin/ResourceListPage.js +258 -30
  255. package/dist/views/metadata-admin/ResourceRouter.d.ts +23 -1
  256. package/dist/views/metadata-admin/SchemaForm.d.ts +34 -1
  257. package/dist/views/metadata-admin/SchemaForm.js +559 -49
  258. package/dist/views/metadata-admin/StudioHomePage.d.ts +22 -0
  259. package/dist/views/metadata-admin/StudioHomePage.js +205 -0
  260. package/dist/views/metadata-admin/anchors.js +255 -24
  261. package/dist/views/metadata-admin/clientValidation.d.ts +50 -0
  262. package/dist/views/metadata-admin/clientValidation.js +169 -0
  263. package/dist/views/metadata-admin/color-variant-field.d.ts +30 -0
  264. package/dist/views/metadata-admin/color-variant-field.js +38 -0
  265. package/dist/views/metadata-admin/createDerive.d.ts +75 -0
  266. package/dist/views/metadata-admin/createDerive.js +179 -0
  267. package/dist/views/metadata-admin/dashboard-schema.d.ts +12 -0
  268. package/dist/views/metadata-admin/dashboard-schema.js +80 -0
  269. package/dist/views/metadata-admin/datasource/DatasourceResourcePage.d.ts +35 -0
  270. package/dist/views/metadata-admin/datasource/DatasourceResourcePage.js +327 -0
  271. package/dist/views/metadata-admin/datasource/register.d.ts +1 -0
  272. package/dist/views/metadata-admin/datasource/register.js +24 -0
  273. package/dist/views/metadata-admin/default-inspector-registry.d.ts +49 -0
  274. package/dist/views/metadata-admin/default-inspector-registry.js +8 -0
  275. package/dist/views/metadata-admin/default-schemas.js +115 -10
  276. package/dist/views/metadata-admin/external/ExternalDatasourcePanel.d.ts +27 -0
  277. package/dist/views/metadata-admin/external/ExternalDatasourcePanel.js +69 -0
  278. package/dist/views/metadata-admin/external/ImportObjectDialog.d.ts +27 -0
  279. package/dist/views/metadata-admin/external/ImportObjectDialog.js +77 -0
  280. package/dist/views/metadata-admin/external/SchemaBrowser.d.ts +16 -0
  281. package/dist/views/metadata-admin/external/SchemaBrowser.js +74 -0
  282. package/dist/views/metadata-admin/external/ValidationPanel.d.ts +16 -0
  283. package/dist/views/metadata-admin/external/ValidationPanel.js +68 -0
  284. package/dist/views/metadata-admin/external/api.d.ts +100 -0
  285. package/dist/views/metadata-admin/external/api.js +124 -0
  286. package/dist/views/metadata-admin/i18n.d.ts +1 -0
  287. package/dist/views/metadata-admin/i18n.js +1252 -2
  288. package/dist/views/metadata-admin/index.d.ts +8 -5
  289. package/dist/views/metadata-admin/index.js +12 -2
  290. package/dist/views/metadata-admin/inspector-registry.d.ts +51 -0
  291. package/dist/views/metadata-admin/inspector-registry.js +11 -0
  292. package/dist/views/metadata-admin/inspectors/ActionDefaultInspector.d.ts +30 -0
  293. package/dist/views/metadata-admin/inspectors/ActionDefaultInspector.js +180 -0
  294. package/dist/views/metadata-admin/inspectors/AppNavInspector.d.ts +16 -0
  295. package/dist/views/metadata-admin/inspectors/AppNavInspector.js +110 -0
  296. package/dist/views/metadata-admin/inspectors/ConditionBuilder.d.ts +29 -0
  297. package/dist/views/metadata-admin/inspectors/ConditionBuilder.js +154 -0
  298. package/dist/views/metadata-admin/inspectors/DashboardDefaultInspector.d.ts +28 -0
  299. package/dist/views/metadata-admin/inspectors/DashboardDefaultInspector.js +110 -0
  300. package/dist/views/metadata-admin/inspectors/DashboardWidgetInspector.d.ts +18 -0
  301. package/dist/views/metadata-admin/inspectors/DashboardWidgetInspector.js +139 -0
  302. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +21 -0
  303. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +221 -0
  304. package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.d.ts +16 -0
  305. package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +126 -0
  306. package/dist/views/metadata-admin/inspectors/FlowInspector.d.ts +12 -0
  307. package/dist/views/metadata-admin/inspectors/FlowInspector.js +9 -0
  308. package/dist/views/metadata-admin/inspectors/FlowKeyValueField.d.ts +30 -0
  309. package/dist/views/metadata-admin/inspectors/FlowKeyValueField.js +125 -0
  310. package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.d.ts +18 -0
  311. package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.js +40 -0
  312. package/dist/views/metadata-admin/inspectors/FlowNodeInspector.d.ts +14 -0
  313. package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +205 -0
  314. package/dist/views/metadata-admin/inspectors/FlowObjectListField.d.ts +26 -0
  315. package/dist/views/metadata-admin/inspectors/FlowObjectListField.js +105 -0
  316. package/dist/views/metadata-admin/inspectors/FlowReferenceField.d.ts +83 -0
  317. package/dist/views/metadata-admin/inspectors/FlowReferenceField.js +181 -0
  318. package/dist/views/metadata-admin/inspectors/FlowStringListField.d.ts +21 -0
  319. package/dist/views/metadata-admin/inspectors/FlowStringListField.js +60 -0
  320. package/dist/views/metadata-admin/inspectors/InspectorComboField.d.ts +40 -0
  321. package/dist/views/metadata-admin/inspectors/InspectorComboField.js +61 -0
  322. package/dist/views/metadata-admin/inspectors/ObjectDefaultInspector.d.ts +21 -0
  323. package/dist/views/metadata-admin/inspectors/ObjectDefaultInspector.js +55 -0
  324. package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.d.ts +23 -0
  325. package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +365 -0
  326. package/dist/views/metadata-admin/inspectors/PageBlockInspector.d.ts +48 -0
  327. package/dist/views/metadata-admin/inspectors/PageBlockInspector.js +332 -0
  328. package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.d.ts +58 -0
  329. package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.js +218 -0
  330. package/dist/views/metadata-admin/inspectors/ViewColumnInspector.d.ts +19 -0
  331. package/dist/views/metadata-admin/inspectors/ViewColumnInspector.js +144 -0
  332. package/dist/views/metadata-admin/inspectors/ViewInspector.d.ts +19 -0
  333. package/dist/views/metadata-admin/inspectors/ViewInspector.js +21 -0
  334. package/dist/views/metadata-admin/inspectors/ViewVariantInspector.d.ts +54 -0
  335. package/dist/views/metadata-admin/inspectors/ViewVariantInspector.js +191 -0
  336. package/dist/views/metadata-admin/inspectors/_shared.d.ts +128 -0
  337. package/dist/views/metadata-admin/inspectors/_shared.js +113 -0
  338. package/dist/views/metadata-admin/inspectors/datasetFilterCondition.d.ts +24 -0
  339. package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +97 -0
  340. package/dist/views/metadata-admin/inspectors/expression-validate.d.ts +26 -0
  341. package/dist/views/metadata-admin/inspectors/expression-validate.js +66 -0
  342. package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +143 -0
  343. package/dist/views/metadata-admin/inspectors/flow-node-config.js +506 -0
  344. package/dist/views/metadata-admin/inspectors/index.d.ts +1 -0
  345. package/dist/views/metadata-admin/inspectors/index.js +45 -0
  346. package/dist/views/metadata-admin/inspectors/json-schema-to-fields.d.ts +40 -0
  347. package/dist/views/metadata-admin/inspectors/json-schema-to-fields.js +227 -0
  348. package/dist/views/metadata-admin/inspectors/useDatasetFields.d.ts +72 -0
  349. package/dist/views/metadata-admin/inspectors/useDatasetFields.js +0 -0
  350. package/dist/views/metadata-admin/issuePath.d.ts +22 -0
  351. package/dist/views/metadata-admin/issuePath.js +65 -0
  352. package/dist/views/metadata-admin/mergeServerFields.d.ts +65 -0
  353. package/dist/views/metadata-admin/mergeServerFields.js +56 -0
  354. package/dist/views/metadata-admin/package-scope.d.ts +26 -0
  355. package/dist/views/metadata-admin/package-scope.js +43 -0
  356. package/dist/views/metadata-admin/preview-registry.d.ts +55 -0
  357. package/dist/views/metadata-admin/previews/ActionPreview.d.ts +25 -0
  358. package/dist/views/metadata-admin/previews/ActionPreview.js +238 -0
  359. package/dist/views/metadata-admin/previews/AddWidgetPicker.d.ts +12 -0
  360. package/dist/views/metadata-admin/previews/AddWidgetPicker.js +56 -0
  361. package/dist/views/metadata-admin/previews/AgentPreview.d.ts +24 -0
  362. package/dist/views/metadata-admin/previews/AgentPreview.js +100 -0
  363. package/dist/views/metadata-admin/previews/AppNavCanvas.d.ts +31 -0
  364. package/dist/views/metadata-admin/previews/AppNavCanvas.js +260 -0
  365. package/dist/views/metadata-admin/previews/AppPreview.d.ts +16 -1
  366. package/dist/views/metadata-admin/previews/AppPreview.js +23 -14
  367. package/dist/views/metadata-admin/previews/BookPreview.d.ts +20 -0
  368. package/dist/views/metadata-admin/previews/BookPreview.js +132 -0
  369. package/dist/views/metadata-admin/previews/DashboardPreview.d.ts +16 -1
  370. package/dist/views/metadata-admin/previews/DashboardPreview.js +110 -8
  371. package/dist/views/metadata-admin/previews/DatasetPreview.d.ts +18 -0
  372. package/dist/views/metadata-admin/previews/DatasetPreview.js +105 -0
  373. package/dist/views/metadata-admin/previews/DatasourcePreview.d.ts +23 -0
  374. package/dist/views/metadata-admin/previews/DatasourcePreview.js +68 -0
  375. package/dist/views/metadata-admin/previews/EmailTemplatePreview.d.ts +14 -1
  376. package/dist/views/metadata-admin/previews/FieldStub.d.ts +30 -0
  377. package/dist/views/metadata-admin/previews/FieldStub.js +104 -0
  378. package/dist/views/metadata-admin/previews/FieldsListEditor.d.ts +50 -0
  379. package/dist/views/metadata-admin/previews/FieldsListEditor.js +97 -0
  380. package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +49 -0
  381. package/dist/views/metadata-admin/previews/FlowCanvas.js +416 -0
  382. package/dist/views/metadata-admin/previews/FlowPreview.d.ts +20 -0
  383. package/dist/views/metadata-admin/previews/FlowPreview.js +120 -0
  384. package/dist/views/metadata-admin/previews/FlowRunsPanel.d.ts +46 -0
  385. package/dist/views/metadata-admin/previews/FlowRunsPanel.js +97 -0
  386. package/dist/views/metadata-admin/previews/FlowSimulatorPanel.d.ts +25 -0
  387. package/dist/views/metadata-admin/previews/FlowSimulatorPanel.js +204 -0
  388. package/dist/views/metadata-admin/previews/JobPreview.d.ts +28 -0
  389. package/dist/views/metadata-admin/previews/JobPreview.js +290 -0
  390. package/dist/views/metadata-admin/previews/ObjectFormCanvas.d.ts +30 -0
  391. package/dist/views/metadata-admin/previews/ObjectFormCanvas.js +547 -0
  392. package/dist/views/metadata-admin/previews/ObjectPreview.d.ts +14 -1
  393. package/dist/views/metadata-admin/previews/ObjectPreview.js +5 -30
  394. package/dist/views/metadata-admin/previews/OutlineStrip.d.ts +32 -0
  395. package/dist/views/metadata-admin/previews/OutlineStrip.js +8 -0
  396. package/dist/views/metadata-admin/previews/PageBlockCanvas.d.ts +49 -0
  397. package/dist/views/metadata-admin/previews/PageBlockCanvas.js +510 -0
  398. package/dist/views/metadata-admin/previews/PagePreview.d.ts +10 -1
  399. package/dist/views/metadata-admin/previews/PagePreview.js +200 -5
  400. package/dist/views/metadata-admin/previews/PermissionPreview.d.ts +27 -0
  401. package/dist/views/metadata-admin/previews/PermissionPreview.js +115 -0
  402. package/dist/views/metadata-admin/previews/PreviewShell.d.ts +29 -6
  403. package/dist/views/metadata-admin/previews/PreviewShell.js +16 -3
  404. package/dist/views/metadata-admin/previews/ReportPreview.d.ts +18 -1
  405. package/dist/views/metadata-admin/previews/ReportPreview.js +23 -15
  406. package/dist/views/metadata-admin/previews/RolePreview.d.ts +19 -0
  407. package/dist/views/metadata-admin/previews/RolePreview.js +14 -0
  408. package/dist/views/metadata-admin/previews/ScreenPreview.d.ts +38 -0
  409. package/dist/views/metadata-admin/previews/ScreenPreview.js +61 -0
  410. package/dist/views/metadata-admin/previews/SkillPreview.d.ts +22 -0
  411. package/dist/views/metadata-admin/previews/SkillPreview.js +34 -0
  412. package/dist/views/metadata-admin/previews/ToolPreview.d.ts +25 -0
  413. package/dist/views/metadata-admin/previews/ToolPreview.js +122 -0
  414. package/dist/views/metadata-admin/previews/TranslationPreview.d.ts +25 -0
  415. package/dist/views/metadata-admin/previews/TranslationPreview.js +52 -0
  416. package/dist/views/metadata-admin/previews/ValidationPreview.d.ts +27 -0
  417. package/dist/views/metadata-admin/previews/ValidationPreview.js +110 -0
  418. package/dist/views/metadata-admin/previews/ViewColumnPanes.d.ts +62 -0
  419. package/dist/views/metadata-admin/previews/ViewColumnPanes.js +140 -0
  420. package/dist/views/metadata-admin/previews/ViewPreview.d.ts +23 -1
  421. package/dist/views/metadata-admin/previews/ViewPreview.js +101 -73
  422. package/dist/views/metadata-admin/previews/block-config.d.ts +82 -0
  423. package/dist/views/metadata-admin/previews/block-config.js +324 -0
  424. package/dist/views/metadata-admin/previews/block-types.d.ts +40 -0
  425. package/dist/views/metadata-admin/previews/block-types.js +110 -0
  426. package/dist/views/metadata-admin/previews/field-types.d.ts +53 -0
  427. package/dist/views/metadata-admin/previews/field-types.js +97 -0
  428. package/dist/views/metadata-admin/previews/flow-canvas-layout.d.ts +102 -0
  429. package/dist/views/metadata-admin/previews/flow-canvas-layout.js +227 -0
  430. package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +96 -0
  431. package/dist/views/metadata-admin/previews/flow-canvas-parts.js +373 -0
  432. package/dist/views/metadata-admin/previews/form-preview.d.ts +24 -0
  433. package/dist/views/metadata-admin/previews/form-preview.js +29 -0
  434. package/dist/views/metadata-admin/previews/index.js +43 -0
  435. package/dist/views/metadata-admin/previews/object-fields-bridge.d.ts +66 -0
  436. package/dist/views/metadata-admin/previews/object-fields-bridge.js +171 -0
  437. package/dist/views/metadata-admin/previews/object-fields-io.d.ts +130 -0
  438. package/dist/views/metadata-admin/previews/object-fields-io.js +243 -0
  439. package/dist/views/metadata-admin/previews/screen-spec.d.ts +43 -0
  440. package/dist/views/metadata-admin/previews/screen-spec.js +108 -0
  441. package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +102 -0
  442. package/dist/views/metadata-admin/previews/simulator/flow-sim-types.js +2 -0
  443. package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.d.ts +15 -0
  444. package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +185 -0
  445. package/dist/views/metadata-admin/previews/simulator/flow-simulator.d.ts +73 -0
  446. package/dist/views/metadata-admin/previews/simulator/flow-simulator.js +426 -0
  447. package/dist/views/metadata-admin/previews/useDatasetCatalog.d.ts +47 -0
  448. package/dist/views/metadata-admin/previews/useDatasetCatalog.js +133 -0
  449. package/dist/views/metadata-admin/previews/useFlowNodePalette.d.ts +44 -0
  450. package/dist/views/metadata-admin/previews/useFlowNodePalette.js +124 -0
  451. package/dist/views/metadata-admin/previews/useMetaOptions.d.ts +8 -0
  452. package/dist/views/metadata-admin/previews/useMetaOptions.js +50 -0
  453. package/dist/views/metadata-admin/previews/useObjectFields.d.ts +23 -0
  454. package/dist/views/metadata-admin/previews/useObjectFields.js +79 -0
  455. package/dist/views/metadata-admin/previews/useObjectOptions.d.ts +8 -0
  456. package/dist/views/metadata-admin/previews/useObjectOptions.js +43 -0
  457. package/dist/views/metadata-admin/previews/view-column-io.d.ts +42 -0
  458. package/dist/views/metadata-admin/previews/view-column-io.js +73 -0
  459. package/dist/views/metadata-admin/previews/widget-types.d.ts +24 -0
  460. package/dist/views/metadata-admin/previews/widget-types.js +40 -0
  461. package/dist/views/metadata-admin/registry.d.ts +140 -19
  462. package/dist/views/metadata-admin/report-schema.d.ts +26 -0
  463. package/dist/views/metadata-admin/report-schema.js +121 -0
  464. package/dist/views/metadata-admin/useMetadata.d.ts +100 -2
  465. package/dist/views/metadata-admin/useMetadata.js +155 -4
  466. package/dist/views/metadata-admin/view-item-normalize.d.ts +20 -0
  467. package/dist/views/metadata-admin/view-item-normalize.js +68 -0
  468. package/dist/views/metadata-admin/view-schema.d.ts +16 -0
  469. package/dist/views/metadata-admin/view-schema.js +107 -0
  470. package/dist/views/metadata-admin/view-variant-model.d.ts +23 -0
  471. package/dist/views/metadata-admin/view-variant-model.js +64 -0
  472. package/dist/views/metadata-admin/widgets.d.ts +89 -1
  473. package/dist/views/metadata-admin/widgets.js +491 -17
  474. package/dist/views/runtime-metadata-persistence.d.ts +78 -0
  475. package/dist/views/runtime-metadata-persistence.js +89 -0
  476. package/dist/views/useOpenRecordList.d.ts +18 -0
  477. package/dist/views/useOpenRecordList.js +36 -0
  478. package/dist/views/userFilterUrlState.d.ts +15 -0
  479. package/dist/views/userFilterUrlState.js +53 -0
  480. package/dist/views/view-config-adapter.d.ts +38 -0
  481. package/dist/views/view-config-adapter.js +80 -0
  482. package/package.json +52 -34
  483. package/dist/views/DesignDrawer.d.ts +0 -28
  484. package/dist/views/DesignDrawer.js +0 -51
  485. package/dist/views/metadata-admin/DesignerEditorWrapper.d.ts +0 -68
  486. 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Button, ShareDialog, } from '@object-ui/components';
19
- import { Share2 } from 'lucide-react';
20
- import { ChatbotEnhanced, useAgents, useObjectChat, useHitlInChat, } from '@object-ui/plugin-chatbot';
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
- const { conversationId: urlConversationId } = useParams();
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 [activeAgent, setActiveAgent] = useState(undefined);
50
- useEffect(() => {
51
- if (!activeAgent && agents.length > 0) {
52
- const preferred = defaultAgentProp ?? envDefaultAgent;
53
- const match = preferred ? agents.find((a) => a.name === preferred) : undefined;
54
- setActiveAgent((match ?? agents[0]).name);
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
- }, [agents, activeAgent, defaultAgentProp, envDefaultAgent]);
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, initialMessages, isLoading: convoLoading } = useChatConversation({
61
- userId,
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 restApiBase = useMemo(() => apiBase.replace(/\/v1\/ai$/, '').replace(/\/ai$/, '') || '/api', [apiBase]);
69
- // After the hook resolves a real id for a fresh `/ai` visit, mirror it into
70
- // the URL so the sidebar's active-row + share/refresh both work.
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
- if (!urlConversationId && conversationId) {
73
- navigate(`/ai/${conversationId}`, { replace: true });
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
- }, [urlConversationId, conversationId, navigate]);
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(AppHeader, { variant: "home" }), _jsx("div", { className: "ml-auto flex items-center gap-2", children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5", onClick: () => setShareOpen(true), disabled: !conversationId, "data-testid": "ai-chat-share-button", title: conversationId ? 'Share this conversation' : 'Start chatting to enable sharing', children: [_jsx(Share2, { className: "h-3.5 w-3.5" }), "Share"] }) })] }), conversationId && (_jsx(ShareDialog, { open: shareOpen, onOpenChange: setShareOpen, objectName: "ai_conversations", recordId: conversationId, recordLabel: "this conversation", apiBase: restApiBase })), _jsxs("div", { className: "flex flex-1 min-h-0 w-full", children: [_jsx(ConversationsSidebar, { userId: userId, apiBase: apiBase, refreshKey: refreshKey, className: "hidden w-72 shrink-0 border-r md:flex" }), _jsx("main", { className: "flex flex-1 min-w-0 flex-col", children: _jsx(ChatPane, { agents: agents, agentsLoading: agentsLoading, agentsError: agentsError, activeAgent: activeAgent, onAgentChange: setActiveAgent, chatApi: chatApi, apiBase: apiBase, conversationId: conversationId, initialMessages: initialMessages, hydrating: convoLoading, onSent: handleSent }, `${chatApi ?? 'local'}:${conversationId ?? 'pending'}`) })] })] }));
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, onAgentChange, chatApi, apiBase, conversationId, initialMessages, hydrating, onSent, }) {
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 ?? 'Assistant';
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.map((m) => ({
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
- const { messages, isLoading, error, sendMessage, stop, reload, clear, } = useObjectChat({
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 = agents.length > 0 ? (_jsxs("div", { className: "flex flex-wrap items-center gap-2 px-4 py-2", children: [_jsx("span", { className: "text-xs text-muted-foreground", children: "Agent:" }), _jsxs(Select, { value: activeAgent, onValueChange: onAgentChange, disabled: agentsLoading, children: [_jsx(SelectTrigger, { className: "h-7 w-[220px] text-xs", "data-testid": "ai-chat-agent-picker", children: _jsx(SelectValue, { placeholder: "Choose agent..." }) }), _jsx(SelectContent, { align: "start", children: agents.map((agent) => (_jsxs(SelectItem, { value: agent.name, className: "text-xs", children: [_jsx("span", { className: "font-medium", children: agent.label }), agent.description ? (_jsx("span", { className: "block text-muted-foreground text-[10px] truncate max-w-[260px]", children: agent.description })) : null] }, agent.name))) })] }), hydrating ? (_jsx("span", { className: "text-[10px] text-muted-foreground", children: "Loading history\u2026" })) : null, agentsError ? (_jsx("span", { className: "text-[10px] text-amber-700 dark:text-amber-400", title: agentsError.message, children: "\u26A0 Offline demo mode \u2014 agent list unavailable" })) : null] })) : null;
151
- return (_jsx(ChatbotEnhanced, { className: "flex-1 min-h-0 rounded-none border-0", maxHeight: "100%", headerSlot: headerSlot, messages: messages, placeholder: activeAgent
152
- ? `Ask ${activeAgentLabel}… (try “${(suggestions?.[0]) ?? 'How can you help?'}”)`
153
- : agentsLoading
154
- ? 'Loading agents…'
155
- : 'Ask anything…', suggestions: suggestions, onSendMessage: handleSend, onClear: clear, onStop: isLoading ? stop : undefined, onReload: reload, isLoading: isLoading, error: error, enableMarkdown: true, onToolApprove: hitl.decide, toolDecisions: hitl.decisions, toolApproveLabel: "Approve & run", toolDenyLabel: "Reject", toolDenyReason: "Operator rejected from chat", "data-testid": "ai-chat-panel" }));
156
- }
157
- const AGENT_SUGGESTIONS = {
158
- data_chat: [
159
- '系统里有多少个用户?列出他们的邮箱。',
160
- '帮我列出最近创建的 5 条记录。',
161
- '统计每个对象的记录数。',
162
- ],
163
- metadata_assistant: [
164
- '系统里注册了哪些对象类型?',
165
- 'sys_user 对象有哪些字段?',
166
- '描述一下用户相关的对象关系。',
167
- ],
168
- };
169
- const GENERIC_SUGGESTIONS = [
170
- 'What can you help me with?',
171
- 'List the available data objects.',
172
- 'Summarize my recent activity.',
173
- ];
174
- function buildAgentSuggestions(agentName, agentLabel) {
175
- if (agentName && AGENT_SUGGESTIONS[agentName]) {
176
- return AGENT_SUGGESTIONS[agentName];
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 AGENT_SUGGESTIONS.data_chat;
861
+ return dataChatSuggestions(t);
181
862
  if (lower.includes('metadata'))
182
- return AGENT_SUGGESTIONS.metadata_assistant;
183
- return GENERIC_SUGGESTIONS;
863
+ return metadataAssistantSuggestions(t);
864
+ return genericSuggestions(t);
184
865
  }
185
866
  export default AiChatPage;