@burdenoff/microfe-bigconsole 2026.714.2 → 2026.714.4
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/dist/bigconsole/BigConsoleRoot.js +102 -97
- package/dist/bigconsole/BigConsoleRoot.js.map +1 -1
- package/dist/bigconsole/assistant/assistantRunStore.js +89 -0
- package/dist/bigconsole/assistant/assistantRunStore.js.map +1 -0
- package/dist/bigconsole/assistant/createSandboxAssistantTransport.js +180 -172
- package/dist/bigconsole/assistant/createSandboxAssistantTransport.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js +24 -24
- package/dist/bigconsole/components/preview/AiPreviewPanel.js +299 -0
- package/dist/bigconsole/components/preview/AiPreviewPanel.js.map +1 -0
- package/dist/bigconsole/components/widgets/PropertiesPanel.js +9 -9
- package/dist/bigconsole/hooks/index.js +5 -5
- package/dist/bigconsole/hooks/useWidgetCatalog.js +11 -11
- package/dist/bigconsole/pages/DashboardBuilderPage.js +3 -3
- package/dist/bigconsole/pages/DashboardViewPage.js +3 -3
- package/package.json +1 -1
|
@@ -6,105 +6,106 @@ import { CreateWorkflowDocument as r, GetWorkflowByKeyDocument as i, ListWorkflo
|
|
|
6
6
|
import { BigConsoleRoutes as o } from "./BigConsoleRoutes.js";
|
|
7
7
|
import { BigConsoleProvider as s } from "./context/BigConsoleContext.js";
|
|
8
8
|
import "./context/index.js";
|
|
9
|
-
import c from "./components/
|
|
10
|
-
import l from "./
|
|
11
|
-
import u from "./
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
9
|
+
import c from "./components/preview/AiPreviewPanel.js";
|
|
10
|
+
import l from "./components/DrilldownModal.js";
|
|
11
|
+
import u from "./hooks/useFilterUrlSync.js";
|
|
12
|
+
import d from "./components/DrilldownDashboardRenderer.js";
|
|
13
|
+
import { useCallback as f, useEffect as p, useLayoutEffect as m, useRef as h, useState as g } from "react";
|
|
14
|
+
import { useSearchParams as _ } from "react-router-dom";
|
|
15
|
+
import { ErrorBoundary as v } from "@burdenoff/fe-libs/shared/components";
|
|
16
|
+
import { FeatureFlagProvider as y } from "@burdenoff/fe-libs/shared/feature-flags";
|
|
17
|
+
import { BarChart3 as b, Boxes as x, Database as S, LayoutDashboard as C, Lightbulb as w, Settings as T } from "lucide-react";
|
|
18
|
+
import { jsx as E, jsxs as D } from "react/jsx-runtime";
|
|
19
|
+
import { resetFluidGridsClient as O, resetFluidGridsConfig as k, resetGlobalStoreClient as A, setFluidGridsClient as j, setFluidGridsConfig as M, setGlobalStoreClient as N } from "@burdenoff/fluidgrids-fe-sdk";
|
|
20
|
+
import { useGatewayClient as P } from "@burdenoff/fe-libs/shared/graphql";
|
|
20
21
|
//#region src/bigconsole/BigConsoleRoot.tsx
|
|
21
|
-
var
|
|
22
|
-
let { registerNavItems:
|
|
23
|
-
|
|
24
|
-
if (
|
|
22
|
+
var F = /* @__PURE__ */ new Set(), I = (I) => {
|
|
23
|
+
let { registerNavItems: L, registerFooterItems: R, basePath: z = "/bigconsole" } = I, B = P("workspace"), V = P("global"), [H, U] = g(!1), [W, G] = g(null), K = (e, t) => (e.endsWith("/") ? e.slice(0, -1) : e) + (t.startsWith("/") ? t : `/${t}`);
|
|
24
|
+
p(() => {
|
|
25
|
+
if (L) return L({
|
|
25
26
|
id: "bigconsole",
|
|
26
27
|
label: "Analytics",
|
|
27
|
-
path:
|
|
28
|
-
icon: /* @__PURE__ */
|
|
28
|
+
path: z,
|
|
29
|
+
icon: /* @__PURE__ */ E(b, { size: 20 }),
|
|
29
30
|
order: 15,
|
|
30
31
|
group: "bigconsole",
|
|
31
32
|
children: [
|
|
32
33
|
{
|
|
33
34
|
id: "bigconsole-home",
|
|
34
35
|
label: "Overview",
|
|
35
|
-
path:
|
|
36
|
-
icon: /* @__PURE__ */
|
|
36
|
+
path: z,
|
|
37
|
+
icon: /* @__PURE__ */ E(b, { size: 16 }),
|
|
37
38
|
order: 10
|
|
38
39
|
},
|
|
39
40
|
{
|
|
40
41
|
id: "bigconsole-dashboards",
|
|
41
42
|
label: "Dashboards",
|
|
42
|
-
path:
|
|
43
|
-
icon: /* @__PURE__ */
|
|
43
|
+
path: K(z, "dashboards"),
|
|
44
|
+
icon: /* @__PURE__ */ E(C, { size: 16 }),
|
|
44
45
|
order: 20
|
|
45
46
|
},
|
|
46
47
|
{
|
|
47
48
|
id: "bigconsole-datasources",
|
|
48
49
|
label: "Data Sources",
|
|
49
|
-
path:
|
|
50
|
-
icon: /* @__PURE__ */
|
|
50
|
+
path: K(z, "datasources"),
|
|
51
|
+
icon: /* @__PURE__ */ E(S, { size: 16 }),
|
|
51
52
|
order: 30
|
|
52
53
|
},
|
|
53
54
|
{
|
|
54
55
|
id: "bigconsole-insights",
|
|
55
56
|
label: "AI Insights",
|
|
56
|
-
path:
|
|
57
|
-
icon: /* @__PURE__ */
|
|
57
|
+
path: K(z, "insights"),
|
|
58
|
+
icon: /* @__PURE__ */ E(w, { size: 16 }),
|
|
58
59
|
order: 40
|
|
59
60
|
},
|
|
60
61
|
{
|
|
61
62
|
id: "bigconsole-widgets",
|
|
62
63
|
label: "Widget Demo",
|
|
63
|
-
path:
|
|
64
|
-
icon: /* @__PURE__ */
|
|
64
|
+
path: K(z, "widgets/demo"),
|
|
65
|
+
icon: /* @__PURE__ */ E(x, { size: 16 }),
|
|
65
66
|
order: 50
|
|
66
67
|
},
|
|
67
68
|
{
|
|
68
69
|
id: "bigconsole-settings",
|
|
69
70
|
label: "Settings",
|
|
70
|
-
path:
|
|
71
|
-
icon: /* @__PURE__ */ T
|
|
71
|
+
path: K(z, "settings"),
|
|
72
|
+
icon: /* @__PURE__ */ E(T, { size: 16 }),
|
|
72
73
|
order: 60
|
|
73
74
|
}
|
|
74
75
|
]
|
|
75
76
|
});
|
|
76
|
-
}, [
|
|
77
|
-
if (
|
|
77
|
+
}, [L, z]), p(() => {
|
|
78
|
+
if (R) return R([{
|
|
78
79
|
id: "bigconsole-status",
|
|
79
80
|
label: "Analytics Ready",
|
|
80
81
|
type: "status",
|
|
81
82
|
order: 15,
|
|
82
83
|
section: "left"
|
|
83
84
|
}]);
|
|
84
|
-
}, [
|
|
85
|
-
|
|
86
|
-
}), [
|
|
87
|
-
if (
|
|
85
|
+
}, [R]), p(() => (B && V && (j(B), N(V), U(!0)), () => {
|
|
86
|
+
O(), A();
|
|
87
|
+
}), [B, V]), p(() => {
|
|
88
|
+
if (H) return;
|
|
88
89
|
let e = setTimeout(() => {
|
|
89
|
-
|
|
90
|
+
H || G("BigConsole could not connect to the gateway. Please refresh the page or check your configuration.");
|
|
90
91
|
}, 1e4);
|
|
91
92
|
return () => clearTimeout(e);
|
|
92
|
-
}, [
|
|
93
|
-
let
|
|
93
|
+
}, [H]);
|
|
94
|
+
let q = f(async (e) => {
|
|
94
95
|
if (e.operation === "created") {
|
|
95
|
-
if (!
|
|
96
|
+
if (!B) {
|
|
96
97
|
console.warn("[BigConsole] onWorkflowSaved: workspace client not ready");
|
|
97
98
|
return;
|
|
98
99
|
}
|
|
99
|
-
if (!
|
|
100
|
-
|
|
100
|
+
if (!F.has(e.workflowKey)) {
|
|
101
|
+
F.add(e.workflowKey);
|
|
101
102
|
try {
|
|
102
|
-
if ((await
|
|
103
|
+
if ((await B.query({
|
|
103
104
|
query: i,
|
|
104
105
|
variables: { key: e.workflowKey },
|
|
105
106
|
fetchPolicy: "network-only"
|
|
106
107
|
})).data?.workflowByKey) return;
|
|
107
|
-
await
|
|
108
|
+
await B.mutate({
|
|
108
109
|
mutation: r,
|
|
109
110
|
variables: { input: {
|
|
110
111
|
name: e.name,
|
|
@@ -117,99 +118,103 @@ var P = /* @__PURE__ */ new Set(), F = (F) => {
|
|
|
117
118
|
} catch (e) {
|
|
118
119
|
console.warn("[BigConsole] onWorkflowSaved: failed to create record:", e);
|
|
119
120
|
} finally {
|
|
120
|
-
|
|
121
|
+
F.delete(e.workflowKey);
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
|
-
}, [
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}, [
|
|
128
|
-
if (
|
|
129
|
-
let e =
|
|
125
|
+
}, [B]), J = h(q);
|
|
126
|
+
m(() => {
|
|
127
|
+
J.current = q;
|
|
128
|
+
}, [q]), p(() => {
|
|
129
|
+
if (I.apiGatewayUrl) {
|
|
130
|
+
let e = I.apiGatewayUrl.endsWith("/") ? I.apiGatewayUrl.slice(0, -1) : I.apiGatewayUrl;
|
|
130
131
|
console.log("[BigConsole] Setting FluidGrids config", {
|
|
131
132
|
apiGatewayUrl: e,
|
|
132
|
-
workspaceId:
|
|
133
|
-
tenantId:
|
|
134
|
-
productId:
|
|
135
|
-
productName:
|
|
136
|
-
}),
|
|
133
|
+
workspaceId: I.workspaceId,
|
|
134
|
+
tenantId: I.tenantId,
|
|
135
|
+
productId: I.productId,
|
|
136
|
+
productName: I.productName
|
|
137
|
+
}), M({
|
|
137
138
|
apiGatewayUrl: e,
|
|
138
|
-
authToken:
|
|
139
|
-
workspaceId:
|
|
140
|
-
tenantId:
|
|
141
|
-
productId:
|
|
142
|
-
productName:
|
|
143
|
-
onWorkflowSaved: (e) =>
|
|
139
|
+
authToken: I.authToken,
|
|
140
|
+
workspaceId: I.workspaceId ?? "",
|
|
141
|
+
tenantId: I.tenantId,
|
|
142
|
+
productId: I.productId ?? "bigconsole",
|
|
143
|
+
productName: I.productName ?? "BigConsole",
|
|
144
|
+
onWorkflowSaved: (e) => J.current(e)
|
|
144
145
|
});
|
|
145
146
|
}
|
|
146
147
|
return () => {
|
|
147
|
-
|
|
148
|
+
k();
|
|
148
149
|
};
|
|
149
150
|
}, [
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
I.apiGatewayUrl,
|
|
152
|
+
I.authToken,
|
|
153
|
+
I.workspaceId,
|
|
154
|
+
I.tenantId,
|
|
155
|
+
I.productId,
|
|
156
|
+
I.productName
|
|
156
157
|
]);
|
|
157
|
-
let [
|
|
158
|
-
|
|
159
|
-
let
|
|
160
|
-
|
|
161
|
-
let e = new URLSearchParams(
|
|
158
|
+
let [Y, X] = _(), Z = e((e) => e.isOpen), Q = e((e) => e.closeModal);
|
|
159
|
+
u();
|
|
160
|
+
let $ = f(() => {
|
|
161
|
+
Q();
|
|
162
|
+
let e = new URLSearchParams(Y);
|
|
162
163
|
Array.from(e.keys()).forEach((t) => {
|
|
163
164
|
(t.startsWith("filter_") || t.startsWith("ctx_")) && e.delete(t);
|
|
164
|
-
}),
|
|
165
|
+
}), X(e, { replace: !0 }), document.activeElement instanceof HTMLElement && document.activeElement.blur();
|
|
165
166
|
}, [
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
]),
|
|
167
|
+
Q,
|
|
168
|
+
Y,
|
|
169
|
+
X
|
|
170
|
+
]), ee = f((e, t) => /* @__PURE__ */ E(d, {
|
|
170
171
|
dashboardId: e,
|
|
171
172
|
params: t
|
|
172
173
|
}), []);
|
|
173
|
-
return
|
|
174
|
-
fallback: /* @__PURE__ */
|
|
174
|
+
return H ? /* @__PURE__ */ E(v, {
|
|
175
|
+
fallback: /* @__PURE__ */ E("div", {
|
|
175
176
|
className: "p-6 text-center text-text-secondary",
|
|
176
177
|
children: "Something went wrong loading BigConsole. Please refresh the page."
|
|
177
178
|
}),
|
|
178
|
-
children: /* @__PURE__ */
|
|
179
|
+
children: /* @__PURE__ */ E(y, {
|
|
179
180
|
workspaceId: null,
|
|
180
181
|
gateway: "global",
|
|
181
182
|
defaultEnabled: !0,
|
|
182
|
-
children: /* @__PURE__ */
|
|
183
|
-
props:
|
|
184
|
-
children: /* @__PURE__ */
|
|
185
|
-
basePath:
|
|
186
|
-
children: /* @__PURE__ */
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
183
|
+
children: /* @__PURE__ */ E(s, {
|
|
184
|
+
props: I,
|
|
185
|
+
children: /* @__PURE__ */ E(n, {
|
|
186
|
+
basePath: z,
|
|
187
|
+
children: /* @__PURE__ */ D(t, { children: [
|
|
188
|
+
/* @__PURE__ */ E(c, {}),
|
|
189
|
+
/* @__PURE__ */ E(o, { ...I }),
|
|
190
|
+
Z && /* @__PURE__ */ E(l, {
|
|
191
|
+
onClose: $,
|
|
192
|
+
renderDashboard: ee
|
|
193
|
+
})
|
|
194
|
+
] })
|
|
190
195
|
})
|
|
191
196
|
})
|
|
192
197
|
})
|
|
193
|
-
}) :
|
|
198
|
+
}) : W ? /* @__PURE__ */ E("div", {
|
|
194
199
|
className: "flex h-full min-h-[200px] w-full items-center justify-center p-6",
|
|
195
|
-
children: /* @__PURE__ */
|
|
200
|
+
children: /* @__PURE__ */ D("div", {
|
|
196
201
|
className: "text-center",
|
|
197
|
-
children: [/* @__PURE__ */
|
|
202
|
+
children: [/* @__PURE__ */ E("p", {
|
|
198
203
|
className: "text-sm text-text-secondary mb-4",
|
|
199
|
-
children:
|
|
200
|
-
}), /* @__PURE__ */
|
|
204
|
+
children: W
|
|
205
|
+
}), /* @__PURE__ */ E("button", {
|
|
201
206
|
type: "button",
|
|
202
207
|
onClick: () => window.location.reload(),
|
|
203
208
|
className: "px-4 py-2 text-sm font-medium text-white bg-action-primary-bg hover:bg-action-primary-bg/90 rounded-md transition-colors",
|
|
204
209
|
children: "Reload Page"
|
|
205
210
|
})]
|
|
206
211
|
})
|
|
207
|
-
}) : /* @__PURE__ */
|
|
212
|
+
}) : /* @__PURE__ */ E("div", {
|
|
208
213
|
className: "flex h-full min-h-[200px] w-full items-center justify-center",
|
|
209
|
-
children: /* @__PURE__ */
|
|
214
|
+
children: /* @__PURE__ */ E("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600" })
|
|
210
215
|
});
|
|
211
216
|
};
|
|
212
217
|
//#endregion
|
|
213
|
-
export {
|
|
218
|
+
export { I as BigConsoleRoot };
|
|
214
219
|
|
|
215
220
|
//# sourceMappingURL=BigConsoleRoot.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BigConsoleRoot.js","names":[],"sources":["../../src/bigconsole/BigConsoleRoot.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useEffect, useCallback, useLayoutEffect, useRef, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { ErrorBoundary } from '@burdenoff/fe-libs/shared/components';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { BarChart3, Database, LayoutDashboard, Lightbulb, Settings, Boxes } from 'lucide-react';\nimport type { BigConsoleRootProps } from './types';\nimport { BigConsoleRoutes } from './BigConsoleRoutes';\nimport { BigConsoleProvider } from './context';\nimport { NavigationProvider, CrossFilterProvider } from './contexts';\nimport { DrilldownModal } from './components/DrilldownModal';\nimport { DrilldownDashboardRenderer } from './components/DrilldownDashboardRenderer';\nimport { useFilterUrlSync } from './hooks/useFilterUrlSync';\nimport { useDrilldownStore } from './store/drilldownStore';\n\n// FluidGrids SDK client injection (needed for workflow components)\nimport {\n setFluidGridsClient,\n resetFluidGridsClient,\n setFluidGridsConfig,\n resetFluidGridsConfig,\n setGlobalStoreClient,\n resetGlobalStoreClient,\n} from '@burdenoff/fluidgrids-fe-sdk';\n// Use the shared gateway Apollo clients from the shell\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport {\n CreateWorkflowDocument,\n GetWorkflowByKeyDocument,\n ListWorkflowsDocument,\n type GetWorkflowByKeyQuery,\n type CreateWorkflowMutation,\n} from '../generated/wspace-operations';\n\n/**\n * Local contract for the workflow-save payload emitted by the embedded FluidGrids builder.\n * Kept explicit here to maintain strict typing at the BigConsole boundary.\n */\ninterface WorkflowSavedResult {\n dbId: string;\n workflowKey: string;\n version: string;\n name: string;\n description?: string;\n operation: 'created' | 'updated' | 'versioned';\n}\n\n// In-flight guard: prevents duplicate BigConsole records when the callback\n// fires twice in rapid succession (React StrictMode, retry, double-click).\n// Server unique constraint on `key` is the ultimate backstop.\nconst inFlightKeys = new Set<string>();\n\n/**\n * Root component for the BigConsole microfrontend.\n *\n * This is the main entry point that host applications import and render.\n * It sets up the necessary providers and renders the internal routing.\n * Automatically registers navigation items with the shell if registration functions are provided.\n *\n * @example\n * ```tsx\n * import { BigConsoleRoot, type BigConsoleRootProps } from '@burdenoff/microfe-bigconsole';\n *\n * function App() {\n * const props: BigConsoleRootProps = {\n * basePath: '/analytics',\n * navigate: (path) => router.push(path),\n * currentUser: { id: '1', email: 'user@example.com' },\n * apiGatewayUrl: 'https://api.example.com',\n * authToken: 'token',\n * };\n *\n * return <BigConsoleRoot {...props} />;\n * }\n * ```\n */\nexport const BigConsoleRoot: FC<BigConsoleRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/bigconsole' } = props;\n\n // Use the shared Apollo clients from the shell (both workspace and global)\n const workspaceClient = useGatewayClient('workspace');\n const globalClient = useGatewayClient('global');\n const [clientReady, setClientReady] = useState(false);\n const [clientError, setClientError] = useState<string | null>(null);\n\n // Helper to join paths correctly, avoiding double slashes\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with the shell\n useEffect(() => {\n if (!registerNavItems) return;\n\n const bigConsoleNavItem = {\n id: 'bigconsole',\n label: 'Analytics',\n path: basePath,\n icon: <BarChart3 size={20} />,\n order: 15,\n group: 'bigconsole',\n children: [\n {\n id: 'bigconsole-home',\n label: 'Overview',\n path: basePath,\n icon: <BarChart3 size={16} />,\n order: 10,\n },\n {\n id: 'bigconsole-dashboards',\n label: 'Dashboards',\n path: joinPath(basePath, 'dashboards'),\n icon: <LayoutDashboard size={16} />,\n order: 20,\n },\n {\n id: 'bigconsole-datasources',\n label: 'Data Sources',\n path: joinPath(basePath, 'datasources'),\n icon: <Database size={16} />,\n order: 30,\n },\n {\n id: 'bigconsole-insights',\n label: 'AI Insights',\n path: joinPath(basePath, 'insights'),\n icon: <Lightbulb size={16} />,\n order: 40,\n },\n {\n id: 'bigconsole-widgets',\n label: 'Widget Demo',\n path: joinPath(basePath, 'widgets/demo'),\n icon: <Boxes size={16} />,\n order: 50,\n },\n {\n id: 'bigconsole-settings',\n label: 'Settings',\n path: joinPath(basePath, 'settings'),\n icon: <Settings size={16} />,\n order: 60,\n },\n ],\n };\n\n const cleanup = registerNavItems(bigConsoleNavItem);\n return cleanup;\n }, [registerNavItems, basePath]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'bigconsole-status',\n label: 'Analytics Ready',\n type: 'status' as const,\n order: 15,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems]);\n\n // Inject Apollo clients into FluidGrids SDK (for workflow components)\n // This synchronizes external stores (module-level client singletons in the SDK)\n // with React's lifecycle. The setState call is intentional — it gates rendering\n // so children don't mount before the SDK clients are available.\n //\n // KNOWN TRADE-OFF: clientReady is NOT reset in cleanup. When workspaceClient/globalClient\n // change references (e.g. token refresh), children continue rendering against a briefly\n // uninitialized SDK client until the next effect run re-injects the new client. This avoids\n // a visible spinner flash mid-session. Failed SDK requests during this short window will\n // be retried by React Query / Apollo's retry policies. If this becomes a real issue,\n // consider using startTransition to reset clientReady without the visual disruption.\n useEffect(() => {\n if (workspaceClient && globalClient) {\n setFluidGridsClient(workspaceClient);\n setGlobalStoreClient(globalClient);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: gate children until external SDK clients are injected\n setClientReady(true);\n }\n return () => {\n resetFluidGridsClient();\n resetGlobalStoreClient();\n };\n }, [workspaceClient, globalClient]);\n\n // Timeout fallback: if gateway clients are never provided (test env, shell\n // misconfiguration, auth failure before hydration), show an error rather\n // than spinning indefinitely.\n useEffect(() => {\n if (clientReady) return;\n const timer = setTimeout(() => {\n if (!clientReady) {\n setClientError(\n 'BigConsole could not connect to the gateway. Please refresh the page or check your configuration.'\n );\n }\n }, 10_000);\n return () => clearTimeout(timer);\n }, [clientReady]);\n\n // Callback: sync workflow metadata to BigConsole after FluidGrids SDK saves\n const handleWorkflowSaved = useCallback(\n async (result: WorkflowSavedResult) => {\n // Only create a BigConsole record for brand-new workflows.\n // 'updated' = edit of existing, 'versioned' = new snapshot of same key — both already have records.\n if (result.operation !== 'created') return;\n\n if (!workspaceClient) {\n console.warn('[BigConsole] onWorkflowSaved: workspace client not ready');\n return;\n }\n\n // In-flight guard: prevent duplicate records from rapid double-fire\n // (React StrictMode, retry, fast double-click).\n if (inFlightKeys.has(result.workflowKey)) return;\n inFlightKeys.add(result.workflowKey);\n\n try {\n // Idempotency check — avoid creating duplicates if callback fires twice\n const existing = await workspaceClient.query<GetWorkflowByKeyQuery>({\n query: GetWorkflowByKeyDocument,\n variables: { key: result.workflowKey },\n fetchPolicy: 'network-only',\n });\n\n if (existing.data?.workflowByKey) {\n return;\n }\n\n // Create BigConsole metadata record linked to the SDK workflow\n await workspaceClient.mutate<CreateWorkflowMutation>({\n mutation: CreateWorkflowDocument,\n variables: {\n input: {\n name: result.name,\n key: result.workflowKey,\n description: result.description,\n externalWorkflowId: result.dbId,\n },\n },\n refetchQueries: [{ query: ListWorkflowsDocument }],\n });\n } catch (err) {\n // Possible: network error, or duplicate-key constraint (race condition).\n // SDK save already succeeded — never block the user.\n console.warn('[BigConsole] onWorkflowSaved: failed to create record:', err);\n } finally {\n inFlightKeys.delete(result.workflowKey);\n }\n },\n [workspaceClient]\n );\n\n // BOFF-1870 follow-up: keep this ref bridge explicit as the breadcrumb for\n // why this callback is decoupled from SDK config updates.\n // Ref-forwarding: decouple the callback from the SDK config effect so that\n // workspaceClient reference changes don't trigger resetFluidGridsConfig() + re-init.\n const handleWorkflowSavedRef = useRef(handleWorkflowSaved);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: keep ref in sync with latest callback\n useLayoutEffect(() => {\n handleWorkflowSavedRef.current = handleWorkflowSaved;\n }, [handleWorkflowSaved]);\n\n // Configure FluidGrids SDK settings\n useEffect(() => {\n if (props.apiGatewayUrl) {\n const normalizedBase = props.apiGatewayUrl.endsWith('/') ? props.apiGatewayUrl.slice(0, -1) : props.apiGatewayUrl;\n\n console.log('[BigConsole] Setting FluidGrids config', {\n apiGatewayUrl: normalizedBase,\n workspaceId: props.workspaceId,\n tenantId: props.tenantId,\n productId: props.productId,\n productName: props.productName,\n });\n\n setFluidGridsConfig({\n apiGatewayUrl: normalizedBase,\n authToken: props.authToken,\n workspaceId: props.workspaceId ?? '',\n tenantId: props.tenantId,\n // Default to 'bigconsole' when unset — the SDK uses productId for workflow isolation\n // (filtering workflows by product). undefined would show all products' workflows.\n productId: props.productId ?? 'bigconsole',\n productName: props.productName ?? 'BigConsole',\n // Stable wrapper via ref — avoids SDK re-init when workspaceClient changes reference\n onWorkflowSaved: (result: WorkflowSavedResult) => handleWorkflowSavedRef.current(result),\n });\n }\n return () => {\n resetFluidGridsConfig();\n };\n // handleWorkflowSavedRef is stable (useRef) — intentionally excluded to prevent SDK re-init on client change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.apiGatewayUrl, props.authToken, props.workspaceId, props.tenantId, props.productId, props.productName]);\n\n // Drilldown modal state from store\n const [searchParams, setSearchParams] = useSearchParams();\n const isModalOpen = useDrilldownStore((state) => state.isOpen);\n const closeModal = useDrilldownStore((state) => state.closeModal);\n useFilterUrlSync();\n\n const handleDrilldownClose = useCallback(() => {\n closeModal();\n // Clear any cross-filter URL params when closing drilldown modal\n const newParams = new URLSearchParams(searchParams);\n Array.from(newParams.keys()).forEach((key) => {\n if (key.startsWith('filter_') || key.startsWith('ctx_')) {\n newParams.delete(key);\n }\n });\n setSearchParams(newParams, { replace: true });\n // Remove focus from any element to prevent blue border\n if (document.activeElement instanceof HTMLElement) {\n document.activeElement.blur();\n }\n }, [closeModal, searchParams, setSearchParams]);\n\n // Render a DrilldownDashboardRenderer inside the modal when a drilldown is active.\n // This callback is only invoked by DrilldownModal when isOpen && dashboardId are set,\n // so the renderer always receives a valid dashboardId.\n // NOTE: Must be declared before the early return below to satisfy React's rules of hooks.\n const renderDrilldownDashboard = useCallback(\n (dashboardId: string, params: Record<string, unknown>) => (\n <DrilldownDashboardRenderer dashboardId={dashboardId} params={params} />\n ),\n []\n );\n\n // Don't render until client is ready (same pattern as microfe-workflows)\n if (!clientReady) {\n if (clientError) {\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center p-6\">\n <div className=\"text-center\">\n <p className=\"text-sm text-text-secondary mb-4\">{clientError}</p>\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"px-4 py-2 text-sm font-medium text-white bg-action-primary-bg hover:bg-action-primary-bg/90 rounded-md transition-colors\"\n >\n Reload Page\n </button>\n </div>\n </div>\n );\n }\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600\" />\n </div>\n );\n }\n\n return (\n <ErrorBoundary\n fallback={\n <div className=\"p-6 text-center text-text-secondary\">\n Something went wrong loading BigConsole. Please refresh the page.\n </div>\n }\n >\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <BigConsoleProvider props={props}>\n <NavigationProvider basePath={basePath}>\n <CrossFilterProvider>\n <BigConsoleRoutes {...props} />\n {isModalOpen && (\n <DrilldownModal onClose={handleDrilldownClose} renderDashboard={renderDrilldownDashboard} />\n )}\n </CrossFilterProvider>\n </NavigationProvider>\n </BigConsoleProvider>\n </FeatureFlagProvider>\n </ErrorBoundary>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BigConsoleRoot.js","names":[],"sources":["../../src/bigconsole/BigConsoleRoot.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useEffect, useCallback, useLayoutEffect, useRef, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { ErrorBoundary } from '@burdenoff/fe-libs/shared/components';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { BarChart3, Database, LayoutDashboard, Lightbulb, Settings, Boxes } from 'lucide-react';\nimport type { BigConsoleRootProps } from './types';\nimport { BigConsoleRoutes } from './BigConsoleRoutes';\nimport { BigConsoleProvider } from './context';\nimport { NavigationProvider, CrossFilterProvider } from './contexts';\nimport { AiPreviewPanel } from './components/preview/AiPreviewPanel';\nimport { DrilldownModal } from './components/DrilldownModal';\nimport { DrilldownDashboardRenderer } from './components/DrilldownDashboardRenderer';\nimport { useFilterUrlSync } from './hooks/useFilterUrlSync';\nimport { useDrilldownStore } from './store/drilldownStore';\n\n// FluidGrids SDK client injection (needed for workflow components)\nimport {\n setFluidGridsClient,\n resetFluidGridsClient,\n setFluidGridsConfig,\n resetFluidGridsConfig,\n setGlobalStoreClient,\n resetGlobalStoreClient,\n} from '@burdenoff/fluidgrids-fe-sdk';\n// Use the shared gateway Apollo clients from the shell\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport {\n CreateWorkflowDocument,\n GetWorkflowByKeyDocument,\n ListWorkflowsDocument,\n type GetWorkflowByKeyQuery,\n type CreateWorkflowMutation,\n} from '../generated/wspace-operations';\n\n/**\n * Local contract for the workflow-save payload emitted by the embedded FluidGrids builder.\n * Kept explicit here to maintain strict typing at the BigConsole boundary.\n */\ninterface WorkflowSavedResult {\n dbId: string;\n workflowKey: string;\n version: string;\n name: string;\n description?: string;\n operation: 'created' | 'updated' | 'versioned';\n}\n\n// In-flight guard: prevents duplicate BigConsole records when the callback\n// fires twice in rapid succession (React StrictMode, retry, double-click).\n// Server unique constraint on `key` is the ultimate backstop.\nconst inFlightKeys = new Set<string>();\n\n/**\n * Root component for the BigConsole microfrontend.\n *\n * This is the main entry point that host applications import and render.\n * It sets up the necessary providers and renders the internal routing.\n * Automatically registers navigation items with the shell if registration functions are provided.\n *\n * @example\n * ```tsx\n * import { BigConsoleRoot, type BigConsoleRootProps } from '@burdenoff/microfe-bigconsole';\n *\n * function App() {\n * const props: BigConsoleRootProps = {\n * basePath: '/analytics',\n * navigate: (path) => router.push(path),\n * currentUser: { id: '1', email: 'user@example.com' },\n * apiGatewayUrl: 'https://api.example.com',\n * authToken: 'token',\n * };\n *\n * return <BigConsoleRoot {...props} />;\n * }\n * ```\n */\nexport const BigConsoleRoot: FC<BigConsoleRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/bigconsole' } = props;\n\n // Use the shared Apollo clients from the shell (both workspace and global)\n const workspaceClient = useGatewayClient('workspace');\n const globalClient = useGatewayClient('global');\n const [clientReady, setClientReady] = useState(false);\n const [clientError, setClientError] = useState<string | null>(null);\n\n // Helper to join paths correctly, avoiding double slashes\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with the shell\n useEffect(() => {\n if (!registerNavItems) return;\n\n const bigConsoleNavItem = {\n id: 'bigconsole',\n label: 'Analytics',\n path: basePath,\n icon: <BarChart3 size={20} />,\n order: 15,\n group: 'bigconsole',\n children: [\n {\n id: 'bigconsole-home',\n label: 'Overview',\n path: basePath,\n icon: <BarChart3 size={16} />,\n order: 10,\n },\n {\n id: 'bigconsole-dashboards',\n label: 'Dashboards',\n path: joinPath(basePath, 'dashboards'),\n icon: <LayoutDashboard size={16} />,\n order: 20,\n },\n {\n id: 'bigconsole-datasources',\n label: 'Data Sources',\n path: joinPath(basePath, 'datasources'),\n icon: <Database size={16} />,\n order: 30,\n },\n {\n id: 'bigconsole-insights',\n label: 'AI Insights',\n path: joinPath(basePath, 'insights'),\n icon: <Lightbulb size={16} />,\n order: 40,\n },\n {\n id: 'bigconsole-widgets',\n label: 'Widget Demo',\n path: joinPath(basePath, 'widgets/demo'),\n icon: <Boxes size={16} />,\n order: 50,\n },\n {\n id: 'bigconsole-settings',\n label: 'Settings',\n path: joinPath(basePath, 'settings'),\n icon: <Settings size={16} />,\n order: 60,\n },\n ],\n };\n\n const cleanup = registerNavItems(bigConsoleNavItem);\n return cleanup;\n }, [registerNavItems, basePath]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'bigconsole-status',\n label: 'Analytics Ready',\n type: 'status' as const,\n order: 15,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems]);\n\n // Inject Apollo clients into FluidGrids SDK (for workflow components)\n // This synchronizes external stores (module-level client singletons in the SDK)\n // with React's lifecycle. The setState call is intentional — it gates rendering\n // so children don't mount before the SDK clients are available.\n //\n // KNOWN TRADE-OFF: clientReady is NOT reset in cleanup. When workspaceClient/globalClient\n // change references (e.g. token refresh), children continue rendering against a briefly\n // uninitialized SDK client until the next effect run re-injects the new client. This avoids\n // a visible spinner flash mid-session. Failed SDK requests during this short window will\n // be retried by React Query / Apollo's retry policies. If this becomes a real issue,\n // consider using startTransition to reset clientReady without the visual disruption.\n useEffect(() => {\n if (workspaceClient && globalClient) {\n setFluidGridsClient(workspaceClient);\n setGlobalStoreClient(globalClient);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: gate children until external SDK clients are injected\n setClientReady(true);\n }\n return () => {\n resetFluidGridsClient();\n resetGlobalStoreClient();\n };\n }, [workspaceClient, globalClient]);\n\n // Timeout fallback: if gateway clients are never provided (test env, shell\n // misconfiguration, auth failure before hydration), show an error rather\n // than spinning indefinitely.\n useEffect(() => {\n if (clientReady) return;\n const timer = setTimeout(() => {\n if (!clientReady) {\n setClientError(\n 'BigConsole could not connect to the gateway. Please refresh the page or check your configuration.'\n );\n }\n }, 10_000);\n return () => clearTimeout(timer);\n }, [clientReady]);\n\n // Callback: sync workflow metadata to BigConsole after FluidGrids SDK saves\n const handleWorkflowSaved = useCallback(\n async (result: WorkflowSavedResult) => {\n // Only create a BigConsole record for brand-new workflows.\n // 'updated' = edit of existing, 'versioned' = new snapshot of same key — both already have records.\n if (result.operation !== 'created') return;\n\n if (!workspaceClient) {\n console.warn('[BigConsole] onWorkflowSaved: workspace client not ready');\n return;\n }\n\n // In-flight guard: prevent duplicate records from rapid double-fire\n // (React StrictMode, retry, fast double-click).\n if (inFlightKeys.has(result.workflowKey)) return;\n inFlightKeys.add(result.workflowKey);\n\n try {\n // Idempotency check — avoid creating duplicates if callback fires twice\n const existing = await workspaceClient.query<GetWorkflowByKeyQuery>({\n query: GetWorkflowByKeyDocument,\n variables: { key: result.workflowKey },\n fetchPolicy: 'network-only',\n });\n\n if (existing.data?.workflowByKey) {\n return;\n }\n\n // Create BigConsole metadata record linked to the SDK workflow\n await workspaceClient.mutate<CreateWorkflowMutation>({\n mutation: CreateWorkflowDocument,\n variables: {\n input: {\n name: result.name,\n key: result.workflowKey,\n description: result.description,\n externalWorkflowId: result.dbId,\n },\n },\n refetchQueries: [{ query: ListWorkflowsDocument }],\n });\n } catch (err) {\n // Possible: network error, or duplicate-key constraint (race condition).\n // SDK save already succeeded — never block the user.\n console.warn('[BigConsole] onWorkflowSaved: failed to create record:', err);\n } finally {\n inFlightKeys.delete(result.workflowKey);\n }\n },\n [workspaceClient]\n );\n\n // BOFF-1870 follow-up: keep this ref bridge explicit as the breadcrumb for\n // why this callback is decoupled from SDK config updates.\n // Ref-forwarding: decouple the callback from the SDK config effect so that\n // workspaceClient reference changes don't trigger resetFluidGridsConfig() + re-init.\n const handleWorkflowSavedRef = useRef(handleWorkflowSaved);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional: keep ref in sync with latest callback\n useLayoutEffect(() => {\n handleWorkflowSavedRef.current = handleWorkflowSaved;\n }, [handleWorkflowSaved]);\n\n // Configure FluidGrids SDK settings\n useEffect(() => {\n if (props.apiGatewayUrl) {\n const normalizedBase = props.apiGatewayUrl.endsWith('/') ? props.apiGatewayUrl.slice(0, -1) : props.apiGatewayUrl;\n\n console.log('[BigConsole] Setting FluidGrids config', {\n apiGatewayUrl: normalizedBase,\n workspaceId: props.workspaceId,\n tenantId: props.tenantId,\n productId: props.productId,\n productName: props.productName,\n });\n\n setFluidGridsConfig({\n apiGatewayUrl: normalizedBase,\n authToken: props.authToken,\n workspaceId: props.workspaceId ?? '',\n tenantId: props.tenantId,\n // Default to 'bigconsole' when unset — the SDK uses productId for workflow isolation\n // (filtering workflows by product). undefined would show all products' workflows.\n productId: props.productId ?? 'bigconsole',\n productName: props.productName ?? 'BigConsole',\n // Stable wrapper via ref — avoids SDK re-init when workspaceClient changes reference\n onWorkflowSaved: (result: WorkflowSavedResult) => handleWorkflowSavedRef.current(result),\n });\n }\n return () => {\n resetFluidGridsConfig();\n };\n // handleWorkflowSavedRef is stable (useRef) — intentionally excluded to prevent SDK re-init on client change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.apiGatewayUrl, props.authToken, props.workspaceId, props.tenantId, props.productId, props.productName]);\n\n // Drilldown modal state from store\n const [searchParams, setSearchParams] = useSearchParams();\n const isModalOpen = useDrilldownStore((state) => state.isOpen);\n const closeModal = useDrilldownStore((state) => state.closeModal);\n useFilterUrlSync();\n\n const handleDrilldownClose = useCallback(() => {\n closeModal();\n // Clear any cross-filter URL params when closing drilldown modal\n const newParams = new URLSearchParams(searchParams);\n Array.from(newParams.keys()).forEach((key) => {\n if (key.startsWith('filter_') || key.startsWith('ctx_')) {\n newParams.delete(key);\n }\n });\n setSearchParams(newParams, { replace: true });\n // Remove focus from any element to prevent blue border\n if (document.activeElement instanceof HTMLElement) {\n document.activeElement.blur();\n }\n }, [closeModal, searchParams, setSearchParams]);\n\n // Render a DrilldownDashboardRenderer inside the modal when a drilldown is active.\n // This callback is only invoked by DrilldownModal when isOpen && dashboardId are set,\n // so the renderer always receives a valid dashboardId.\n // NOTE: Must be declared before the early return below to satisfy React's rules of hooks.\n const renderDrilldownDashboard = useCallback(\n (dashboardId: string, params: Record<string, unknown>) => (\n <DrilldownDashboardRenderer dashboardId={dashboardId} params={params} />\n ),\n []\n );\n\n // Don't render until client is ready (same pattern as microfe-workflows)\n if (!clientReady) {\n if (clientError) {\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center p-6\">\n <div className=\"text-center\">\n <p className=\"text-sm text-text-secondary mb-4\">{clientError}</p>\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"px-4 py-2 text-sm font-medium text-white bg-action-primary-bg hover:bg-action-primary-bg/90 rounded-md transition-colors\"\n >\n Reload Page\n </button>\n </div>\n </div>\n );\n }\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-4 border-gray-300 border-t-blue-600\" />\n </div>\n );\n }\n\n return (\n <ErrorBoundary\n fallback={\n <div className=\"p-6 text-center text-text-secondary\">\n Something went wrong loading BigConsole. Please refresh the page.\n </div>\n }\n >\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <BigConsoleProvider props={props}>\n <NavigationProvider basePath={basePath}>\n <CrossFilterProvider>\n {/* Sits ABOVE the routed page, so when the assistant starts building\n you watch it happen on whatever screen you were already on —\n rather than having to guess which list to go and refresh. */}\n <AiPreviewPanel />\n <BigConsoleRoutes {...props} />\n {isModalOpen && (\n <DrilldownModal onClose={handleDrilldownClose} renderDashboard={renderDrilldownDashboard} />\n )}\n </CrossFilterProvider>\n </NavigationProvider>\n </BigConsoleProvider>\n </FeatureFlagProvider>\n </ErrorBoundary>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { create as e } from "zustand";
|
|
2
|
+
//#region src/bigconsole/assistant/assistantRunStore.ts
|
|
3
|
+
var t = [
|
|
4
|
+
"datasink",
|
|
5
|
+
"dashboard",
|
|
6
|
+
"parser",
|
|
7
|
+
"widget"
|
|
8
|
+
], n = {
|
|
9
|
+
datasink: "Data Sink",
|
|
10
|
+
dashboard: "Dashboard",
|
|
11
|
+
parser: "Parser",
|
|
12
|
+
widget: "Widget"
|
|
13
|
+
}, r = {
|
|
14
|
+
datasink: "pending",
|
|
15
|
+
dashboard: "pending",
|
|
16
|
+
parser: "pending",
|
|
17
|
+
widget: "pending"
|
|
18
|
+
}, i = /\/bigconsole\/(datasinks|dashboards|parsers)\/([A-Za-z0-9_-]+)/g, a = /widget\s+(added|created)/i, o = /^⏳\s*(.+?)…?$/, s = {
|
|
19
|
+
datasinks: "datasink",
|
|
20
|
+
dashboards: "dashboard",
|
|
21
|
+
parsers: "parser"
|
|
22
|
+
};
|
|
23
|
+
function c(e, t) {
|
|
24
|
+
let n = {
|
|
25
|
+
pending: 0,
|
|
26
|
+
running: 1,
|
|
27
|
+
skipped: 2,
|
|
28
|
+
done: 3
|
|
29
|
+
};
|
|
30
|
+
return n[t] > n[e] ? t : e;
|
|
31
|
+
}
|
|
32
|
+
var l = e()((e) => ({
|
|
33
|
+
isRunning: !1,
|
|
34
|
+
previewOpen: !1,
|
|
35
|
+
hasRun: !1,
|
|
36
|
+
prompt: "",
|
|
37
|
+
steps: { ...r },
|
|
38
|
+
entityIds: {},
|
|
39
|
+
caption: "",
|
|
40
|
+
error: null,
|
|
41
|
+
updatedAt: null,
|
|
42
|
+
startRun: (t) => e({
|
|
43
|
+
isRunning: !0,
|
|
44
|
+
previewOpen: !0,
|
|
45
|
+
hasRun: !0,
|
|
46
|
+
prompt: t,
|
|
47
|
+
steps: {
|
|
48
|
+
...r,
|
|
49
|
+
datasink: "running"
|
|
50
|
+
},
|
|
51
|
+
entityIds: {},
|
|
52
|
+
caption: "",
|
|
53
|
+
error: null,
|
|
54
|
+
updatedAt: Date.now()
|
|
55
|
+
}),
|
|
56
|
+
applyProgress: (n) => e((e) => {
|
|
57
|
+
let r = { ...e.steps }, l = { ...e.entityIds };
|
|
58
|
+
for (let e of n.matchAll(i)) {
|
|
59
|
+
let t = s[e[1] ?? ""], n = e[2];
|
|
60
|
+
!t || !n || (r[t] = c(r[t], "done"), l[t] || (l[t] = n));
|
|
61
|
+
}
|
|
62
|
+
a.test(n) && (r.widget = c(r.widget, "done"), r.parser !== "done" && (r.parser = c(r.parser, "skipped")));
|
|
63
|
+
let u = t.find((e) => r[e] === "pending" || r[e] === "running");
|
|
64
|
+
return u && (r[u] = c(r[u], "running")), {
|
|
65
|
+
steps: r,
|
|
66
|
+
entityIds: l,
|
|
67
|
+
caption: n.split("\n").map((e) => e.trim()).reverse().map((e) => o.exec(e)?.[1]).find((e) => !!e) ?? e.caption,
|
|
68
|
+
updatedAt: Date.now()
|
|
69
|
+
};
|
|
70
|
+
}),
|
|
71
|
+
finishRun: (n) => e((e) => {
|
|
72
|
+
let r = { ...e.steps };
|
|
73
|
+
if (n) for (let e of t) r[e] === "running" && (r[e] = "pending");
|
|
74
|
+
else for (let e of t) (r[e] === "running" || r[e] === "pending") && (r[e] = "skipped");
|
|
75
|
+
return {
|
|
76
|
+
isRunning: !1,
|
|
77
|
+
error: n,
|
|
78
|
+
steps: r,
|
|
79
|
+
caption: "",
|
|
80
|
+
updatedAt: Date.now()
|
|
81
|
+
};
|
|
82
|
+
}),
|
|
83
|
+
openPreview: () => e({ previewOpen: !0 }),
|
|
84
|
+
closePreview: () => e({ previewOpen: !1 })
|
|
85
|
+
}));
|
|
86
|
+
//#endregion
|
|
87
|
+
export { t as PREVIEW_STEPS, n as PREVIEW_STEP_LABEL, l as useAssistantRunStore };
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=assistantRunStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assistantRunStore.js","names":[],"sources":["../../../src/bigconsole/assistant/assistantRunStore.ts"],"sourcesContent":["/**\n * What the assistant is building, right now.\n *\n * The agent is instructed to narrate each step and to print a link the moment a\n * create lands (\"✅ Data sink created — [Open the data sink](/bigconsole/datasinks/<id>)\").\n * That narration is the only live signal the frontend gets — there is no event\n * stream from the agent — so this store turns it into something a UI can render:\n * a monotone DataSink → Dashboard → Parser → Widget rail, plus the id of each\n * entity as it appears.\n *\n * Two rules make the parsing survive a model that phrases things differently:\n *\n * 1. Match on the LINK PATH (`/bigconsole/datasinks/<id>`), never on the ✅\n * sentence. The path is copied from the mutation response; the sentence is\n * prose and the model is free to reword it.\n * 2. Never regress. `progress.content` is REPLACED on every poll (not appended)\n * and the ⏳ tool lines come only from the agent's newest message, so a\n * shorter string arriving later must not rewind the rail.\n */\nimport { create } from 'zustand';\n\nexport type PreviewStepKey = 'datasink' | 'dashboard' | 'parser' | 'widget';\nexport type PreviewStepStatus = 'pending' | 'running' | 'done' | 'skipped';\n\nexport const PREVIEW_STEPS: readonly PreviewStepKey[] = ['datasink', 'dashboard', 'parser', 'widget'];\n\nexport const PREVIEW_STEP_LABEL: Record<PreviewStepKey, string> = {\n datasink: 'Data Sink',\n dashboard: 'Dashboard',\n parser: 'Parser',\n widget: 'Widget',\n};\n\ninterface AssistantRunState {\n /** A turn is in flight. */\n isRunning: boolean;\n /** The panel is on screen. Auto-opens when a turn starts; the user can close it. */\n previewOpen: boolean;\n /** True once any turn has run — gates the \"Preview\" button. */\n hasRun: boolean;\n prompt: string;\n steps: Record<PreviewStepKey, PreviewStepStatus>;\n /** Ids harvested from the agent's links. A widget has no link of its own. */\n entityIds: { datasink?: string; dashboard?: string; parser?: string };\n /** Newest \"⏳ …\" line — what the agent says it is doing this second. */\n caption: string;\n /** Set when the turn fails, so the panel can stop pretending to be live. */\n error: string | null;\n /** Epoch ms of the last progress tick — drives the \"updated 3s ago\" strip. */\n updatedAt: number | null;\n\n startRun: (prompt: string) => void;\n applyProgress: (content: string) => void;\n finishRun: (error: string | null) => void;\n openPreview: () => void;\n closePreview: () => void;\n}\n\nconst IDLE_STEPS: Record<PreviewStepKey, PreviewStepStatus> = {\n datasink: 'pending',\n dashboard: 'pending',\n parser: 'pending',\n widget: 'pending',\n};\n\n/** `/bigconsole/datasinks/<id>` — the trustworthy part of the narration. */\nconst ENTITY_LINK = /\\/bigconsole\\/(datasinks|dashboards|parsers)\\/([A-Za-z0-9_-]+)/g;\n/** The widget links to its dashboard, so it can only be detected from the prose. */\nconst WIDGET_DONE = /widget\\s+(added|created)/i;\nconst RUNNING_LINE = /^⏳\\s*(.+?)…?$/;\n\nconst LINK_TO_STEP: Record<string, PreviewStepKey> = {\n datasinks: 'datasink',\n dashboards: 'dashboard',\n parsers: 'parser',\n};\n\n/** Only ever moves a step forward. */\nfunction advance(current: PreviewStepStatus, next: PreviewStepStatus): PreviewStepStatus {\n const rank: Record<PreviewStepStatus, number> = { pending: 0, running: 1, skipped: 2, done: 3 };\n return rank[next] > rank[current] ? next : current;\n}\n\nexport const useAssistantRunStore = create<AssistantRunState>()((set) => ({\n isRunning: false,\n previewOpen: false,\n hasRun: false,\n prompt: '',\n steps: { ...IDLE_STEPS },\n entityIds: {},\n caption: '',\n error: null,\n updatedAt: null,\n\n startRun: (prompt) =>\n set({\n isRunning: true,\n // Auto-open: the whole point is that you SEE the build happen without\n // having to go looking for it.\n previewOpen: true,\n hasRun: true,\n prompt,\n steps: { ...IDLE_STEPS, datasink: 'running' },\n entityIds: {},\n caption: '',\n error: null,\n updatedAt: Date.now(),\n }),\n\n applyProgress: (content) =>\n set((state) => {\n const steps = { ...state.steps };\n const entityIds = { ...state.entityIds };\n\n for (const match of content.matchAll(ENTITY_LINK)) {\n const step = LINK_TO_STEP[match[1] ?? ''];\n const id = match[2];\n if (!step || !id) continue;\n steps[step] = advance(steps[step], 'done');\n if (!entityIds[step as keyof typeof entityIds]) {\n entityIds[step as keyof typeof entityIds] = id;\n }\n }\n\n if (WIDGET_DONE.test(content)) {\n steps.widget = advance(steps.widget, 'done');\n // The parser is optional — the agent skips it when the rows are already\n // shaped. If the widget landed and no parser link ever appeared, it was\n // skipped, not stuck.\n if (steps.parser !== 'done') steps.parser = advance(steps.parser, 'skipped');\n }\n\n // The first step that has not finished is the one being worked on.\n const active = PREVIEW_STEPS.find((key) => steps[key] === 'pending' || steps[key] === 'running');\n if (active) steps[active] = advance(steps[active], 'running');\n\n const caption =\n content\n .split('\\n')\n .map((line) => line.trim())\n .reverse()\n .map((line) => RUNNING_LINE.exec(line)?.[1])\n .find((label): label is string => Boolean(label)) ?? state.caption;\n\n return { steps, entityIds, caption, updatedAt: Date.now() };\n }),\n\n finishRun: (error) =>\n set((state) => {\n const steps = { ...state.steps };\n if (!error) {\n // A clean finish means nothing is still in flight. Anything left running\n // never produced a link — mark it skipped rather than leaving a spinner\n // on screen forever.\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running' || steps[key] === 'pending') steps[key] = 'skipped';\n }\n } else {\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n }\n return { isRunning: false, error, steps, caption: '', updatedAt: Date.now() };\n }),\n\n openPreview: () => set({ previewOpen: true }),\n closePreview: () => set({ previewOpen: false }),\n}));\n"],"mappings":";;AAwBA,IAAa,IAA2C;CAAC;CAAY;CAAa;CAAU;CAAS,EAExF,IAAqD;CAChE,UAAU;CACV,WAAW;CACX,QAAQ;CACR,QAAQ;CACT,EA2BK,IAAwD;CAC5D,UAAU;CACV,WAAW;CACX,QAAQ;CACR,QAAQ;CACT,EAGK,IAAc,mEAEd,IAAc,6BACd,IAAe,iBAEf,IAA+C;CACnD,WAAW;CACX,YAAY;CACZ,SAAS;CACV;AAGD,SAAS,EAAQ,GAA4B,GAA4C;CACvF,IAAM,IAA0C;EAAE,SAAS;EAAG,SAAS;EAAG,SAAS;EAAG,MAAM;EAAG;AAC/F,QAAO,EAAK,KAAQ,EAAK,KAAW,IAAO;;AAG7C,IAAa,IAAuB,GAA2B,EAAE,OAAS;CACxE,WAAW;CACX,aAAa;CACb,QAAQ;CACR,QAAQ;CACR,OAAO,EAAE,GAAG,GAAY;CACxB,WAAW,EAAE;CACb,SAAS;CACT,OAAO;CACP,WAAW;CAEX,WAAW,MACT,EAAI;EACF,WAAW;EAGX,aAAa;EACb,QAAQ;EACR;EACA,OAAO;GAAE,GAAG;GAAY,UAAU;GAAW;EAC7C,WAAW,EAAE;EACb,SAAS;EACT,OAAO;EACP,WAAW,KAAK,KAAK;EACtB,CAAC;CAEJ,gBAAgB,MACd,GAAK,MAAU;EACb,IAAM,IAAQ,EAAE,GAAG,EAAM,OAAO,EAC1B,IAAY,EAAE,GAAG,EAAM,WAAW;AAExC,OAAK,IAAM,KAAS,EAAQ,SAAS,EAAY,EAAE;GACjD,IAAM,IAAO,EAAa,EAAM,MAAM,KAChC,IAAK,EAAM;AACb,IAAC,KAAQ,CAAC,MACd,EAAM,KAAQ,EAAQ,EAAM,IAAO,OAAO,EACrC,EAAU,OACb,EAAU,KAAkC;;AAIhD,EAAI,EAAY,KAAK,EAAQ,KAC3B,EAAM,SAAS,EAAQ,EAAM,QAAQ,OAAO,EAIxC,EAAM,WAAW,WAAQ,EAAM,SAAS,EAAQ,EAAM,QAAQ,UAAU;EAI9E,IAAM,IAAS,EAAc,MAAM,MAAQ,EAAM,OAAS,aAAa,EAAM,OAAS,UAAU;AAWhG,SAVI,MAAQ,EAAM,KAAU,EAAQ,EAAM,IAAS,UAAU,GAUtD;GAAE;GAAO;GAAW,SAPzB,EACG,MAAM,KAAK,CACX,KAAK,MAAS,EAAK,MAAM,CAAC,CAC1B,SAAS,CACT,KAAK,MAAS,EAAa,KAAK,EAAK,GAAG,GAAG,CAC3C,MAAM,MAA2B,EAAQ,EAAO,IAAI,EAAM;GAE3B,WAAW,KAAK,KAAK;GAAE;GAC3D;CAEJ,YAAY,MACV,GAAK,MAAU;EACb,IAAM,IAAQ,EAAE,GAAG,EAAM,OAAO;AAChC,MAAK,EAQH,MAAK,IAAM,KAAO,EAChB,CAAI,EAAM,OAAS,cAAW,EAAM,KAAO;YALxC,IAAM,KAAO,EAChB,EAAI,EAAM,OAAS,aAAa,EAAM,OAAS,eAAW,EAAM,KAAO;AAO3E,SAAO;GAAE,WAAW;GAAO;GAAO;GAAO,SAAS;GAAI,WAAW,KAAK,KAAK;GAAE;GAC7E;CAEJ,mBAAmB,EAAI,EAAE,aAAa,IAAM,CAAC;CAC7C,oBAAoB,EAAI,EAAE,aAAa,IAAO,CAAC;CAChD,EAAE"}
|