@burdenoff/microfe-bigconsole 2026.717.1 → 2026.717.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -19,8 +19,21 @@ var t = [
|
|
|
19
19
|
datasinks: "datasink",
|
|
20
20
|
dashboards: "dashboard",
|
|
21
21
|
parsers: "parser"
|
|
22
|
-
}
|
|
23
|
-
|
|
22
|
+
}, c = [
|
|
23
|
+
{
|
|
24
|
+
step: "datasink",
|
|
25
|
+
re: /"createDataSink"\s*:\s*\{[^{}]*?"id"\s*:\s*"([A-Za-z0-9_-]+)"/
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
step: "dashboard",
|
|
29
|
+
re: /"createBigConsoleDashboard"\s*:\s*\{[^{}]*?"id"\s*:\s*"([A-Za-z0-9_-]+)"/
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
step: "parser",
|
|
33
|
+
re: /"createParser"\s*:\s*\{[^{}]*?"id"\s*:\s*"([A-Za-z0-9_-]+)"/
|
|
34
|
+
}
|
|
35
|
+
], l = /"createBigConsoleWidget"\s*:\s*\{/;
|
|
36
|
+
function u(e, t) {
|
|
24
37
|
let n = {
|
|
25
38
|
pending: 0,
|
|
26
39
|
running: 1,
|
|
@@ -29,7 +42,7 @@ function c(e, t) {
|
|
|
29
42
|
};
|
|
30
43
|
return n[t] > n[e] ? t : e;
|
|
31
44
|
}
|
|
32
|
-
var
|
|
45
|
+
var d = e()((e) => ({
|
|
33
46
|
isRunning: !1,
|
|
34
47
|
previewOpen: !1,
|
|
35
48
|
hasRun: !1,
|
|
@@ -40,39 +53,45 @@ var l = e()((e) => ({
|
|
|
40
53
|
caption: "",
|
|
41
54
|
error: null,
|
|
42
55
|
updatedAt: null,
|
|
43
|
-
startRun: (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
startRun: (n) => e((e) => {
|
|
57
|
+
let r = { ...e.steps };
|
|
58
|
+
for (let e of t) r[e] === "running" && (r[e] = "pending");
|
|
59
|
+
let i = t.find((e) => r[e] === "pending");
|
|
60
|
+
return i && (r[i] = "running"), {
|
|
61
|
+
isRunning: !0,
|
|
62
|
+
previewOpen: !0,
|
|
63
|
+
hasRun: !0,
|
|
64
|
+
prompt: n,
|
|
65
|
+
steps: r,
|
|
66
|
+
caption: "",
|
|
67
|
+
error: null,
|
|
68
|
+
updatedAt: Date.now()
|
|
69
|
+
};
|
|
56
70
|
}),
|
|
57
71
|
applyProgress: (n) => e((e) => {
|
|
58
|
-
let r = { ...e.steps },
|
|
72
|
+
let r = { ...e.steps }, d = { ...e.entityIds }, f = (e, t) => {
|
|
73
|
+
r[e] = u(r[e], "done"), t && e !== "widget" && (d[e] = t);
|
|
74
|
+
};
|
|
59
75
|
for (let e of n.matchAll(i)) {
|
|
60
|
-
let t = s[e[1] ?? ""]
|
|
61
|
-
|
|
76
|
+
let t = s[e[1] ?? ""];
|
|
77
|
+
t && e[2] && f(t, e[2]);
|
|
78
|
+
}
|
|
79
|
+
for (let { step: e, re: t } of c) {
|
|
80
|
+
let r = t.exec(n);
|
|
81
|
+
r?.[1] && f(e, r[1]);
|
|
62
82
|
}
|
|
63
|
-
a.test(n) && (r.widget =
|
|
64
|
-
let
|
|
65
|
-
return
|
|
83
|
+
(l.test(n) || a.test(n)) && (r.widget = u(r.widget, "done"), r.parser !== "done" && (r.parser = u(r.parser, "skipped")));
|
|
84
|
+
let p = t.find((e) => r[e] === "pending" || r[e] === "running");
|
|
85
|
+
return p && (r[p] = u(r[p], "running")), {
|
|
66
86
|
steps: r,
|
|
67
|
-
entityIds:
|
|
87
|
+
entityIds: d,
|
|
68
88
|
caption: n.split("\n").map((e) => e.trim()).reverse().map((e) => o.exec(e)?.[1]).find((e) => !!e) ?? e.caption,
|
|
69
89
|
updatedAt: Date.now()
|
|
70
90
|
};
|
|
71
91
|
}),
|
|
72
92
|
finishRun: (n) => e((e) => {
|
|
73
93
|
let r = { ...e.steps };
|
|
74
|
-
|
|
75
|
-
else for (let e of t) (r[e] === "running" || r[e] === "pending") && (r[e] = "skipped");
|
|
94
|
+
for (let e of t) r[e] === "running" && (r[e] = "pending");
|
|
76
95
|
return {
|
|
77
96
|
isRunning: !1,
|
|
78
97
|
error: n,
|
|
@@ -86,6 +105,6 @@ var l = e()((e) => ({
|
|
|
86
105
|
setRichMounted: (t) => e({ richMounted: t })
|
|
87
106
|
}));
|
|
88
107
|
//#endregion
|
|
89
|
-
export { t as PREVIEW_STEPS, n as PREVIEW_STEP_LABEL,
|
|
108
|
+
export { t as PREVIEW_STEPS, n as PREVIEW_STEP_LABEL, d as useAssistantRunStore };
|
|
90
109
|
|
|
91
110
|
//# sourceMappingURL=assistantRunStore.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistantRunStore.js","names":[],"sources":["../../../src/bigconsole/assistant/assistantRunStore.ts"],"sourcesContent":["/**\n * What the assistant is building, right now.\n *\n * The agent is instructed to narrate each step and to print a link the moment a\n * create lands (\"✅ Data sink created — [Open the data sink](/bigconsole/datasinks/<id>)\").\n * That narration is the only live signal the frontend gets — there is no event\n * stream from the agent — so this store turns it into something a UI can render:\n * a monotone DataSink → Dashboard → Parser → Widget rail, plus the id of each\n * entity as it appears.\n *\n * Two rules make the parsing survive a model that phrases things differently:\n *\n * 1. Match on the LINK PATH (`/bigconsole/datasinks/<id>`), never on the ✅\n * sentence. The path is copied from the mutation response; the sentence is\n * prose and the model is free to reword it.\n * 2. Never regress. `progress.content` is REPLACED on every poll (not appended)\n * and the ⏳ tool lines come only from the agent's newest message, so a\n * shorter string arriving later must not rewind the rail.\n */\nimport { create } from 'zustand';\n\nexport type PreviewStepKey = 'datasink' | 'dashboard' | 'parser' | 'widget';\nexport type PreviewStepStatus = 'pending' | 'running' | 'done' | 'skipped';\n\nexport const PREVIEW_STEPS: readonly PreviewStepKey[] = ['datasink', 'dashboard', 'parser', 'widget'];\n\nexport const PREVIEW_STEP_LABEL: Record<PreviewStepKey, string> = {\n datasink: 'Data Sink',\n dashboard: 'Dashboard',\n parser: 'Parser',\n widget: 'Widget',\n};\n\ninterface AssistantRunState {\n /** A turn is in flight. */\n isRunning: boolean;\n /** The panel is on screen. Auto-opens when a turn starts; the user can close it. */\n previewOpen: boolean;\n /** True once any turn has run — gates the \"Preview\" button. */\n hasRun: boolean;\n /**\n * The full, context-rich panel (mounted inside BigConsoleRoot, with live entity\n * tabs) is on screen right now. The lightweight app-shell panel reads this and\n * steps aside so the two never render at once. It flips false the instant you\n * leave a /bigconsole page — that is what lets the mini panel take over and keep\n * the build visible on whatever screen you moved to.\n */\n richMounted: boolean;\n prompt: string;\n steps: Record<PreviewStepKey, PreviewStepStatus>;\n /** Ids harvested from the agent's links. A widget has no link of its own. */\n entityIds: { datasink?: string; dashboard?: string; parser?: string };\n /** Newest \"⏳ …\" line — what the agent says it is doing this second. */\n caption: string;\n /** Set when the turn fails, so the panel can stop pretending to be live. */\n error: string | null;\n /** Epoch ms of the last progress tick — drives the \"updated 3s ago\" strip. */\n updatedAt: number | null;\n\n startRun: (prompt: string) => void;\n applyProgress: (content: string) => void;\n finishRun: (error: string | null) => void;\n openPreview: () => void;\n closePreview: () => void;\n setRichMounted: (mounted: boolean) => void;\n}\n\nconst IDLE_STEPS: Record<PreviewStepKey, PreviewStepStatus> = {\n datasink: 'pending',\n dashboard: 'pending',\n parser: 'pending',\n widget: 'pending',\n};\n\n/** `/bigconsole/datasinks/<id>` — the trustworthy part of the narration. */\nconst ENTITY_LINK = /\\/bigconsole\\/(datasinks|dashboards|parsers)\\/([A-Za-z0-9_-]+)/g;\n/** The widget links to its dashboard, so it can only be detected from the prose. */\nconst WIDGET_DONE = /widget\\s+(added|created)/i;\nconst RUNNING_LINE = /^⏳\\s*(.+?)…?$/;\n\nconst LINK_TO_STEP: Record<string, PreviewStepKey> = {\n datasinks: 'datasink',\n dashboards: 'dashboard',\n parsers: 'parser',\n};\n\n/** Only ever moves a step forward. */\nfunction advance(current: PreviewStepStatus, next: PreviewStepStatus): PreviewStepStatus {\n const rank: Record<PreviewStepStatus, number> = { pending: 0, running: 1, skipped: 2, done: 3 };\n return rank[next] > rank[current] ? next : current;\n}\n\nexport const useAssistantRunStore = create<AssistantRunState>()((set) => ({\n isRunning: false,\n previewOpen: false,\n hasRun: false,\n richMounted: false,\n prompt: '',\n steps: { ...IDLE_STEPS },\n entityIds: {},\n caption: '',\n error: null,\n updatedAt: null,\n\n startRun: (prompt) =>\n set({\n isRunning: true,\n // Auto-open: the whole point is that you SEE the build happen without\n // having to go looking for it.\n previewOpen: true,\n hasRun: true,\n prompt,\n steps: { ...IDLE_STEPS, datasink: 'running' },\n entityIds: {},\n caption: '',\n error: null,\n updatedAt: Date.now(),\n }),\n\n applyProgress: (content) =>\n set((state) => {\n const steps = { ...state.steps };\n const entityIds = { ...state.entityIds };\n\n for (const match of content.matchAll(ENTITY_LINK)) {\n const step = LINK_TO_STEP[match[1] ?? ''];\n const id = match[2];\n if (!step || !id) continue;\n steps[step] = advance(steps[step], 'done');\n if (!entityIds[step as keyof typeof entityIds]) {\n entityIds[step as keyof typeof entityIds] = id;\n }\n }\n\n if (WIDGET_DONE.test(content)) {\n steps.widget = advance(steps.widget, 'done');\n // The parser is optional — the agent skips it when the rows are already\n // shaped. If the widget landed and no parser link ever appeared, it was\n // skipped, not stuck.\n if (steps.parser !== 'done') steps.parser = advance(steps.parser, 'skipped');\n }\n\n // The first step that has not finished is the one being worked on.\n const active = PREVIEW_STEPS.find((key) => steps[key] === 'pending' || steps[key] === 'running');\n if (active) steps[active] = advance(steps[active], 'running');\n\n const caption =\n content\n .split('\\n')\n .map((line) => line.trim())\n .reverse()\n .map((line) => RUNNING_LINE.exec(line)?.[1])\n .find((label): label is string => Boolean(label)) ?? state.caption;\n\n return { steps, entityIds, caption, updatedAt: Date.now() };\n }),\n\n finishRun: (error) =>\n set((state) => {\n const steps = { ...state.steps };\n if (!error) {\n // A clean finish means nothing is still in flight. Anything left running\n // never produced a link — mark it skipped rather than leaving a spinner\n // on screen forever.\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running' || steps[key] === 'pending') steps[key] = 'skipped';\n }\n } else {\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n }\n return { isRunning: false, error, steps, caption: '', updatedAt: Date.now() };\n }),\n\n openPreview: () => set({ previewOpen: true }),\n closePreview: () => set({ previewOpen: false }),\n setRichMounted: (mounted) => set({ richMounted: mounted }),\n}));\n"],"mappings":";;AAwBA,IAAa,IAA2C;CAAC;CAAY;CAAa;CAAU;CAAS,EAExF,IAAqD;CAChE,UAAU;CACV,WAAW;CACX,QAAQ;CACR,QAAQ;CACT,EAoCK,IAAwD;CAC5D,UAAU;CACV,WAAW;CACX,QAAQ;CACR,QAAQ;CACT,EAGK,IAAc,mEAEd,IAAc,6BACd,IAAe,iBAEf,IAA+C;CACnD,WAAW;CACX,YAAY;CACZ,SAAS;CACV;AAGD,SAAS,EAAQ,GAA4B,GAA4C;CACvF,IAAM,IAA0C;EAAE,SAAS;EAAG,SAAS;EAAG,SAAS;EAAG,MAAM;EAAG;AAC/F,QAAO,EAAK,KAAQ,EAAK,KAAW,IAAO;;AAG7C,IAAa,IAAuB,GAA2B,EAAE,OAAS;CACxE,WAAW;CACX,aAAa;CACb,QAAQ;CACR,aAAa;CACb,QAAQ;CACR,OAAO,EAAE,GAAG,GAAY;CACxB,WAAW,EAAE;CACb,SAAS;CACT,OAAO;CACP,WAAW;CAEX,WAAW,MACT,EAAI;EACF,WAAW;EAGX,aAAa;EACb,QAAQ;EACR;EACA,OAAO;GAAE,GAAG;GAAY,UAAU;GAAW;EAC7C,WAAW,EAAE;EACb,SAAS;EACT,OAAO;EACP,WAAW,KAAK,KAAK;EACtB,CAAC;CAEJ,gBAAgB,MACd,GAAK,MAAU;EACb,IAAM,IAAQ,EAAE,GAAG,EAAM,OAAO,EAC1B,IAAY,EAAE,GAAG,EAAM,WAAW;AAExC,OAAK,IAAM,KAAS,EAAQ,SAAS,EAAY,EAAE;GACjD,IAAM,IAAO,EAAa,EAAM,MAAM,KAChC,IAAK,EAAM;AACb,IAAC,KAAQ,CAAC,MACd,EAAM,KAAQ,EAAQ,EAAM,IAAO,OAAO,EACrC,EAAU,OACb,EAAU,KAAkC;;AAIhD,EAAI,EAAY,KAAK,EAAQ,KAC3B,EAAM,SAAS,EAAQ,EAAM,QAAQ,OAAO,EAIxC,EAAM,WAAW,WAAQ,EAAM,SAAS,EAAQ,EAAM,QAAQ,UAAU;EAI9E,IAAM,IAAS,EAAc,MAAM,MAAQ,EAAM,OAAS,aAAa,EAAM,OAAS,UAAU;AAWhG,SAVI,MAAQ,EAAM,KAAU,EAAQ,EAAM,IAAS,UAAU,GAUtD;GAAE;GAAO;GAAW,SAPzB,EACG,MAAM,KAAK,CACX,KAAK,MAAS,EAAK,MAAM,CAAC,CAC1B,SAAS,CACT,KAAK,MAAS,EAAa,KAAK,EAAK,GAAG,GAAG,CAC3C,MAAM,MAA2B,EAAQ,EAAO,IAAI,EAAM;GAE3B,WAAW,KAAK,KAAK;GAAE;GAC3D;CAEJ,YAAY,MACV,GAAK,MAAU;EACb,IAAM,IAAQ,EAAE,GAAG,EAAM,OAAO;AAChC,MAAK,EAQH,MAAK,IAAM,KAAO,EAChB,CAAI,EAAM,OAAS,cAAW,EAAM,KAAO;YALxC,IAAM,KAAO,EAChB,EAAI,EAAM,OAAS,aAAa,EAAM,OAAS,eAAW,EAAM,KAAO;AAO3E,SAAO;GAAE,WAAW;GAAO;GAAO;GAAO,SAAS;GAAI,WAAW,KAAK,KAAK;GAAE;GAC7E;CAEJ,mBAAmB,EAAI,EAAE,aAAa,IAAM,CAAC;CAC7C,oBAAoB,EAAI,EAAE,aAAa,IAAO,CAAC;CAC/C,iBAAiB,MAAY,EAAI,EAAE,aAAa,GAAS,CAAC;CAC3D,EAAE"}
|
|
1
|
+
{"version":3,"file":"assistantRunStore.js","names":[],"sources":["../../../src/bigconsole/assistant/assistantRunStore.ts"],"sourcesContent":["/**\n * What the assistant is building, right now.\n *\n * The agent's narration is the only live signal the frontend gets — there is no\n * event stream — so this store turns it into something a UI can render: a\n * monotone DataSink → Dashboard → Parser → Widget rail, plus the id of each\n * entity as it appears.\n *\n * Three rules make the parsing survive a model that phrases things differently:\n *\n * 1. Read the id from BOTH the link path (`/bigconsole/datasinks/<id>`) AND the\n * raw mutation JSON (`{ \"createDataSink\": { \"id\": \"<uuid>\", … } }`). The agent\n * freely prints one, the other, or both — if we only watched for the link a\n * turn that printed just JSON would leave the tab stuck on \"Waiting…\".\n * 2. Never regress. `progress.content` is REPLACED on every poll (not appended),\n * so a shorter string arriving later must not rewind the rail.\n * 3. Accumulate across an incremental conversation. Each prompt builds one piece;\n * a new prompt must NOT wipe the pieces already built, or the earlier tabs go\n * blank. `startRun` preserves prior steps/ids and only lights up the next one.\n */\nimport { create } from 'zustand';\n\nexport type PreviewStepKey = 'datasink' | 'dashboard' | 'parser' | 'widget';\nexport type PreviewStepStatus = 'pending' | 'running' | 'done' | 'skipped';\n\nexport const PREVIEW_STEPS: readonly PreviewStepKey[] = ['datasink', 'dashboard', 'parser', 'widget'];\n\nexport const PREVIEW_STEP_LABEL: Record<PreviewStepKey, string> = {\n datasink: 'Data Sink',\n dashboard: 'Dashboard',\n parser: 'Parser',\n widget: 'Widget',\n};\n\ninterface AssistantRunState {\n /** A turn is in flight. */\n isRunning: boolean;\n /** The panel is on screen. Auto-opens when a turn starts; the user can close it. */\n previewOpen: boolean;\n /** True once any turn has run — gates the \"Preview\" button. */\n hasRun: boolean;\n /**\n * The full, context-rich panel (mounted inside BigConsoleRoot, with live entity\n * tabs) is on screen right now. The lightweight app-shell panel reads this and\n * steps aside so the two never render at once. It flips false the instant you\n * leave a /bigconsole page — that is what lets the mini panel take over and keep\n * the build visible on whatever screen you moved to.\n */\n richMounted: boolean;\n prompt: string;\n steps: Record<PreviewStepKey, PreviewStepStatus>;\n /** Ids harvested from the agent's links. A widget has no link of its own. */\n entityIds: { datasink?: string; dashboard?: string; parser?: string };\n /** Newest \"⏳ …\" line — what the agent says it is doing this second. */\n caption: string;\n /** Set when the turn fails, so the panel can stop pretending to be live. */\n error: string | null;\n /** Epoch ms of the last progress tick — drives the \"updated 3s ago\" strip. */\n updatedAt: number | null;\n\n startRun: (prompt: string) => void;\n applyProgress: (content: string) => void;\n finishRun: (error: string | null) => void;\n openPreview: () => void;\n closePreview: () => void;\n setRichMounted: (mounted: boolean) => void;\n}\n\nconst IDLE_STEPS: Record<PreviewStepKey, PreviewStepStatus> = {\n datasink: 'pending',\n dashboard: 'pending',\n parser: 'pending',\n widget: 'pending',\n};\n\n/** `/bigconsole/datasinks/<id>` — the trustworthy part of the narration. */\nconst ENTITY_LINK = /\\/bigconsole\\/(datasinks|dashboards|parsers)\\/([A-Za-z0-9_-]+)/g;\n/** The widget links to its dashboard, so it can only be detected from the prose. */\nconst WIDGET_DONE = /widget\\s+(added|created)/i;\nconst RUNNING_LINE = /^⏳\\s*(.+?)…?$/;\n\nconst LINK_TO_STEP: Record<string, PreviewStepKey> = {\n datasinks: 'datasink',\n dashboards: 'dashboard',\n parsers: 'parser',\n};\n\n/**\n * The id straight out of a raw mutation response the agent printed instead of\n * (or as well as) a link, e.g. `{ \"createDataSink\": { \"id\": \"<uuid>\", … } }`.\n * `[^{}]` keeps each match inside its own object so we grab that mutation's id,\n * not a neighbour's. This is what stops \"Waiting…\" when no link is narrated.\n */\nconst JSON_ID_STEPS: ReadonlyArray<{ step: Exclude<PreviewStepKey, 'widget'>; re: RegExp }> = [\n { step: 'datasink', re: /\"createDataSink\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n { step: 'dashboard', re: /\"createBigConsoleDashboard\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n { step: 'parser', re: /\"createParser\"\\s*:\\s*\\{[^{}]*?\"id\"\\s*:\\s*\"([A-Za-z0-9_-]+)\"/ },\n];\n/** The widget has no page of its own; its create response is enough to mark it done. */\nconst WIDGET_CREATE_JSON = /\"createBigConsoleWidget\"\\s*:\\s*\\{/;\n\n/** Only ever moves a step forward. */\nfunction advance(current: PreviewStepStatus, next: PreviewStepStatus): PreviewStepStatus {\n const rank: Record<PreviewStepStatus, number> = { pending: 0, running: 1, skipped: 2, done: 3 };\n return rank[next] > rank[current] ? next : current;\n}\n\nexport const useAssistantRunStore = create<AssistantRunState>()((set) => ({\n isRunning: false,\n previewOpen: false,\n hasRun: false,\n richMounted: false,\n prompt: '',\n steps: { ...IDLE_STEPS },\n entityIds: {},\n caption: '',\n error: null,\n updatedAt: null,\n\n startRun: (prompt) =>\n set((state) => {\n // A new prompt CONTINUES the same use case (incremental flow) — keep every\n // step and id already built. Clear any stale spinner from the prior turn,\n // then light up the earliest step that has not landed yet so the \"working\n // on it\" indicator sits on the right tab.\n const steps = { ...state.steps };\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n const next = PREVIEW_STEPS.find((key) => steps[key] === 'pending');\n if (next) steps[next] = 'running';\n return {\n isRunning: true,\n // Auto-open: the whole point is that you SEE the build happen without\n // having to go looking for it.\n previewOpen: true,\n hasRun: true,\n prompt,\n steps,\n // entityIds intentionally preserved (merged) so earlier tabs keep content.\n caption: '',\n error: null,\n updatedAt: Date.now(),\n };\n }),\n\n applyProgress: (content) =>\n set((state) => {\n const steps = { ...state.steps };\n const entityIds = { ...state.entityIds };\n\n const markDone = (step: PreviewStepKey, id?: string) => {\n steps[step] = advance(steps[step], 'done');\n // Always record the freshest id seen this turn (never \"first wins\"): an\n // incremental prompt that rebuilds a piece must point the tab at the new\n // one. `step !== 'widget'` narrows to the keys entityIds actually holds.\n if (id && step !== 'widget') entityIds[step] = id;\n };\n\n // 1) Markdown link paths.\n for (const match of content.matchAll(ENTITY_LINK)) {\n const step = LINK_TO_STEP[match[1] ?? ''];\n if (step && match[2]) markDone(step, match[2]);\n }\n // 2) Raw mutation JSON (the agent often prints this instead of a link).\n for (const { step, re } of JSON_ID_STEPS) {\n const match = re.exec(content);\n if (match?.[1]) markDone(step, match[1]);\n }\n\n if (WIDGET_CREATE_JSON.test(content) || WIDGET_DONE.test(content)) {\n steps.widget = advance(steps.widget, 'done');\n // The parser is optional — the agent skips it when the rows are already\n // shaped. If the widget landed and the parser was never built, it was\n // skipped, not stuck.\n if (steps.parser !== 'done') steps.parser = advance(steps.parser, 'skipped');\n }\n\n // The first step that has not finished is the one being worked on.\n const active = PREVIEW_STEPS.find((key) => steps[key] === 'pending' || steps[key] === 'running');\n if (active) steps[active] = advance(steps[active], 'running');\n\n const caption =\n content\n .split('\\n')\n .map((line) => line.trim())\n .reverse()\n .map((line) => RUNNING_LINE.exec(line)?.[1])\n .find((label): label is string => Boolean(label)) ?? state.caption;\n\n return { steps, entityIds, caption, updatedAt: Date.now() };\n }),\n\n finishRun: (error) =>\n set((state) => {\n // The turn is over — stop the live spinner. A step still 'running' didn't\n // land this turn, so drop it back to 'pending' (a later prompt can pick it\n // up). NEVER blanket-skip pending steps: in the incremental flow the later\n // steps simply have not been asked for yet, and 'done' steps must stay done.\n const steps = { ...state.steps };\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n return { isRunning: false, error, steps, caption: '', updatedAt: Date.now() };\n }),\n\n openPreview: () => set({ previewOpen: true }),\n closePreview: () => set({ previewOpen: false }),\n setRichMounted: (mounted) => set({ richMounted: mounted }),\n}));\n"],"mappings":"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"}
|
|
@@ -32,8 +32,13 @@ function w() {
|
|
|
32
32
|
let e = window.setInterval(() => R(Date.now()), 1e3);
|
|
33
33
|
return () => window.clearInterval(e);
|
|
34
34
|
}, [w]), u(() => {
|
|
35
|
-
let e = t.
|
|
36
|
-
|
|
35
|
+
let e = t.filter((e) => D[e] === "done"), n = e[e.length - 1];
|
|
36
|
+
if (n) {
|
|
37
|
+
I(n);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let r = t.find((e) => D[e] === "running");
|
|
41
|
+
r && I(r);
|
|
37
42
|
}, [D]);
|
|
38
43
|
let B = j ? Math.max(0, Math.floor((L - j) / 1e3)) : 0, V = a();
|
|
39
44
|
return E ? T ? /* @__PURE__ */ S("section", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiPreviewPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewPanel.tsx"],"sourcesContent":["/**\n * AI Preview — watch the assistant build, with the real content inline.\n *\n * Opens by itself the moment a prompt starts. A step rail (driven by the agent's\n * own narration) shows DataSink → Dashboard → Parser → Widget filling in, and\n * the four tabs show the ACTUAL content of each entity as it lands — the data\n * sink's rows, the live dashboard, the parser's transformed output, the rendered\n * widget charts — not a list of links. The tab auto-switches to whatever step is\n * being worked on, and each tab has an \"Open\" button (top-right) that jumps to\n * the real page.\n *\n * The content renderers need Apollo + the BigConsole providers, which is why\n * this full panel lives inside BigConsoleRoot; the store-only app-shell mini\n * panel (AiPreviewMiniPanel) keeps the lighter link view for other pages.\n */\nimport { useEffect, useState } from 'react';\nimport { Button, Tabs, TabsContent, TabsList, TabsTrigger } from '@burdenoff/fe-libs/ui';\nimport { ArrowUpRight, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, type PreviewStepKey, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useBigConsoleNavigate } from '../../context';\nimport { StepRail, formatAgo, useAssistantSidebarWidth } from './previewShared';\nimport { DashboardContent, DataSinkContent, ParserContent, WidgetsContent } from './AiPreviewContent';\n\n/** A tab's top bar: what this tab is, plus a jump-to-the-real-page button. */\nfunction TabHeader({ label, onOpen }: { label: string; onOpen?: () => void }) {\n return (\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <span className=\"text-xs font-semibold uppercase tracking-wide text-text-muted\">{label}</span>\n {onOpen ? (\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen} data-testid=\"ai-preview-open-entity\">\n Open\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n ) : null}\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 // Tick the \"updated Xs ago\" label while a turn is live.\n useEffect(() => {\n if (!isRunning) return;\n const intervalId = window.setInterval(() => setNow(Date.now()), 1000);\n return () => window.clearInterval(intervalId);\n }, [isRunning]);\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 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 — each shows the real content of the built entity */}\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 Sink\n </TabsTrigger>\n <TabsTrigger value=\"dashboard\" data-testid=\"ai-preview-tab-dashboard\">\n Dashboard\n </TabsTrigger>\n <TabsTrigger value=\"parser\" data-testid=\"ai-preview-tab-parser\">\n Parser\n </TabsTrigger>\n <TabsTrigger value=\"widget\" data-testid=\"ai-preview-tab-widget\">\n Widget\n </TabsTrigger>\n </TabsList>\n\n <div className=\"max-h-[440px] overflow-y-auto p-3\">\n <TabsContent value=\"datasink\">\n <TabHeader\n label=\"Data Sink\"\n onOpen={entityIds.datasink ? () => navigate(`/datasinks/${entityIds.datasink}`) : undefined}\n />\n <DataSinkContent id={entityIds.datasink} live={isRunning} />\n </TabsContent>\n\n <TabsContent value=\"dashboard\">\n <TabHeader\n label=\"Dashboard\"\n onOpen={entityIds.dashboard ? () => navigate(`/dashboards/${entityIds.dashboard}`) : undefined}\n />\n <DashboardContent dashboardId={entityIds.dashboard} />\n </TabsContent>\n\n <TabsContent value=\"parser\">\n <TabHeader\n label=\"Parser\"\n onOpen={entityIds.parser ? () => navigate(`/parsers/${entityIds.parser}`) : undefined}\n />\n <ParserContent parserId={entityIds.parser} live={isRunning} />\n </TabsContent>\n\n <TabsContent value=\"widget\">\n <TabHeader label=\"Widget\" onOpen={dashboardId ? () => navigate(`/dashboards/${dashboardId}`) : undefined} />\n <WidgetsContent dashboardId={dashboardId} live={isRunning} />\n </TabsContent>\n </div>\n </Tabs>\n\n {error ? (\n <p\n className=\"border-t border-border-subtle px-3 py-2 text-xs text-status-error-text\"\n data-testid=\"ai-preview-error\"\n >\n {error}\n </p>\n ) : null}\n </section>\n );\n}\n\nexport default AiPreviewPanel;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AiPreviewPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewPanel.tsx"],"sourcesContent":["/**\n * AI Preview — watch the assistant build, with the real content inline.\n *\n * Opens by itself the moment a prompt starts. A step rail (driven by the agent's\n * own narration) shows DataSink → Dashboard → Parser → Widget filling in, and\n * the four tabs show the ACTUAL content of each entity as it lands — the data\n * sink's rows, the live dashboard, the parser's transformed output, the rendered\n * widget charts — not a list of links. The tab auto-switches to whatever step is\n * being worked on, and each tab has an \"Open\" button (top-right) that jumps to\n * the real page.\n *\n * The content renderers need Apollo + the BigConsole providers, which is why\n * this full panel lives inside BigConsoleRoot; the store-only app-shell mini\n * panel (AiPreviewMiniPanel) keeps the lighter link view for other pages.\n */\nimport { useEffect, useState } from 'react';\nimport { Button, Tabs, TabsContent, TabsList, TabsTrigger } from '@burdenoff/fe-libs/ui';\nimport { ArrowUpRight, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { PREVIEW_STEPS, type PreviewStepKey, useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { useBigConsoleNavigate } from '../../context';\nimport { StepRail, formatAgo, useAssistantSidebarWidth } from './previewShared';\nimport { DashboardContent, DataSinkContent, ParserContent, WidgetsContent } from './AiPreviewContent';\n\n/** A tab's top bar: what this tab is, plus a jump-to-the-real-page button. */\nfunction TabHeader({ label, onOpen }: { label: string; onOpen?: () => void }) {\n return (\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <span className=\"text-xs font-semibold uppercase tracking-wide text-text-muted\">{label}</span>\n {onOpen ? (\n <Button size=\"sm\" variant=\"outline\" onClick={onOpen} data-testid=\"ai-preview-open-entity\">\n Open\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n ) : null}\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 // Tick the \"updated Xs ago\" label while a turn is live.\n useEffect(() => {\n if (!isRunning) return;\n const intervalId = window.setInterval(() => setNow(Date.now()), 1000);\n return () => window.clearInterval(intervalId);\n }, [isRunning]);\n\n // Follow the build by showing the LATEST thing that actually landed — so the\n // moment the data sink is created you see its rows, not an empty \"waiting for\n // the dashboard\" tab. (The rail still shows the spinner on the next step.) If\n // nothing has landed yet, sit on whatever is being worked on.\n useEffect(() => {\n const done = PREVIEW_STEPS.filter((key) => steps[key] === 'done');\n const latestDone = done[done.length - 1];\n if (latestDone) {\n setTab(latestDone);\n return;\n }\n const running = PREVIEW_STEPS.find((key) => steps[key] === 'running');\n if (running) setTab(running);\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 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 — each shows the real content of the built entity */}\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 Sink\n </TabsTrigger>\n <TabsTrigger value=\"dashboard\" data-testid=\"ai-preview-tab-dashboard\">\n Dashboard\n </TabsTrigger>\n <TabsTrigger value=\"parser\" data-testid=\"ai-preview-tab-parser\">\n Parser\n </TabsTrigger>\n <TabsTrigger value=\"widget\" data-testid=\"ai-preview-tab-widget\">\n Widget\n </TabsTrigger>\n </TabsList>\n\n <div className=\"max-h-[440px] overflow-y-auto p-3\">\n <TabsContent value=\"datasink\">\n <TabHeader\n label=\"Data Sink\"\n onOpen={entityIds.datasink ? () => navigate(`/datasinks/${entityIds.datasink}`) : undefined}\n />\n <DataSinkContent id={entityIds.datasink} live={isRunning} />\n </TabsContent>\n\n <TabsContent value=\"dashboard\">\n <TabHeader\n label=\"Dashboard\"\n onOpen={entityIds.dashboard ? () => navigate(`/dashboards/${entityIds.dashboard}`) : undefined}\n />\n <DashboardContent dashboardId={entityIds.dashboard} />\n </TabsContent>\n\n <TabsContent value=\"parser\">\n <TabHeader\n label=\"Parser\"\n onOpen={entityIds.parser ? () => navigate(`/parsers/${entityIds.parser}`) : undefined}\n />\n <ParserContent parserId={entityIds.parser} live={isRunning} />\n </TabsContent>\n\n <TabsContent value=\"widget\">\n <TabHeader label=\"Widget\" onOpen={dashboardId ? () => navigate(`/dashboards/${dashboardId}`) : undefined} />\n <WidgetsContent dashboardId={dashboardId} live={isRunning} />\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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"}
|