@burdenoff/microfe-bigconsole 2026.714.3 → 2026.714.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -3,62 +3,62 @@ import "../../context/index.js";
|
|
|
3
3
|
import { PREVIEW_STEPS as t, PREVIEW_STEP_LABEL as n, useAssistantRunStore as r } from "../../assistant/assistantRunStore.js";
|
|
4
4
|
import i from "../../hooks/useDashboardOperations.js";
|
|
5
5
|
import a from "../../hooks/useDataSinkOperations.js";
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import { useEffect as
|
|
9
|
-
import { Check as
|
|
10
|
-
import { Badge as
|
|
11
|
-
import { jsx as
|
|
6
|
+
import ee from "../../hooks/useParserOperations.js";
|
|
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";
|
|
12
12
|
//#region src/bigconsole/components/preview/AiPreviewPanel.tsx
|
|
13
|
-
var
|
|
14
|
-
function
|
|
15
|
-
return e === "done" ? /* @__PURE__ */
|
|
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
16
|
}
|
|
17
|
-
function
|
|
18
|
-
return /* @__PURE__ */
|
|
17
|
+
function A({ steps: e }) {
|
|
18
|
+
return /* @__PURE__ */ w("div", {
|
|
19
19
|
className: "flex flex-wrap items-center gap-1",
|
|
20
20
|
"data-testid": "ai-preview-step-rail",
|
|
21
|
-
children: t.map((r, i) => /* @__PURE__ */
|
|
21
|
+
children: t.map((r, i) => /* @__PURE__ */ T("div", {
|
|
22
22
|
className: "flex items-center gap-1",
|
|
23
|
-
children: [/* @__PURE__ */
|
|
23
|
+
children: [/* @__PURE__ */ T("span", {
|
|
24
24
|
"data-testid": `ai-preview-step-${r}`,
|
|
25
25
|
"data-status": e[r],
|
|
26
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
27
|
children: [
|
|
28
|
-
/* @__PURE__ */
|
|
28
|
+
/* @__PURE__ */ w(k, { status: e[r] }),
|
|
29
29
|
n[r],
|
|
30
|
-
e[r] === "skipped" ? /* @__PURE__ */
|
|
30
|
+
e[r] === "skipped" ? /* @__PURE__ */ w("span", {
|
|
31
31
|
className: "text-[10px]",
|
|
32
32
|
children: "skipped"
|
|
33
33
|
}) : null
|
|
34
34
|
]
|
|
35
|
-
}), i < t.length - 1 ? /* @__PURE__ */
|
|
35
|
+
}), i < t.length - 1 ? /* @__PURE__ */ w(f, { className: "size-3.5 text-text-muted" }) : null]
|
|
36
36
|
}, r))
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
|
-
function
|
|
39
|
+
function j(e) {
|
|
40
40
|
return e < 5 ? "just now" : e < 60 ? `${String(e)}s ago` : `${String(Math.floor(e / 60))}m ago`;
|
|
41
41
|
}
|
|
42
|
-
function
|
|
43
|
-
return /* @__PURE__ */
|
|
42
|
+
function M({ id: e, title: t, meta: n, isNew: r, onOpen: i }) {
|
|
43
|
+
return /* @__PURE__ */ T("div", {
|
|
44
44
|
"data-testid": "ai-preview-row",
|
|
45
45
|
"data-entity-id": e,
|
|
46
46
|
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__ */
|
|
47
|
+
children: [/* @__PURE__ */ T("div", {
|
|
48
48
|
className: "min-w-0",
|
|
49
|
-
children: [/* @__PURE__ */
|
|
49
|
+
children: [/* @__PURE__ */ w("p", {
|
|
50
50
|
className: "truncate text-sm font-medium text-text-primary",
|
|
51
51
|
children: t
|
|
52
|
-
}), n ? /* @__PURE__ */
|
|
52
|
+
}), n ? /* @__PURE__ */ w("p", {
|
|
53
53
|
className: "truncate text-xs text-text-muted",
|
|
54
54
|
children: n
|
|
55
55
|
}) : null]
|
|
56
|
-
}), /* @__PURE__ */
|
|
56
|
+
}), /* @__PURE__ */ T("div", {
|
|
57
57
|
className: "flex shrink-0 items-center gap-2",
|
|
58
|
-
children: [r ? /* @__PURE__ */
|
|
58
|
+
children: [r ? /* @__PURE__ */ T(_, {
|
|
59
59
|
className: "bg-status-success-bg text-status-success-text",
|
|
60
|
-
children: [/* @__PURE__ */
|
|
61
|
-
}) : null, i ? /* @__PURE__ */
|
|
60
|
+
children: [/* @__PURE__ */ w(h, { className: "mr-1 size-3" }), "just built"]
|
|
61
|
+
}) : null, i ? /* @__PURE__ */ w(v, {
|
|
62
62
|
size: "sm",
|
|
63
63
|
variant: "ghost",
|
|
64
64
|
onClick: i,
|
|
@@ -67,188 +67,203 @@ function N({ id: e, title: t, meta: n, isNew: r, onOpen: i }) {
|
|
|
67
67
|
})]
|
|
68
68
|
});
|
|
69
69
|
}
|
|
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
|
+
}
|
|
70
84
|
function P({ loading: e, empty: t }) {
|
|
71
|
-
return e ? /* @__PURE__ */
|
|
85
|
+
return e ? /* @__PURE__ */ T("div", {
|
|
72
86
|
className: "flex items-center gap-2 px-1 py-6 text-sm text-text-muted",
|
|
73
|
-
children: [/* @__PURE__ */
|
|
74
|
-
}) : /* @__PURE__ */
|
|
87
|
+
children: [/* @__PURE__ */ w(y, { className: "size-4" }), "Loading…"]
|
|
88
|
+
}) : /* @__PURE__ */ w("p", {
|
|
75
89
|
className: "px-1 py-6 text-sm text-text-muted",
|
|
76
90
|
children: t
|
|
77
91
|
});
|
|
78
92
|
}
|
|
79
93
|
function F() {
|
|
80
|
-
let n = e(),
|
|
81
|
-
consoleId:
|
|
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,
|
|
82
96
|
skipFetch: !0
|
|
83
|
-
}),
|
|
84
|
-
sinks:
|
|
85
|
-
dashboards:
|
|
86
|
-
parsers:
|
|
87
|
-
widgets:
|
|
97
|
+
}), K = o(void 0, H, { skipFetch: !H }), q = l({
|
|
98
|
+
sinks: U.refetch,
|
|
99
|
+
dashboards: W.refetch,
|
|
100
|
+
parsers: G.refetch,
|
|
101
|
+
widgets: K.refetch
|
|
88
102
|
});
|
|
89
|
-
|
|
90
|
-
sinks:
|
|
91
|
-
dashboards:
|
|
92
|
-
parsers:
|
|
93
|
-
widgets:
|
|
94
|
-
},
|
|
95
|
-
if (!
|
|
103
|
+
q.current = {
|
|
104
|
+
sinks: U.refetch,
|
|
105
|
+
dashboards: W.refetch,
|
|
106
|
+
parsers: G.refetch,
|
|
107
|
+
widgets: K.refetch
|
|
108
|
+
}, s(() => {
|
|
109
|
+
if (!m) return;
|
|
96
110
|
let e = Infinity;
|
|
97
|
-
|
|
111
|
+
d || (e = Date.now() + D);
|
|
98
112
|
let t = () => {
|
|
99
|
-
typeof document < "u" && document.visibilityState !== "visible" || Date.now() > e || (
|
|
113
|
+
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()));
|
|
100
114
|
};
|
|
101
115
|
t();
|
|
102
|
-
let n = window.setInterval(t,
|
|
116
|
+
let n = window.setInterval(t, E), r = () => {
|
|
103
117
|
document.visibilityState === "visible" && t();
|
|
104
118
|
};
|
|
105
119
|
return document.addEventListener("visibilitychange", r), () => {
|
|
106
120
|
window.clearInterval(n), document.removeEventListener("visibilitychange", r);
|
|
107
121
|
};
|
|
108
122
|
}, [
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
]),
|
|
113
|
-
let e = t.find((e) =>
|
|
114
|
-
e &&
|
|
115
|
-
}, [
|
|
116
|
-
let
|
|
117
|
-
return
|
|
123
|
+
m,
|
|
124
|
+
d,
|
|
125
|
+
H
|
|
126
|
+
]), s(() => {
|
|
127
|
+
let e = t.find((e) => _[e] === "running");
|
|
128
|
+
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", {
|
|
118
132
|
"data-testid": "ai-preview-panel",
|
|
119
133
|
"aria-label": "AI preview",
|
|
134
|
+
style: { marginRight: Y ? Y + 16 : void 0 },
|
|
120
135
|
className: "mx-4 mt-3 overflow-hidden rounded-lg border border-border-default bg-bg-sunken shadow-sm md:mx-6",
|
|
121
136
|
children: [
|
|
122
|
-
/* @__PURE__ */
|
|
137
|
+
/* @__PURE__ */ T("div", {
|
|
123
138
|
className: "flex items-center justify-between gap-3 border-b border-border-subtle bg-bg-surface px-3 py-2",
|
|
124
|
-
children: [/* @__PURE__ */
|
|
139
|
+
children: [/* @__PURE__ */ T("div", {
|
|
125
140
|
className: "flex min-w-0 items-center gap-2",
|
|
126
141
|
children: [
|
|
127
|
-
/* @__PURE__ */
|
|
128
|
-
/* @__PURE__ */
|
|
142
|
+
/* @__PURE__ */ w(h, { className: "size-4 shrink-0 text-action-primary-bg" }),
|
|
143
|
+
/* @__PURE__ */ w("h2", {
|
|
129
144
|
className: "truncate text-sm font-semibold text-text-primary",
|
|
130
145
|
children: "AI Preview"
|
|
131
146
|
}),
|
|
132
|
-
|
|
147
|
+
d ? /* @__PURE__ */ T("span", {
|
|
133
148
|
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",
|
|
134
149
|
"data-testid": "ai-preview-live",
|
|
135
|
-
children: [/* @__PURE__ */
|
|
150
|
+
children: [/* @__PURE__ */ T("span", {
|
|
136
151
|
className: "relative flex size-2",
|
|
137
|
-
children: [/* @__PURE__ */
|
|
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" })]
|
|
138
153
|
}), "Live"]
|
|
139
154
|
}) : null,
|
|
140
|
-
/* @__PURE__ */
|
|
155
|
+
/* @__PURE__ */ T("span", {
|
|
141
156
|
className: "truncate text-xs text-text-muted",
|
|
142
157
|
"data-testid": "ai-preview-ago",
|
|
143
158
|
children: [
|
|
144
|
-
|
|
159
|
+
d ? k || "Working…" : F ? "Stopped" : "Done",
|
|
145
160
|
" · updated",
|
|
146
161
|
" ",
|
|
147
|
-
|
|
162
|
+
j(J)
|
|
148
163
|
]
|
|
149
164
|
})
|
|
150
165
|
]
|
|
151
|
-
}), /* @__PURE__ */
|
|
166
|
+
}), /* @__PURE__ */ w(v, {
|
|
152
167
|
size: "sm",
|
|
153
168
|
variant: "ghost",
|
|
154
|
-
onClick:
|
|
169
|
+
onClick: L,
|
|
155
170
|
"aria-label": "Close preview",
|
|
156
171
|
"data-testid": "ai-preview-close",
|
|
157
|
-
children: /* @__PURE__ */
|
|
172
|
+
children: /* @__PURE__ */ w(g, { className: "size-4" })
|
|
158
173
|
})]
|
|
159
174
|
}),
|
|
160
|
-
/* @__PURE__ */
|
|
175
|
+
/* @__PURE__ */ w("div", {
|
|
161
176
|
className: "border-b border-border-subtle px-3 py-2",
|
|
162
|
-
children: /* @__PURE__ */
|
|
177
|
+
children: /* @__PURE__ */ w(A, { steps: _ })
|
|
163
178
|
}),
|
|
164
|
-
/* @__PURE__ */
|
|
165
|
-
value:
|
|
166
|
-
onValueChange: (e) =>
|
|
167
|
-
children: [/* @__PURE__ */
|
|
179
|
+
/* @__PURE__ */ T(b, {
|
|
180
|
+
value: R,
|
|
181
|
+
onValueChange: (e) => z(e),
|
|
182
|
+
children: [/* @__PURE__ */ T(S, {
|
|
168
183
|
className: "mx-3 mt-2",
|
|
169
184
|
children: [
|
|
170
|
-
/* @__PURE__ */
|
|
185
|
+
/* @__PURE__ */ w(C, {
|
|
171
186
|
value: "datasink",
|
|
172
187
|
"data-testid": "ai-preview-tab-datasink",
|
|
173
188
|
children: "Data Sinks"
|
|
174
189
|
}),
|
|
175
|
-
/* @__PURE__ */
|
|
190
|
+
/* @__PURE__ */ w(C, {
|
|
176
191
|
value: "dashboard",
|
|
177
192
|
"data-testid": "ai-preview-tab-dashboard",
|
|
178
193
|
children: "Dashboards"
|
|
179
194
|
}),
|
|
180
|
-
/* @__PURE__ */
|
|
195
|
+
/* @__PURE__ */ w(C, {
|
|
181
196
|
value: "parser",
|
|
182
197
|
"data-testid": "ai-preview-tab-parser",
|
|
183
198
|
children: "Parsers"
|
|
184
199
|
}),
|
|
185
|
-
/* @__PURE__ */
|
|
200
|
+
/* @__PURE__ */ w(C, {
|
|
186
201
|
value: "widget",
|
|
187
202
|
"data-testid": "ai-preview-tab-widget",
|
|
188
203
|
children: "Widgets"
|
|
189
204
|
})
|
|
190
205
|
]
|
|
191
|
-
}), /* @__PURE__ */
|
|
206
|
+
}), /* @__PURE__ */ T("div", {
|
|
192
207
|
className: "max-h-[320px] overflow-y-auto p-3",
|
|
193
208
|
children: [
|
|
194
|
-
/* @__PURE__ */
|
|
209
|
+
/* @__PURE__ */ w(x, {
|
|
195
210
|
value: "datasink",
|
|
196
211
|
className: "space-y-2",
|
|
197
|
-
children: X.length === 0 ? /* @__PURE__ */
|
|
198
|
-
loading:
|
|
212
|
+
children: X.length === 0 ? /* @__PURE__ */ w(P, {
|
|
213
|
+
loading: U.loading,
|
|
199
214
|
empty: "No data sinks yet."
|
|
200
|
-
}) : X.map((e) => /* @__PURE__ */
|
|
215
|
+
}) : X.map((e) => /* @__PURE__ */ w(M, {
|
|
201
216
|
id: e.id,
|
|
202
217
|
title: e.name ?? e.key,
|
|
203
218
|
meta: e.status,
|
|
204
|
-
isNew: e.id ===
|
|
219
|
+
isNew: e.id === y.datasink,
|
|
205
220
|
onOpen: () => n(`/datasinks/${e.id}`)
|
|
206
221
|
}, e.id))
|
|
207
222
|
}),
|
|
208
|
-
/* @__PURE__ */
|
|
223
|
+
/* @__PURE__ */ w(x, {
|
|
209
224
|
value: "dashboard",
|
|
210
225
|
className: "space-y-2",
|
|
211
|
-
children: Z.length === 0 ? /* @__PURE__ */
|
|
212
|
-
loading:
|
|
226
|
+
children: Z.length === 0 ? /* @__PURE__ */ w(P, {
|
|
227
|
+
loading: W.loading,
|
|
213
228
|
empty: "No dashboards yet."
|
|
214
|
-
}) : Z.map((e) => /* @__PURE__ */
|
|
229
|
+
}) : Z.map((e) => /* @__PURE__ */ w(M, {
|
|
215
230
|
id: e.id,
|
|
216
231
|
title: e.name,
|
|
217
232
|
meta: `${String(e.widgetCount ?? 0)} widget(s)`,
|
|
218
|
-
isNew: e.id ===
|
|
233
|
+
isNew: e.id === y.dashboard,
|
|
219
234
|
onOpen: () => n(`/dashboards/${e.id}`)
|
|
220
235
|
}, e.id))
|
|
221
236
|
}),
|
|
222
|
-
/* @__PURE__ */
|
|
237
|
+
/* @__PURE__ */ w(x, {
|
|
223
238
|
value: "parser",
|
|
224
239
|
className: "space-y-2",
|
|
225
|
-
children:
|
|
226
|
-
loading:
|
|
240
|
+
children: H ? Q.length === 0 ? /* @__PURE__ */ w(P, {
|
|
241
|
+
loading: G.loading,
|
|
227
242
|
empty: "No parsers on this dashboard yet."
|
|
228
|
-
}) : Q.map((e) => /* @__PURE__ */
|
|
243
|
+
}) : Q.map((e) => /* @__PURE__ */ w(M, {
|
|
229
244
|
id: e.id,
|
|
230
245
|
title: e.name,
|
|
231
246
|
meta: `${e.type} · ${e.status}`,
|
|
232
|
-
isNew: e.id ===
|
|
247
|
+
isNew: e.id === y.parser,
|
|
233
248
|
onOpen: () => n(`/parsers/${e.id}`)
|
|
234
|
-
}, e.id)) : /* @__PURE__ */
|
|
249
|
+
}, e.id)) : /* @__PURE__ */ w("p", {
|
|
235
250
|
className: "px-1 py-6 text-sm text-text-muted",
|
|
236
251
|
children: "Parsers are listed per dashboard — waiting for the dashboard to be created."
|
|
237
252
|
})
|
|
238
253
|
}),
|
|
239
|
-
/* @__PURE__ */
|
|
254
|
+
/* @__PURE__ */ w(x, {
|
|
240
255
|
value: "widget",
|
|
241
256
|
className: "space-y-2",
|
|
242
|
-
children:
|
|
243
|
-
loading:
|
|
257
|
+
children: H ? $.length === 0 ? /* @__PURE__ */ w(P, {
|
|
258
|
+
loading: K.loading,
|
|
244
259
|
empty: "No widgets on this dashboard yet."
|
|
245
|
-
}) : $.map((e) => /* @__PURE__ */
|
|
260
|
+
}) : $.map((e) => /* @__PURE__ */ w(M, {
|
|
246
261
|
id: e.id,
|
|
247
262
|
title: e.title,
|
|
248
263
|
meta: e.type,
|
|
249
|
-
isNew: e.id === $[$.length - 1]?.id &&
|
|
250
|
-
onOpen: () => n(`/dashboards/${
|
|
251
|
-
}, e.id)) : /* @__PURE__ */
|
|
264
|
+
isNew: e.id === $[$.length - 1]?.id && _.widget === "done",
|
|
265
|
+
onOpen: () => n(`/dashboards/${H}`)
|
|
266
|
+
}, e.id)) : /* @__PURE__ */ w("p", {
|
|
252
267
|
className: "px-1 py-6 text-sm text-text-muted",
|
|
253
268
|
children: "Widgets live on a dashboard — waiting for the dashboard to be created."
|
|
254
269
|
})
|
|
@@ -256,23 +271,24 @@ function F() {
|
|
|
256
271
|
]
|
|
257
272
|
})]
|
|
258
273
|
}),
|
|
259
|
-
F ? /* @__PURE__ */
|
|
274
|
+
F ? /* @__PURE__ */ w("p", {
|
|
260
275
|
className: "border-t border-border-subtle px-3 py-2 text-xs text-status-error-text",
|
|
261
276
|
"data-testid": "ai-preview-error",
|
|
262
277
|
children: F
|
|
263
278
|
}) : null
|
|
264
279
|
]
|
|
265
|
-
}) : /* @__PURE__ */
|
|
280
|
+
}) : /* @__PURE__ */ w("div", {
|
|
266
281
|
className: "flex justify-end px-4 pt-3 md:px-6",
|
|
267
|
-
|
|
282
|
+
style: { marginRight: Y },
|
|
283
|
+
children: /* @__PURE__ */ T(v, {
|
|
268
284
|
size: "sm",
|
|
269
285
|
variant: "outline",
|
|
270
|
-
onClick:
|
|
286
|
+
onClick: te,
|
|
271
287
|
"data-testid": "ai-preview-open",
|
|
272
288
|
children: [
|
|
273
|
-
/* @__PURE__ */
|
|
289
|
+
/* @__PURE__ */ w(h, { className: "mr-1.5 size-4" }),
|
|
274
290
|
"Preview",
|
|
275
|
-
|
|
291
|
+
d ? /* @__PURE__ */ w(p, { className: "ml-1.5 size-3.5 animate-spin" }) : null
|
|
276
292
|
]
|
|
277
293
|
})
|
|
278
294
|
}) : null;
|
|
@@ -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\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 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\">\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 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;;;AAIV,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,IAAc,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,EAAoB;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,GAE7E,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,WAAU;YAHZ;GAME,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;MAhKR,kBAAC,OAAD;EAAK,WAAU;YACb,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, 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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"}
|