@burdenoff/microfe-bigconsole 2026.716.2 → 2026.716.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.
Files changed (32) hide show
  1. package/dist/bigconsole/BigConsoleRoot.js +100 -90
  2. package/dist/bigconsole/BigConsoleRoot.js.map +1 -1
  3. package/dist/bigconsole/assistant/assistantRunStore.js +3 -1
  4. package/dist/bigconsole/assistant/assistantRunStore.js.map +1 -1
  5. package/dist/bigconsole/assistant/index.js +2 -1
  6. package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js +70 -60
  7. package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js.map +1 -1
  8. package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js +187 -175
  9. package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js.map +1 -1
  10. package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js +133 -0
  11. package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js.map +1 -0
  12. package/dist/bigconsole/components/preview/AiPreviewPanel.js +88 -136
  13. package/dist/bigconsole/components/preview/AiPreviewPanel.js.map +1 -1
  14. package/dist/bigconsole/components/preview/previewShared.js +61 -0
  15. package/dist/bigconsole/components/preview/previewShared.js.map +1 -0
  16. package/dist/bigconsole/hooks/useDashboardOperations.js +72 -67
  17. package/dist/bigconsole/hooks/useDashboardOperations.js.map +1 -1
  18. package/dist/bigconsole/hooks/useDashboardTemplates.js +108 -105
  19. package/dist/bigconsole/hooks/useDashboardTemplates.js.map +1 -1
  20. package/dist/bigconsole/hooks/useDashboardViews.js +77 -74
  21. package/dist/bigconsole/hooks/useDashboardViews.js.map +1 -1
  22. package/dist/bigconsole/hooks/useImportDashboard.js +68 -64
  23. package/dist/bigconsole/hooks/useImportDashboard.js.map +1 -1
  24. package/dist/bigconsole/pages/DashboardsPage.js +240 -234
  25. package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
  26. package/dist/bigconsole/pages/InsightsPage.js +161 -143
  27. package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
  28. package/dist/bigconsole/providers/BigConsoleQuotaProvider.js +70 -0
  29. package/dist/bigconsole/providers/BigConsoleQuotaProvider.js.map +1 -0
  30. package/dist/bigconsole/utils/quota.js +86 -0
  31. package/dist/bigconsole/utils/quota.js.map +1 -0
  32. package/package.json +2 -2
@@ -1,64 +1,37 @@
1
1
  import { useBigConsoleNavigate as e } from "../../context/BigConsoleContext.js";
2
2
  import "../../context/index.js";
3
- import { PREVIEW_STEPS as t, PREVIEW_STEP_LABEL as n, useAssistantRunStore as r } from "../../assistant/assistantRunStore.js";
4
- import i from "../../hooks/useDashboardOperations.js";
5
- import a from "../../hooks/useDataSinkOperations.js";
6
- import ee from "../../hooks/useParserOperations.js";
3
+ import { PREVIEW_STEPS as t, useAssistantRunStore as n } from "../../assistant/assistantRunStore.js";
4
+ import r from "../../hooks/useDashboardOperations.js";
5
+ import i from "../../hooks/useDataSinkOperations.js";
6
+ import a from "../../hooks/useParserOperations.js";
7
7
  import o from "../../hooks/useWidgetOperations.js";
8
- import { useEffect as s, useMemo as c, useRef as l, useState as u } from "react";
9
- import { Check as d, ChevronRight as f, Loader2 as p, Minus as m, Sparkles as h, X as g } from "lucide-react";
10
- import { Badge as _, Button as v, Spinner as y, Tabs as b, TabsContent as x, TabsList as S, TabsTrigger as C } from "@burdenoff/fe-libs/ui";
11
- import { jsx as w, jsxs as T } from "react/jsx-runtime";
8
+ import { EmptyOrLoading as s, StepRail as ee, formatAgo as c, useAssistantSidebarWidth as te } from "./previewShared.js";
9
+ import { useEffect as l, useMemo as u, useRef as d, useState as f } from "react";
10
+ import { Loader2 as p, Sparkles as m, X as h } from "lucide-react";
11
+ import { Badge as g, Button as _, Tabs as ne, TabsContent as v, TabsList as y, TabsTrigger as b } from "@burdenoff/fe-libs/ui";
12
+ import { jsx as x, jsxs as S } from "react/jsx-runtime";
12
13
  //#region src/bigconsole/components/preview/AiPreviewPanel.tsx
13
- var E = 3500, D = 3e4, O = "default";
14
- function k({ status: e }) {
15
- return e === "done" ? /* @__PURE__ */ w(d, { className: "size-3.5 text-status-success-text" }) : e === "running" ? /* @__PURE__ */ w(p, { className: "size-3.5 animate-spin text-action-primary-bg" }) : e === "skipped" ? /* @__PURE__ */ w(m, { className: "size-3.5 text-text-muted" }) : /* @__PURE__ */ w("span", { className: "size-1.5 rounded-full bg-border-strong" });
16
- }
17
- function A({ steps: e }) {
18
- return /* @__PURE__ */ w("div", {
19
- className: "flex flex-wrap items-center gap-1",
20
- "data-testid": "ai-preview-step-rail",
21
- children: t.map((r, i) => /* @__PURE__ */ T("div", {
22
- className: "flex items-center gap-1",
23
- children: [/* @__PURE__ */ T("span", {
24
- "data-testid": `ai-preview-step-${r}`,
25
- "data-status": e[r],
26
- className: ["inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium", e[r] === "done" ? "border-status-success-border bg-status-success-bg text-status-success-text" : e[r] === "running" ? "border-border-strong bg-bg-surface text-text-primary" : "border-border-default text-text-muted"].join(" "),
27
- children: [
28
- /* @__PURE__ */ w(k, { status: e[r] }),
29
- n[r],
30
- e[r] === "skipped" ? /* @__PURE__ */ w("span", {
31
- className: "text-[10px]",
32
- children: "skipped"
33
- }) : null
34
- ]
35
- }), i < t.length - 1 ? /* @__PURE__ */ w(f, { className: "size-3.5 text-text-muted" }) : null]
36
- }, r))
37
- });
38
- }
39
- function j(e) {
40
- return e < 5 ? "just now" : e < 60 ? `${String(e)}s ago` : `${String(Math.floor(e / 60))}m ago`;
41
- }
42
- function M({ id: e, title: t, meta: n, isNew: r, onOpen: i }) {
43
- return /* @__PURE__ */ T("div", {
14
+ var C = 3500, w = 3e4, T = "default";
15
+ function E({ id: e, title: t, meta: n, isNew: r, onOpen: i }) {
16
+ return /* @__PURE__ */ S("div", {
44
17
  "data-testid": "ai-preview-row",
45
18
  "data-entity-id": e,
46
19
  className: ["flex items-center justify-between gap-3 rounded-md border px-3 py-2", r ? "border-status-success-border bg-status-success-bg/30" : "border-border-subtle bg-bg-surface"].join(" "),
47
- children: [/* @__PURE__ */ T("div", {
20
+ children: [/* @__PURE__ */ S("div", {
48
21
  className: "min-w-0",
49
- children: [/* @__PURE__ */ w("p", {
22
+ children: [/* @__PURE__ */ x("p", {
50
23
  className: "truncate text-sm font-medium text-text-primary",
51
24
  children: t
52
- }), n ? /* @__PURE__ */ w("p", {
25
+ }), n ? /* @__PURE__ */ x("p", {
53
26
  className: "truncate text-xs text-text-muted",
54
27
  children: n
55
28
  }) : null]
56
- }), /* @__PURE__ */ T("div", {
29
+ }), /* @__PURE__ */ S("div", {
57
30
  className: "flex shrink-0 items-center gap-2",
58
- children: [r ? /* @__PURE__ */ T(_, {
31
+ children: [r ? /* @__PURE__ */ S(g, {
59
32
  className: "bg-status-success-bg text-status-success-text",
60
- children: [/* @__PURE__ */ w(h, { className: "mr-1 size-3" }), "just built"]
61
- }) : null, i ? /* @__PURE__ */ w(v, {
33
+ children: [/* @__PURE__ */ x(m, { className: "mr-1 size-3" }), "just built"]
34
+ }) : null, i ? /* @__PURE__ */ x(_, {
62
35
  size: "sm",
63
36
  variant: "ghost",
64
37
  onClick: i,
@@ -67,34 +40,13 @@ function M({ id: e, title: t, meta: n, isNew: r, onOpen: i }) {
67
40
  })]
68
41
  });
69
42
  }
70
- function N() {
71
- let [e, t] = u(0);
72
- return s(() => {
73
- let e = () => {
74
- let e = document.querySelector("[data-testid=\"assistant-widget-window\"][data-layout=\"sidebar\"]")?.getBoundingClientRect(), n = e && e.width < window.innerWidth * .75 ? Math.round(e.width) : 0;
75
- t((e) => e === n ? e : n);
76
- };
77
- e();
78
- let n = window.setInterval(e, 500);
79
- return window.addEventListener("resize", e), () => {
80
- window.clearInterval(n), window.removeEventListener("resize", e);
81
- };
82
- }, []), e;
83
- }
84
- function P({ loading: e, empty: t }) {
85
- return e ? /* @__PURE__ */ T("div", {
86
- className: "flex items-center gap-2 px-1 py-6 text-sm text-text-muted",
87
- children: [/* @__PURE__ */ w(y, { className: "size-4" }), "Loading…"]
88
- }) : /* @__PURE__ */ w("p", {
89
- className: "px-1 py-6 text-sm text-text-muted",
90
- children: t
91
- });
92
- }
93
- function F() {
94
- let n = e(), d = r((e) => e.isRunning), f = r((e) => e.previewOpen), m = r((e) => e.hasRun), _ = r((e) => e.steps), y = r((e) => e.entityIds), k = r((e) => e.caption), F = r((e) => e.error), I = r((e) => e.updatedAt), te = r((e) => e.openPreview), L = r((e) => e.closePreview), [R, z] = u("datasink"), [B, V] = u(() => Date.now()), H = y.dashboard, U = a({ skipFetch: !0 }), W = i(), G = ee({
95
- consoleId: H ?? O,
43
+ function D() {
44
+ let g = e(), D = n((e) => e.isRunning), O = n((e) => e.previewOpen), k = n((e) => e.hasRun), A = n((e) => e.steps), j = n((e) => e.entityIds), M = n((e) => e.caption), N = n((e) => e.error), P = n((e) => e.updatedAt), F = n((e) => e.openPreview), I = n((e) => e.closePreview), L = n((e) => e.setRichMounted);
45
+ l(() => (L(!0), () => L(!1)), [L]);
46
+ let [R, z] = f("datasink"), [B, V] = f(() => Date.now()), H = j.dashboard, U = i({ skipFetch: !0 }), W = r(), G = a({
47
+ consoleId: H ?? T,
96
48
  skipFetch: !0
97
- }), K = o(void 0, H, { skipFetch: !H }), q = l({
49
+ }), K = o(void 0, H, { skipFetch: !H }), q = d({
98
50
  sinks: U.refetch,
99
51
  dashboards: W.refetch,
100
52
  parsers: G.refetch,
@@ -105,165 +57,165 @@ function F() {
105
57
  dashboards: W.refetch,
106
58
  parsers: G.refetch,
107
59
  widgets: K.refetch
108
- }, s(() => {
109
- if (!m) return;
60
+ }, l(() => {
61
+ if (!k) return;
110
62
  let e = Infinity;
111
- d || (e = Date.now() + D);
63
+ D || (e = Date.now() + w);
112
64
  let t = () => {
113
65
  typeof document < "u" && document.visibilityState !== "visible" || Date.now() > e || (q.current.sinks(), q.current.dashboards(), H && (q.current.parsers(), q.current.widgets()), V(Date.now()));
114
66
  };
115
67
  t();
116
- let n = window.setInterval(t, E), r = () => {
68
+ let n = window.setInterval(t, C), r = () => {
117
69
  document.visibilityState === "visible" && t();
118
70
  };
119
71
  return document.addEventListener("visibilitychange", r), () => {
120
72
  window.clearInterval(n), document.removeEventListener("visibilitychange", r);
121
73
  };
122
74
  }, [
123
- m,
124
- d,
75
+ k,
76
+ D,
125
77
  H
126
- ]), s(() => {
127
- let e = t.find((e) => _[e] === "running");
78
+ ]), l(() => {
79
+ let e = t.find((e) => A[e] === "running");
128
80
  e && z(e);
129
- }, [_]);
130
- let J = I ? Math.max(0, Math.floor((B - I) / 1e3)) : 0, Y = N(), X = c(() => U.dataSinks.slice(0, 25), [U.dataSinks]), Z = c(() => W.dashboards.slice(0, 25), [W.dashboards]), Q = c(() => G.parsers.slice(0, 25), [G.parsers]), $ = c(() => K.widgets.slice(0, 25), [K.widgets]);
131
- return m ? f ? /* @__PURE__ */ T("section", {
81
+ }, [A]);
82
+ let J = P ? Math.max(0, Math.floor((B - P) / 1e3)) : 0, Y = te(), X = u(() => U.dataSinks.slice(0, 25), [U.dataSinks]), Z = u(() => W.dashboards.slice(0, 25), [W.dashboards]), Q = u(() => G.parsers.slice(0, 25), [G.parsers]), $ = u(() => K.widgets.slice(0, 25), [K.widgets]);
83
+ return k ? O ? /* @__PURE__ */ S("section", {
132
84
  "data-testid": "ai-preview-panel",
133
85
  "aria-label": "AI preview",
134
86
  style: { marginRight: Y ? Y + 16 : void 0 },
135
87
  className: "mx-4 mt-3 overflow-hidden rounded-lg border border-border-default bg-bg-sunken shadow-sm md:mx-6",
136
88
  children: [
137
- /* @__PURE__ */ T("div", {
89
+ /* @__PURE__ */ S("div", {
138
90
  className: "flex items-center justify-between gap-3 border-b border-border-subtle bg-bg-surface px-3 py-2",
139
- children: [/* @__PURE__ */ T("div", {
91
+ children: [/* @__PURE__ */ S("div", {
140
92
  className: "flex min-w-0 items-center gap-2",
141
93
  children: [
142
- /* @__PURE__ */ w(h, { className: "size-4 shrink-0 text-action-primary-bg" }),
143
- /* @__PURE__ */ w("h2", {
94
+ /* @__PURE__ */ x(m, { className: "size-4 shrink-0 text-action-primary-bg" }),
95
+ /* @__PURE__ */ x("h2", {
144
96
  className: "truncate text-sm font-semibold text-text-primary",
145
97
  children: "AI Preview"
146
98
  }),
147
- d ? /* @__PURE__ */ T("span", {
99
+ D ? /* @__PURE__ */ S("span", {
148
100
  className: "inline-flex items-center gap-1.5 rounded-full bg-status-success-bg px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-status-success-text",
149
101
  "data-testid": "ai-preview-live",
150
- children: [/* @__PURE__ */ T("span", {
102
+ children: [/* @__PURE__ */ S("span", {
151
103
  className: "relative flex size-2",
152
- children: [/* @__PURE__ */ w("span", { className: "absolute inline-flex size-full animate-ping rounded-full bg-status-success-text opacity-75" }), /* @__PURE__ */ w("span", { className: "relative inline-flex size-2 rounded-full bg-status-success-text" })]
104
+ children: [/* @__PURE__ */ x("span", { className: "absolute inline-flex size-full animate-ping rounded-full bg-status-success-text opacity-75" }), /* @__PURE__ */ x("span", { className: "relative inline-flex size-2 rounded-full bg-status-success-text" })]
153
105
  }), "Live"]
154
106
  }) : null,
155
- /* @__PURE__ */ T("span", {
107
+ /* @__PURE__ */ S("span", {
156
108
  className: "truncate text-xs text-text-muted",
157
109
  "data-testid": "ai-preview-ago",
158
110
  children: [
159
- d ? k || "Working…" : F ? "Stopped" : "Done",
111
+ D ? M || "Working…" : N ? "Stopped" : "Done",
160
112
  " · updated",
161
113
  " ",
162
- j(J)
114
+ c(J)
163
115
  ]
164
116
  })
165
117
  ]
166
- }), /* @__PURE__ */ w(v, {
118
+ }), /* @__PURE__ */ x(_, {
167
119
  size: "sm",
168
120
  variant: "ghost",
169
- onClick: L,
121
+ onClick: I,
170
122
  "aria-label": "Close preview",
171
123
  "data-testid": "ai-preview-close",
172
- children: /* @__PURE__ */ w(g, { className: "size-4" })
124
+ children: /* @__PURE__ */ x(h, { className: "size-4" })
173
125
  })]
174
126
  }),
175
- /* @__PURE__ */ w("div", {
127
+ /* @__PURE__ */ x("div", {
176
128
  className: "border-b border-border-subtle px-3 py-2",
177
- children: /* @__PURE__ */ w(A, { steps: _ })
129
+ children: /* @__PURE__ */ x(ee, { steps: A })
178
130
  }),
179
- /* @__PURE__ */ T(b, {
131
+ /* @__PURE__ */ S(ne, {
180
132
  value: R,
181
133
  onValueChange: (e) => z(e),
182
- children: [/* @__PURE__ */ T(S, {
134
+ children: [/* @__PURE__ */ S(y, {
183
135
  className: "mx-3 mt-2",
184
136
  children: [
185
- /* @__PURE__ */ w(C, {
137
+ /* @__PURE__ */ x(b, {
186
138
  value: "datasink",
187
139
  "data-testid": "ai-preview-tab-datasink",
188
140
  children: "Data Sinks"
189
141
  }),
190
- /* @__PURE__ */ w(C, {
142
+ /* @__PURE__ */ x(b, {
191
143
  value: "dashboard",
192
144
  "data-testid": "ai-preview-tab-dashboard",
193
145
  children: "Dashboards"
194
146
  }),
195
- /* @__PURE__ */ w(C, {
147
+ /* @__PURE__ */ x(b, {
196
148
  value: "parser",
197
149
  "data-testid": "ai-preview-tab-parser",
198
150
  children: "Parsers"
199
151
  }),
200
- /* @__PURE__ */ w(C, {
152
+ /* @__PURE__ */ x(b, {
201
153
  value: "widget",
202
154
  "data-testid": "ai-preview-tab-widget",
203
155
  children: "Widgets"
204
156
  })
205
157
  ]
206
- }), /* @__PURE__ */ T("div", {
158
+ }), /* @__PURE__ */ S("div", {
207
159
  className: "max-h-[320px] overflow-y-auto p-3",
208
160
  children: [
209
- /* @__PURE__ */ w(x, {
161
+ /* @__PURE__ */ x(v, {
210
162
  value: "datasink",
211
163
  className: "space-y-2",
212
- children: X.length === 0 ? /* @__PURE__ */ w(P, {
164
+ children: X.length === 0 ? /* @__PURE__ */ x(s, {
213
165
  loading: U.loading,
214
166
  empty: "No data sinks yet."
215
- }) : X.map((e) => /* @__PURE__ */ w(M, {
167
+ }) : X.map((e) => /* @__PURE__ */ x(E, {
216
168
  id: e.id,
217
169
  title: e.name ?? e.key,
218
170
  meta: e.status,
219
- isNew: e.id === y.datasink,
220
- onOpen: () => n(`/datasinks/${e.id}`)
171
+ isNew: e.id === j.datasink,
172
+ onOpen: () => g(`/datasinks/${e.id}`)
221
173
  }, e.id))
222
174
  }),
223
- /* @__PURE__ */ w(x, {
175
+ /* @__PURE__ */ x(v, {
224
176
  value: "dashboard",
225
177
  className: "space-y-2",
226
- children: Z.length === 0 ? /* @__PURE__ */ w(P, {
178
+ children: Z.length === 0 ? /* @__PURE__ */ x(s, {
227
179
  loading: W.loading,
228
180
  empty: "No dashboards yet."
229
- }) : Z.map((e) => /* @__PURE__ */ w(M, {
181
+ }) : Z.map((e) => /* @__PURE__ */ x(E, {
230
182
  id: e.id,
231
183
  title: e.name,
232
184
  meta: `${String(e.widgetCount ?? 0)} widget(s)`,
233
- isNew: e.id === y.dashboard,
234
- onOpen: () => n(`/dashboards/${e.id}`)
185
+ isNew: e.id === j.dashboard,
186
+ onOpen: () => g(`/dashboards/${e.id}`)
235
187
  }, e.id))
236
188
  }),
237
- /* @__PURE__ */ w(x, {
189
+ /* @__PURE__ */ x(v, {
238
190
  value: "parser",
239
191
  className: "space-y-2",
240
- children: H ? Q.length === 0 ? /* @__PURE__ */ w(P, {
192
+ children: H ? Q.length === 0 ? /* @__PURE__ */ x(s, {
241
193
  loading: G.loading,
242
194
  empty: "No parsers on this dashboard yet."
243
- }) : Q.map((e) => /* @__PURE__ */ w(M, {
195
+ }) : Q.map((e) => /* @__PURE__ */ x(E, {
244
196
  id: e.id,
245
197
  title: e.name,
246
198
  meta: `${e.type} · ${e.status}`,
247
- isNew: e.id === y.parser,
248
- onOpen: () => n(`/parsers/${e.id}`)
249
- }, e.id)) : /* @__PURE__ */ w("p", {
199
+ isNew: e.id === j.parser,
200
+ onOpen: () => g(`/parsers/${e.id}`)
201
+ }, e.id)) : /* @__PURE__ */ x("p", {
250
202
  className: "px-1 py-6 text-sm text-text-muted",
251
203
  children: "Parsers are listed per dashboard — waiting for the dashboard to be created."
252
204
  })
253
205
  }),
254
- /* @__PURE__ */ w(x, {
206
+ /* @__PURE__ */ x(v, {
255
207
  value: "widget",
256
208
  className: "space-y-2",
257
- children: H ? $.length === 0 ? /* @__PURE__ */ w(P, {
209
+ children: H ? $.length === 0 ? /* @__PURE__ */ x(s, {
258
210
  loading: K.loading,
259
211
  empty: "No widgets on this dashboard yet."
260
- }) : $.map((e) => /* @__PURE__ */ w(M, {
212
+ }) : $.map((e) => /* @__PURE__ */ x(E, {
261
213
  id: e.id,
262
214
  title: e.title,
263
215
  meta: e.type,
264
- isNew: e.id === $[$.length - 1]?.id && _.widget === "done",
265
- onOpen: () => n(`/dashboards/${H}`)
266
- }, e.id)) : /* @__PURE__ */ w("p", {
216
+ isNew: e.id === $[$.length - 1]?.id && A.widget === "done",
217
+ onOpen: () => g(`/dashboards/${H}`)
218
+ }, e.id)) : /* @__PURE__ */ x("p", {
267
219
  className: "px-1 py-6 text-sm text-text-muted",
268
220
  children: "Widgets live on a dashboard — waiting for the dashboard to be created."
269
221
  })
@@ -271,29 +223,29 @@ function F() {
271
223
  ]
272
224
  })]
273
225
  }),
274
- F ? /* @__PURE__ */ w("p", {
226
+ N ? /* @__PURE__ */ x("p", {
275
227
  className: "border-t border-border-subtle px-3 py-2 text-xs text-status-error-text",
276
228
  "data-testid": "ai-preview-error",
277
- children: F
229
+ children: N
278
230
  }) : null
279
231
  ]
280
- }) : /* @__PURE__ */ w("div", {
232
+ }) : /* @__PURE__ */ x("div", {
281
233
  className: "flex justify-end px-4 pt-3 md:px-6",
282
234
  style: { marginRight: Y },
283
- children: /* @__PURE__ */ T(v, {
235
+ children: /* @__PURE__ */ S(_, {
284
236
  size: "sm",
285
237
  variant: "outline",
286
- onClick: te,
238
+ onClick: F,
287
239
  "data-testid": "ai-preview-open",
288
240
  children: [
289
- /* @__PURE__ */ w(h, { className: "mr-1.5 size-4" }),
241
+ /* @__PURE__ */ x(m, { className: "mr-1.5 size-4" }),
290
242
  "Preview",
291
- d ? /* @__PURE__ */ w(p, { className: "ml-1.5 size-3.5 animate-spin" }) : null
243
+ D ? /* @__PURE__ */ x(p, { className: "ml-1.5 size-3.5 animate-spin" }) : null
292
244
  ]
293
245
  })
294
246
  }) : null;
295
247
  }
296
248
  //#endregion
297
- export { F as default };
249
+ export { D as default };
298
250
 
299
251
  //# sourceMappingURL=AiPreviewPanel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiPreviewPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewPanel.tsx"],"sourcesContent":["/**\n * AI Preview — watch the assistant build.\n *\n * Opens by itself the moment a prompt starts, and shows the DataSink → Dashboard\n * → Parser → Widget chain filling in live: a step rail driven by the agent's own\n * narration, and four tabs listing the real entities as they land.\n *\n * Why polling and not a subscription: the four entity types have no working\n * GraphQL subscriptions (DataSink's are declared in the SDL but have no\n * resolvers; the others have none at all). The same 3.5s poll is already used by\n * DashboardViewPage for exactly this reason. Note the real freshness floor is\n * fe-libs' ~15s shared read cache, not this interval.\n */\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { Badge, Button, Spinner, Tabs, TabsContent, TabsList, TabsTrigger } from '@burdenoff/fe-libs/ui';\nimport { Check, ChevronRight, Loader2, Minus, Sparkles, X } from 'lucide-react';\n\nimport {\n PREVIEW_STEP_LABEL,\n PREVIEW_STEPS,\n type PreviewStepKey,\n type PreviewStepStatus,\n useAssistantRunStore,\n} from '../../assistant/assistantRunStore';\nimport { useBigConsoleNavigate } from '../../context';\nimport { useDashboardOperations } from '../../hooks/useDashboardOperations';\nimport { useDataSinkOperations } from '../../hooks/useDataSinkOperations';\nimport { useParserOperations } from '../../hooks/useParserOperations';\nimport { useWidgetOperations } from '../../hooks/useWidgetOperations';\n\n/** Matches DashboardViewPage's live cadence. */\nconst LIVE_POLL_MS = 3500;\n/** Keep polling briefly after the turn ends — the last write can land late. */\nconst TRAILING_POLL_MS = 30_000;\n/** Parsers are listed per console; a console IS a dashboard. */\nconst DEFAULT_CONSOLE_ID = 'default';\n\nfunction StepPip({ status }: { status: PreviewStepStatus }) {\n if (status === 'done') return <Check className=\"size-3.5 text-status-success-text\" />;\n if (status === 'running') return <Loader2 className=\"size-3.5 animate-spin text-action-primary-bg\" />;\n if (status === 'skipped') return <Minus className=\"size-3.5 text-text-muted\" />;\n return <span className=\"size-1.5 rounded-full bg-border-strong\" />;\n}\n\nfunction StepRail({ steps }: { steps: Record<PreviewStepKey, PreviewStepStatus> }) {\n return (\n <div className=\"flex flex-wrap items-center gap-1\" data-testid=\"ai-preview-step-rail\">\n {PREVIEW_STEPS.map((key, index) => (\n <div key={key} className=\"flex items-center gap-1\">\n <span\n data-testid={`ai-preview-step-${key}`}\n data-status={steps[key]}\n className={[\n 'inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium',\n steps[key] === 'done'\n ? 'border-status-success-border bg-status-success-bg text-status-success-text'\n : steps[key] === 'running'\n ? 'border-border-strong bg-bg-surface text-text-primary'\n : 'border-border-default text-text-muted',\n ].join(' ')}\n >\n <StepPip status={steps[key]} />\n {PREVIEW_STEP_LABEL[key]}\n {steps[key] === 'skipped' ? <span className=\"text-[10px]\">skipped</span> : null}\n </span>\n {index < PREVIEW_STEPS.length - 1 ? <ChevronRight className=\"size-3.5 text-text-muted\" /> : null}\n </div>\n ))}\n </div>\n );\n}\n\nfunction formatAgo(seconds: number): string {\n if (seconds < 5) return 'just now';\n if (seconds < 60) return `${String(seconds)}s ago`;\n return `${String(Math.floor(seconds / 60))}m ago`;\n}\n\ninterface RowProps {\n id: string;\n title: string;\n meta?: string;\n isNew: boolean;\n onOpen?: () => void;\n}\n\nfunction EntityRow({ id, title, meta, isNew, onOpen }: RowProps) {\n return (\n <div\n data-testid=\"ai-preview-row\"\n data-entity-id={id}\n className={[\n 'flex items-center justify-between gap-3 rounded-md border px-3 py-2',\n isNew ? 'border-status-success-border bg-status-success-bg/30' : 'border-border-subtle bg-bg-surface',\n ].join(' ')}\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{title}</p>\n {meta ? <p className=\"truncate text-xs text-text-muted\">{meta}</p> : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {isNew ? (\n <Badge className=\"bg-status-success-bg text-status-success-text\">\n <Sparkles className=\"mr-1 size-3\" />\n just built\n </Badge>\n ) : null}\n {onOpen ? (\n <Button size=\"sm\" variant=\"ghost\" onClick={onOpen}>\n Open\n </Button>\n ) : null}\n </div>\n </div>\n );\n}\n\n/**\n * Width of the assistant sidebar, or 0 when it is closed.\n *\n * Polled rather than subscribed: the widget lives in fe-libs, is mounted by the\n * app shell, and emits nothing when it opens or closes. Reading the DOM is the\n * only honest signal available, and it is cheap.\n */\nfunction useAssistantSidebarWidth(): number {\n const [width, setWidth] = useState(0);\n\n useEffect(() => {\n const measure = () => {\n const el = document.querySelector('[data-testid=\"assistant-widget-window\"][data-layout=\"sidebar\"]');\n const rect = el?.getBoundingClientRect();\n // Below `sm` the sidebar is full-width; reserving that would leave no room\n // for the preview at all, so on small screens let it stack underneath.\n const next = rect && rect.width < window.innerWidth * 0.75 ? Math.round(rect.width) : 0;\n setWidth((current) => (current === next ? current : next));\n };\n measure();\n const intervalId = window.setInterval(measure, 500);\n window.addEventListener('resize', measure);\n return () => {\n window.clearInterval(intervalId);\n window.removeEventListener('resize', measure);\n };\n }, []);\n\n return width;\n}\n\nfunction EmptyOrLoading({ loading, empty }: { loading: boolean; empty: string }) {\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 px-1 py-6 text-sm text-text-muted\">\n <Spinner className=\"size-4\" />\n Loading…\n </div>\n );\n }\n return <p className=\"px-1 py-6 text-sm text-text-muted\">{empty}</p>;\n}\n\nexport function AiPreviewPanel() {\n const navigate = useBigConsoleNavigate();\n\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const steps = useAssistantRunStore((state) => state.steps);\n const entityIds = useAssistantRunStore((state) => state.entityIds);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n const openPreview = useAssistantRunStore((state) => state.openPreview);\n const closePreview = useAssistantRunStore((state) => state.closePreview);\n\n const [tab, setTab] = useState<PreviewStepKey>('datasink');\n const [now, setNow] = useState(() => Date.now());\n\n const dashboardId = entityIds.dashboard;\n\n // The panel does its own fetching, so the tabs are live even before the user\n // opens them — the \"just built\" highlight must be right the instant they look.\n const sinks = useDataSinkOperations({ skipFetch: true });\n const dashboards = useDashboardOperations();\n const parsers = useParserOperations({ consoleId: dashboardId ?? DEFAULT_CONSOLE_ID, skipFetch: true });\n const widgets = useWidgetOperations(undefined, dashboardId, { skipFetch: !dashboardId });\n\n const refetchRef = useRef({\n sinks: sinks.refetch,\n dashboards: dashboards.refetch,\n parsers: parsers.refetch,\n widgets: widgets.refetch,\n });\n refetchRef.current = {\n sinks: sinks.refetch,\n dashboards: dashboards.refetch,\n parsers: parsers.refetch,\n widgets: widgets.refetch,\n };\n\n // Follow the build. Keeps polling for a short while after the turn settles so\n // the final widget does not miss the panel by a second.\n useEffect(() => {\n if (!hasRun) return;\n\n let stopAt = Number.POSITIVE_INFINITY;\n if (!isRunning) stopAt = Date.now() + TRAILING_POLL_MS;\n\n const poll = () => {\n if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return;\n if (Date.now() > stopAt) return;\n void refetchRef.current.sinks();\n void refetchRef.current.dashboards();\n if (dashboardId) {\n void refetchRef.current.parsers();\n void refetchRef.current.widgets();\n }\n setNow(Date.now());\n };\n\n poll();\n const intervalId = window.setInterval(poll, LIVE_POLL_MS);\n const onVisible = () => {\n if (document.visibilityState === 'visible') poll();\n };\n document.addEventListener('visibilitychange', onVisible);\n return () => {\n window.clearInterval(intervalId);\n document.removeEventListener('visibilitychange', onVisible);\n };\n }, [hasRun, isRunning, dashboardId]);\n\n // Jump the user to whatever is being worked on, so they are not left staring\n // at the Data Sinks tab while the parser is the thing actually happening.\n useEffect(() => {\n const active = PREVIEW_STEPS.find((key) => steps[key] === 'running');\n if (active) setTab(active);\n }, [steps]);\n\n const agoSeconds = updatedAt ? Math.max(0, Math.floor((now - updatedAt) / 1000)) : 0;\n\n // Keep clear of the assistant.\n //\n // The assistant sidebar is `position: fixed` — it OVERLAYS the page instead of\n // reflowing it. A panel that spans the full content width therefore runs\n // underneath it, and everything in that overlapped strip (tabs, Open buttons)\n // silently stops taking clicks. Measure the real element rather than hardcoding\n // 560px: the widget is responsive, and it can be closed mid-run.\n const assistantWidth = useAssistantSidebarWidth();\n\n const sinkRows = useMemo(() => sinks.dataSinks.slice(0, 25), [sinks.dataSinks]);\n const dashboardRows = useMemo(() => dashboards.dashboards.slice(0, 25), [dashboards.dashboards]);\n const parserRows = useMemo(() => parsers.parsers.slice(0, 25), [parsers.parsers]);\n const widgetRows = useMemo(() => widgets.widgets.slice(0, 25), [widgets.widgets]);\n\n if (!hasRun) return null;\n\n if (!previewOpen) {\n return (\n <div className=\"flex justify-end px-4 pt-3 md:px-6\" style={{ marginRight: assistantWidth }}>\n <Button size=\"sm\" variant=\"outline\" onClick={openPreview} data-testid=\"ai-preview-open\">\n <Sparkles className=\"mr-1.5 size-4\" />\n Preview\n {isRunning ? <Loader2 className=\"ml-1.5 size-3.5 animate-spin\" /> : null}\n </Button>\n </div>\n );\n }\n\n return (\n <section\n data-testid=\"ai-preview-panel\"\n aria-label=\"AI preview\"\n style={{ marginRight: assistantWidth ? assistantWidth + 16 : undefined }}\n className=\"mx-4 mt-3 overflow-hidden rounded-lg border border-border-default bg-bg-sunken shadow-sm md:mx-6\"\n >\n {/* Header + live strip */}\n <div className=\"flex items-center justify-between gap-3 border-b border-border-subtle bg-bg-surface px-3 py-2\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <Sparkles className=\"size-4 shrink-0 text-action-primary-bg\" />\n <h2 className=\"truncate text-sm font-semibold text-text-primary\">AI Preview</h2>\n {isRunning ? (\n <span\n className=\"inline-flex items-center gap-1.5 rounded-full bg-status-success-bg px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-status-success-text\"\n data-testid=\"ai-preview-live\"\n >\n <span className=\"relative flex size-2\">\n <span className=\"absolute inline-flex size-full animate-ping rounded-full bg-status-success-text opacity-75\" />\n <span className=\"relative inline-flex size-2 rounded-full bg-status-success-text\" />\n </span>\n Live\n </span>\n ) : null}\n <span className=\"truncate text-xs text-text-muted\" data-testid=\"ai-preview-ago\">\n {isRunning ? (caption ? caption : 'Working…') : error ? 'Stopped' : 'Done'} · updated{' '}\n {formatAgo(agoSeconds)}\n </span>\n </div>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={closePreview}\n aria-label=\"Close preview\"\n data-testid=\"ai-preview-close\"\n >\n <X className=\"size-4\" />\n </Button>\n </div>\n\n {/* Step rail */}\n <div className=\"border-b border-border-subtle px-3 py-2\">\n <StepRail steps={steps} />\n </div>\n\n {/* Tabs */}\n <Tabs value={tab} onValueChange={(value) => setTab(value as PreviewStepKey)}>\n <TabsList className=\"mx-3 mt-2\">\n <TabsTrigger value=\"datasink\" data-testid=\"ai-preview-tab-datasink\">\n Data Sinks\n </TabsTrigger>\n <TabsTrigger value=\"dashboard\" data-testid=\"ai-preview-tab-dashboard\">\n Dashboards\n </TabsTrigger>\n <TabsTrigger value=\"parser\" data-testid=\"ai-preview-tab-parser\">\n Parsers\n </TabsTrigger>\n <TabsTrigger value=\"widget\" data-testid=\"ai-preview-tab-widget\">\n Widgets\n </TabsTrigger>\n </TabsList>\n\n <div className=\"max-h-[320px] overflow-y-auto p-3\">\n <TabsContent value=\"datasink\" className=\"space-y-2\">\n {sinkRows.length === 0 ? (\n <EmptyOrLoading loading={sinks.loading} empty=\"No data sinks yet.\" />\n ) : (\n sinkRows.map((sink) => (\n <EntityRow\n key={sink.id}\n id={sink.id}\n title={sink.name ?? sink.key}\n meta={sink.status}\n isNew={sink.id === entityIds.datasink}\n onOpen={() => navigate(`/datasinks/${sink.id}`)}\n />\n ))\n )}\n </TabsContent>\n\n <TabsContent value=\"dashboard\" className=\"space-y-2\">\n {dashboardRows.length === 0 ? (\n <EmptyOrLoading loading={dashboards.loading} empty=\"No dashboards yet.\" />\n ) : (\n dashboardRows.map((dashboard) => (\n <EntityRow\n key={dashboard.id}\n id={dashboard.id}\n title={dashboard.name}\n meta={`${String(dashboard.widgetCount ?? 0)} widget(s)`}\n isNew={dashboard.id === entityIds.dashboard}\n onOpen={() => navigate(`/dashboards/${dashboard.id}`)}\n />\n ))\n )}\n </TabsContent>\n\n <TabsContent value=\"parser\" className=\"space-y-2\">\n {!dashboardId ? (\n <p className=\"px-1 py-6 text-sm text-text-muted\">\n Parsers are listed per dashboard — waiting for the dashboard to be created.\n </p>\n ) : parserRows.length === 0 ? (\n <EmptyOrLoading loading={parsers.loading} empty=\"No parsers on this dashboard yet.\" />\n ) : (\n parserRows.map((parser) => (\n <EntityRow\n key={parser.id}\n id={parser.id}\n title={parser.name}\n meta={`${parser.type} · ${parser.status}`}\n isNew={parser.id === entityIds.parser}\n onOpen={() => navigate(`/parsers/${parser.id}`)}\n />\n ))\n )}\n </TabsContent>\n\n <TabsContent value=\"widget\" className=\"space-y-2\">\n {!dashboardId ? (\n <p className=\"px-1 py-6 text-sm text-text-muted\">\n Widgets live on a dashboard — waiting for the dashboard to be created.\n </p>\n ) : widgetRows.length === 0 ? (\n <EmptyOrLoading loading={widgets.loading} empty=\"No widgets on this dashboard yet.\" />\n ) : (\n widgetRows.map((widget) => (\n <EntityRow\n key={widget.id}\n id={widget.id}\n title={widget.title}\n meta={widget.type}\n // A widget has no link of its own in the narration, so the most\n // recent one on the dashboard being built is the new one.\n isNew={widget.id === widgetRows[widgetRows.length - 1]?.id && steps.widget === 'done'}\n onOpen={() => navigate(`/dashboards/${dashboardId}`)}\n />\n ))\n )}\n </TabsContent>\n </div>\n </Tabs>\n\n {error ? (\n <p\n className=\"border-t border-border-subtle px-3 py-2 text-xs text-status-error-text\"\n data-testid=\"ai-preview-error\"\n >\n {error}\n </p>\n ) : null}\n </section>\n );\n}\n\nexport default AiPreviewPanel;\n"],"mappings":"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"}
1
+ {"version":3,"file":"AiPreviewPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewPanel.tsx"],"sourcesContent":["/**\n * AI Preview — watch the assistant build.\n *\n * Opens by itself the moment a prompt starts, and shows the DataSink → Dashboard\n * → Parser → Widget chain filling in live: a step rail driven by the agent's own\n * narration, and four tabs listing the real entities as they land.\n *\n * Why polling and not a subscription: the four entity types have no working\n * GraphQL subscriptions (DataSink's are declared in the SDL but have no\n * resolvers; the others have none at all). The same 3.5s poll is already used by\n * DashboardViewPage for exactly this reason. Note the real freshness floor is\n * fe-libs' ~15s shared read cache, not this interval.\n */\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { Badge, Button, Tabs, TabsContent, TabsList, TabsTrigger } from '@burdenoff/fe-libs/ui';\nimport { Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, type PreviewStepKey, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useBigConsoleNavigate } from '../../context';\nimport { useDashboardOperations } from '../../hooks/useDashboardOperations';\nimport { useDataSinkOperations } from '../../hooks/useDataSinkOperations';\nimport { useParserOperations } from '../../hooks/useParserOperations';\nimport { useWidgetOperations } from '../../hooks/useWidgetOperations';\nimport { EmptyOrLoading, StepRail, formatAgo, useAssistantSidebarWidth } from './previewShared';\n\n/** Matches DashboardViewPage's live cadence. */\nconst LIVE_POLL_MS = 3500;\n/** Keep polling briefly after the turn ends — the last write can land late. */\nconst TRAILING_POLL_MS = 30_000;\n/** Parsers are listed per console; a console IS a dashboard. */\nconst DEFAULT_CONSOLE_ID = 'default';\n\ninterface RowProps {\n id: string;\n title: string;\n meta?: string;\n isNew: boolean;\n onOpen?: () => void;\n}\n\nfunction EntityRow({ id, title, meta, isNew, onOpen }: RowProps) {\n return (\n <div\n data-testid=\"ai-preview-row\"\n data-entity-id={id}\n className={[\n 'flex items-center justify-between gap-3 rounded-md border px-3 py-2',\n isNew ? 'border-status-success-border bg-status-success-bg/30' : 'border-border-subtle bg-bg-surface',\n ].join(' ')}\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{title}</p>\n {meta ? <p className=\"truncate text-xs text-text-muted\">{meta}</p> : null}\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {isNew ? (\n <Badge className=\"bg-status-success-bg text-status-success-text\">\n <Sparkles className=\"mr-1 size-3\" />\n just built\n </Badge>\n ) : null}\n {onOpen ? (\n <Button size=\"sm\" variant=\"ghost\" onClick={onOpen}>\n Open\n </Button>\n ) : null}\n </div>\n </div>\n );\n}\n\nexport function AiPreviewPanel() {\n const navigate = useBigConsoleNavigate();\n\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const steps = useAssistantRunStore((state) => state.steps);\n const entityIds = useAssistantRunStore((state) => state.entityIds);\n const caption = useAssistantRunStore((state) => state.caption);\n const error = useAssistantRunStore((state) => state.error);\n const updatedAt = useAssistantRunStore((state) => state.updatedAt);\n const openPreview = useAssistantRunStore((state) => state.openPreview);\n const closePreview = useAssistantRunStore((state) => state.closePreview);\n const setRichMounted = useAssistantRunStore((state) => state.setRichMounted);\n\n // Tell the app-shell mini panel to stand down while this full panel is on a\n // /bigconsole page. On unmount (e.g. navigating to /workflows mid-run) this\n // flips false and the mini panel takes over so the build stays visible.\n useEffect(() => {\n setRichMounted(true);\n return () => setRichMounted(false);\n }, [setRichMounted]);\n\n const [tab, setTab] = useState<PreviewStepKey>('datasink');\n const [now, setNow] = useState(() => Date.now());\n\n const dashboardId = entityIds.dashboard;\n\n // The panel does its own fetching, so the tabs are live even before the user\n // opens them — the \"just built\" highlight must be right the instant they look.\n const sinks = useDataSinkOperations({ skipFetch: true });\n const dashboards = useDashboardOperations();\n const parsers = useParserOperations({ consoleId: dashboardId ?? DEFAULT_CONSOLE_ID, skipFetch: true });\n const widgets = useWidgetOperations(undefined, dashboardId, { skipFetch: !dashboardId });\n\n const refetchRef = useRef({\n sinks: sinks.refetch,\n dashboards: dashboards.refetch,\n parsers: parsers.refetch,\n widgets: widgets.refetch,\n });\n refetchRef.current = {\n sinks: sinks.refetch,\n dashboards: dashboards.refetch,\n parsers: parsers.refetch,\n widgets: widgets.refetch,\n };\n\n // Follow the build. Keeps polling for a short while after the turn settles so\n // the final widget does not miss the panel by a second.\n useEffect(() => {\n if (!hasRun) return;\n\n let stopAt = Number.POSITIVE_INFINITY;\n if (!isRunning) stopAt = Date.now() + TRAILING_POLL_MS;\n\n const poll = () => {\n if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return;\n if (Date.now() > stopAt) return;\n void refetchRef.current.sinks();\n void refetchRef.current.dashboards();\n if (dashboardId) {\n void refetchRef.current.parsers();\n void refetchRef.current.widgets();\n }\n setNow(Date.now());\n };\n\n poll();\n const intervalId = window.setInterval(poll, LIVE_POLL_MS);\n const onVisible = () => {\n if (document.visibilityState === 'visible') poll();\n };\n document.addEventListener('visibilitychange', onVisible);\n return () => {\n window.clearInterval(intervalId);\n document.removeEventListener('visibilitychange', onVisible);\n };\n }, [hasRun, isRunning, dashboardId]);\n\n // Jump the user to whatever is being worked on, so they are not left staring\n // at the Data Sinks tab while the parser is the thing actually happening.\n useEffect(() => {\n const active = PREVIEW_STEPS.find((key) => steps[key] === 'running');\n if (active) setTab(active);\n }, [steps]);\n\n const agoSeconds = updatedAt ? Math.max(0, Math.floor((now - updatedAt) / 1000)) : 0;\n\n // Keep clear of the assistant.\n //\n // The assistant sidebar is `position: fixed` — it OVERLAYS the page instead of\n // reflowing it. A panel that spans the full content width therefore runs\n // underneath it, and everything in that overlapped strip (tabs, Open buttons)\n // silently stops taking clicks. Measure the real element rather than hardcoding\n // 560px: the widget is responsive, and it can be closed mid-run.\n const assistantWidth = useAssistantSidebarWidth();\n\n const sinkRows = useMemo(() => sinks.dataSinks.slice(0, 25), [sinks.dataSinks]);\n const dashboardRows = useMemo(() => dashboards.dashboards.slice(0, 25), [dashboards.dashboards]);\n const parserRows = useMemo(() => parsers.parsers.slice(0, 25), [parsers.parsers]);\n const widgetRows = useMemo(() => widgets.widgets.slice(0, 25), [widgets.widgets]);\n\n if (!hasRun) return null;\n\n if (!previewOpen) {\n return (\n <div className=\"flex justify-end px-4 pt-3 md:px-6\" style={{ marginRight: assistantWidth }}>\n <Button size=\"sm\" variant=\"outline\" onClick={openPreview} data-testid=\"ai-preview-open\">\n <Sparkles className=\"mr-1.5 size-4\" />\n Preview\n {isRunning ? <Loader2 className=\"ml-1.5 size-3.5 animate-spin\" /> : null}\n </Button>\n </div>\n );\n }\n\n return (\n <section\n data-testid=\"ai-preview-panel\"\n aria-label=\"AI preview\"\n style={{ marginRight: assistantWidth ? assistantWidth + 16 : undefined }}\n className=\"mx-4 mt-3 overflow-hidden rounded-lg border border-border-default bg-bg-sunken shadow-sm md:mx-6\"\n >\n {/* Header + live strip */}\n <div className=\"flex items-center justify-between gap-3 border-b border-border-subtle bg-bg-surface px-3 py-2\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <Sparkles className=\"size-4 shrink-0 text-action-primary-bg\" />\n <h2 className=\"truncate text-sm font-semibold text-text-primary\">AI Preview</h2>\n {isRunning ? (\n <span\n className=\"inline-flex items-center gap-1.5 rounded-full bg-status-success-bg px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-status-success-text\"\n data-testid=\"ai-preview-live\"\n >\n <span className=\"relative flex size-2\">\n <span className=\"absolute inline-flex size-full animate-ping rounded-full bg-status-success-text opacity-75\" />\n <span className=\"relative inline-flex size-2 rounded-full bg-status-success-text\" />\n </span>\n Live\n </span>\n ) : null}\n <span className=\"truncate text-xs text-text-muted\" data-testid=\"ai-preview-ago\">\n {isRunning ? (caption ? caption : 'Working…') : error ? 'Stopped' : 'Done'} · updated{' '}\n {formatAgo(agoSeconds)}\n </span>\n </div>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={closePreview}\n aria-label=\"Close preview\"\n data-testid=\"ai-preview-close\"\n >\n <X className=\"size-4\" />\n </Button>\n </div>\n\n {/* Step rail */}\n <div className=\"border-b border-border-subtle px-3 py-2\">\n <StepRail steps={steps} />\n </div>\n\n {/* Tabs */}\n <Tabs value={tab} onValueChange={(value) => setTab(value as PreviewStepKey)}>\n <TabsList className=\"mx-3 mt-2\">\n <TabsTrigger value=\"datasink\" data-testid=\"ai-preview-tab-datasink\">\n Data Sinks\n </TabsTrigger>\n <TabsTrigger value=\"dashboard\" data-testid=\"ai-preview-tab-dashboard\">\n Dashboards\n </TabsTrigger>\n <TabsTrigger value=\"parser\" data-testid=\"ai-preview-tab-parser\">\n Parsers\n </TabsTrigger>\n <TabsTrigger value=\"widget\" data-testid=\"ai-preview-tab-widget\">\n Widgets\n </TabsTrigger>\n </TabsList>\n\n <div className=\"max-h-[320px] overflow-y-auto p-3\">\n <TabsContent value=\"datasink\" className=\"space-y-2\">\n {sinkRows.length === 0 ? (\n <EmptyOrLoading loading={sinks.loading} empty=\"No data sinks yet.\" />\n ) : (\n sinkRows.map((sink) => (\n <EntityRow\n key={sink.id}\n id={sink.id}\n title={sink.name ?? sink.key}\n meta={sink.status}\n isNew={sink.id === entityIds.datasink}\n onOpen={() => navigate(`/datasinks/${sink.id}`)}\n />\n ))\n )}\n </TabsContent>\n\n <TabsContent value=\"dashboard\" className=\"space-y-2\">\n {dashboardRows.length === 0 ? (\n <EmptyOrLoading loading={dashboards.loading} empty=\"No dashboards yet.\" />\n ) : (\n dashboardRows.map((dashboard) => (\n <EntityRow\n key={dashboard.id}\n id={dashboard.id}\n title={dashboard.name}\n meta={`${String(dashboard.widgetCount ?? 0)} widget(s)`}\n isNew={dashboard.id === entityIds.dashboard}\n onOpen={() => navigate(`/dashboards/${dashboard.id}`)}\n />\n ))\n )}\n </TabsContent>\n\n <TabsContent value=\"parser\" className=\"space-y-2\">\n {!dashboardId ? (\n <p className=\"px-1 py-6 text-sm text-text-muted\">\n Parsers are listed per dashboard — waiting for the dashboard to be created.\n </p>\n ) : parserRows.length === 0 ? (\n <EmptyOrLoading loading={parsers.loading} empty=\"No parsers on this dashboard yet.\" />\n ) : (\n parserRows.map((parser) => (\n <EntityRow\n key={parser.id}\n id={parser.id}\n title={parser.name}\n meta={`${parser.type} · ${parser.status}`}\n isNew={parser.id === entityIds.parser}\n onOpen={() => navigate(`/parsers/${parser.id}`)}\n />\n ))\n )}\n </TabsContent>\n\n <TabsContent value=\"widget\" className=\"space-y-2\">\n {!dashboardId ? (\n <p className=\"px-1 py-6 text-sm text-text-muted\">\n Widgets live on a dashboard — waiting for the dashboard to be created.\n </p>\n ) : widgetRows.length === 0 ? (\n <EmptyOrLoading loading={widgets.loading} empty=\"No widgets on this dashboard yet.\" />\n ) : (\n widgetRows.map((widget) => (\n <EntityRow\n key={widget.id}\n id={widget.id}\n title={widget.title}\n meta={widget.type}\n // A widget has no link of its own in the narration, so the most\n // recent one on the dashboard being built is the new one.\n isNew={widget.id === widgetRows[widgetRows.length - 1]?.id && steps.widget === 'done'}\n onOpen={() => navigate(`/dashboards/${dashboardId}`)}\n />\n ))\n )}\n </TabsContent>\n </div>\n </Tabs>\n\n {error ? (\n <p\n className=\"border-t border-border-subtle px-3 py-2 text-xs text-status-error-text\"\n data-testid=\"ai-preview-error\"\n >\n {error}\n </p>\n ) : null}\n </section>\n );\n}\n\nexport default AiPreviewPanel;\n"],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import { PREVIEW_STEPS as e, PREVIEW_STEP_LABEL as t } from "../../assistant/assistantRunStore.js";
2
+ import { useEffect as n, useState as r } from "react";
3
+ import { Check as i, ChevronRight as a, Loader2 as o, Minus as s } from "lucide-react";
4
+ import { Spinner as c } from "@burdenoff/fe-libs/ui";
5
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
6
+ //#region src/bigconsole/components/preview/previewShared.tsx
7
+ function d({ status: e }) {
8
+ return e === "done" ? /* @__PURE__ */ l(i, { className: "size-3.5 text-status-success-text" }) : e === "running" ? /* @__PURE__ */ l(o, { className: "size-3.5 animate-spin text-action-primary-bg" }) : e === "skipped" ? /* @__PURE__ */ l(s, { className: "size-3.5 text-text-muted" }) : /* @__PURE__ */ l("span", { className: "size-1.5 rounded-full bg-border-strong" });
9
+ }
10
+ function f({ steps: n }) {
11
+ return /* @__PURE__ */ l("div", {
12
+ className: "flex flex-wrap items-center gap-1",
13
+ "data-testid": "ai-preview-step-rail",
14
+ children: e.map((r, i) => /* @__PURE__ */ u("div", {
15
+ className: "flex items-center gap-1",
16
+ children: [/* @__PURE__ */ u("span", {
17
+ "data-testid": `ai-preview-step-${r}`,
18
+ "data-status": n[r],
19
+ className: ["inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium", n[r] === "done" ? "border-status-success-border bg-status-success-bg text-status-success-text" : n[r] === "running" ? "border-border-strong bg-bg-surface text-text-primary" : "border-border-default text-text-muted"].join(" "),
20
+ children: [
21
+ /* @__PURE__ */ l(d, { status: n[r] }),
22
+ t[r],
23
+ n[r] === "skipped" ? /* @__PURE__ */ l("span", {
24
+ className: "text-[10px]",
25
+ children: "skipped"
26
+ }) : null
27
+ ]
28
+ }), i < e.length - 1 ? /* @__PURE__ */ l(a, { className: "size-3.5 text-text-muted" }) : null]
29
+ }, r))
30
+ });
31
+ }
32
+ function p(e) {
33
+ return e < 5 ? "just now" : e < 60 ? `${String(e)}s ago` : `${String(Math.floor(e / 60))}m ago`;
34
+ }
35
+ function m({ loading: e, empty: t }) {
36
+ return e ? /* @__PURE__ */ u("div", {
37
+ className: "flex items-center gap-2 px-1 py-6 text-sm text-text-muted",
38
+ children: [/* @__PURE__ */ l(c, { className: "size-4" }), "Loading…"]
39
+ }) : /* @__PURE__ */ l("p", {
40
+ className: "px-1 py-6 text-sm text-text-muted",
41
+ children: t
42
+ });
43
+ }
44
+ function h() {
45
+ let [e, t] = r(0);
46
+ return n(() => {
47
+ let e = () => {
48
+ let e = document.querySelector("[data-testid=\"assistant-widget-window\"][data-layout=\"sidebar\"]")?.getBoundingClientRect(), n = e && e.width < window.innerWidth * .75 ? Math.round(e.width) : 0;
49
+ t((e) => e === n ? e : n);
50
+ };
51
+ e();
52
+ let n = window.setInterval(e, 500);
53
+ return window.addEventListener("resize", e), () => {
54
+ window.clearInterval(n), window.removeEventListener("resize", e);
55
+ };
56
+ }, []), e;
57
+ }
58
+ //#endregion
59
+ export { m as EmptyOrLoading, f as StepRail, p as formatAgo, h as useAssistantSidebarWidth };
60
+
61
+ //# sourceMappingURL=previewShared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"previewShared.js","names":[],"sources":["../../../../src/bigconsole/components/preview/previewShared.tsx"],"sourcesContent":["/**\n * Presentational bits shared by the two AI-preview surfaces:\n * - AiPreviewPanel — the full panel with live entity tabs, mounted inside\n * BigConsoleRoot (needs the BigConsole Apollo context).\n * - AiPreviewMiniPanel — the lightweight, store-only panel mounted by the app\n * shell so the build stays visible on ANY page.\n *\n * Nothing here touches Apollo, React Query, or BigConsole context — it is pure\n * store-shape + DOM, which is exactly why the mini panel can live in the app\n * shell without dragging the data layer along.\n */\nimport { useEffect, useState } from 'react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { Check, ChevronRight, Loader2, Minus } from 'lucide-react';\n\nimport {\n PREVIEW_STEP_LABEL,\n PREVIEW_STEPS,\n type PreviewStepKey,\n type PreviewStepStatus,\n} from '../../assistant/assistantRunStore';\n\nexport function StepPip({ status }: { status: PreviewStepStatus }) {\n if (status === 'done') return <Check className=\"size-3.5 text-status-success-text\" />;\n if (status === 'running') return <Loader2 className=\"size-3.5 animate-spin text-action-primary-bg\" />;\n if (status === 'skipped') return <Minus className=\"size-3.5 text-text-muted\" />;\n return <span className=\"size-1.5 rounded-full bg-border-strong\" />;\n}\n\nexport function StepRail({ steps }: { steps: Record<PreviewStepKey, PreviewStepStatus> }) {\n return (\n <div className=\"flex flex-wrap items-center gap-1\" data-testid=\"ai-preview-step-rail\">\n {PREVIEW_STEPS.map((key, index) => (\n <div key={key} className=\"flex items-center gap-1\">\n <span\n data-testid={`ai-preview-step-${key}`}\n data-status={steps[key]}\n className={[\n 'inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium',\n steps[key] === 'done'\n ? 'border-status-success-border bg-status-success-bg text-status-success-text'\n : steps[key] === 'running'\n ? 'border-border-strong bg-bg-surface text-text-primary'\n : 'border-border-default text-text-muted',\n ].join(' ')}\n >\n <StepPip status={steps[key]} />\n {PREVIEW_STEP_LABEL[key]}\n {steps[key] === 'skipped' ? <span className=\"text-[10px]\">skipped</span> : null}\n </span>\n {index < PREVIEW_STEPS.length - 1 ? <ChevronRight className=\"size-3.5 text-text-muted\" /> : null}\n </div>\n ))}\n </div>\n );\n}\n\nexport function formatAgo(seconds: number): string {\n if (seconds < 5) return 'just now';\n if (seconds < 60) return `${String(seconds)}s ago`;\n return `${String(Math.floor(seconds / 60))}m ago`;\n}\n\nexport function EmptyOrLoading({ loading, empty }: { loading: boolean; empty: string }) {\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 px-1 py-6 text-sm text-text-muted\">\n <Spinner className=\"size-4\" />\n Loading…\n </div>\n );\n }\n return <p className=\"px-1 py-6 text-sm text-text-muted\">{empty}</p>;\n}\n\n/**\n * Width of the assistant sidebar, or 0 when it is closed.\n *\n * Polled rather than subscribed: the widget lives in fe-libs, is mounted by the\n * app shell, and emits nothing when it opens or closes. Reading the DOM is the\n * only honest signal available, and it is cheap.\n */\nexport function useAssistantSidebarWidth(): number {\n const [width, setWidth] = useState(0);\n\n useEffect(() => {\n const measure = () => {\n const el = document.querySelector('[data-testid=\"assistant-widget-window\"][data-layout=\"sidebar\"]');\n const rect = el?.getBoundingClientRect();\n // Below `sm` the sidebar is full-width; reserving that would leave no room\n // for the preview at all, so on small screens let it stack underneath.\n const next = rect && rect.width < window.innerWidth * 0.75 ? Math.round(rect.width) : 0;\n setWidth((current) => (current === next ? current : next));\n };\n measure();\n const intervalId = window.setInterval(measure, 500);\n window.addEventListener('resize', measure);\n return () => {\n window.clearInterval(intervalId);\n window.removeEventListener('resize', measure);\n };\n }, []);\n\n return width;\n}\n"],"mappings":";;;;;;AAsBA,SAAgB,EAAQ,EAAE,aAAyC;AAIjE,QAHI,MAAW,SAAe,kBAAC,GAAD,EAAO,WAAU,qCAAsC,CAAA,GACjF,MAAW,YAAkB,kBAAC,GAAD,EAAS,WAAU,gDAAiD,CAAA,GACjG,MAAW,YAAkB,kBAAC,GAAD,EAAO,WAAU,4BAA6B,CAAA,GACxE,kBAAC,QAAD,EAAM,WAAU,0CAA2C,CAAA;;AAGpE,SAAgB,EAAS,EAAE,YAA+D;AACxF,QACE,kBAAC,OAAD;EAAK,WAAU;EAAoC,eAAY;YAC5D,EAAc,KAAK,GAAK,MACvB,kBAAC,OAAD;GAAe,WAAU;aAAzB,CACE,kBAAC,QAAD;IACE,eAAa,mBAAmB;IAChC,eAAa,EAAM;IACnB,WAAW,CACT,wFACA,EAAM,OAAS,SACX,+EACA,EAAM,OAAS,YACb,yDACA,wCACP,CAAC,KAAK,IAAI;cAVb;KAYE,kBAAC,GAAD,EAAS,QAAQ,EAAM,IAAQ,CAAA;KAC9B,EAAmB;KACnB,EAAM,OAAS,YAAY,kBAAC,QAAD;MAAM,WAAU;gBAAc;MAAc,CAAA,GAAG;KACtE;OACN,IAAQ,EAAc,SAAS,IAAI,kBAAC,GAAD,EAAc,WAAU,4BAA6B,CAAA,GAAG,KACxF;KAlBI,EAkBJ,CACN;EACE,CAAA;;AAIV,SAAgB,EAAU,GAAyB;AAGjD,QAFI,IAAU,IAAU,aACpB,IAAU,KAAW,GAAG,OAAO,EAAQ,CAAC,SACrC,GAAG,OAAO,KAAK,MAAM,IAAU,GAAG,CAAC,CAAC;;AAG7C,SAAgB,EAAe,EAAE,YAAS,YAA8C;AAStF,QARI,IAEA,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAS,WAAU,UAAW,CAAA,EAAA,WAE1B;MAGH,kBAAC,KAAD;EAAG,WAAU;YAAqC;EAAU,CAAA;;AAUrE,SAAgB,IAAmC;CACjD,IAAM,CAAC,GAAO,KAAY,EAAS,EAAE;AAoBrC,QAlBA,QAAgB;EACd,IAAM,UAAgB;GAEpB,IAAM,IADK,SAAS,cAAc,qEAAiE,EAClF,uBAAuB,EAGlC,IAAO,KAAQ,EAAK,QAAQ,OAAO,aAAa,MAAO,KAAK,MAAM,EAAK,MAAM,GAAG;AACtF,MAAU,MAAa,MAAY,IAAO,IAAU,EAAM;;AAE5D,KAAS;EACT,IAAM,IAAa,OAAO,YAAY,GAAS,IAAI;AAEnD,SADA,OAAO,iBAAiB,UAAU,EAAQ,QAC7B;AAEX,GADA,OAAO,cAAc,EAAW,EAChC,OAAO,oBAAoB,UAAU,EAAQ;;IAE9C,EAAE,CAAC,EAEC"}