@burdenoff/microfe-bigconsole 2026.716.3 → 2026.717.1

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.
@@ -7,10 +7,10 @@ import { BigConsoleRoutes as o } from "./BigConsoleRoutes.js";
7
7
  import { BigConsoleProvider as s } from "./context/BigConsoleContext.js";
8
8
  import "./context/index.js";
9
9
  import { BigConsoleQuotaProvider as c } from "./providers/BigConsoleQuotaProvider.js";
10
- import l from "./components/preview/AiPreviewPanel.js";
11
- import u from "./components/DrilldownModal.js";
12
- import d from "./hooks/useFilterUrlSync.js";
13
- import f from "./components/DrilldownDashboardRenderer.js";
10
+ import l from "./hooks/useFilterUrlSync.js";
11
+ import u from "./components/DrilldownDashboardRenderer.js";
12
+ import d from "./components/preview/AiPreviewPanel.js";
13
+ import f from "./components/DrilldownModal.js";
14
14
  import { useCallback as p, useEffect as m, useLayoutEffect as h, useRef as g, useState as _ } from "react";
15
15
  import { useSearchParams as v } from "react-router-dom";
16
16
  import { ErrorBoundary as y } from "@burdenoff/fe-libs/shared/components";
@@ -157,7 +157,7 @@ var F = i, I = r, L = a, R = /* @__PURE__ */ new Set(), z = (r) => {
157
157
  r.productName
158
158
  ]);
159
159
  let [X, Z] = v(), Q = e((e) => e.isOpen), $ = e((e) => e.closeModal);
160
- d();
160
+ l();
161
161
  let te = p(() => {
162
162
  $();
163
163
  let e = new URLSearchParams(X);
@@ -174,7 +174,7 @@ var F = i, I = r, L = a, R = /* @__PURE__ */ new Set(), z = (r) => {
174
174
  return;
175
175
  }
176
176
  window.location.assign("/billing");
177
- }, [B]), re = p((e, t) => /* @__PURE__ */ E(f, {
177
+ }, [B]), re = p((e, t) => /* @__PURE__ */ E(u, {
178
178
  dashboardId: e,
179
179
  params: t
180
180
  }), []);
@@ -194,9 +194,9 @@ var F = i, I = r, L = a, R = /* @__PURE__ */ new Set(), z = (r) => {
194
194
  children: /* @__PURE__ */ E(n, {
195
195
  basePath: z,
196
196
  children: /* @__PURE__ */ D(t, { children: [
197
- /* @__PURE__ */ E(l, {}),
197
+ /* @__PURE__ */ E(d, {}),
198
198
  /* @__PURE__ */ E(o, { ...r }),
199
- Q && /* @__PURE__ */ E(u, {
199
+ Q && /* @__PURE__ */ E(f, {
200
200
  onClose: te,
201
201
  renderDashboard: re
202
202
  })
@@ -33,6 +33,7 @@ var l = e()((e) => ({
33
33
  isRunning: !1,
34
34
  previewOpen: !1,
35
35
  hasRun: !1,
36
+ richMounted: !1,
36
37
  prompt: "",
37
38
  steps: { ...r },
38
39
  entityIds: {},
@@ -81,7 +82,8 @@ var l = e()((e) => ({
81
82
  };
82
83
  }),
83
84
  openPreview: () => e({ previewOpen: !0 }),
84
- closePreview: () => e({ previewOpen: !1 })
85
+ closePreview: () => e({ previewOpen: !1 }),
86
+ setRichMounted: (t) => e({ richMounted: t })
85
87
  }));
86
88
  //#endregion
87
89
  export { t as PREVIEW_STEPS, n as PREVIEW_STEP_LABEL, l as useAssistantRunStore };
@@ -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 prompt: string;\n steps: Record<PreviewStepKey, PreviewStepStatus>;\n /** Ids harvested from the agent's links. A widget has no link of its own. */\n entityIds: { datasink?: string; dashboard?: string; parser?: string };\n /** Newest \"⏳ …\" line — what the agent says it is doing this second. */\n caption: string;\n /** Set when the turn fails, so the panel can stop pretending to be live. */\n error: string | null;\n /** Epoch ms of the last progress tick — drives the \"updated 3s ago\" strip. */\n updatedAt: number | null;\n\n startRun: (prompt: string) => void;\n applyProgress: (content: string) => void;\n finishRun: (error: string | null) => void;\n openPreview: () => void;\n closePreview: () => void;\n}\n\nconst IDLE_STEPS: Record<PreviewStepKey, PreviewStepStatus> = {\n datasink: 'pending',\n dashboard: 'pending',\n parser: 'pending',\n widget: 'pending',\n};\n\n/** `/bigconsole/datasinks/<id>` — the trustworthy part of the narration. */\nconst ENTITY_LINK = /\\/bigconsole\\/(datasinks|dashboards|parsers)\\/([A-Za-z0-9_-]+)/g;\n/** The widget links to its dashboard, so it can only be detected from the prose. */\nconst WIDGET_DONE = /widget\\s+(added|created)/i;\nconst RUNNING_LINE = /^⏳\\s*(.+?)…?$/;\n\nconst LINK_TO_STEP: Record<string, PreviewStepKey> = {\n datasinks: 'datasink',\n dashboards: 'dashboard',\n parsers: 'parser',\n};\n\n/** Only ever moves a step forward. */\nfunction advance(current: PreviewStepStatus, next: PreviewStepStatus): PreviewStepStatus {\n const rank: Record<PreviewStepStatus, number> = { pending: 0, running: 1, skipped: 2, done: 3 };\n return rank[next] > rank[current] ? next : current;\n}\n\nexport const useAssistantRunStore = create<AssistantRunState>()((set) => ({\n isRunning: false,\n previewOpen: false,\n hasRun: false,\n prompt: '',\n steps: { ...IDLE_STEPS },\n entityIds: {},\n caption: '',\n error: null,\n updatedAt: null,\n\n startRun: (prompt) =>\n set({\n isRunning: true,\n // Auto-open: the whole point is that you SEE the build happen without\n // having to go looking for it.\n previewOpen: true,\n hasRun: true,\n prompt,\n steps: { ...IDLE_STEPS, datasink: 'running' },\n entityIds: {},\n caption: '',\n error: null,\n updatedAt: Date.now(),\n }),\n\n applyProgress: (content) =>\n set((state) => {\n const steps = { ...state.steps };\n const entityIds = { ...state.entityIds };\n\n for (const match of content.matchAll(ENTITY_LINK)) {\n const step = LINK_TO_STEP[match[1] ?? ''];\n const id = match[2];\n if (!step || !id) continue;\n steps[step] = advance(steps[step], 'done');\n if (!entityIds[step as keyof typeof entityIds]) {\n entityIds[step as keyof typeof entityIds] = id;\n }\n }\n\n if (WIDGET_DONE.test(content)) {\n steps.widget = advance(steps.widget, 'done');\n // The parser is optional — the agent skips it when the rows are already\n // shaped. If the widget landed and no parser link ever appeared, it was\n // skipped, not stuck.\n if (steps.parser !== 'done') steps.parser = advance(steps.parser, 'skipped');\n }\n\n // The first step that has not finished is the one being worked on.\n const active = PREVIEW_STEPS.find((key) => steps[key] === 'pending' || steps[key] === 'running');\n if (active) steps[active] = advance(steps[active], 'running');\n\n const caption =\n content\n .split('\\n')\n .map((line) => line.trim())\n .reverse()\n .map((line) => RUNNING_LINE.exec(line)?.[1])\n .find((label): label is string => Boolean(label)) ?? state.caption;\n\n return { steps, entityIds, caption, updatedAt: Date.now() };\n }),\n\n finishRun: (error) =>\n set((state) => {\n const steps = { ...state.steps };\n if (!error) {\n // A clean finish means nothing is still in flight. Anything left running\n // never produced a link — mark it skipped rather than leaving a spinner\n // on screen forever.\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running' || steps[key] === 'pending') steps[key] = 'skipped';\n }\n } else {\n for (const key of PREVIEW_STEPS) {\n if (steps[key] === 'running') steps[key] = 'pending';\n }\n }\n return { isRunning: false, error, steps, caption: '', updatedAt: Date.now() };\n }),\n\n openPreview: () => set({ previewOpen: true }),\n closePreview: () => set({ previewOpen: false }),\n}));\n"],"mappings":";;AAwBA,IAAa,IAA2C;CAAC;CAAY;CAAa;CAAU;CAAS,EAExF,IAAqD;CAChE,UAAU;CACV,WAAW;CACX,QAAQ;CACR,QAAQ;CACT,EA2BK,IAAwD;CAC5D,UAAU;CACV,WAAW;CACX,QAAQ;CACR,QAAQ;CACT,EAGK,IAAc,mEAEd,IAAc,6BACd,IAAe,iBAEf,IAA+C;CACnD,WAAW;CACX,YAAY;CACZ,SAAS;CACV;AAGD,SAAS,EAAQ,GAA4B,GAA4C;CACvF,IAAM,IAA0C;EAAE,SAAS;EAAG,SAAS;EAAG,SAAS;EAAG,MAAM;EAAG;AAC/F,QAAO,EAAK,KAAQ,EAAK,KAAW,IAAO;;AAG7C,IAAa,IAAuB,GAA2B,EAAE,OAAS;CACxE,WAAW;CACX,aAAa;CACb,QAAQ;CACR,QAAQ;CACR,OAAO,EAAE,GAAG,GAAY;CACxB,WAAW,EAAE;CACb,SAAS;CACT,OAAO;CACP,WAAW;CAEX,WAAW,MACT,EAAI;EACF,WAAW;EAGX,aAAa;EACb,QAAQ;EACR;EACA,OAAO;GAAE,GAAG;GAAY,UAAU;GAAW;EAC7C,WAAW,EAAE;EACb,SAAS;EACT,OAAO;EACP,WAAW,KAAK,KAAK;EACtB,CAAC;CAEJ,gBAAgB,MACd,GAAK,MAAU;EACb,IAAM,IAAQ,EAAE,GAAG,EAAM,OAAO,EAC1B,IAAY,EAAE,GAAG,EAAM,WAAW;AAExC,OAAK,IAAM,KAAS,EAAQ,SAAS,EAAY,EAAE;GACjD,IAAM,IAAO,EAAa,EAAM,MAAM,KAChC,IAAK,EAAM;AACb,IAAC,KAAQ,CAAC,MACd,EAAM,KAAQ,EAAQ,EAAM,IAAO,OAAO,EACrC,EAAU,OACb,EAAU,KAAkC;;AAIhD,EAAI,EAAY,KAAK,EAAQ,KAC3B,EAAM,SAAS,EAAQ,EAAM,QAAQ,OAAO,EAIxC,EAAM,WAAW,WAAQ,EAAM,SAAS,EAAQ,EAAM,QAAQ,UAAU;EAI9E,IAAM,IAAS,EAAc,MAAM,MAAQ,EAAM,OAAS,aAAa,EAAM,OAAS,UAAU;AAWhG,SAVI,MAAQ,EAAM,KAAU,EAAQ,EAAM,IAAS,UAAU,GAUtD;GAAE;GAAO;GAAW,SAPzB,EACG,MAAM,KAAK,CACX,KAAK,MAAS,EAAK,MAAM,CAAC,CAC1B,SAAS,CACT,KAAK,MAAS,EAAa,KAAK,EAAK,GAAG,GAAG,CAC3C,MAAM,MAA2B,EAAQ,EAAO,IAAI,EAAM;GAE3B,WAAW,KAAK,KAAK;GAAE;GAC3D;CAEJ,YAAY,MACV,GAAK,MAAU;EACb,IAAM,IAAQ,EAAE,GAAG,EAAM,OAAO;AAChC,MAAK,EAQH,MAAK,IAAM,KAAO,EAChB,CAAI,EAAM,OAAS,cAAW,EAAM,KAAO;YALxC,IAAM,KAAO,EAChB,EAAI,EAAM,OAAS,aAAa,EAAM,OAAS,eAAW,EAAM,KAAO;AAO3E,SAAO;GAAE,WAAW;GAAO;GAAO;GAAO,SAAS;GAAI,WAAW,KAAK,KAAK;GAAE;GAC7E;CAEJ,mBAAmB,EAAI,EAAE,aAAa,IAAM,CAAC;CAC7C,oBAAoB,EAAI,EAAE,aAAa,IAAO,CAAC;CAChD,EAAE"}
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,3 +1,4 @@
1
1
  import { gatherPageContext as e, resolveBigConsoleConversationContext as t } from "./pageContext.js";
2
2
  import { useSandboxAssistantTransport as n } from "./createSandboxAssistantTransport.js";
3
- export { e as gatherPageContext, t as resolveBigConsoleConversationContext, n as useSandboxAssistantTransport };
3
+ import r from "../components/preview/AiPreviewMiniPanel.js";
4
+ export { r as AiPreviewMiniPanel, e as gatherPageContext, t as resolveBigConsoleConversationContext, n as useSandboxAssistantTransport };
@@ -5,9 +5,9 @@ import { CrossFilterProvider as n } from "../../contexts/CrossFilterContext.js";
5
5
  import "../../contexts/index.js";
6
6
  import { useBigConsole as r } from "../../context/BigConsoleContext.js";
7
7
  import "../../context/index.js";
8
- import i from "../../hooks/useWidgetOperations.js";
9
- import { getWidgetDefinition as a } from "../widgets/WidgetRegistry.js";
10
- import o from "../../hooks/useWidgetCatalog.js";
8
+ import { getWidgetDefinition as i } from "../widgets/WidgetRegistry.js";
9
+ import a from "../../hooks/useWidgetCatalog.js";
10
+ import o from "../../hooks/useWidgetOperations.js";
11
11
  import s from "../../hooks/useDashboardImageExport.js";
12
12
  import { useInstalledWidgets as c } from "../../hooks/useInstalledWidgets.js";
13
13
  import l from "./DashboardGrid.js";
@@ -32,14 +32,14 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
32
32
  apiGatewayUrl: P,
33
33
  authToken: F,
34
34
  enabled: M
35
- }), { definitions: Ee } = o({
35
+ }), { definitions: Ee } = a({
36
36
  workspaceId: N,
37
37
  apiGatewayUrl: P,
38
38
  authToken: F,
39
39
  enabled: M
40
40
  }), I = t((e) => e.zoomLevel), L = t((e) => e.sidebarCollapsed), R = t((e) => e.toggleSidebar), z = !L, B = t((e) => e.setGlobalFilterValues), De = p((e) => {
41
41
  B(e);
42
- }, [B]), { createWidget: V } = i(v, f, { skipFetch: !0 }), H = e((e) => e.widgets), U = ce(() => {
42
+ }, [B]), { createWidget: V } = o(v, f, { skipFetch: !0 }), H = e((e) => e.widgets), U = ce(() => {
43
43
  let e = Array.from(H.values());
44
44
  return v ? e.filter((e) => e.pageId === v) : e;
45
45
  }, [H, v]), Oe = e((e) => e.selectedWidgetId), W = e((e) => e.selectWidget), G = e((e) => e.addWidget), K = e((e) => e.updateWidgetPosition), q = e((e) => e.configPanelOpen), ke = e((e) => e.closeConfigPanel);
@@ -68,14 +68,14 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
68
68
  W(e);
69
69
  }, [W]), Y = p(async (e, t) => {
70
70
  D(null);
71
- let n = t?.definition || (e.type ? a(e.type) : null), r = {
71
+ let n = t?.definition || (e.type ? i(e.type) : null), r = {
72
72
  width: n?.defaultSize?.width ?? 4,
73
73
  height: n?.defaultSize?.height ?? 4
74
- }, i;
75
- if (t?.position) i = t.position;
74
+ }, a;
75
+ if (t?.position) a = t.position;
76
76
  else {
77
77
  let { x: e, y: t } = oe(U, r, { cols: 12 });
78
- i = {
78
+ a = {
79
79
  x: e,
80
80
  y: t,
81
81
  width: r.width,
@@ -93,7 +93,7 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
93
93
  renderer: e.renderer,
94
94
  template: e.template,
95
95
  config: e.config || {},
96
- position: i,
96
+ position: a,
97
97
  refreshInterval: e.refreshInterval ?? void 0,
98
98
  dataSinkId: o.dataSinkId,
99
99
  parserId: o.parserId
@@ -116,11 +116,11 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
116
116
  template: e.template,
117
117
  refreshInterval: e.refreshInterval,
118
118
  config: e.config || {},
119
- position: i,
120
- positionX: i.x,
121
- positionY: i.y,
122
- positionWidth: i.width,
123
- positionHeight: i.height,
119
+ position: a,
120
+ positionX: a.x,
121
+ positionY: a.y,
122
+ positionWidth: a.width,
123
+ positionHeight: a.height,
124
124
  dataSinkId: o.dataSinkId,
125
125
  parserId: o.parserId,
126
126
  translations: [],
@@ -146,19 +146,19 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
146
146
  if (!t) return;
147
147
  let n = JSON.parse(t);
148
148
  if (!n || typeof n != "object" || !("type" in n)) return;
149
- let r = n, i = a(r.type), o = null;
149
+ let r = n, a = i(r.type), o = null;
150
150
  r.defaultConfig && r.category && (o = {
151
- ...i,
152
- name: r.name || i.name,
151
+ ...a,
152
+ name: r.name || a.name,
153
153
  category: r.category,
154
154
  defaultConfig: r.defaultConfig,
155
- defaultSize: r.defaultSize || i.defaultSize
155
+ defaultSize: r.defaultSize || a.defaultSize
156
156
  });
157
157
  let s = S.current?.getBoundingClientRect();
158
158
  if (!s) return;
159
159
  let c = {
160
- width: i.defaultSize.width,
161
- height: i.defaultSize.height
160
+ width: a.defaultSize.width,
161
+ height: a.defaultSize.height
162
162
  }, { x: l, y: u } = ae(e.clientX, e.clientY, s, w, c, { cols: 12 }), d = {
163
163
  x: l,
164
164
  y: u,
@@ -167,8 +167,8 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
167
167
  };
168
168
  Y({
169
169
  type: r.type,
170
- title: r.name || i.name,
171
- config: r.defaultConfig || i.defaultConfig
170
+ title: r.name || a.name,
171
+ config: r.defaultConfig || a.defaultConfig
172
172
  }, {
173
173
  position: d,
174
174
  definition: o
@@ -179,7 +179,7 @@ var v = f(function({ dashboardId: f, pageId: v, title: y, onPaste: le, onSave: u
179
179
  }, [w, Y]), Pe = p((e) => {
180
180
  e.preventDefault(), e.dataTransfer.dropEffect = "copy";
181
181
  }, []), Fe = p((e, t) => {
182
- let n = t || a(e);
182
+ let n = t || i(e);
183
183
  Y({
184
184
  type: e,
185
185
  title: n.name,
@@ -0,0 +1,116 @@
1
+ import e from "../datasink/DataSinkTableViewer.js";
2
+ import t from "../../hooks/useWidgetOperations.js";
3
+ import n from "../../hooks/useDataSinkOperations.js";
4
+ import r from "../../hooks/useParserOperations.js";
5
+ import i from "../widgets/WidgetWrapper.js";
6
+ import a from "../DrilldownDashboardRenderer.js";
7
+ import { useEffect as o, useRef as s, useState as c } from "react";
8
+ import { Spinner as l } from "@burdenoff/fe-libs/ui";
9
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
10
+ //#region src/bigconsole/components/preview/AiPreviewContent.tsx
11
+ var f = 4e3, p = 6e4;
12
+ function m({ label: e }) {
13
+ return /* @__PURE__ */ d("div", {
14
+ className: "flex items-center gap-2 px-1 py-8 text-sm text-text-muted",
15
+ children: [/* @__PURE__ */ u(l, { className: "size-4" }), e]
16
+ });
17
+ }
18
+ function h({ text: e }) {
19
+ return /* @__PURE__ */ u("p", {
20
+ className: "px-1 py-8 text-sm text-text-muted",
21
+ children: e
22
+ });
23
+ }
24
+ function g(e, t, n) {
25
+ let [r, i] = c(null), [a, l] = c(!0), u = s(e);
26
+ return u.current = e, o(() => {
27
+ if (!t) {
28
+ l(!1);
29
+ return;
30
+ }
31
+ let e = !1, r = Date.now(), a = async () => {
32
+ let t = await u.current();
33
+ e || (t != null && i(t), l(!1));
34
+ };
35
+ a();
36
+ let o = window.setInterval(() => {
37
+ !n || Date.now() - r > p || typeof document < "u" && document.visibilityState !== "visible" || a();
38
+ }, f);
39
+ return () => {
40
+ e = !0, window.clearInterval(o);
41
+ };
42
+ }, [t, n]), {
43
+ data: r,
44
+ loading: a
45
+ };
46
+ }
47
+ function _({ id: t, live: r }) {
48
+ let { getDataSinkData: i } = n({ skipFetch: !0 }), { data: a, loading: o } = g(async () => {
49
+ let e = await i({ id: t }, { limit: 50 });
50
+ return e ? e.raw : null;
51
+ }, t, r);
52
+ return t ? o ? /* @__PURE__ */ u(m, { label: "Loading the data sink’s data…" }) : /* @__PURE__ */ u("div", {
53
+ "data-testid": "ai-preview-content-datasink",
54
+ children: /* @__PURE__ */ u(e, {
55
+ data: a,
56
+ maxHeight: "300px"
57
+ })
58
+ }) : /* @__PURE__ */ u(h, { text: "Waiting for the data sink to be created…" });
59
+ }
60
+ function v({ dashboardId: e }) {
61
+ return e ? /* @__PURE__ */ u("div", {
62
+ "data-testid": "ai-preview-content-dashboard",
63
+ className: "max-h-[340px] overflow-y-auto",
64
+ children: /* @__PURE__ */ u(a, {
65
+ dashboardId: e,
66
+ params: {}
67
+ })
68
+ }) : /* @__PURE__ */ u(h, { text: "Waiting for the dashboard to be created…" });
69
+ }
70
+ function y({ parserId: t, live: n }) {
71
+ let { executeParser: i } = r({ skipFetch: !0 }), { data: a, loading: o } = g(async () => {
72
+ let e = await i(t ?? "");
73
+ return e ? e.output : null;
74
+ }, t, n);
75
+ return t ? o ? /* @__PURE__ */ u(m, { label: "Running the parser…" }) : /* @__PURE__ */ d("div", {
76
+ "data-testid": "ai-preview-content-parser",
77
+ children: [/* @__PURE__ */ u("p", {
78
+ className: "mb-2 px-1 text-xs text-text-muted",
79
+ children: "Parser output (transformed rows):"
80
+ }), /* @__PURE__ */ u(e, {
81
+ data: a,
82
+ maxHeight: "280px"
83
+ })]
84
+ }) : /* @__PURE__ */ u(h, { text: "No parser in this build — the rows were already shaped." });
85
+ }
86
+ function b(e) {
87
+ let t = e.toUpperCase();
88
+ return t.includes("METRIC") || t.includes("KPI") || t.includes("CARD") || t.includes("STAT") ? 160 : t.includes("TABLE") || t.includes("LIST") ? 280 : 320;
89
+ }
90
+ function x({ dashboardId: e, live: n }) {
91
+ let { widgets: r, loading: a, refetch: c } = t(void 0, e, { skipFetch: !e }), l = s(c);
92
+ return l.current = c, o(() => {
93
+ if (!e) return;
94
+ let t = Date.now(), r = window.setInterval(() => {
95
+ !n || Date.now() - t > p || typeof document < "u" && document.visibilityState !== "visible" || l.current();
96
+ }, f);
97
+ return () => window.clearInterval(r);
98
+ }, [e, n]), e ? a && r.length === 0 ? /* @__PURE__ */ u(m, { label: "Loading widgets…" }) : r.length === 0 ? /* @__PURE__ */ u(h, { text: "No widgets on this dashboard yet." }) : /* @__PURE__ */ u("div", {
99
+ "data-testid": "ai-preview-content-widget",
100
+ className: "space-y-3",
101
+ children: r.map((e) => /* @__PURE__ */ u("div", {
102
+ className: "overflow-hidden rounded-md border border-border-subtle bg-bg-surface",
103
+ style: { height: b(e.type) },
104
+ children: /* @__PURE__ */ u(i, {
105
+ widget: e,
106
+ isLoading: !1,
107
+ error: null,
108
+ filterValues: {}
109
+ })
110
+ }, e.id))
111
+ }) : /* @__PURE__ */ u(h, { text: "Waiting for the dashboard — widgets live on it." });
112
+ }
113
+ //#endregion
114
+ export { v as DashboardContent, _ as DataSinkContent, y as ParserContent, x as WidgetsContent };
115
+
116
+ //# sourceMappingURL=AiPreviewContent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiPreviewContent.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewContent.tsx"],"sourcesContent":["/**\n * Live content renderers for the AI preview tabs.\n *\n * Each tab shows the REAL content of the entity the assistant just built —\n * the data sink's rows, the live dashboard, the parser's transformed output,\n * the rendered widget charts — not a list with an Open button. They reuse the\n * same components the real pages use (DataSinkTableViewer, DrilldownDashboard-\n * Renderer, WidgetWrapper) so what you see in the preview is what you get.\n *\n * These require Apollo + the BigConsole providers, which is why they live only\n * in the full AiPreviewPanel (mounted inside BigConsoleRoot), never in the\n * store-only app-shell mini panel.\n */\nimport { useEffect, useRef, useState } from 'react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\n\nimport { DataSinkTableViewer } from '../datasink/DataSinkTableViewer';\nimport { DrilldownDashboardRenderer } from '../DrilldownDashboardRenderer';\nimport { WidgetWrapper } from '../widgets/WidgetWrapper';\nimport { useDataSinkOperations } from '../../hooks/useDataSinkOperations';\nimport { useParserOperations } from '../../hooks/useParserOperations';\nimport { useWidgetOperations } from '../../hooks/useWidgetOperations';\n\n/** While the build is live, re-pull content on this cadence so it fills in. */\nconst CONTENT_POLL_MS = 4000;\n/** Stop the live re-pull after this long even if the turn is still running. */\nconst CONTENT_POLL_WINDOW_MS = 60_000;\n\nfunction CenteredSpinner({ label }: { label: string }) {\n return (\n <div className=\"flex items-center gap-2 px-1 py-8 text-sm text-text-muted\">\n <Spinner className=\"size-4\" />\n {label}\n </div>\n );\n}\n\nfunction Waiting({ text }: { text: string }) {\n return <p className=\"px-1 py-8 text-sm text-text-muted\">{text}</p>;\n}\n\n/**\n * Fetch-on-mount, then re-fetch every CONTENT_POLL_MS while `live`, bounded to\n * CONTENT_POLL_WINDOW_MS. Returns { data, loading, done }. `fetcher` must be\n * stable-ish; we hold it in a ref so the effect keys only on `key`/`live`.\n */\nfunction useLiveContent<T>(fetcher: () => Promise<T | null>, key: string | undefined, live: boolean) {\n const [data, setData] = useState<T | null>(null);\n const [loading, setLoading] = useState(true);\n const fetcherRef = useRef(fetcher);\n fetcherRef.current = fetcher;\n\n useEffect(() => {\n if (!key) {\n setLoading(false);\n return;\n }\n let cancelled = false;\n const startedAt = Date.now();\n const pull = async () => {\n const result = await fetcherRef.current();\n if (cancelled) return;\n if (result !== null && result !== undefined) setData(result);\n setLoading(false);\n };\n void pull();\n const intervalId = window.setInterval(() => {\n if (!live || Date.now() - startedAt > CONTENT_POLL_WINDOW_MS) return;\n if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return;\n void pull();\n }, CONTENT_POLL_MS);\n return () => {\n cancelled = true;\n window.clearInterval(intervalId);\n };\n }, [key, live]);\n\n return { data, loading };\n}\n\nexport function DataSinkContent({ id, live }: { id?: string; live: boolean }) {\n const { getDataSinkData } = useDataSinkOperations({ skipFetch: true });\n const { data, loading } = useLiveContent(\n async () => {\n const res = await getDataSinkData({ id }, { limit: 50 });\n return res ? res.raw : null;\n },\n id,\n live\n );\n\n if (!id) return <Waiting text=\"Waiting for the data sink to be created…\" />;\n if (loading) return <CenteredSpinner label=\"Loading the data sink’s data…\" />;\n return (\n <div data-testid=\"ai-preview-content-datasink\">\n <DataSinkTableViewer data={data} maxHeight=\"300px\" />\n </div>\n );\n}\n\nexport function DashboardContent({ dashboardId }: { dashboardId?: string }) {\n if (!dashboardId) return <Waiting text=\"Waiting for the dashboard to be created…\" />;\n return (\n <div data-testid=\"ai-preview-content-dashboard\" className=\"max-h-[340px] overflow-y-auto\">\n <DrilldownDashboardRenderer dashboardId={dashboardId} params={{}} />\n </div>\n );\n}\n\nexport function ParserContent({ parserId, live }: { parserId?: string; live: boolean }) {\n const { executeParser } = useParserOperations({ skipFetch: true });\n const { data, loading } = useLiveContent(\n async () => {\n const res = await executeParser(parserId ?? '');\n return res ? res.output : null;\n },\n parserId,\n live\n );\n\n if (!parserId) return <Waiting text=\"No parser in this build — the rows were already shaped.\" />;\n if (loading) return <CenteredSpinner label=\"Running the parser…\" />;\n return (\n <div data-testid=\"ai-preview-content-parser\">\n <p className=\"mb-2 px-1 text-xs text-text-muted\">Parser output (transformed rows):</p>\n <DataSinkTableViewer data={data} maxHeight=\"280px\" />\n </div>\n );\n}\n\n/** Recharts needs a definite pixel height or it renders 0px tall. */\nfunction widgetBoxHeight(type: string): number {\n const t = type.toUpperCase();\n if (t.includes('METRIC') || t.includes('KPI') || t.includes('CARD') || t.includes('STAT')) return 160;\n if (t.includes('TABLE') || t.includes('LIST')) return 280;\n return 320;\n}\n\nexport function WidgetsContent({ dashboardId, live }: { dashboardId?: string; live: boolean }) {\n const { widgets, loading, refetch } = useWidgetOperations(undefined, dashboardId, {\n skipFetch: !dashboardId,\n });\n\n const refetchRef = useRef(refetch);\n refetchRef.current = refetch;\n useEffect(() => {\n if (!dashboardId) return;\n const startedAt = Date.now();\n const intervalId = window.setInterval(() => {\n if (!live || Date.now() - startedAt > CONTENT_POLL_WINDOW_MS) return;\n if (typeof document !== 'undefined' && document.visibilityState !== 'visible') return;\n void refetchRef.current();\n }, CONTENT_POLL_MS);\n return () => window.clearInterval(intervalId);\n }, [dashboardId, live]);\n\n if (!dashboardId) return <Waiting text=\"Waiting for the dashboard — widgets live on it.\" />;\n if (loading && widgets.length === 0) return <CenteredSpinner label=\"Loading widgets…\" />;\n if (widgets.length === 0) return <Waiting text=\"No widgets on this dashboard yet.\" />;\n\n return (\n <div data-testid=\"ai-preview-content-widget\" className=\"space-y-3\">\n {widgets.map((widget) => (\n <div\n key={widget.id}\n className=\"overflow-hidden rounded-md border border-border-subtle bg-bg-surface\"\n style={{ height: widgetBoxHeight(widget.type) }}\n >\n <WidgetWrapper widget={widget} isLoading={false} error={null} filterValues={{}} />\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ import { useAssistantRunStore as e } from "../../assistant/assistantRunStore.js";
2
+ import { StepRail as t, formatAgo as n, useAssistantSidebarWidth as r } from "./previewShared.js";
3
+ import { useEffect as i, useState as a } from "react";
4
+ import { ArrowUpRight as o, Loader2 as s, Sparkles as c, X as l } from "lucide-react";
5
+ import { Button as u } from "@burdenoff/fe-libs/ui";
6
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
7
+ //#region src/bigconsole/components/preview/AiPreviewMiniPanel.tsx
8
+ function p({ onNavigate: p }) {
9
+ let m = e((e) => e.isRunning), h = e((e) => e.previewOpen), g = e((e) => e.hasRun), _ = e((e) => e.richMounted), v = e((e) => e.steps), y = e((e) => e.entityIds), b = e((e) => e.caption), x = e((e) => e.error), S = e((e) => e.updatedAt), C = e((e) => e.openPreview), w = e((e) => e.closePreview), [T, E] = a(() => Date.now());
10
+ i(() => {
11
+ if (!m) return;
12
+ let e = window.setInterval(() => E(Date.now()), 1e3);
13
+ return () => window.clearInterval(e);
14
+ }, [m]);
15
+ let D = r(), O = (e) => {
16
+ p ? p(e) : typeof window < "u" && window.location.assign(e);
17
+ };
18
+ if (!g || _) return null;
19
+ if (!h) return /* @__PURE__ */ d("div", {
20
+ className: "flex justify-end px-4 pt-1 md:px-6",
21
+ style: { marginRight: D },
22
+ children: /* @__PURE__ */ f(u, {
23
+ size: "sm",
24
+ variant: "outline",
25
+ onClick: C,
26
+ "data-testid": "ai-preview-open",
27
+ children: [
28
+ /* @__PURE__ */ d(c, { className: "mr-1.5 size-4" }),
29
+ "Preview",
30
+ m ? /* @__PURE__ */ d(s, { className: "ml-1.5 size-3.5 animate-spin" }) : null
31
+ ]
32
+ })
33
+ });
34
+ let k = S ? Math.max(0, Math.floor((T - S) / 1e3)) : 0, A = [];
35
+ return y.datasink && A.push({
36
+ key: "datasink",
37
+ label: "Data Sink",
38
+ path: `/bigconsole/datasinks/${y.datasink}`
39
+ }), y.dashboard && A.push({
40
+ key: "dashboard",
41
+ label: "Dashboard",
42
+ path: `/bigconsole/dashboards/${y.dashboard}`
43
+ }), y.parser && A.push({
44
+ key: "parser",
45
+ label: "Parser",
46
+ path: `/bigconsole/parsers/${y.parser}`
47
+ }), /* @__PURE__ */ f("section", {
48
+ "data-testid": "ai-preview-panel",
49
+ "data-variant": "mini",
50
+ "aria-label": "AI preview",
51
+ style: { marginRight: D ? D + 16 : void 0 },
52
+ className: "mx-4 mt-1 mb-3 overflow-hidden rounded-lg border border-border-default bg-bg-sunken shadow-sm md:mx-6",
53
+ children: [
54
+ /* @__PURE__ */ f("div", {
55
+ className: "flex items-center justify-between gap-3 border-b border-border-subtle bg-bg-surface px-3 py-2",
56
+ children: [/* @__PURE__ */ f("div", {
57
+ className: "flex min-w-0 items-center gap-2",
58
+ children: [
59
+ /* @__PURE__ */ d(c, { className: "size-4 shrink-0 text-action-primary-bg" }),
60
+ /* @__PURE__ */ d("h2", {
61
+ className: "truncate text-sm font-semibold text-text-primary",
62
+ children: "AI Preview"
63
+ }),
64
+ m ? /* @__PURE__ */ f("span", {
65
+ 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",
66
+ "data-testid": "ai-preview-live",
67
+ children: [/* @__PURE__ */ f("span", {
68
+ className: "relative flex size-2",
69
+ children: [/* @__PURE__ */ d("span", { className: "absolute inline-flex size-full animate-ping rounded-full bg-status-success-text opacity-75" }), /* @__PURE__ */ d("span", { className: "relative inline-flex size-2 rounded-full bg-status-success-text" })]
70
+ }), "Live"]
71
+ }) : null,
72
+ /* @__PURE__ */ f("span", {
73
+ className: "truncate text-xs text-text-muted",
74
+ "data-testid": "ai-preview-ago",
75
+ children: [
76
+ m ? b || "Working…" : x ? "Stopped" : "Done",
77
+ " · updated",
78
+ " ",
79
+ n(k)
80
+ ]
81
+ })
82
+ ]
83
+ }), /* @__PURE__ */ d(u, {
84
+ size: "sm",
85
+ variant: "ghost",
86
+ onClick: w,
87
+ "aria-label": "Close preview",
88
+ "data-testid": "ai-preview-close",
89
+ children: /* @__PURE__ */ d(l, { className: "size-4" })
90
+ })]
91
+ }),
92
+ /* @__PURE__ */ d("div", {
93
+ className: "border-b border-border-subtle px-3 py-2",
94
+ children: /* @__PURE__ */ d(t, { steps: v })
95
+ }),
96
+ /* @__PURE__ */ f("div", {
97
+ className: "space-y-2 p-3",
98
+ children: [A.length === 0 ? /* @__PURE__ */ d("p", {
99
+ className: "px-1 py-4 text-sm text-text-muted",
100
+ children: m ? "Building… links appear here as each piece is created." : "Nothing built yet."
101
+ }) : A.map((e) => /* @__PURE__ */ f("div", {
102
+ "data-testid": "ai-preview-row",
103
+ className: "flex items-center justify-between gap-3 rounded-md border border-border-subtle bg-bg-surface px-3 py-2",
104
+ children: [/* @__PURE__ */ f("p", {
105
+ className: "truncate text-sm font-medium text-text-primary",
106
+ children: [e.label, " ready"]
107
+ }), /* @__PURE__ */ f(u, {
108
+ size: "sm",
109
+ variant: "ghost",
110
+ onClick: () => O(e.path),
111
+ children: ["Open", /* @__PURE__ */ d(o, { className: "ml-1 size-3.5" })]
112
+ })]
113
+ }, e.key)), y.dashboard ? /* @__PURE__ */ f(u, {
114
+ size: "sm",
115
+ variant: "outline",
116
+ className: "w-full",
117
+ "data-testid": "ai-preview-open-in-bigconsole",
118
+ onClick: () => O(`/bigconsole/dashboards/${y.dashboard ?? ""}`),
119
+ children: ["Open in BigConsole for the live view", /* @__PURE__ */ d(o, { className: "ml-1 size-3.5" })]
120
+ }) : null]
121
+ }),
122
+ x ? /* @__PURE__ */ d("p", {
123
+ className: "border-t border-border-subtle px-3 py-2 text-xs text-status-error-text",
124
+ "data-testid": "ai-preview-error",
125
+ children: x
126
+ }) : null
127
+ ]
128
+ });
129
+ }
130
+ //#endregion
131
+ export { p as default };
132
+
133
+ //# sourceMappingURL=AiPreviewMiniPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiPreviewMiniPanel.js","names":[],"sources":["../../../../src/bigconsole/components/preview/AiPreviewMiniPanel.tsx"],"sourcesContent":["/**\n * AI Preview — the app-shell copy.\n *\n * The full <AiPreviewPanel> lives inside BigConsoleRoot and only exists on\n * /bigconsole pages, but the assistant widget is global: you can start a build\n * from anywhere. This lightweight panel is mounted by the app shell so the build\n * is visible on WHATEVER page you are on — workflows, store, workspaces, search.\n *\n * It is deliberately store-only: no Apollo, no BigConsole context, no data\n * fetching. That is what lets it mount above the router. It shows the same live\n * step rail and caption as the full panel, plus click-through links to each\n * entity as it lands. The rich per-entity tabs stay on the /bigconsole pages,\n * where the data layer actually exists.\n *\n * It renders nothing while the full panel is up (`richMounted`), so the two\n * never stack. Leave a /bigconsole page mid-run and the full panel unmounts,\n * `richMounted` flips false, and this one takes over without a flicker.\n */\nimport { useEffect, useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { ArrowUpRight, Loader2, Sparkles, X } from 'lucide-react';\n\nimport { useAssistantRunStore } from '../../assistant/assistantRunStore';\nimport { StepRail, formatAgo, useAssistantSidebarWidth } from './previewShared';\n\ninterface AiPreviewMiniPanelProps {\n /**\n * Route to an in-app path. The links are absolute (`/bigconsole/...`) because\n * this panel renders outside the BigConsole router. Falls back to a full\n * navigation when the host does not supply a handler.\n */\n onNavigate?: (path: string) => void;\n}\n\ninterface QuickLink {\n key: string;\n label: string;\n path: string;\n}\n\nexport function AiPreviewMiniPanel({ onNavigate }: AiPreviewMiniPanelProps) {\n const isRunning = useAssistantRunStore((state) => state.isRunning);\n const previewOpen = useAssistantRunStore((state) => state.previewOpen);\n const hasRun = useAssistantRunStore((state) => state.hasRun);\n const richMounted = useAssistantRunStore((state) => state.richMounted);\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 [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (!isRunning) return;\n const id = window.setInterval(() => setNow(Date.now()), 1000);\n return () => window.clearInterval(id);\n }, [isRunning]);\n\n const assistantWidth = useAssistantSidebarWidth();\n\n const go = (path: string) => {\n if (onNavigate) onNavigate(path);\n else if (typeof window !== 'undefined') window.location.assign(path);\n };\n\n // Nothing has run, or the full in-context panel is already on screen.\n if (!hasRun || richMounted) return null;\n\n if (!previewOpen) {\n return (\n <div className=\"flex justify-end px-4 pt-1 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 const agoSeconds = updatedAt ? Math.max(0, Math.floor((now - updatedAt) / 1000)) : 0;\n\n const links: QuickLink[] = [];\n if (entityIds.datasink)\n links.push({ key: 'datasink', label: 'Data Sink', path: `/bigconsole/datasinks/${entityIds.datasink}` });\n if (entityIds.dashboard)\n links.push({ key: 'dashboard', label: 'Dashboard', path: `/bigconsole/dashboards/${entityIds.dashboard}` });\n if (entityIds.parser) links.push({ key: 'parser', label: 'Parser', path: `/bigconsole/parsers/${entityIds.parser}` });\n\n return (\n <section\n data-testid=\"ai-preview-panel\"\n data-variant=\"mini\"\n aria-label=\"AI preview\"\n style={{ marginRight: assistantWidth ? assistantWidth + 16 : undefined }}\n className=\"mx-4 mt-1 mb-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 {/* Quick links to what has been built so far. The full per-entity tabs live\n on the /bigconsole pages; here we surface the direct links instead. */}\n <div className=\"space-y-2 p-3\">\n {links.length === 0 ? (\n <p className=\"px-1 py-4 text-sm text-text-muted\">\n {isRunning ? 'Building… links appear here as each piece is created.' : 'Nothing built yet.'}\n </p>\n ) : (\n links.map((link) => (\n <div\n key={link.key}\n data-testid=\"ai-preview-row\"\n className=\"flex items-center justify-between gap-3 rounded-md border border-border-subtle bg-bg-surface px-3 py-2\"\n >\n <p className=\"truncate text-sm font-medium text-text-primary\">{link.label} ready</p>\n <Button size=\"sm\" variant=\"ghost\" onClick={() => go(link.path)}>\n Open\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n </div>\n ))\n )}\n {entityIds.dashboard ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"w-full\"\n data-testid=\"ai-preview-open-in-bigconsole\"\n onClick={() => go(`/bigconsole/dashboards/${entityIds.dashboard ?? ''}`)}\n >\n Open in BigConsole for the live view\n <ArrowUpRight className=\"ml-1 size-3.5\" />\n </Button>\n ) : null}\n </div>\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 AiPreviewMiniPanel;\n"],"mappings":"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"}