@object-ui/app-shell 6.2.0 → 6.2.2
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 +61 -0
- package/dist/console/AppContent.js +30 -1
- package/dist/console/ai/AiChatPage.js +19 -22
- package/dist/layout/ConsoleFloatingChatbot.js +44 -24
- package/dist/services/builtinComponents.js +10 -7
- package/dist/views/metadata-admin/DesignerEditorWrapper.d.ts +11 -1
- package/dist/views/metadata-admin/DesignerEditorWrapper.js +21 -3
- package/dist/views/metadata-admin/DirectoryPage.js +1 -1
- package/dist/views/metadata-admin/EmbeddedItemEditor.d.ts +15 -0
- package/dist/views/metadata-admin/EmbeddedItemEditor.js +194 -0
- package/dist/views/metadata-admin/MetadataDetailDrawer.d.ts +13 -0
- package/dist/views/metadata-admin/MetadataDetailDrawer.js +28 -0
- package/dist/views/metadata-admin/PageShell.js +9 -2
- package/dist/views/metadata-admin/QuickFind.js +2 -2
- package/dist/views/metadata-admin/RelatedPanel.d.ts +37 -0
- package/dist/views/metadata-admin/RelatedPanel.js +173 -0
- package/dist/views/metadata-admin/ResourceEditPage.d.ts +9 -3
- package/dist/views/metadata-admin/ResourceEditPage.js +105 -14
- package/dist/views/metadata-admin/ResourceHistoryPage.d.ts +3 -3
- package/dist/views/metadata-admin/ResourceHistoryPage.js +6 -3
- package/dist/views/metadata-admin/ResourceListPage.d.ts +2 -2
- package/dist/views/metadata-admin/ResourceListPage.js +6 -4
- package/dist/views/metadata-admin/SchemaForm.js +10 -1
- package/dist/views/metadata-admin/anchors.d.ts +1 -0
- package/dist/views/metadata-admin/anchors.js +235 -0
- package/dist/views/metadata-admin/index.d.ts +7 -3
- package/dist/views/metadata-admin/index.js +14 -2
- package/dist/views/metadata-admin/preview-registry.d.ts +43 -0
- package/dist/views/metadata-admin/preview-registry.js +18 -0
- package/dist/views/metadata-admin/previews/AppPreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/AppPreview.js +101 -0
- package/dist/views/metadata-admin/previews/DashboardPreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/DashboardPreview.js +25 -0
- package/dist/views/metadata-admin/previews/EmailTemplatePreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/EmailTemplatePreview.js +65 -0
- package/dist/views/metadata-admin/previews/ObjectPreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/ObjectPreview.js +36 -0
- package/dist/views/metadata-admin/previews/PagePreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/PagePreview.js +26 -0
- package/dist/views/metadata-admin/previews/PreviewShell.d.ts +40 -0
- package/dist/views/metadata-admin/previews/PreviewShell.js +49 -0
- package/dist/views/metadata-admin/previews/ReportPreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/ReportPreview.js +27 -0
- package/dist/views/metadata-admin/previews/ViewPreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/ViewPreview.js +113 -0
- package/dist/views/metadata-admin/previews/index.d.ts +1 -0
- package/dist/views/metadata-admin/previews/index.js +26 -0
- package/dist/views/metadata-admin/registry.d.ts +124 -0
- package/dist/views/metadata-admin/registry.js +58 -0
- package/package.json +26 -26
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# @object-ui/app-shell — Changelog
|
|
2
2
|
|
|
3
|
+
## 6.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c5821ce: `AiChatPage` no longer PATCHes a client-side title-from-first-message
|
|
8
|
+
on the freshly-created conversation. The server (`@objectstack/service-ai`
|
|
9
|
+
≥ next minor) now generates a concise LLM-summarised title fire-and-forget
|
|
10
|
+
after the first assistant turn lands, and a client-side truncated title
|
|
11
|
+
would race that and win — pinning every conversation row to a 40-char
|
|
12
|
+
substring of the first user message instead of a real summary.
|
|
13
|
+
|
|
14
|
+
Drop the PATCH; bump the sidebar list a couple of times (2.5 s + 6 s)
|
|
15
|
+
to pick up the LLM title whenever the model finally responds.
|
|
16
|
+
|
|
17
|
+
- 3b35084: Fix: floating chatbot now replays persisted conversation history on mount.
|
|
18
|
+
|
|
19
|
+
The right-corner floating chatbot (`ConsoleFloatingChatbot`) was passing only
|
|
20
|
+
`conversationId` to its inner `useObjectChat`, dropping the `initialMessages`
|
|
21
|
+
returned by `useChatConversation`. Backend persistence already worked — the
|
|
22
|
+
server-side `ai_conversation` + `ai_message` rows were created and survived a
|
|
23
|
+
page refresh — but the UI started each session with just the static "welcome"
|
|
24
|
+
bubble, making users believe their history had been lost.
|
|
25
|
+
|
|
26
|
+
Now matches the `/ai/:conversationId` full-page chat: history is hydrated
|
|
27
|
+
into the chat surface, and the welcome bubble is suppressed when prior turns
|
|
28
|
+
exist (showing it above real user/assistant turns is confusing).
|
|
29
|
+
|
|
30
|
+
- Updated dependencies [a66f788]
|
|
31
|
+
- @object-ui/react@6.2.2
|
|
32
|
+
- @object-ui/components@6.2.2
|
|
33
|
+
- @object-ui/fields@6.2.2
|
|
34
|
+
- @object-ui/layout@6.2.2
|
|
35
|
+
- @object-ui/plugin-editor@6.2.2
|
|
36
|
+
- @object-ui/types@6.2.2
|
|
37
|
+
- @object-ui/core@6.2.2
|
|
38
|
+
- @object-ui/i18n@6.2.2
|
|
39
|
+
- @object-ui/data-objectstack@6.2.2
|
|
40
|
+
- @object-ui/auth@6.2.2
|
|
41
|
+
- @object-ui/permissions@6.2.2
|
|
42
|
+
- @object-ui/collaboration@6.2.2
|
|
43
|
+
- @object-ui/providers@6.2.2
|
|
44
|
+
|
|
45
|
+
## 6.2.1
|
|
46
|
+
|
|
47
|
+
### Patch Changes
|
|
48
|
+
|
|
49
|
+
- bc269b0: fix
|
|
50
|
+
- @object-ui/types@6.2.1
|
|
51
|
+
- @object-ui/core@6.2.1
|
|
52
|
+
- @object-ui/i18n@6.2.1
|
|
53
|
+
- @object-ui/react@6.2.1
|
|
54
|
+
- @object-ui/components@6.2.1
|
|
55
|
+
- @object-ui/fields@6.2.1
|
|
56
|
+
- @object-ui/layout@6.2.1
|
|
57
|
+
- @object-ui/data-objectstack@6.2.1
|
|
58
|
+
- @object-ui/auth@6.2.1
|
|
59
|
+
- @object-ui/permissions@6.2.1
|
|
60
|
+
- @object-ui/plugin-editor@6.2.1
|
|
61
|
+
- @object-ui/collaboration@6.2.1
|
|
62
|
+
- @object-ui/providers@6.2.1
|
|
63
|
+
|
|
3
64
|
## 6.2.0
|
|
4
65
|
|
|
5
66
|
### Minor Changes
|
|
@@ -40,6 +40,13 @@ const ReportView = lazy(() => import('../views/ReportView').then(m => ({ default
|
|
|
40
40
|
const SearchResultsPage = lazy(() => import('../views/SearchResultsPage').then(m => ({ default: m.SearchResultsPage })));
|
|
41
41
|
const RecordFormPage = lazy(() => import('../views/RecordFormPage').then(m => ({ default: m.RecordFormPage })));
|
|
42
42
|
const ComponentNavView = lazy(() => import('../views/ComponentNavView').then(m => ({ default: m.ComponentNavView })));
|
|
43
|
+
// Metadata admin — mounted under /apps/:app/metadata. Lives at the top
|
|
44
|
+
// level so URLs read like a normal nested resource (RFC-style) instead of
|
|
45
|
+
// piggy-backing on the legacy ComponentRegistry fan-out.
|
|
46
|
+
const MetadataDirectoryPage = lazy(() => import('../views/metadata-admin').then(m => ({ default: m.MetadataDirectoryPage })));
|
|
47
|
+
const MetadataResourceListPage = lazy(() => import('../views/metadata-admin').then(m => ({ default: m.MetadataResourceListPage })));
|
|
48
|
+
const MetadataResourceEditPage = lazy(() => import('../views/metadata-admin').then(m => ({ default: m.MetadataResourceEditPage })));
|
|
49
|
+
const MetadataResourceHistoryPage = lazy(() => import('../views/metadata-admin').then(m => ({ default: m.MetadataResourceHistoryPage })));
|
|
43
50
|
// Designer pages — sourced from @object-ui/plugin-designer so third-party hosts
|
|
44
51
|
// can opt out by not registering these routes.
|
|
45
52
|
const CreateAppPage = lazy(() => import('@object-ui/plugin-designer').then(m => ({ default: m.CreateAppPage })));
|
|
@@ -259,7 +266,7 @@ export function AppContent({ extraRoutes, extraRoutesNoApp } = {}) {
|
|
|
259
266
|
const expressionUser = user
|
|
260
267
|
? { name: user.name, email: user.email, role: user.role ?? 'user' }
|
|
261
268
|
: { name: 'Anonymous', email: '', role: 'guest' };
|
|
262
|
-
return (_jsxs(ExpressionProvider, { user: expressionUser, app: activeApp, data: {}, features: features, children: [_jsx(NavigationSyncEffect, {}), _jsxs(ConsoleLayout, { activeAppName: activeApp.name, activeApp: activeApp, onAppChange: handleAppChange, objects: allObjects, connectionState: connectionState, userId: user?.id, children: [_jsx(CommandPalette, { apps: apps, activeApp: activeApp, objects: allObjects, onAppChange: handleAppChange, dataSource: dataSource }), _jsx(KeyboardShortcutsDialog, {}), _jsx(OnboardingWalkthrough, {}), _jsx(ErrorBoundary, { children: _jsx(Suspense, { fallback: _jsx(LoadingScreen, {}), children: _jsx(RouteFader, { children: _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(Navigate, { to: resolveLandingRoute(activeApp), replace: true }) }), _jsx(Route, { path: ":objectName", element: _jsx(ObjectView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit, externalRefreshKey: refreshKey }) }), _jsx(Route, { path: ":objectName/new", element: _jsx(RecordFormPage, { mode: "create" }) }), _jsx(Route, { path: ":objectName/view/:viewId", element: _jsx(ObjectView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit, externalRefreshKey: refreshKey }) }), _jsx(Route, { path: ":objectName/record/:recordId", element: _jsx(RecordDetailView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit }, refreshKey) }), _jsx(Route, { path: ":objectName/record/:recordId/edit", element: _jsx(RecordFormPage, { mode: "edit" }) }), _jsx(Route, { path: "dashboard/:dashboardName", element: _jsx(DashboardView, { dataSource: dataSource }) }), _jsx(Route, { path: "report/:reportName", element: _jsx(ReportView, { dataSource: dataSource }) }), _jsx(Route, { path: "page/:pageName", element: _jsx(PageView, {}) }), _jsx(Route, { path: "component/:ns/:name/*", element: _jsx(ComponentNavView, {}) }), _jsx(Route, { path: "design/page/:pageName", element: _jsx(PageDesignPage, {}) }), _jsx(Route, { path: "design/dashboard/:dashboardName", element: _jsx(DashboardDesignPage, {}) }), _jsx(Route, { path: "search", element: _jsx(SearchResultsPage, {}) }), _jsx(Route, { path: "create-app", element: _jsx(CreateAppPage, {}) }), _jsx(Route, { path: "edit-app/:editAppName", element: _jsx(EditAppPage, {}) }), _jsx(Route, { path: "system/marketplace", element: _jsx(MarketplacePage, {}) }), _jsx(Route, { path: "system/marketplace/installed", element: _jsx(MarketplaceInstalledPage, {}) }), _jsx(Route, { path: "system/marketplace/:packageId", element: _jsx(MarketplacePackagePage, {}) }), extraRoutes, _jsx(Route, { path: ":objectName/:maybeRecordId", element: _jsx(ShorthandRecordRedirect, {}) }), _jsx(Route, { path: "*", element: _jsx(RouteNotFound, {}) })] }) }) }) }), currentObjectDef && (_jsx(ModalForm, { schema: {
|
|
269
|
+
return (_jsxs(ExpressionProvider, { user: expressionUser, app: activeApp, data: {}, features: features, children: [_jsx(NavigationSyncEffect, {}), _jsxs(ConsoleLayout, { activeAppName: activeApp.name, activeApp: activeApp, onAppChange: handleAppChange, objects: allObjects, connectionState: connectionState, userId: user?.id, children: [_jsx(CommandPalette, { apps: apps, activeApp: activeApp, objects: allObjects, onAppChange: handleAppChange, dataSource: dataSource }), _jsx(KeyboardShortcutsDialog, {}), _jsx(OnboardingWalkthrough, {}), _jsx(ErrorBoundary, { children: _jsx(Suspense, { fallback: _jsx(LoadingScreen, {}), children: _jsx(RouteFader, { children: _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(Navigate, { to: resolveLandingRoute(activeApp), replace: true }) }), _jsxs(Route, { path: "metadata", children: [_jsx(Route, { index: true, element: _jsx(MetadataDirectoryPage, {}) }), _jsx(Route, { path: ":type", element: _jsx(MetadataResourceListPage, {}) }), _jsx(Route, { path: ":type/new", element: _jsx(MetadataResourceEditPage, { createMode: true }) }), _jsx(Route, { path: ":type/:name", element: _jsx(MetadataResourceEditPage, {}) }), _jsx(Route, { path: ":type/:name/history", element: _jsx(MetadataResourceHistoryPage, {}) })] }), _jsx(Route, { path: ":objectName", element: _jsx(ObjectView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit, externalRefreshKey: refreshKey }) }), _jsx(Route, { path: ":objectName/new", element: _jsx(RecordFormPage, { mode: "create" }) }), _jsx(Route, { path: ":objectName/view/:viewId", element: _jsx(ObjectView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit, externalRefreshKey: refreshKey }) }), _jsx(Route, { path: ":objectName/record/:recordId", element: _jsx(RecordDetailView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit }, refreshKey) }), _jsx(Route, { path: ":objectName/record/:recordId/edit", element: _jsx(RecordFormPage, { mode: "edit" }) }), _jsx(Route, { path: "dashboard/:dashboardName", element: _jsx(DashboardView, { dataSource: dataSource }) }), _jsx(Route, { path: "report/:reportName", element: _jsx(ReportView, { dataSource: dataSource }) }), _jsx(Route, { path: "page/:pageName", element: _jsx(PageView, {}) }), _jsx(Route, { path: "component/:ns/:name/*", element: _jsx(ComponentNavView, {}) }), _jsx(Route, { path: "component/metadata/directory", element: _jsx(LegacyMetadataRedirect, { mode: "directory" }) }), _jsx(Route, { path: "component/metadata/resource/*", element: _jsx(LegacyMetadataRedirect, { mode: "resource" }) }), _jsx(Route, { path: "design/page/:pageName", element: _jsx(PageDesignPage, {}) }), _jsx(Route, { path: "design/dashboard/:dashboardName", element: _jsx(DashboardDesignPage, {}) }), _jsx(Route, { path: "search", element: _jsx(SearchResultsPage, {}) }), _jsx(Route, { path: "create-app", element: _jsx(CreateAppPage, {}) }), _jsx(Route, { path: "edit-app/:editAppName", element: _jsx(EditAppPage, {}) }), _jsx(Route, { path: "system/marketplace", element: _jsx(MarketplacePage, {}) }), _jsx(Route, { path: "system/marketplace/installed", element: _jsx(MarketplaceInstalledPage, {}) }), _jsx(Route, { path: "system/marketplace/:packageId", element: _jsx(MarketplacePackagePage, {}) }), extraRoutes, _jsx(Route, { path: ":objectName/:maybeRecordId", element: _jsx(ShorthandRecordRedirect, {}) }), _jsx(Route, { path: "*", element: _jsx(RouteNotFound, {}) })] }) }) }) }), currentObjectDef && (_jsx(ModalForm, { schema: {
|
|
263
270
|
type: 'object-form',
|
|
264
271
|
formType: 'modal',
|
|
265
272
|
objectName: currentObjectDef.name,
|
|
@@ -378,6 +385,7 @@ const RESERVED_SECOND_SEGMENTS = new Set([
|
|
|
378
385
|
'new', 'view', 'record', 'edit',
|
|
379
386
|
'dashboard', 'report', 'page',
|
|
380
387
|
'design', 'search', 'create-app', 'edit-app',
|
|
388
|
+
'metadata',
|
|
381
389
|
]);
|
|
382
390
|
function looksLikeRecordId(segment) {
|
|
383
391
|
if (!segment)
|
|
@@ -390,6 +398,27 @@ function looksLikeRecordId(segment) {
|
|
|
390
398
|
// Most record ids are at least 6 chars (UUID, ULID, nanoid all >=8).
|
|
391
399
|
return segment.length >= 6;
|
|
392
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Translates pre-refactor metadata admin URLs
|
|
403
|
+
* (`/apps/:app/component/metadata/resource/:name?type=:type`,
|
|
404
|
+
* `/apps/:app/component/metadata/directory`) into the new REST-style
|
|
405
|
+
* shape (`/apps/:app/metadata/:type/:name`). Keeps bookmarks and any
|
|
406
|
+
* still-unmigrated link producers working.
|
|
407
|
+
*/
|
|
408
|
+
function LegacyMetadataRedirect({ mode }) {
|
|
409
|
+
const location = useLocation();
|
|
410
|
+
const appBase = location.pathname.replace(/\/component\/metadata\/.*$/, '');
|
|
411
|
+
if (mode === 'directory') {
|
|
412
|
+
return _jsx(Navigate, { to: `${appBase}/metadata${location.search}${location.hash}`, replace: true });
|
|
413
|
+
}
|
|
414
|
+
const sp = new URLSearchParams(location.search);
|
|
415
|
+
const type = sp.get('type') ?? '';
|
|
416
|
+
const tail = location.pathname.match(/\/component\/metadata\/resource(\/.*)?$/)?.[1] ?? '';
|
|
417
|
+
const target = type
|
|
418
|
+
? `${appBase}/metadata/${encodeURIComponent(type)}${tail}${location.hash}`
|
|
419
|
+
: `${appBase}/metadata${location.hash}`;
|
|
420
|
+
return _jsx(Navigate, { to: target, replace: true });
|
|
421
|
+
}
|
|
393
422
|
/**
|
|
394
423
|
* Redirects `/apps/:appName/:objectName/:recordId` shorthand to the
|
|
395
424
|
* canonical `/apps/:appName/:objectName/record/:recordId` so externally
|
|
@@ -15,7 +15,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
15
15
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
16
16
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
17
17
|
import { useAuth } from '@object-ui/auth';
|
|
18
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@object-ui/components';
|
|
18
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Button, ShareDialog, } from '@object-ui/components';
|
|
19
|
+
import { Share2 } from 'lucide-react';
|
|
19
20
|
import { ChatbotEnhanced, useAgents, useObjectChat, useHitlInChat, } from '@object-ui/plugin-chatbot';
|
|
20
21
|
import { AppHeader } from '../../layout/AppHeader';
|
|
21
22
|
import { useNavigationContext } from '../../context/NavigationContext';
|
|
@@ -63,6 +64,8 @@ export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentPro
|
|
|
63
64
|
activeId: urlConversationId,
|
|
64
65
|
});
|
|
65
66
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
67
|
+
const [shareOpen, setShareOpen] = useState(false);
|
|
68
|
+
const restApiBase = useMemo(() => apiBase.replace(/\/v1\/ai$/, '').replace(/\/ai$/, '') || '/api', [apiBase]);
|
|
66
69
|
// After the hook resolves a real id for a fresh `/ai` visit, mirror it into
|
|
67
70
|
// the URL so the sidebar's active-row + share/refresh both work.
|
|
68
71
|
useEffect(() => {
|
|
@@ -81,32 +84,26 @@ export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentPro
|
|
|
81
84
|
const handleSent = useCallback((firstUserMessage) => {
|
|
82
85
|
// New user turn → bump sidebar list so the row's preview/timestamp refreshes.
|
|
83
86
|
setRefreshKey((k) => k + 1);
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
87
|
+
// Server now generates a concise LLM-summarised title fire-and-forget
|
|
88
|
+
// after the first assistant turn lands (see service-ai
|
|
89
|
+
// `summarizeConversation`). We don't PATCH a truncated preview from the
|
|
90
|
+
// client anymore — that races the LLM and wins, which would block the
|
|
91
|
+
// real title. Instead, bump the sidebar a couple of times so the new
|
|
92
|
+
// title is picked up whenever the model finally responds.
|
|
88
93
|
if (!firstUserMessage || !conversationId)
|
|
89
94
|
return;
|
|
90
95
|
if (titledRef.current.has(conversationId))
|
|
91
96
|
return;
|
|
92
97
|
titledRef.current.add(conversationId);
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
})
|
|
103
|
-
.then(() => setRefreshKey((k) => k + 1))
|
|
104
|
-
.catch(() => {
|
|
105
|
-
// Leave it untitled; user can rename manually.
|
|
106
|
-
titledRef.current.delete(conversationId);
|
|
107
|
-
});
|
|
108
|
-
}, [apiBase, conversationId]);
|
|
109
|
-
return (_jsxs("div", { className: "flex h-svh w-full flex-col bg-background", "data-testid": "ai-chat-page", children: [_jsx("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" }) }), _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'}`) })] })] }));
|
|
98
|
+
const bump = () => setRefreshKey((k) => k + 1);
|
|
99
|
+
const t1 = setTimeout(bump, 2500);
|
|
100
|
+
const t2 = setTimeout(bump, 6000);
|
|
101
|
+
// Best-effort: if the component unmounts before the bumps fire, the
|
|
102
|
+
// setRefreshKey call is a no-op so we don't bother tracking the timers.
|
|
103
|
+
void t1;
|
|
104
|
+
void t2;
|
|
105
|
+
}, [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'}`) })] })] }));
|
|
110
107
|
}
|
|
111
108
|
function ChatPane({ agents, agentsLoading, agentsError, activeAgent, onAgentChange, chatApi, apiBase, conversationId, initialMessages, hydrating, onSent, }) {
|
|
112
109
|
const activeAgentLabel = useMemo(() => {
|
|
@@ -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
|
/**
|
|
3
3
|
* ConsoleFloatingChatbot
|
|
4
4
|
*
|
|
@@ -13,7 +13,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
13
13
|
*/
|
|
14
14
|
import React from 'react';
|
|
15
15
|
import { FloatingChatbot, useObjectChat, useAgents, useHitlInChat, } from '@object-ui/plugin-chatbot';
|
|
16
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@object-ui/components';
|
|
16
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Button, ShareDialog, } from '@object-ui/components';
|
|
17
|
+
import { Share2 } from 'lucide-react';
|
|
17
18
|
import { useChatConversation } from '../hooks';
|
|
18
19
|
const DEFAULT_AI_PATH = '/api/v1/ai';
|
|
19
20
|
function resolveApiBase(explicit) {
|
|
@@ -26,8 +27,19 @@ function resolveApiBase(explicit) {
|
|
|
26
27
|
const serverUrl = env.VITE_SERVER_URL ?? '';
|
|
27
28
|
return `${serverUrl.replace(/\/$/, '')}${DEFAULT_AI_PATH}`;
|
|
28
29
|
}
|
|
29
|
-
function ChatbotInner({ appLabel, objects, agents, agentsLoading, agentsError, activeAgent, onAgentChange, chatApi, apiBase, defaultOpen = false, conversationId, }) {
|
|
30
|
+
function ChatbotInner({ appLabel, objects, agents, agentsLoading, agentsError, activeAgent, onAgentChange, chatApi, apiBase, defaultOpen = false, conversationId, initialMessages: persistedMessages, }) {
|
|
30
31
|
const objectNames = objects.map((o) => o.label || o.name).join(', ');
|
|
32
|
+
// Replay persisted history when present. Suppress the static "welcome"
|
|
33
|
+
// bubble in that case — showing it above real prior turns is confusing.
|
|
34
|
+
const hydratedHistory = React.useMemo(() => {
|
|
35
|
+
if (!persistedMessages || persistedMessages.length === 0)
|
|
36
|
+
return [];
|
|
37
|
+
return persistedMessages.map((m) => ({
|
|
38
|
+
id: m.id,
|
|
39
|
+
role: m.role,
|
|
40
|
+
content: m.parts.map((p) => p.text).join(''),
|
|
41
|
+
}));
|
|
42
|
+
}, [persistedMessages]);
|
|
31
43
|
const activeAgentLabel = React.useMemo(() => {
|
|
32
44
|
const found = agents.find((a) => a.name === activeAgent);
|
|
33
45
|
return found?.label ?? activeAgent ?? appLabel;
|
|
@@ -45,13 +57,15 @@ function ChatbotInner({ appLabel, objects, agents, agentsLoading, agentsError, a
|
|
|
45
57
|
agentName: activeAgent,
|
|
46
58
|
},
|
|
47
59
|
},
|
|
48
|
-
initialMessages:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
initialMessages: hydratedHistory.length > 0
|
|
61
|
+
? hydratedHistory
|
|
62
|
+
: [
|
|
63
|
+
{
|
|
64
|
+
id: 'welcome',
|
|
65
|
+
role: 'assistant',
|
|
66
|
+
content: welcomeContent,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
55
69
|
// Local-mode fallback: only used when `chatApi` is undefined (no agent
|
|
56
70
|
// resolved yet, or no backend available). Keeps the UI usable.
|
|
57
71
|
autoResponse: !chatApi,
|
|
@@ -73,18 +87,24 @@ function ChatbotInner({ appLabel, objects, agents, agentsLoading, agentsError, a
|
|
|
73
87
|
},
|
|
74
88
|
});
|
|
75
89
|
const headerExtra = agents.length > 0 ? (_jsxs(Select, { value: activeAgent, onValueChange: onAgentChange, disabled: agentsLoading, children: [_jsx(SelectTrigger, { className: "h-7 w-[180px] text-xs", "data-testid": "floating-chatbot-agent-picker", children: _jsx(SelectValue, { placeholder: "Choose agent..." }) }), _jsx(SelectContent, { align: "end", 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-[220px]", children: agent.description })) : null] }, agent.name))) })] })) : null;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
// Share-link control. Sits to the left of the panel's built-in
|
|
91
|
+
// fullscreen / close buttons so users can mint a public link without
|
|
92
|
+
// jumping out to the full `/ai/:id` page.
|
|
93
|
+
const [shareOpen, setShareOpen] = React.useState(false);
|
|
94
|
+
const restApiBase = React.useMemo(() => apiBase.replace(/\/v1\/ai$/, '').replace(/\/ai$/, '') || '/api', [apiBase]);
|
|
95
|
+
const headerActions = conversationId ? (_jsx(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", "aria-label": "Share conversation", "data-testid": "floating-chatbot-share", onClick: () => setShareOpen(true), children: _jsx(Share2, { className: "h-4 w-4" }) })) : null;
|
|
96
|
+
return (_jsxs(_Fragment, { children: [_jsx(FloatingChatbot, { floatingConfig: {
|
|
97
|
+
position: 'bottom-right',
|
|
98
|
+
defaultOpen,
|
|
99
|
+
panelWidth: 420,
|
|
100
|
+
panelHeight: 560,
|
|
101
|
+
title: `${appLabel} Assistant`,
|
|
102
|
+
triggerSize: 56,
|
|
103
|
+
}, headerExtra: headerExtra, headerActions: headerActions, messages: messages, placeholder: activeAgent
|
|
104
|
+
? `Ask ${activeAgentLabel}...`
|
|
105
|
+
: agentsLoading
|
|
106
|
+
? 'Loading agents...'
|
|
107
|
+
: 'Ask anything...', onSendMessage: (content) => sendMessage(content), 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" }), conversationId && (_jsx(ShareDialog, { open: shareOpen, onOpenChange: setShareOpen, objectName: "ai_conversations", recordId: conversationId, recordLabel: "this conversation", apiBase: restApiBase }))] }));
|
|
88
108
|
}
|
|
89
109
|
export default function ConsoleFloatingChatbot({ appLabel, objects, apiBase: apiBaseProp, defaultAgent: defaultAgentProp, defaultOpen = false, userId, }) {
|
|
90
110
|
const apiBase = React.useMemo(() => resolveApiBase(apiBaseProp), [apiBaseProp]);
|
|
@@ -105,7 +125,7 @@ export default function ConsoleFloatingChatbot({ appLabel, objects, apiBase: api
|
|
|
105
125
|
// Server-backed conversation. Scoped by agent so each agent gets its own
|
|
106
126
|
// persistent history. Hook is inert until `userId` is provided; without it
|
|
107
127
|
// the FAB continues to work in local-only mode (no persistence).
|
|
108
|
-
const { conversationId } = useChatConversation({
|
|
128
|
+
const { conversationId, initialMessages } = useChatConversation({
|
|
109
129
|
userId,
|
|
110
130
|
scope: activeAgent,
|
|
111
131
|
apiBase,
|
|
@@ -113,5 +133,5 @@ export default function ConsoleFloatingChatbot({ appLabel, objects, apiBase: api
|
|
|
113
133
|
// `key` forces a clean remount whenever the chat endpoint OR the resolved
|
|
114
134
|
// conversation id changes — required because `useObjectChat` locks its mode
|
|
115
135
|
// (api vs local) and its `conversationId` on first render.
|
|
116
|
-
return (_jsx(ChatbotInner, { appLabel: appLabel, objects: objects, agents: agents, agentsLoading: agentsLoading, agentsError: agentsError, activeAgent: activeAgent, onAgentChange: setActiveAgent, chatApi: chatApi, apiBase: apiBase, defaultOpen: defaultOpen, conversationId: conversationId }, `${chatApi ?? 'local'}:${conversationId ?? 'pending'}`));
|
|
136
|
+
return (_jsx(ChatbotInner, { appLabel: appLabel, objects: objects, agents: agents, agentsLoading: agentsLoading, agentsError: agentsError, activeAgent: activeAgent, onAgentChange: setActiveAgent, chatApi: chatApi, apiBase: apiBase, defaultOpen: defaultOpen, conversationId: conversationId, initialMessages: initialMessages }, `${chatApi ?? 'local'}:${conversationId ?? 'pending'}`));
|
|
117
137
|
}
|
|
@@ -21,7 +21,7 @@ import { lazy, Suspense } from 'react';
|
|
|
21
21
|
import { registerAppComponent } from './componentRegistry';
|
|
22
22
|
import { MetadataDirectoryPage, MetadataResourceRouter, registerMetadataResource, } from '../views/metadata-admin';
|
|
23
23
|
import { PermissionMatrixEditPage } from '../views/metadata-admin/PermissionMatrixEditor';
|
|
24
|
-
import {
|
|
24
|
+
import { DesignerEditorBody } from '../views/metadata-admin/DesignerEditorWrapper';
|
|
25
25
|
/* -------------------------------------------------------------------------- */
|
|
26
26
|
/* 1) Top-level admin pages — bound to `metadata:directory` + `metadata:resource` */
|
|
27
27
|
/* -------------------------------------------------------------------------- */
|
|
@@ -104,7 +104,7 @@ const ObjectViewConfigurator = lazy(() => import('@object-ui/plugin-designer').t
|
|
|
104
104
|
const DashboardEditor = lazy(() => import('@object-ui/plugin-designer').then((m) => ({ default: m.DashboardEditor })));
|
|
105
105
|
const PageCanvasEditor = lazy(() => import('@object-ui/plugin-designer').then((m) => ({ default: m.PageCanvasEditor })));
|
|
106
106
|
function ViewEditPage(props) {
|
|
107
|
-
return (_jsx(
|
|
107
|
+
return (_jsx(DesignerEditorBody, { ...props, fromMetadata: (raw) => {
|
|
108
108
|
// Normalize backend view metadata into the shape ObjectViewConfigurator expects.
|
|
109
109
|
// Built-in views may omit `columns` or use legacy field names.
|
|
110
110
|
const base = raw && typeof raw === 'object' ? raw : {};
|
|
@@ -119,10 +119,10 @@ function ViewEditPage(props) {
|
|
|
119
119
|
}, renderDesigner: (value, onChange, readOnly) => (_jsx(Suspense, { fallback: _jsx(DesignerFallback, { label: "view designer" }), children: _jsx(ObjectViewConfigurator, { config: value, onChange: (next) => onChange(next), readOnly: readOnly }) })) }));
|
|
120
120
|
}
|
|
121
121
|
function DashboardEditPage(props) {
|
|
122
|
-
return (_jsx(
|
|
122
|
+
return (_jsx(DesignerEditorBody, { ...props, renderDesigner: (value, onChange, readOnly) => (_jsx(Suspense, { fallback: _jsx(DesignerFallback, { label: "dashboard editor" }), children: _jsx(DashboardEditor, { schema: value, onChange: (next) => onChange(next), readOnly: readOnly }) })) }));
|
|
123
123
|
}
|
|
124
124
|
function PageEditPage(props) {
|
|
125
|
-
return (_jsx(
|
|
125
|
+
return (_jsx(DesignerEditorBody, { ...props, renderDesigner: (value, onChange, readOnly) => (_jsx(Suspense, { fallback: _jsx(DesignerFallback, { label: "page canvas" }), children: _jsx(PageCanvasEditor, { schema: value, onChange: (next) => onChange(next), readOnly: readOnly }) })) }));
|
|
126
126
|
}
|
|
127
127
|
function DesignerFallback({ label }) {
|
|
128
128
|
return (_jsxs("div", { className: "p-6 text-sm text-muted-foreground", children: ["Loading ", label, "\u2026"] }));
|
|
@@ -132,7 +132,8 @@ registerMetadataResource({
|
|
|
132
132
|
label: 'Views',
|
|
133
133
|
description: 'Saved list / kanban / calendar / gantt configurations on top of an object.',
|
|
134
134
|
domain: 'ui',
|
|
135
|
-
|
|
135
|
+
DesignerTab: ViewEditPage,
|
|
136
|
+
designerTabLabel: 'View designer',
|
|
136
137
|
listColumns: [
|
|
137
138
|
{ key: 'name', label: 'Name', width: '30%' },
|
|
138
139
|
{ key: 'object', label: 'Object', width: '20%' },
|
|
@@ -145,7 +146,8 @@ registerMetadataResource({
|
|
|
145
146
|
label: 'Dashboards',
|
|
146
147
|
description: 'Composed dashboards with charts, KPIs, and tables.',
|
|
147
148
|
domain: 'ui',
|
|
148
|
-
|
|
149
|
+
DesignerTab: DashboardEditPage,
|
|
150
|
+
designerTabLabel: 'Dashboard designer',
|
|
149
151
|
listColumns: [
|
|
150
152
|
{ key: 'name', label: 'Name', width: '30%' },
|
|
151
153
|
{ key: 'label', label: 'Label', width: '30%' },
|
|
@@ -157,7 +159,8 @@ registerMetadataResource({
|
|
|
157
159
|
label: 'Pages',
|
|
158
160
|
description: 'Visual page layouts authored in the Page Canvas editor.',
|
|
159
161
|
domain: 'ui',
|
|
160
|
-
|
|
162
|
+
DesignerTab: PageEditPage,
|
|
163
|
+
designerTabLabel: 'Page canvas',
|
|
161
164
|
listColumns: [
|
|
162
165
|
{ key: 'name', label: 'Name', width: '30%' },
|
|
163
166
|
{ key: 'label', label: 'Label', width: '30%' },
|
|
@@ -55,4 +55,14 @@ export interface DesignerEditorWrapperProps<TValue = any> {
|
|
|
55
55
|
*/
|
|
56
56
|
toMetadata?: (value: TValue) => unknown;
|
|
57
57
|
}
|
|
58
|
-
export declare function DesignerEditorWrapper<TValue = any>(
|
|
58
|
+
export declare function DesignerEditorWrapper<TValue = any>(props: DesignerEditorWrapperProps<TValue>): import("react/jsx-runtime").JSX.Element;
|
|
59
|
+
/**
|
|
60
|
+
* Embedded variant — same state machine, but no surrounding `PageShell`.
|
|
61
|
+
* Used by `ResourceEditPage` to host the designer inside a tab alongside
|
|
62
|
+
* the generic Form / Layers / References tabs. The action toolbar (Save /
|
|
63
|
+
* Reset / Refresh) is rendered inline at the top of the panel so the tab
|
|
64
|
+
* remains self-sufficient.
|
|
65
|
+
*/
|
|
66
|
+
export declare function DesignerEditorBody<TValue = any>({ type, name, renderDesigner, fromMetadata, toMetadata, withChrome, }: DesignerEditorWrapperProps<TValue> & {
|
|
67
|
+
withChrome?: boolean;
|
|
68
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -47,7 +47,17 @@ import { PageShell } from './PageShell';
|
|
|
47
47
|
import { useMetadataClient, useMetadataTypes } from './useMetadata';
|
|
48
48
|
import { resolveResourceConfig } from './registry';
|
|
49
49
|
import { t, detectLocale } from './i18n';
|
|
50
|
-
export function DesignerEditorWrapper(
|
|
50
|
+
export function DesignerEditorWrapper(props) {
|
|
51
|
+
return _jsx(DesignerEditorBody, { ...props, withChrome: true });
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Embedded variant — same state machine, but no surrounding `PageShell`.
|
|
55
|
+
* Used by `ResourceEditPage` to host the designer inside a tab alongside
|
|
56
|
+
* the generic Form / Layers / References tabs. The action toolbar (Save /
|
|
57
|
+
* Reset / Refresh) is rendered inline at the top of the panel so the tab
|
|
58
|
+
* remains self-sufficient.
|
|
59
|
+
*/
|
|
60
|
+
export function DesignerEditorBody({ type, name, renderDesigner, fromMetadata, toMetadata, withChrome = false, }) {
|
|
51
61
|
const navigate = useNavigate();
|
|
52
62
|
const client = useMetadataClient();
|
|
53
63
|
const { entries } = useMetadataTypes(client);
|
|
@@ -134,7 +144,15 @@ export function DesignerEditorWrapper({ type, name, renderDesigner, fromMetadata
|
|
|
134
144
|
}
|
|
135
145
|
}
|
|
136
146
|
if (loading || value == null) {
|
|
137
|
-
|
|
147
|
+
const loadingBody = (_jsxs("div", { className: "p-6 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " ", t('engine.edit.loading', locale), " ", type, "/", name, "\u2026"] }));
|
|
148
|
+
return withChrome ? (_jsx(PageShell, { entry: entry, itemName: name, children: loadingBody })) : (loadingBody);
|
|
149
|
+
}
|
|
150
|
+
const toolbarButtons = (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: load, disabled: saving, children: [_jsx(RefreshCw, { className: "h-4 w-4 mr-1" }), " ", t('engine.list.refresh', locale)] }), withChrome && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigate(`./history`), children: [_jsx(HistoryIcon, { className: "h-4 w-4 mr-1" }), " ", t('engine.edit.history', locale)] })), writable && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: doReset, disabled: saving, children: [_jsx(RotateCcw, { className: "h-4 w-4 mr-1" }), " ", t('engine.edit.reset', locale)] })), writable && (_jsxs(Button, { size: "sm", onClick: () => doSave(false), disabled: saving || !dirty, children: [saving ? (_jsx(Loader2, { className: "h-4 w-4 mr-1 animate-spin" })) : (_jsx(Save, { className: "h-4 w-4 mr-1" })), t('engine.edit.save', locale)] }))] }));
|
|
151
|
+
const banners = (_jsxs(_Fragment, { children: [!writable && (_jsx("div", { className: "mx-6 mt-4 rounded-md border bg-muted/40 px-3 py-2 text-xs text-muted-foreground", children: t('engine.edit.readOnlyHint', locale) })), error && (_jsxs("div", { className: "mx-6 mt-4 rounded-md border border-destructive/40 bg-destructive/5 p-3 text-sm text-destructive flex items-start gap-2", children: [_jsx(AlertTriangle, { className: "h-4 w-4 mt-0.5 shrink-0" }), _jsx("span", { children: error })] })), dirty && writable && (_jsxs("div", { className: "mx-6 mt-3 flex items-center gap-2 rounded-md border border-amber-300/50 bg-amber-50 dark:bg-amber-950/30 px-3 py-1.5 text-xs", children: [_jsx(Badge, { variant: "outline", children: t('engine.edit.unsaved', locale) }), _jsx("span", { children: t('engine.edit.unsavedHint', locale) })] }))] }));
|
|
152
|
+
const designerArea = (_jsx("div", { className: "flex-1 min-h-0 overflow-auto", children: renderDesigner(value, (next) => setValue(next), !writable) }));
|
|
153
|
+
const destructiveDialog = (_jsx(Dialog, { open: !!destructive, onOpenChange: (open) => !open && setDestructive(null), children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { className: "flex items-center gap-2", children: [_jsx(AlertTriangle, { className: "h-4 w-4 text-amber-500" }), " ", t('engine.edit.destructive', locale)] }), _jsx(DialogDescription, { children: t('engine.edit.destructiveHint', locale) })] }), _jsx("ul", { className: "text-sm space-y-1 max-h-64 overflow-auto", children: destructive?.issues.map((i, idx) => (_jsxs("li", { className: "border-l-2 border-amber-500 pl-2", children: [i.kind && _jsx(Badge, { variant: "outline", className: "mr-2", children: i.kind }), i.message ?? JSON.stringify(i)] }, idx))) }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: () => setDestructive(null), children: t('engine.cancel', locale) }), _jsxs(Button, { variant: "destructive", onClick: () => destructive && doSave(true, destructive.pending), disabled: saving, children: [saving && _jsx(Loader2, { className: "h-4 w-4 mr-1 animate-spin" }), t('engine.edit.forceSave', locale)] })] })] }) }));
|
|
154
|
+
if (!withChrome) {
|
|
155
|
+
return (_jsxs("div", { className: "flex flex-col h-full overflow-hidden", children: [_jsx("div", { className: "flex items-center justify-end gap-1 px-6 pt-3", children: toolbarButtons }), banners, designerArea, destructiveDialog] }));
|
|
138
156
|
}
|
|
139
|
-
return (_jsxs(PageShell, { entry: entry ?? { type, label: type }, itemName: name, subtitle: t('engine.edit.bespokeDesigner', locale), actions:
|
|
157
|
+
return (_jsxs(PageShell, { entry: entry ?? { type, label: type }, itemName: name, subtitle: t('engine.edit.bespokeDesigner', locale), actions: toolbarButtons, children: [_jsxs("div", { className: "flex flex-col h-full overflow-hidden", children: [banners, designerArea] }), destructiveDialog] }));
|
|
140
158
|
}
|
|
@@ -126,7 +126,7 @@ function DomainChip({ domain, label, active, count, onClick, }) {
|
|
|
126
126
|
function TypeTile({ entry, locale }) {
|
|
127
127
|
// Prefer the locale-table translation; fall back to server's `label` (typically English).
|
|
128
128
|
const label = translateMetadataType(entry.type, locale, entry.label);
|
|
129
|
-
return (_jsxs(Link, { to:
|
|
129
|
+
return (_jsxs(Link, { to: `./${encodeURIComponent(entry.type)}`, className: "block p-4 border rounded-lg hover:border-primary hover:bg-accent transition-colors", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-medium truncate", children: label }), _jsx("code", { className: "text-xs text-muted-foreground font-mono", children: entry.type })] }), entry.allowOrgOverride ? (_jsx(Badge, { className: 'text-[10px] shrink-0 ' +
|
|
130
130
|
(entry.overrideSource === 'env'
|
|
131
131
|
? 'bg-amber-100 text-amber-800 hover:bg-amber-100'
|
|
132
132
|
: 'bg-emerald-100 text-emerald-800 hover:bg-emerald-100'), title: entry.overrideSource === 'env'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface EmbeddedItemEditorProps {
|
|
2
|
+
parentType: string;
|
|
3
|
+
parentName: string;
|
|
4
|
+
/** Dotted path inside parent body where the collection lives. */
|
|
5
|
+
embeddedPath?: string;
|
|
6
|
+
/** Key of the item within the collection (e.g. field name). */
|
|
7
|
+
itemName: string;
|
|
8
|
+
/** Metadata type whose schema/form drives the form. */
|
|
9
|
+
editAs?: string;
|
|
10
|
+
/** Snapshot of the item at the moment of opening (initial draft). */
|
|
11
|
+
initialRaw: Record<string, unknown>;
|
|
12
|
+
/** Called after a successful save with the freshly-saved item. */
|
|
13
|
+
onSaved?: (item: Record<string, unknown>) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function EmbeddedItemEditor({ parentType, parentName, embeddedPath, itemName, editAs, initialRaw, onSaved, }: EmbeddedItemEditorProps): import("react/jsx-runtime").JSX.Element;
|