@burdenoff/fluidgrids-fe-sdk 2026.1105.1 → 2026.1105.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +30 -0
  2. package/dist/ExecutionDetailPage-JXGPSLS3.js +30 -0
  3. package/dist/{WorkflowsList-EV725VG5.js → ExecutionsListPage-U3TC5JUI.js} +11 -8
  4. package/dist/{WorkflowBuilder-CRR5WP4V.js → WorkflowBuilder-6DOD6WWH.js} +16 -13
  5. package/dist/{ExecutionsListPage-NQIQXMTY.js → WorkflowsList-XOWRQNST.js} +10 -8
  6. package/dist/{agentTools-PN4W7QZH.js → agentTools-YQBN33WG.js} +7 -5
  7. package/dist/{chunk-VFVN7D3A.js → chunk-22U3TUKZ.js} +3 -93
  8. package/dist/{chunk-XLI75E3D.js → chunk-2WUVF4EQ.js} +2 -2
  9. package/dist/{chunk-GVIYGJQG.js → chunk-3MZOYJ5I.js} +1 -1
  10. package/dist/{chunk-L44BRLW5.js → chunk-3ZYRLGDF.js} +612 -22
  11. package/dist/{chunk-FCT6POFX.js → chunk-4NNF7DIB.js} +1 -1
  12. package/dist/chunk-5DVP2UYH.js +1924 -0
  13. package/dist/{chunk-JDQYGH4Q.js → chunk-EKDUYCTS.js} +4 -4
  14. package/dist/{chunk-6P7DOBVN.js → chunk-EYQ7GJRW.js} +2 -2
  15. package/dist/{chunk-HLAUK5GM.js → chunk-FVWRGL7S.js} +1 -1
  16. package/dist/{chunk-AZKVI4YN.js → chunk-G33OMSW6.js} +8 -6
  17. package/dist/{chunk-D7G5VWSH.js → chunk-G774FI2E.js} +2 -2
  18. package/dist/{chunk-DANOMJ5I.js → chunk-IJ7UIDCW.js} +7 -6
  19. package/dist/{chunk-EC2AN74G.js → chunk-IL343FKX.js} +1 -1
  20. package/dist/chunk-JYYN3CLL.js +32 -0
  21. package/dist/{chunk-O2EB7UT2.js → chunk-KAWRHIJZ.js} +2 -2
  22. package/dist/{chunk-JC4D7RQ6.js → chunk-KN37RIFC.js} +5 -4
  23. package/dist/{chunk-AASHQI77.js → chunk-KZCNJAMK.js} +2 -1
  24. package/dist/{chunk-BHAKNT5M.js → chunk-LDWBMCX4.js} +4 -3
  25. package/dist/{chunk-MSQAULLG.js → chunk-MA6I4KY5.js} +186 -7
  26. package/dist/{chunk-R3Z7W4EX.js → chunk-MP44TAOO.js} +276 -196
  27. package/dist/{chunk-FJSXXO5W.js → chunk-MY7SEUXM.js} +1 -1
  28. package/dist/{chunk-I3QPAEL6.js → chunk-NMQENSPW.js} +15 -14
  29. package/dist/chunk-NZAYYUQ4.js +172 -0
  30. package/dist/{chunk-6EZJ5J3B.js → chunk-NZFPCTIS.js} +4 -4
  31. package/dist/{chunk-MZFJL4X3.js → chunk-OXXXUAWM.js} +1 -1
  32. package/dist/chunk-SDZEAIIE.js +9 -0
  33. package/dist/{chunk-CGR3AEZU.js → chunk-TCIBTMJI.js} +68 -2
  34. package/dist/{chunk-4QB6S7YW.js → chunk-TJAG2DQT.js} +9 -38
  35. package/dist/{chunk-MQN6YDX4.js → chunk-TLHX4DK4.js} +7 -6
  36. package/dist/{chunk-AG3VC7Y2.js → chunk-VESQH55Q.js} +2 -2
  37. package/dist/chunk-WZ3EJVS6.js +93 -0
  38. package/dist/{chunk-OCJTTHTS.js → chunk-Y7FKJJMF.js} +1 -9
  39. package/dist/{chunk-HOTVUVZG.js → chunk-YGOGJDN4.js} +2 -2
  40. package/dist/{chunk-AVHBWJRY.js → chunk-Z3ABLERJ.js} +3 -1
  41. package/dist/{chunk-C26QAWCT.js → chunk-ZBPRNWKY.js} +235 -2146
  42. package/dist/{chunk-2QOAJF7V.js → chunk-ZLBTUCC2.js} +4 -3
  43. package/dist/code-editor-field-WWPDMF4O.js +3 -0
  44. package/dist/executions.js +16 -12
  45. package/dist/history.js +9 -6
  46. package/dist/index.d.ts +77 -1
  47. package/dist/index.js +46 -41
  48. package/dist/{node-detail-view-3VG7XFIZ.js → node-detail-view-U5HOP2WX.js} +35 -9
  49. package/dist/nodes.js +2 -2
  50. package/dist/observability.js +9 -6
  51. package/dist/{quick-config-panel-GB7SDWHD.js → quick-config-panel-XAISBW6Y.js} +10 -7
  52. package/dist/settings.js +9 -6
  53. package/dist/templates.js +15 -12
  54. package/dist/workflow-builder.js +17 -14
  55. package/dist/workflow-preview.d.ts +158 -0
  56. package/dist/workflow-preview.js +721 -0
  57. package/dist/workflows-list.js +10 -8
  58. package/package.json +5 -1
  59. package/dist/ExecutionDetailPage-YBAMVPMB.js +0 -26
  60. package/dist/code-editor-field-XWUVOKTA.js +0 -2
@@ -1,9 +1,10 @@
1
- import { FailureTriagePanel } from './chunk-CGR3AEZU.js';
2
- import { useDataSinks, useDataSinkData, useInsightsData, useRunReplayActions } from './chunk-FCT6POFX.js';
1
+ import { formatBytes, getFailureBreakdown, getTopFailingWorkflows, getTopFailingNodes, percentage, FailureTriagePanel, formatDuration, prettyJson } from './chunk-TCIBTMJI.js';
2
+ import { aiSpendPeriod, AI_SPEND_RANGE_DAYS, evidenceFacts, monthlySavingLabel, evidenceWindowDays, shortRunId, DISMISS_REASONS, MAX_DISMISS_NOTE_LENGTH, aiCost, formatTokens, runsOfWorkflowPath } from './chunk-NZAYYUQ4.js';
3
+ import { useDataSinks, useDataSinkData, useInsightsData, useRunReplayActions } from './chunk-4NNF7DIB.js';
3
4
  import { proposalCanvasPath } from './chunk-GLYINMOR.js';
4
- import { useContinuousOptimisationEnabled } from './chunk-C26QAWCT.js';
5
+ import { useContinuousOptimisationEnabled } from './chunk-ZBPRNWKY.js';
5
6
  import { useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS, SdkPermissionButton } from './chunk-5375NUIO.js';
6
- import { SetContinuousOptimisationEnabledDocument, FgRunOptimisationScanDocument, ProposeOptimisationDocument, WorkflowProposalDocument, DismissOptimisationSuggestionDocument, SnoozeOptimisationSuggestionDocument, OptimisationSuggestionsDocument, ContinuousOptimisationSettingsDocument } from './chunk-AVHBWJRY.js';
7
+ import { SetContinuousOptimisationEnabledDocument, FgRunOptimisationScanDocument, ProposeOptimisationDocument, WorkflowProposalDocument, DismissOptimisationSuggestionDocument, SnoozeOptimisationSuggestionDocument, WorkspaceAiUsageDocument, OptimisationSuggestionsDocument, ContinuousOptimisationSettingsDocument } from './chunk-Z3ABLERJ.js';
7
8
  import { useTr } from './chunk-UULVIT25.js';
8
9
  import { getFluidGridsClient } from './chunk-3IXU7WUC.js';
9
10
  import { throwIfResultError } from './chunk-US5QSLPV.js';
@@ -12,75 +13,10 @@ import { Icon } from '@iconify/react';
12
13
  import { Button } from '@burdenoff/fe-libs/button';
13
14
  import { Input, Button as Button$1, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Label, Textarea, DialogFooter } from '@burdenoff/fe-libs/ui';
14
15
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
15
- import { useNavigate } from 'react-router';
16
+ import { useNavigate, Link } from 'react-router';
16
17
  import { toast } from 'sonner';
17
18
  import { Lightbulb, Scissors, Timer, ShieldCheck, DatabaseZap, BadgeDollarSign, RotateCcw } from 'lucide-react';
18
19
 
19
- // src/modules/observability/utils.ts
20
- function percentage(value, total) {
21
- if (total <= 0) return 0;
22
- return Math.round(value / total * 1e3) / 10;
23
- }
24
- function getFailureBreakdown(runs) {
25
- const counts = /* @__PURE__ */ new Map();
26
- for (const run of runs) {
27
- const key = run.errorType || "UNKNOWN";
28
- counts.set(key, (counts.get(key) ?? 0) + 1);
29
- }
30
- return [...counts.entries()].map(([key, count]) => ({ key, count })).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
31
- }
32
- function getTopFailingWorkflows(runs) {
33
- const counts = /* @__PURE__ */ new Map();
34
- for (const run of runs) {
35
- const current = counts.get(run.workflowKey);
36
- if (current) {
37
- current.count += 1;
38
- } else {
39
- counts.set(run.workflowKey, {
40
- workflowKey: run.workflowKey,
41
- label: run.workflow?.name || run.workflowKey,
42
- count: 1
43
- });
44
- }
45
- }
46
- return [...counts.values()].sort(
47
- (a, b) => b.count - a.count || a.label.localeCompare(b.label)
48
- );
49
- }
50
- function getTopFailingNodes(nodeUsageStats) {
51
- return [...nodeUsageStats].filter((stat) => stat.failureCount > 0).sort(
52
- (a, b) => b.failureCount - a.failureCount || a.nodeType.localeCompare(b.nodeType)
53
- );
54
- }
55
- function formatDuration(seconds) {
56
- if (seconds === null || seconds === void 0 || !Number.isFinite(seconds)) {
57
- return "\u2014";
58
- }
59
- if (seconds < 1) return `${Math.round(seconds * 1e3)}ms`;
60
- if (seconds < 60) return `${seconds.toFixed(1)}s`;
61
- const minutes = Math.floor(seconds / 60);
62
- const remainingSeconds = Math.round(seconds % 60);
63
- return `${minutes}m ${remainingSeconds}s`;
64
- }
65
- function formatBytes(bytes) {
66
- if (!Number.isFinite(bytes) || bytes < 0) return "\u2014";
67
- if (bytes < 1024) return `${bytes} B`;
68
- const units = ["KB", "MB", "GB", "TB"];
69
- let value = bytes / 1024;
70
- let unit = units[0];
71
- for (let i = 1; i < units.length && value >= 1024; i += 1) {
72
- value /= 1024;
73
- unit = units[i];
74
- }
75
- return `${value.toFixed(value >= 10 ? 0 : 1)} ${unit}`;
76
- }
77
- function prettyJson(value) {
78
- try {
79
- return JSON.stringify(value, null, 2) ?? "null";
80
- } catch {
81
- return String(value);
82
- }
83
- }
84
20
  function valueLabel(value) {
85
21
  if (value === null || value === void 0) return "\u2014";
86
22
  if (typeof value === "object") return JSON.stringify(value);
@@ -580,132 +516,6 @@ function useContinuousOptimisation(active) {
580
516
  };
581
517
  }
582
518
 
583
- // src/modules/observability/optimisation.ts
584
- var DEFAULT_WINDOW_DAYS = 30;
585
- var DISMISS_REASONS = [
586
- "NOT_APPLICABLE" /* NotApplicable */,
587
- "ACCEPTED_COST" /* AcceptedCost */,
588
- "WILL_FIX_LATER" /* WillFixLater */,
589
- "OTHER" /* Other */
590
- ];
591
- var MAX_DISMISS_NOTE_LENGTH = 500;
592
- function isRecord(value) {
593
- return typeof value === "object" && value !== null && !Array.isArray(value);
594
- }
595
- function evidenceRecord(evidence) {
596
- if (typeof evidence === "string") {
597
- try {
598
- const parsed = JSON.parse(evidence);
599
- return isRecord(parsed) ? parsed : {};
600
- } catch {
601
- return {};
602
- }
603
- }
604
- return isRecord(evidence) ? evidence : {};
605
- }
606
- function numberAt(evidence, key) {
607
- const value = evidence[key];
608
- return typeof value === "number" && Number.isFinite(value) ? value : null;
609
- }
610
- function formatShare(value) {
611
- return `${Math.round(value * 100)}%`;
612
- }
613
- function formatSeconds(ms) {
614
- const seconds = ms / 1e3;
615
- return `${Number.isInteger(seconds) ? seconds : seconds.toFixed(1)} s`;
616
- }
617
- function formatCount(value) {
618
- return Math.round(value).toLocaleString("en-US");
619
- }
620
- function formatMicroUsd(value) {
621
- const dollars = value / 1e6;
622
- if (dollars > 0 && dollars < 0.01) return "< $0.01";
623
- return `$${dollars.toLocaleString("en-US", {
624
- minimumFractionDigits: 2,
625
- maximumFractionDigits: 2
626
- })}`;
627
- }
628
- function monthlySavingLabel(microUsd) {
629
- if (microUsd === null || microUsd === void 0) return null;
630
- if (!Number.isFinite(microUsd) || microUsd <= 0) return null;
631
- return formatMicroUsd(microUsd);
632
- }
633
- function evidenceWindowDays(evidence) {
634
- const days = numberAt(evidenceRecord(evidence), "windowDays");
635
- return days !== null && days > 0 ? days : DEFAULT_WINDOW_DAYS;
636
- }
637
- var FACTS = {
638
- ["RAISE_TIMEOUT" /* RaiseTimeout */]: [
639
- { id: "executions", format: formatCount },
640
- { id: "timeoutRate", format: formatShare },
641
- { id: "currentTimeoutMs", format: formatSeconds },
642
- {
643
- id: "p95CompletedMs",
644
- format: formatSeconds
645
- },
646
- {
647
- id: "suggestedTimeoutMs",
648
- format: formatSeconds
649
- }
650
- ],
651
- ["ADD_RETRY" /* AddRetry */]: [
652
- { id: "executions", format: formatCount },
653
- {
654
- id: "transientFailures",
655
- format: formatCount
656
- },
657
- {
658
- id: "transientFailureRate",
659
- format: formatShare
660
- }
661
- ],
662
- ["UNUSED_NODE" /* UnusedNode */]: [{ id: "completedRuns", format: formatCount }],
663
- ["ENABLE_CACHE" /* EnableCache */]: [
664
- { id: "calls", format: formatCount },
665
- { id: "repeatedCalls", format: formatCount },
666
- { id: "repeatShare", format: formatShare },
667
- { id: "costMicroUsd", format: formatMicroUsd }
668
- ],
669
- ["CHEAPER_MODEL" /* CheaperModel */]: [],
670
- ["MISSING_GUARDRAIL" /* MissingGuardrail */]: []
671
- };
672
- function evidenceFacts(kind, evidence) {
673
- const record = evidenceRecord(evidence);
674
- const facts = [];
675
- for (const spec of FACTS[kind] ?? []) {
676
- const value = numberAt(record, spec.id);
677
- if (value === null) continue;
678
- facts.push({ id: spec.id, value: spec.format(value) });
679
- }
680
- if (kind === "UNUSED_NODE" /* UnusedNode */) {
681
- const removed = record.removedNodeIds;
682
- if (Array.isArray(removed) && removed.length > 0) {
683
- facts.push({
684
- id: "removedNodeIds",
685
- value: removed.map(String).join(", ")
686
- });
687
- }
688
- }
689
- if (kind === "ADD_RETRY" /* AddRetry */) {
690
- const byType = record.failuresByType;
691
- if (isRecord(byType)) {
692
- const parts = Object.entries(byType).filter(
693
- (entry) => typeof entry[1] === "number" && entry[1] > 0
694
- ).map(([type, count]) => `${type} ${formatCount(count)}`);
695
- if (parts.length > 0) {
696
- facts.push({
697
- id: "failuresByType",
698
- value: parts.join(", ")
699
- });
700
- }
701
- }
702
- }
703
- return facts;
704
- }
705
- function shortRunId(runId) {
706
- return runId.length > 8 ? runId.slice(0, 8) : runId;
707
- }
708
-
709
519
  // src/modules/observability/optimisation-labels.ts
710
520
  function kindLabel(tr, kind) {
711
521
  switch (kind) {
@@ -1339,6 +1149,273 @@ function DismissDialog({
1339
1149
  }
1340
1150
  );
1341
1151
  }
1152
+ async function fetchWorkspaceAiUsage(from, to) {
1153
+ const result = await getFluidGridsClient().query({
1154
+ query: WorkspaceAiUsageDocument,
1155
+ variables: { from, to },
1156
+ fetchPolicy: "network-only"
1157
+ });
1158
+ throwIfResultError(result);
1159
+ return result.data?.workspaceAiUsage ?? null;
1160
+ }
1161
+ function useWorkspaceAiUsage(days) {
1162
+ const [sent, setSent] = useState(0);
1163
+ const [settled, setSettled] = useState(null);
1164
+ const request = useRef(0);
1165
+ const read = useCallback(async () => {
1166
+ const id = ++request.current;
1167
+ setSent(id);
1168
+ const { from, to } = aiSpendPeriod(days);
1169
+ let next;
1170
+ try {
1171
+ next = { usage: await fetchWorkspaceAiUsage(from, to), error: null };
1172
+ } catch (err) {
1173
+ next = {
1174
+ usage: null,
1175
+ error: err instanceof Error ? err.message : String(err)
1176
+ };
1177
+ }
1178
+ if (id !== request.current) return;
1179
+ setSettled({ request: id, ...next });
1180
+ }, [days]);
1181
+ useEffect(() => {
1182
+ void read();
1183
+ }, [read]);
1184
+ const loading = (settled?.request ?? -1) !== sent || sent === 0;
1185
+ return {
1186
+ usage: settled?.usage ?? null,
1187
+ loading,
1188
+ error: settled && settled.request === sent ? settled.error : null,
1189
+ refetch: read
1190
+ };
1191
+ }
1192
+ function rangeLabel(days, tr) {
1193
+ switch (days) {
1194
+ case 7:
1195
+ return tr("fluidgrids.insights.aiSpend.range7", "7 days");
1196
+ case 30:
1197
+ return tr("fluidgrids.insights.aiSpend.range30", "30 days");
1198
+ case 90:
1199
+ return tr("fluidgrids.insights.aiSpend.range90", "90 days");
1200
+ }
1201
+ }
1202
+ function Cost({ usage, tr }) {
1203
+ const cost = aiCost(usage);
1204
+ if (cost.kind === "priced") return /* @__PURE__ */ jsx(Fragment, { children: cost.text });
1205
+ if (cost.kind === "unpriced") {
1206
+ return /* @__PURE__ */ jsx("span", { className: "text-text-secondary", children: tr("fluidgrids.insights.aiSpend.notPricedYet", "Not priced yet") });
1207
+ }
1208
+ return /* @__PURE__ */ jsx(Fragment, { children: "\u2014" });
1209
+ }
1210
+ function AiSpendPanel() {
1211
+ const tr = useTr();
1212
+ const [days, setDays] = useState(7);
1213
+ const { usage, loading, error, refetch } = useWorkspaceAiUsage(days);
1214
+ return /* @__PURE__ */ jsxs(
1215
+ "section",
1216
+ {
1217
+ className: "rounded-xl border border-border-secondary bg-background-secondary p-4 sm:p-5",
1218
+ "aria-label": tr("fluidgrids.insights.aiSpend.title", "AI spend"),
1219
+ "data-testid": "insights-ai-spend",
1220
+ children: [
1221
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
1222
+ /* @__PURE__ */ jsxs("div", { children: [
1223
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold text-text-primary", children: tr("fluidgrids.insights.aiSpend.title", "AI spend") }),
1224
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-text-secondary", children: tr(
1225
+ "fluidgrids.insights.aiSpend.subtitle",
1226
+ "Model calls made by your workflow runs, most expensive workflow first."
1227
+ ) })
1228
+ ] }),
1229
+ /* @__PURE__ */ jsx(RangeSelector, { days, onChange: setDays, tr })
1230
+ ] }),
1231
+ /* @__PURE__ */ jsx(
1232
+ AiSpendBody,
1233
+ {
1234
+ usage,
1235
+ loading,
1236
+ error,
1237
+ onRetry: () => void refetch(),
1238
+ tr
1239
+ }
1240
+ )
1241
+ ]
1242
+ }
1243
+ );
1244
+ }
1245
+ function RangeSelector({
1246
+ days,
1247
+ onChange,
1248
+ tr
1249
+ }) {
1250
+ return /* @__PURE__ */ jsx(
1251
+ "div",
1252
+ {
1253
+ className: "flex shrink-0 gap-1 rounded-lg border border-border-secondary p-1",
1254
+ role: "group",
1255
+ "aria-label": tr("fluidgrids.insights.aiSpend.rangeLabel", "Period"),
1256
+ children: AI_SPEND_RANGE_DAYS.map((option) => /* @__PURE__ */ jsx(
1257
+ "button",
1258
+ {
1259
+ type: "button",
1260
+ "aria-pressed": days === option,
1261
+ onClick: () => onChange(option),
1262
+ className: `rounded px-3 py-1 text-sm ${days === option ? "bg-action-primary-bg text-action-primary-text" : "text-text-secondary hover:text-text-primary"}`,
1263
+ children: rangeLabel(option, tr)
1264
+ },
1265
+ option
1266
+ ))
1267
+ }
1268
+ );
1269
+ }
1270
+ function AiSpendBody({
1271
+ usage,
1272
+ loading,
1273
+ error,
1274
+ onRetry,
1275
+ tr
1276
+ }) {
1277
+ if (error) {
1278
+ return /* @__PURE__ */ jsxs(
1279
+ "div",
1280
+ {
1281
+ role: "alert",
1282
+ className: "mt-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-status-error-text",
1283
+ children: [
1284
+ /* @__PURE__ */ jsx("p", { children: tr(
1285
+ "fluidgrids.insights.aiSpend.loadError",
1286
+ "Could not load AI spend: {{message}}",
1287
+ { message: error }
1288
+ ) }),
1289
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "mt-2", onClick: onRetry, children: tr("fluidgrids.insights.aiSpend.retry", "Try again") })
1290
+ ]
1291
+ }
1292
+ );
1293
+ }
1294
+ if (!usage) {
1295
+ return loading ? /* @__PURE__ */ jsx("div", { className: "mt-4 grid gap-3 sm:grid-cols-3", "aria-busy": "true", children: [1, 2, 3].map((item) => /* @__PURE__ */ jsx(
1296
+ "div",
1297
+ {
1298
+ className: "h-20 animate-pulse rounded-lg bg-background-tertiary"
1299
+ },
1300
+ item
1301
+ )) }) : null;
1302
+ }
1303
+ return /* @__PURE__ */ jsxs("div", { className: loading ? "opacity-60" : void 0, "aria-busy": loading, children: [
1304
+ /* @__PURE__ */ jsx(SpendTotals, { total: usage.total, tr }),
1305
+ usage.lines.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-lg border border-dashed border-border-secondary p-6 text-center", children: [
1306
+ /* @__PURE__ */ jsx(
1307
+ Icon,
1308
+ {
1309
+ icon: "mdi:robot-off-outline",
1310
+ width: 28,
1311
+ className: "mx-auto text-text-secondary"
1312
+ }
1313
+ ),
1314
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-text-secondary", children: tr(
1315
+ "fluidgrids.insights.aiSpend.empty",
1316
+ "No workflow run made a model call in this period."
1317
+ ) })
1318
+ ] }) : /* @__PURE__ */ jsx(SpendTable, { lines: usage.lines, tr }),
1319
+ usage.truncated ? /* @__PURE__ */ jsx("p", { className: "mt-3 text-xs text-text-secondary", children: tr(
1320
+ "fluidgrids.insights.aiSpend.truncated",
1321
+ "Showing the top {{count}} workflows by spend; the totals above include every workflow.",
1322
+ { count: usage.lines.length }
1323
+ ) }) : null
1324
+ ] });
1325
+ }
1326
+ function SpendTotals({ total, tr }) {
1327
+ const totalCost = aiCost(total);
1328
+ return /* @__PURE__ */ jsxs("div", { className: "mt-4 grid gap-3 sm:grid-cols-3", children: [
1329
+ /* @__PURE__ */ jsx(
1330
+ Total,
1331
+ {
1332
+ label: tr("fluidgrids.insights.aiSpend.totalCost", "Estimated cost"),
1333
+ detail: totalCost.kind === "priced" && totalCost.partial ? tr(
1334
+ "fluidgrids.insights.aiSpend.partialCost",
1335
+ "Calls not priced yet: {{count}}",
1336
+ { count: totalCost.unpricedCalls }
1337
+ ) : null,
1338
+ children: /* @__PURE__ */ jsx(Cost, { usage: total, tr })
1339
+ }
1340
+ ),
1341
+ /* @__PURE__ */ jsx(
1342
+ Total,
1343
+ {
1344
+ label: tr("fluidgrids.insights.aiSpend.totalCalls", "Model calls"),
1345
+ children: total.calls.toLocaleString("en-US")
1346
+ }
1347
+ ),
1348
+ /* @__PURE__ */ jsxs(
1349
+ Total,
1350
+ {
1351
+ label: tr("fluidgrids.insights.aiSpend.totalTokens", "Tokens in / out"),
1352
+ detail: total.cachedInputTokens > 0 ? tr(
1353
+ "fluidgrids.insights.aiSpend.cachedTokens",
1354
+ "{{count}} input tokens cached",
1355
+ { count: formatTokens(total.cachedInputTokens) }
1356
+ ) : null,
1357
+ children: [
1358
+ formatTokens(total.inputTokens),
1359
+ " / ",
1360
+ formatTokens(total.outputTokens)
1361
+ ]
1362
+ }
1363
+ )
1364
+ ] });
1365
+ }
1366
+ function Total({
1367
+ label,
1368
+ children,
1369
+ detail
1370
+ }) {
1371
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border-secondary bg-background-primary p-3", children: [
1372
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-text-secondary", children: label }),
1373
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xl font-semibold text-text-primary break-words", children }),
1374
+ detail && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-text-secondary", children: detail })
1375
+ ] });
1376
+ }
1377
+ function SpendTable({
1378
+ lines,
1379
+ tr
1380
+ }) {
1381
+ return /* @__PURE__ */ jsx("div", { className: "mt-4 overflow-x-auto rounded-lg border border-border-secondary", children: /* @__PURE__ */ jsxs("table", { className: "w-full min-w-[640px] text-sm", children: [
1382
+ /* @__PURE__ */ jsx("thead", { className: "bg-background-tertiary text-left text-xs text-text-secondary", children: /* @__PURE__ */ jsxs("tr", { children: [
1383
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium", children: tr("fluidgrids.insights.aiSpend.colWorkflow", "Workflow") }),
1384
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: tr("fluidgrids.insights.aiSpend.colRuns", "Runs") }),
1385
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: tr("fluidgrids.insights.aiSpend.colCalls", "Calls") }),
1386
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: tr("fluidgrids.insights.aiSpend.colTokensIn", "Tokens in") }),
1387
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: tr("fluidgrids.insights.aiSpend.colTokensOut", "Tokens out") }),
1388
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium", children: tr("fluidgrids.insights.aiSpend.colCost", "Cost") })
1389
+ ] }) }),
1390
+ /* @__PURE__ */ jsx("tbody", { children: lines.map((line) => /* @__PURE__ */ jsxs(
1391
+ "tr",
1392
+ {
1393
+ className: "border-t border-border-secondary",
1394
+ children: [
1395
+ /* @__PURE__ */ jsx("td", { className: "max-w-[16rem] px-3 py-2", children: /* @__PURE__ */ jsx(
1396
+ Link,
1397
+ {
1398
+ to: runsOfWorkflowPath(line.workflowKey),
1399
+ className: "block truncate font-mono text-text-link underline-offset-2 hover:text-text-linkHover hover:underline",
1400
+ title: tr(
1401
+ "fluidgrids.insights.aiSpend.openRuns",
1402
+ "Open the runs of {{workflow}}",
1403
+ { workflow: line.workflowKey }
1404
+ ),
1405
+ children: line.workflowKey
1406
+ }
1407
+ ) }),
1408
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right text-text-primary", children: line.runs.toLocaleString("en-US") }),
1409
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right text-text-primary", children: line.usage.calls.toLocaleString("en-US") }),
1410
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right text-text-primary", children: formatTokens(line.usage.inputTokens) }),
1411
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right text-text-primary", children: formatTokens(line.usage.outputTokens) }),
1412
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right font-medium text-text-primary", children: /* @__PURE__ */ jsx(Cost, { usage: line.usage, tr }) })
1413
+ ]
1414
+ },
1415
+ line.workflowKey
1416
+ )) })
1417
+ ] }) });
1418
+ }
1342
1419
  function normalizedRate(summary) {
1343
1420
  const supplied = summary.workflowSuccessRate;
1344
1421
  if (supplied !== null && supplied !== void 0) {
@@ -1527,6 +1604,9 @@ function InsightsPage({ showErrors = false }) {
1527
1604
  )
1528
1605
  }
1529
1606
  ),
1607
+ activeTab === "overview" && // AI spend (BOFF-9937): its own query and period, so it loads and
1608
+ // fails independently of the run summary below.
1609
+ /* @__PURE__ */ jsx("div", { className: "mb-6", children: /* @__PURE__ */ jsx(AiSpendPanel, {}) }),
1530
1610
  loading && !summary ? /* @__PURE__ */ jsx(LoadingState, {}) : activeTab === "overview" && summary ? /* @__PURE__ */ jsx(
1531
1611
  OverviewContent,
1532
1612
  {
@@ -1,4 +1,4 @@
1
- import { useFlowStore } from './chunk-C26QAWCT.js';
1
+ import { useFlowStore } from './chunk-5DVP2UYH.js';
2
2
  import { useTr } from './chunk-UULVIT25.js';
3
3
  import { useState, useRef, useCallback, useEffect } from 'react';
4
4
  import { cn } from '@burdenoff/fe-libs/utils';
@@ -1,25 +1,26 @@
1
+ import { computeAttachPosition, firstInputHandleId } from './chunk-3MZOYJ5I.js';
1
2
  import { deepClone, applyTemplateInputs } from './chunk-GEMNVFPK.js';
2
3
  import { templatePreview } from './chunk-B6CCE2RW.js';
3
4
  import { Input } from './chunk-IEVSFCFS.js';
4
5
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter as DialogFooter$1 } from './chunk-YXMFRQYV.js';
5
6
  import { Badge as Badge$1, Button } from './chunk-BQXVT32O.js';
6
- import { LogsPanel } from './chunk-FJSXXO5W.js';
7
- import { computeAttachPosition, firstInputHandleId } from './chunk-GVIYGJQG.js';
8
- import { QuotaExhaustedDialog, WorkflowShareDialog, useWorkflowCodeExport, orderedExportFiles, fileBaseName, downloadWorkflowCodeExport, isLosslessReport, sortFidelityItems, isLossyFidelityItem, lineOfNode } from './chunk-O2EB7UT2.js';
9
- import { emptyTestCaseDraft, EntityTagPicker, useWorkflowProposal, WorkflowProposalCard, useWorkflowTests, pinnedOutputsFromCanvas, enabledCaseIds, isTestRunOpen, MAX_TEST_CASES, draftFromTestCase, testRunSummaryText, testStatusLabel, TEST_STATUS_TONE_CLASS, testStatusTone, MAX_CASE_NAME, nextSortOrder, draftToInput } from './chunk-6P7DOBVN.js';
10
- import { buildNodeDisplayMap } from './chunk-EC2AN74G.js';
7
+ import { LogsPanel } from './chunk-MY7SEUXM.js';
8
+ import { QuotaExhaustedDialog, WorkflowShareDialog, useWorkflowCodeExport, orderedExportFiles, fileBaseName, downloadWorkflowCodeExport, isLosslessReport, sortFidelityItems, isLossyFidelityItem, lineOfNode } from './chunk-KAWRHIJZ.js';
9
+ import { emptyTestCaseDraft, EntityTagPicker, useWorkflowProposal, WorkflowProposalCard, useWorkflowTests, pinnedOutputsFromCanvas, enabledCaseIds, isTestRunOpen, MAX_TEST_CASES, draftFromTestCase, testRunSummaryText, testStatusLabel, TEST_STATUS_TONE_CLASS, testStatusTone, MAX_CASE_NAME, nextSortOrder, draftToInput } from './chunk-EYQ7GJRW.js';
10
+ import { buildNodeDisplayMap } from './chunk-IL343FKX.js';
11
11
  import { AiCreditBalanceSlot } from './chunk-Y53CVZVV.js';
12
- import { countLifecycle, VersionLifecycleBar, VersionDiffDialog } from './chunk-4QB6S7YW.js';
13
- import { useRestoreWorkflowVersion, useWorkflowVersions, useActivateWorkflow, useDeleteWorkflow, useDeactivateWorkflow } from './chunk-HOTVUVZG.js';
12
+ import { countLifecycle, VersionLifecycleBar, VersionDiffDialog } from './chunk-TJAG2DQT.js';
13
+ import { useRestoreWorkflowVersion, useWorkflowVersions, useActivateWorkflow, useDeleteWorkflow, useDeactivateWorkflow } from './chunk-YGOGJDN4.js';
14
14
  import { useRequestedProposal } from './chunk-GLYINMOR.js';
15
15
  import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
16
- import { leaseCallQueue, RUNS_QUOTA_NAME, useFlowStore, BILLING_PLANS_PATH, emitUpgradeClicked, FluidGridsContext, emitWorkflowCreated, emitTemplateUsed, ensureNodeConfigsLoaded, NODE_CONFIGS, useEditLeaseStore, useEditLeaseLocked, registerWorkflowNodeTypes, emitNodeInstalled, useNodeExecution, usePaletteGates, onNodeTypesUpdate, NODE_TYPES, getNodeMetadata, useIsValidConnection, refetchStoreNodes, AVAILABLE_NODES, EDIT_SESSION_ID, useInsertNode, NODE_TYPE_DRAG_DATA_FORMAT, NODE_OPERATION_DRAG_DATA_FORMAT, useSaveStatusStore, useManualSave, useDurableFunctionNodesEnabled, CODE_WORKFLOW_NODE_TYPE, bannerOf, editStateFor, useSaveWorkflow, STICKY_NOTE_TYPE, filterPaletteNodes, useFluidGridsAnalytics, useGuardedNavigate, sessionBeforeReload, forgetSessionForReload, releasesOnClose, touchesSavedFields, isLocked, watchLeavingLosesChanges, workflowFingerprint, savedEverythingBeforeLeaving, buildStickyNode, NODES_METADATA, NodeTypeIcon, shouldReleaseForIdle, tickAction, rememberSessionForReload, leavingLosesChanges, buildOperationSeed, useDurableParityEnabled, useConnections, ConnectionFormDialog, useWorkflowTestsEnabled, useFluidGridsFlag, FLUIDGRIDS_FLAGS, useFlowValidator, saveIndicatorOf, departingSave, inAppLeaveTarget, isNodeConfigsInitialized, phaseAfterAcquire, rememberOwnStatusChange, useRunGuard, getNodeDetail, isImageIcon, NODES, synchronizeOperationUpdate, NodePropertyPanel, loadDefinitionJson, canAutosave, isNodeTypeHiddenFromPalette, isTriggerOperation, canRenderOperationPanel, resolveOperationName, ConnectionSelector, AUTOSAVE_RETRY_DELAYS_MS, AUTOSAVE_DEBOUNCE_MS, editingLeaseLocked, autosaveAllowedFor, isFlowComplete, rememberRevision, phaseAfterTakeOver } from './chunk-C26QAWCT.js';
16
+ import { leaseCallQueue, RUNS_QUOTA_NAME, BILLING_PLANS_PATH, emitUpgradeClicked, FluidGridsContext, emitWorkflowCreated, emitTemplateUsed, ensureNodeConfigsLoaded, NODE_CONFIGS, useEditLeaseStore, useEditLeaseLocked, registerWorkflowNodeTypes, emitNodeInstalled, useNodeExecution, usePaletteGates, onNodeTypesUpdate, NODE_TYPES, getNodeMetadata, refetchStoreNodes, AVAILABLE_NODES, EDIT_SESSION_ID, useInsertNode, useSaveStatusStore, useManualSave, useDurableFunctionNodesEnabled, bannerOf, editStateFor, useSaveWorkflow, filterPaletteNodes, useFluidGridsAnalytics, useGuardedNavigate, sessionBeforeReload, forgetSessionForReload, releasesOnClose, touchesSavedFields, isLocked, watchLeavingLosesChanges, workflowFingerprint, savedEverythingBeforeLeaving, buildStickyNode, NODES_METADATA, shouldReleaseForIdle, tickAction, rememberSessionForReload, leavingLosesChanges, buildOperationSeed, useDurableParityEnabled, useConnections, ConnectionFormDialog, useWorkflowTestsEnabled, useFluidGridsFlag, FLUIDGRIDS_FLAGS, useFlowValidator, saveIndicatorOf, departingSave, inAppLeaveTarget, isNodeConfigsInitialized, phaseAfterAcquire, rememberOwnStatusChange, useRunGuard, getNodeDetail, NODES, NodePropertyPanel, loadDefinitionJson, canAutosave, isNodeTypeHiddenFromPalette, ConnectionSelector, AUTOSAVE_RETRY_DELAYS_MS, AUTOSAVE_DEBOUNCE_MS, editingLeaseLocked, autosaveAllowedFor, isFlowComplete, rememberRevision, phaseAfterTakeOver } from './chunk-ZBPRNWKY.js';
17
17
  import { SdkPermissionButton, FLUIDGRIDS_PERMISSIONS, useFluidGridsPermission, SdkPermissionGate } from './chunk-5375NUIO.js';
18
18
  import { cn } from './chunk-JL7CASN5.js';
19
- import { BuilderReadOnlyProvider, useBuilderReadOnly } from './chunk-OCJTTHTS.js';
19
+ import { BuilderReadOnlyProvider, useBuilderReadOnly } from './chunk-SDZEAIIE.js';
20
+ import { useFlowStore, useIsValidConnection, NODE_TYPE_DRAG_DATA_FORMAT, NODE_OPERATION_DRAG_DATA_FORMAT, CODE_WORKFLOW_NODE_TYPE, STICKY_NOTE_TYPE, NodeTypeIcon, isImageIcon, synchronizeOperationUpdate, isTriggerOperation, canRenderOperationPanel, resolveOperationName } from './chunk-5DVP2UYH.js';
20
21
  import { getGlobalStoreClient } from './chunk-AU7LACOY.js';
21
22
  import { GetMyNodeInstallationsDocument, BrowseWorkflowTemplatesDocument, InstallWorkflowNodeDocument, SubmitWorkflowTemplateDocument, MyWorkflowTemplateSubmissionsDocument, WithdrawWorkflowTemplateSubmissionDocument, InstallPurchasedNodeDocument, BrowseWorkflowNodesDocument, UninstallWorkflowNodeDocument, CreateStoreOrderDocument, GetNodeInstallableVersionsDocument, OrganizationPrivateNodesDocument, InstallOrganizationPrivateNodeDocument, UninstallOrganizationPrivateNodeDocument } from './chunk-G77IGC3Y.js';
22
- import { WorkspaceRunsQuotaDocument, WorkflowGenerationTemplatesDocument, ReleaseWorkflowEditLeaseDocument, NodeSchemaIntrospectionDocument, AcquireWorkflowEditLeaseDocument, WorkflowEnvironmentsDocument, WorkflowPromotionRequestsDocument, DeployWorkflowVersionToDevDocument, RequestWorkflowPromotionDocument, ApproveWorkflowPromotionDocument, RejectWorkflowPromotionDocument, CancelWorkflowPromotionDocument, UpdateWorkflowEnvironmentPolicyDocument, WorkflowGitRepositoryDocument, WorkflowGitPushesDocument, WorkflowGitCommitsDocument, LinkWorkflowGitRepositoryDocument, UnlinkWorkflowGitRepositoryDocument, PushWorkflowToGitDocument, WorkflowGitDiffDocument, RestoreWorkflowFromGitDocument, WorkflowAuthoringDocument, WorkflowEditLeaseDocument, WorkflowVersioningDocument, WorkflowVersionDrainageDocument, ActivateWorkflowVersionCheckedDocument, WorkflowDefinitionDocument, NodeUsageStatusDocument, RemoveNodeFromWorkflowsDocument, SetWorkflowRolloutDocument, DeleteWorkflowRolloutDocument, TakeOverWorkflowEditLeaseDocument } from './chunk-AVHBWJRY.js';
23
+ import { WorkspaceRunsQuotaDocument, WorkflowGenerationTemplatesDocument, ReleaseWorkflowEditLeaseDocument, NodeSchemaIntrospectionDocument, AcquireWorkflowEditLeaseDocument, WorkflowEnvironmentsDocument, WorkflowPromotionRequestsDocument, DeployWorkflowVersionToDevDocument, RequestWorkflowPromotionDocument, ApproveWorkflowPromotionDocument, RejectWorkflowPromotionDocument, CancelWorkflowPromotionDocument, UpdateWorkflowEnvironmentPolicyDocument, WorkflowGitRepositoryDocument, WorkflowGitPushesDocument, WorkflowGitCommitsDocument, LinkWorkflowGitRepositoryDocument, UnlinkWorkflowGitRepositoryDocument, PushWorkflowToGitDocument, WorkflowGitDiffDocument, RestoreWorkflowFromGitDocument, WorkflowAuthoringDocument, WorkflowEditLeaseDocument, WorkflowVersioningDocument, WorkflowVersionDrainageDocument, ActivateWorkflowVersionCheckedDocument, WorkflowDefinitionDocument, NodeUsageStatusDocument, RemoveNodeFromWorkflowsDocument, SetWorkflowRolloutDocument, DeleteWorkflowRolloutDocument, TakeOverWorkflowEditLeaseDocument } from './chunk-Z3ABLERJ.js';
23
24
  import { useTr } from './chunk-UULVIT25.js';
24
25
  import { waitForFluidGridsClient, getFluidGridsClient } from './chunk-3IXU7WUC.js';
25
26
  import { throwIfResultError, describeOperationError, emitFluidGridsEvent } from './chunk-US5QSLPV.js';
@@ -20180,7 +20181,7 @@ function FidelityReportList({
20180
20181
  );
20181
20182
  }
20182
20183
  var LazyCodeEditorField = lazy(
20183
- () => import('./code-editor-field-XWUVOKTA.js').then((m) => ({
20184
+ () => import('./code-editor-field-WWPDMF4O.js').then((m) => ({
20184
20185
  default: m.CodeEditorField
20185
20186
  }))
20186
20187
  );
@@ -21240,12 +21241,12 @@ function EditLeaseBanner() {
21240
21241
  );
21241
21242
  }
21242
21243
  var LazyNodeDetailView = lazy(
21243
- () => import('./node-detail-view-3VG7XFIZ.js').then((m) => ({
21244
+ () => import('./node-detail-view-U5HOP2WX.js').then((m) => ({
21244
21245
  default: m.NodeDetailView
21245
21246
  }))
21246
21247
  );
21247
21248
  var LazyQuickConfigPanel = lazy(
21248
- () => import('./quick-config-panel-GB7SDWHD.js').then((m) => ({
21249
+ () => import('./quick-config-panel-XAISBW6Y.js').then((m) => ({
21249
21250
  default: m.QuickConfigPanel
21250
21251
  }))
21251
21252
  );
@@ -21307,7 +21308,7 @@ var FlowBuilder = ({ readOnly = false }) => {
21307
21308
  );
21308
21309
  useEffect(() => {
21309
21310
  let cancelled = false;
21310
- void import('./agentTools-PN4W7QZH.js').then(({ createBuilderAgentTools }) => {
21311
+ void import('./agentTools-YQBN33WG.js').then(({ createBuilderAgentTools }) => {
21311
21312
  if (cancelled) return;
21312
21313
  setAgentTools(
21313
21314
  createBuilderAgentTools({