@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.
- package/dist/bigconsole/BigConsoleRoot.js +100 -90
- package/dist/bigconsole/BigConsoleRoot.js.map +1 -1
- package/dist/bigconsole/assistant/assistantRunStore.js +3 -1
- package/dist/bigconsole/assistant/assistantRunStore.js.map +1 -1
- package/dist/bigconsole/assistant/index.js +2 -1
- package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js +70 -60
- package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js.map +1 -1
- package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js +187 -175
- package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js.map +1 -1
- package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js +133 -0
- package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js.map +1 -0
- package/dist/bigconsole/components/preview/AiPreviewPanel.js +88 -136
- package/dist/bigconsole/components/preview/AiPreviewPanel.js.map +1 -1
- package/dist/bigconsole/components/preview/previewShared.js +61 -0
- package/dist/bigconsole/components/preview/previewShared.js.map +1 -0
- package/dist/bigconsole/hooks/useDashboardOperations.js +72 -67
- package/dist/bigconsole/hooks/useDashboardOperations.js.map +1 -1
- package/dist/bigconsole/hooks/useDashboardTemplates.js +108 -105
- package/dist/bigconsole/hooks/useDashboardTemplates.js.map +1 -1
- package/dist/bigconsole/hooks/useDashboardViews.js +77 -74
- package/dist/bigconsole/hooks/useDashboardViews.js.map +1 -1
- package/dist/bigconsole/hooks/useImportDashboard.js +68 -64
- package/dist/bigconsole/hooks/useImportDashboard.js.map +1 -1
- package/dist/bigconsole/pages/DashboardsPage.js +240 -234
- package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
- package/dist/bigconsole/pages/InsightsPage.js +161 -143
- package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
- package/dist/bigconsole/providers/BigConsoleQuotaProvider.js +70 -0
- package/dist/bigconsole/providers/BigConsoleQuotaProvider.js.map +1 -0
- package/dist/bigconsole/utils/quota.js +86 -0
- package/dist/bigconsole/utils/quota.js.map +1 -0
- 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,
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
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 {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
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
|
|
14
|
-
function
|
|
15
|
-
return
|
|
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__ */
|
|
20
|
+
children: [/* @__PURE__ */ S("div", {
|
|
48
21
|
className: "min-w-0",
|
|
49
|
-
children: [/* @__PURE__ */
|
|
22
|
+
children: [/* @__PURE__ */ x("p", {
|
|
50
23
|
className: "truncate text-sm font-medium text-text-primary",
|
|
51
24
|
children: t
|
|
52
|
-
}), n ? /* @__PURE__ */
|
|
25
|
+
}), n ? /* @__PURE__ */ x("p", {
|
|
53
26
|
className: "truncate text-xs text-text-muted",
|
|
54
27
|
children: n
|
|
55
28
|
}) : null]
|
|
56
|
-
}), /* @__PURE__ */
|
|
29
|
+
}), /* @__PURE__ */ S("div", {
|
|
57
30
|
className: "flex shrink-0 items-center gap-2",
|
|
58
|
-
children: [r ? /* @__PURE__ */
|
|
31
|
+
children: [r ? /* @__PURE__ */ S(g, {
|
|
59
32
|
className: "bg-status-success-bg text-status-success-text",
|
|
60
|
-
children: [/* @__PURE__ */
|
|
61
|
-
}) : null, i ? /* @__PURE__ */
|
|
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
|
|
71
|
-
let
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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 =
|
|
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
|
-
},
|
|
109
|
-
if (!
|
|
60
|
+
}, l(() => {
|
|
61
|
+
if (!k) return;
|
|
110
62
|
let e = Infinity;
|
|
111
|
-
|
|
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,
|
|
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
|
-
|
|
124
|
-
|
|
75
|
+
k,
|
|
76
|
+
D,
|
|
125
77
|
H
|
|
126
|
-
]),
|
|
127
|
-
let e = t.find((e) =>
|
|
78
|
+
]), l(() => {
|
|
79
|
+
let e = t.find((e) => A[e] === "running");
|
|
128
80
|
e && z(e);
|
|
129
|
-
}, [
|
|
130
|
-
let J =
|
|
131
|
-
return
|
|
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__ */
|
|
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__ */
|
|
91
|
+
children: [/* @__PURE__ */ S("div", {
|
|
140
92
|
className: "flex min-w-0 items-center gap-2",
|
|
141
93
|
children: [
|
|
142
|
-
/* @__PURE__ */
|
|
143
|
-
/* @__PURE__ */
|
|
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
|
-
|
|
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__ */
|
|
102
|
+
children: [/* @__PURE__ */ S("span", {
|
|
151
103
|
className: "relative flex size-2",
|
|
152
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
107
|
+
/* @__PURE__ */ S("span", {
|
|
156
108
|
className: "truncate text-xs text-text-muted",
|
|
157
109
|
"data-testid": "ai-preview-ago",
|
|
158
110
|
children: [
|
|
159
|
-
|
|
111
|
+
D ? M || "Working…" : N ? "Stopped" : "Done",
|
|
160
112
|
" · updated",
|
|
161
113
|
" ",
|
|
162
|
-
|
|
114
|
+
c(J)
|
|
163
115
|
]
|
|
164
116
|
})
|
|
165
117
|
]
|
|
166
|
-
}), /* @__PURE__ */
|
|
118
|
+
}), /* @__PURE__ */ x(_, {
|
|
167
119
|
size: "sm",
|
|
168
120
|
variant: "ghost",
|
|
169
|
-
onClick:
|
|
121
|
+
onClick: I,
|
|
170
122
|
"aria-label": "Close preview",
|
|
171
123
|
"data-testid": "ai-preview-close",
|
|
172
|
-
children: /* @__PURE__ */
|
|
124
|
+
children: /* @__PURE__ */ x(h, { className: "size-4" })
|
|
173
125
|
})]
|
|
174
126
|
}),
|
|
175
|
-
/* @__PURE__ */
|
|
127
|
+
/* @__PURE__ */ x("div", {
|
|
176
128
|
className: "border-b border-border-subtle px-3 py-2",
|
|
177
|
-
children: /* @__PURE__ */
|
|
129
|
+
children: /* @__PURE__ */ x(ee, { steps: A })
|
|
178
130
|
}),
|
|
179
|
-
/* @__PURE__ */
|
|
131
|
+
/* @__PURE__ */ S(ne, {
|
|
180
132
|
value: R,
|
|
181
133
|
onValueChange: (e) => z(e),
|
|
182
|
-
children: [/* @__PURE__ */
|
|
134
|
+
children: [/* @__PURE__ */ S(y, {
|
|
183
135
|
className: "mx-3 mt-2",
|
|
184
136
|
children: [
|
|
185
|
-
/* @__PURE__ */
|
|
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__ */
|
|
142
|
+
/* @__PURE__ */ x(b, {
|
|
191
143
|
value: "dashboard",
|
|
192
144
|
"data-testid": "ai-preview-tab-dashboard",
|
|
193
145
|
children: "Dashboards"
|
|
194
146
|
}),
|
|
195
|
-
/* @__PURE__ */
|
|
147
|
+
/* @__PURE__ */ x(b, {
|
|
196
148
|
value: "parser",
|
|
197
149
|
"data-testid": "ai-preview-tab-parser",
|
|
198
150
|
children: "Parsers"
|
|
199
151
|
}),
|
|
200
|
-
/* @__PURE__ */
|
|
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__ */
|
|
158
|
+
}), /* @__PURE__ */ S("div", {
|
|
207
159
|
className: "max-h-[320px] overflow-y-auto p-3",
|
|
208
160
|
children: [
|
|
209
|
-
/* @__PURE__ */
|
|
161
|
+
/* @__PURE__ */ x(v, {
|
|
210
162
|
value: "datasink",
|
|
211
163
|
className: "space-y-2",
|
|
212
|
-
children: X.length === 0 ? /* @__PURE__ */
|
|
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__ */
|
|
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 ===
|
|
220
|
-
onOpen: () =>
|
|
171
|
+
isNew: e.id === j.datasink,
|
|
172
|
+
onOpen: () => g(`/datasinks/${e.id}`)
|
|
221
173
|
}, e.id))
|
|
222
174
|
}),
|
|
223
|
-
/* @__PURE__ */
|
|
175
|
+
/* @__PURE__ */ x(v, {
|
|
224
176
|
value: "dashboard",
|
|
225
177
|
className: "space-y-2",
|
|
226
|
-
children: Z.length === 0 ? /* @__PURE__ */
|
|
178
|
+
children: Z.length === 0 ? /* @__PURE__ */ x(s, {
|
|
227
179
|
loading: W.loading,
|
|
228
180
|
empty: "No dashboards yet."
|
|
229
|
-
}) : Z.map((e) => /* @__PURE__ */
|
|
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 ===
|
|
234
|
-
onOpen: () =>
|
|
185
|
+
isNew: e.id === j.dashboard,
|
|
186
|
+
onOpen: () => g(`/dashboards/${e.id}`)
|
|
235
187
|
}, e.id))
|
|
236
188
|
}),
|
|
237
|
-
/* @__PURE__ */
|
|
189
|
+
/* @__PURE__ */ x(v, {
|
|
238
190
|
value: "parser",
|
|
239
191
|
className: "space-y-2",
|
|
240
|
-
children: H ? Q.length === 0 ? /* @__PURE__ */
|
|
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__ */
|
|
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 ===
|
|
248
|
-
onOpen: () =>
|
|
249
|
-
}, e.id)) : /* @__PURE__ */
|
|
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__ */
|
|
206
|
+
/* @__PURE__ */ x(v, {
|
|
255
207
|
value: "widget",
|
|
256
208
|
className: "space-y-2",
|
|
257
|
-
children: H ? $.length === 0 ? /* @__PURE__ */
|
|
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__ */
|
|
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 &&
|
|
265
|
-
onOpen: () =>
|
|
266
|
-
}, e.id)) : /* @__PURE__ */
|
|
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
|
-
|
|
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:
|
|
229
|
+
children: N
|
|
278
230
|
}) : null
|
|
279
231
|
]
|
|
280
|
-
}) : /* @__PURE__ */
|
|
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__ */
|
|
235
|
+
children: /* @__PURE__ */ S(_, {
|
|
284
236
|
size: "sm",
|
|
285
237
|
variant: "outline",
|
|
286
|
-
onClick:
|
|
238
|
+
onClick: F,
|
|
287
239
|
"data-testid": "ai-preview-open",
|
|
288
240
|
children: [
|
|
289
|
-
/* @__PURE__ */
|
|
241
|
+
/* @__PURE__ */ x(m, { className: "mr-1.5 size-4" }),
|
|
290
242
|
"Preview",
|
|
291
|
-
|
|
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 {
|
|
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":";;;;;;;;;;;;AA+BA,IAAM,IAAe,MAEf,IAAmB,KAEnB,IAAqB;AAE3B,SAAS,EAAQ,EAAE,aAAyC;AAI1D,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,SAAS,EAAS,EAAE,YAA+D;AACjF,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,SAAS,EAAU,GAAyB;AAG1C,QAFI,IAAU,IAAU,aACpB,IAAU,KAAW,GAAG,OAAO,EAAQ,CAAC,SACrC,GAAG,OAAO,KAAK,MAAM,IAAU,GAAG,CAAC,CAAC;;AAW7C,SAAS,EAAU,EAAE,OAAI,UAAO,SAAM,UAAO,aAAoB;AAC/D,QACE,kBAAC,OAAD;EACE,eAAY;EACZ,kBAAgB;EAChB,WAAW,CACT,uEACA,IAAQ,yDAAyD,qCAClE,CAAC,KAAK,IAAI;YANb,CAQE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAAkD;IAAU,CAAA,EACxE,IAAO,kBAAC,KAAD;IAAG,WAAU;cAAoC;IAAS,CAAA,GAAG,KACjE;MACN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,IACC,kBAAC,GAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,EAAA,aAE9B;QACN,MACH,IACC,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAQ;IAAQ,SAAS;cAAQ;IAE1C,CAAA,GACP,KACA;KACF;;;AAWV,SAAS,IAAmC;CAC1C,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;;AAGT,SAAS,EAAe,EAAE,YAAS,YAA8C;AAS/E,QARI,IAEA,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAS,WAAU,UAAW,CAAA,EAAA,WAE1B;MAGH,kBAAC,KAAD;EAAG,WAAU;YAAqC;EAAU,CAAA;;AAGrE,SAAgB,IAAiB;CAC/B,IAAM,IAAW,GAAuB,EAElC,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAc,GAAsB,MAAU,EAAM,YAAY,EAChE,IAAS,GAAsB,MAAU,EAAM,OAAO,EACtD,IAAQ,GAAsB,MAAU,EAAM,MAAM,EACpD,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,IAAU,GAAsB,MAAU,EAAM,QAAQ,EACxD,IAAQ,GAAsB,MAAU,EAAM,MAAM,EACpD,IAAY,GAAsB,MAAU,EAAM,UAAU,EAC5D,KAAc,GAAsB,MAAU,EAAM,YAAY,EAChE,IAAe,GAAsB,MAAU,EAAM,aAAa,EAElE,CAAC,GAAK,KAAU,EAAyB,WAAW,EACpD,CAAC,GAAK,KAAU,QAAe,KAAK,KAAK,CAAC,EAE1C,IAAc,EAAU,WAIxB,IAAQ,EAAsB,EAAE,WAAW,IAAM,CAAC,EAClD,IAAa,GAAwB,EACrC,IAAU,GAAoB;EAAE,WAAW,KAAe;EAAoB,WAAW;EAAM,CAAC,EAChG,IAAU,EAAoB,KAAA,GAAW,GAAa,EAAE,WAAW,CAAC,GAAa,CAAC,EAElF,IAAa,EAAO;EACxB,OAAO,EAAM;EACb,YAAY,EAAW;EACvB,SAAS,EAAQ;EACjB,SAAS,EAAQ;EAClB,CAAC;AA0CF,CAzCA,EAAW,UAAU;EACnB,OAAO,EAAM;EACb,YAAY,EAAW;EACvB,SAAS,EAAQ;EACjB,SAAS,EAAQ;EAClB,EAID,QAAgB;AACd,MAAI,CAAC,EAAQ;EAEb,IAAI,IAAS;AACb,EAAK,MAAW,IAAS,KAAK,KAAK,GAAG;EAEtC,IAAM,UAAa;AACb,UAAO,WAAa,OAAe,SAAS,oBAAoB,aAChE,KAAK,KAAK,GAAG,MACZ,EAAW,QAAQ,OAAO,EAC1B,EAAW,QAAQ,YAAY,EAChC,MACG,EAAW,QAAQ,SAAS,EAC5B,EAAW,QAAQ,SAAS,GAEnC,EAAO,KAAK,KAAK,CAAC;;AAGpB,KAAM;EACN,IAAM,IAAa,OAAO,YAAY,GAAM,EAAa,EACnD,UAAkB;AACtB,GAAI,SAAS,oBAAoB,aAAW,GAAM;;AAGpD,SADA,SAAS,iBAAiB,oBAAoB,EAAU,QAC3C;AAEX,GADA,OAAO,cAAc,EAAW,EAChC,SAAS,oBAAoB,oBAAoB,EAAU;;IAE5D;EAAC;EAAQ;EAAW;EAAY,CAAC,EAIpC,QAAgB;EACd,IAAM,IAAS,EAAc,MAAM,MAAQ,EAAM,OAAS,UAAU;AACpE,EAAI,KAAQ,EAAO,EAAO;IACzB,CAAC,EAAM,CAAC;CAEX,IAAM,IAAa,IAAY,KAAK,IAAI,GAAG,KAAK,OAAO,IAAM,KAAa,IAAK,CAAC,GAAG,GAS7E,IAAiB,GAA0B,EAE3C,IAAW,QAAc,EAAM,UAAU,MAAM,GAAG,GAAG,EAAE,CAAC,EAAM,UAAU,CAAC,EACzE,IAAgB,QAAc,EAAW,WAAW,MAAM,GAAG,GAAG,EAAE,CAAC,EAAW,WAAW,CAAC,EAC1F,IAAa,QAAc,EAAQ,QAAQ,MAAM,GAAG,GAAG,EAAE,CAAC,EAAQ,QAAQ,CAAC,EAC3E,IAAa,QAAc,EAAQ,QAAQ,MAAM,GAAG,GAAG,EAAE,CAAC,EAAQ,QAAQ,CAAC;AAgBjF,QAdK,IAEA,IAaH,kBAAC,WAAD;EACE,eAAY;EACZ,cAAW;EACX,OAAO,EAAE,aAAa,IAAiB,IAAiB,KAAK,KAAA,GAAW;EACxE,WAAU;YAJZ;GAOE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAU,WAAU,0CAA2C,CAAA;MAC/D,kBAAC,MAAD;OAAI,WAAU;iBAAmD;OAAe,CAAA;MAC/E,IACC,kBAAC,QAAD;OACE,WAAU;OACV,eAAY;iBAFd,CAIE,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,QAAD,EAAM,WAAU,8FAA+F,CAAA,EAC/G,kBAAC,QAAD,EAAM,WAAU,mEAAoE,CAAA,CAC/E;kBAEF;WACL;MACJ,kBAAC,QAAD;OAAM,WAAU;OAAmC,eAAY;iBAA/D;QACG,IAAa,KAAoB,aAAc,IAAQ,YAAY;QAAO;QAAW;QACrF,EAAU,EAAW;QACjB;;MACH;QACN,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,SAAS;KACT,cAAW;KACX,eAAY;eAEZ,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;KACjB,CAAA,CACL;;GAGN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAiB,UAAS,CAAA;IACtB,CAAA;GAGN,kBAAC,GAAD;IAAM,OAAO;IAAK,gBAAgB,MAAU,EAAO,EAAwB;cAA3E,CACE,kBAAC,GAAD;KAAU,WAAU;eAApB;MACE,kBAAC,GAAD;OAAa,OAAM;OAAW,eAAY;iBAA0B;OAEtD,CAAA;MACd,kBAAC,GAAD;OAAa,OAAM;OAAY,eAAY;iBAA2B;OAExD,CAAA;MACd,kBAAC,GAAD;OAAa,OAAM;OAAS,eAAY;iBAAwB;OAElD,CAAA;MACd,kBAAC,GAAD;OAAa,OAAM;OAAS,eAAY;iBAAwB;OAElD,CAAA;MACL;QAEX,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAa,OAAM;OAAW,WAAU;iBACrC,EAAS,WAAW,IACnB,kBAAC,GAAD;QAAgB,SAAS,EAAM;QAAS,OAAM;QAAuB,CAAA,GAErE,EAAS,KAAK,MACZ,kBAAC,GAAD;QAEE,IAAI,EAAK;QACT,OAAO,EAAK,QAAQ,EAAK;QACzB,MAAM,EAAK;QACX,OAAO,EAAK,OAAO,EAAU;QAC7B,cAAc,EAAS,cAAc,EAAK,KAAK;QAC/C,EANK,EAAK,GAMV,CACF;OAEQ,CAAA;MAEd,kBAAC,GAAD;OAAa,OAAM;OAAY,WAAU;iBACtC,EAAc,WAAW,IACxB,kBAAC,GAAD;QAAgB,SAAS,EAAW;QAAS,OAAM;QAAuB,CAAA,GAE1E,EAAc,KAAK,MACjB,kBAAC,GAAD;QAEE,IAAI,EAAU;QACd,OAAO,EAAU;QACjB,MAAM,GAAG,OAAO,EAAU,eAAe,EAAE,CAAC;QAC5C,OAAO,EAAU,OAAO,EAAU;QAClC,cAAc,EAAS,eAAe,EAAU,KAAK;QACrD,EANK,EAAU,GAMf,CACF;OAEQ,CAAA;MAEd,kBAAC,GAAD;OAAa,OAAM;OAAS,WAAU;iBAClC,IAIE,EAAW,WAAW,IACxB,kBAAC,GAAD;QAAgB,SAAS,EAAQ;QAAS,OAAM;QAAsC,CAAA,GAEtF,EAAW,KAAK,MACd,kBAAC,GAAD;QAEE,IAAI,EAAO;QACX,OAAO,EAAO;QACd,MAAM,GAAG,EAAO,KAAK,KAAK,EAAO;QACjC,OAAO,EAAO,OAAO,EAAU;QAC/B,cAAc,EAAS,YAAY,EAAO,KAAK;QAC/C,EANK,EAAO,GAMZ,CACF,GAfF,kBAAC,KAAD;QAAG,WAAU;kBAAoC;QAE7C,CAAA;OAeM,CAAA;MAEd,kBAAC,GAAD;OAAa,OAAM;OAAS,WAAU;iBAClC,IAIE,EAAW,WAAW,IACxB,kBAAC,GAAD;QAAgB,SAAS,EAAQ;QAAS,OAAM;QAAsC,CAAA,GAEtF,EAAW,KAAK,MACd,kBAAC,GAAD;QAEE,IAAI,EAAO;QACX,OAAO,EAAO;QACd,MAAM,EAAO;QAGb,OAAO,EAAO,OAAO,EAAW,EAAW,SAAS,IAAI,MAAM,EAAM,WAAW;QAC/E,cAAc,EAAS,eAAe,IAAc;QACpD,EARK,EAAO,GAQZ,CACF,GAjBF,kBAAC,KAAD;QAAG,WAAU;kBAAoC;QAE7C,CAAA;OAiBM,CAAA;MACV;OACD;;GAEN,IACC,kBAAC,KAAD;IACE,WAAU;IACV,eAAY;cAEX;IACC,CAAA,GACF;GACI;MAjKR,kBAAC,OAAD;EAAK,WAAU;EAAqC,OAAO,EAAE,aAAa,GAAgB;YACxF,kBAAC,GAAD;GAAQ,MAAK;GAAK,SAAQ;GAAU,SAAS;GAAa,eAAY;aAAtE;IACE,kBAAC,GAAD,EAAU,WAAU,iBAAkB,CAAA;;IAErC,IAAY,kBAAC,GAAD,EAAS,WAAU,gCAAiC,CAAA,GAAG;IAC7D;;EACL,CAAA,GAVU"}
|
|
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"}
|