@burdenoff/fluidgrids-fe-sdk 2026.1104.3 → 2026.1105.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.
Files changed (69) hide show
  1. package/README.md +38 -0
  2. package/dist/ExecutionDetailPage-HQB2SWBP.js +28 -0
  3. package/dist/ExecutionsListPage-RSLL634L.js +22 -0
  4. package/dist/WorkflowBuilder-E3PNZX74.js +32 -0
  5. package/dist/WorkflowsList-473FRRP2.js +21 -0
  6. package/dist/{agentTools-SZZN6TP3.js → agentTools-5N3ZEZDW.js} +8 -7
  7. package/dist/{chunk-NIY6R42C.js → chunk-2KUZBEWW.js} +7 -5
  8. package/dist/{chunk-NTWG3CJX.js → chunk-3IXU7WUC.js} +32 -21
  9. package/dist/{chunk-AAIZ6S6E.js → chunk-3QCRKKTI.js} +70 -88
  10. package/dist/{chunk-WTV6SQYK.js → chunk-3XUWPDKJ.js} +616 -25
  11. package/dist/{chunk-XCSCD3IU.js → chunk-5CFCFA6O.js} +8 -7
  12. package/dist/{chunk-DFIX6YWP.js → chunk-62CCBMDC.js} +40 -21
  13. package/dist/{chunk-KHXRYQYP.js → chunk-64JN2TVX.js} +4 -3
  14. package/dist/{chunk-NYBL5WHP.js → chunk-6IS7EXIY.js} +3 -3
  15. package/dist/{chunk-YYVRBROH.js → chunk-6ZK4MRVA.js} +1 -1
  16. package/dist/{chunk-4YCMMBSK.js → chunk-AJVU5YW6.js} +3 -3
  17. package/dist/{chunk-NGXJHYV5.js → chunk-AMXJLVZN.js} +2 -2
  18. package/dist/{chunk-PXIFUMLA.js → chunk-AU7LACOY.js} +16 -11
  19. package/dist/{chunk-GEY7RXRZ.js → chunk-CH3YUGWR.js} +4 -4
  20. package/dist/{chunk-XNU2PLJ2.js → chunk-CNKTQ5MG.js} +189 -9
  21. package/dist/{chunk-BIB7IH26.js → chunk-EYQ7GJRW.js} +5 -5
  22. package/dist/{chunk-X6PIU7FE.js → chunk-GKFMK7FF.js} +4 -3
  23. package/dist/{chunk-NJRCAE6K.js → chunk-HK76ZUAK.js} +5 -5
  24. package/dist/{chunk-F7LQL43U.js → chunk-IL343FKX.js} +2 -2
  25. package/dist/{chunk-5D4XS3WJ.js → chunk-J3YQCEP3.js} +18 -4
  26. package/dist/{chunk-4EUEKSEQ.js → chunk-L2N6DDOI.js} +2 -2
  27. package/dist/chunk-NZAYYUQ4.js +172 -0
  28. package/dist/{chunk-ON7LK6QH.js → chunk-O7CJJNW6.js} +278 -197
  29. package/dist/{chunk-ZDCJPQ5M.js → chunk-OXXXUAWM.js} +2 -2
  30. package/dist/{chunk-D4WA2M2N.js → chunk-QFC6E3WD.js} +4 -3
  31. package/dist/{chunk-AFX3EKFN.js → chunk-QFGZXWTM.js} +16 -4
  32. package/dist/{chunk-IK7IGG6T.js → chunk-QPLFALVV.js} +9 -8
  33. package/dist/{chunk-MF7JLSJX.js → chunk-SALZ3LQB.js} +11 -2
  34. package/dist/{chunk-SS7Y66P5.js → chunk-TCIBTMJI.js} +70 -4
  35. package/dist/{chunk-OMB3WMDQ.js → chunk-TNFBRXU7.js} +11 -10
  36. package/dist/chunk-UEG2WC3G.js +502 -0
  37. package/dist/chunk-US5QSLPV.js +316 -0
  38. package/dist/{chunk-5UHAB75U.js → chunk-W5WML3OO.js} +1 -1
  39. package/dist/chunk-WZ3EJVS6.js +93 -0
  40. package/dist/{chunk-Q72EOY4T.js → chunk-X25NCWG2.js} +9 -3
  41. package/dist/{chunk-VDJFTZ2H.js → chunk-XFH6SLDK.js} +4 -93
  42. package/dist/{chunk-TBHAZK52.js → chunk-Y6M7K4UG.js} +1 -1
  43. package/dist/{chunk-AVHBWJRY.js → chunk-Z3ABLERJ.js} +3 -1
  44. package/dist/client-YVZMLEQX.js +3 -0
  45. package/dist/config-66FUPSG5.js +1 -0
  46. package/dist/executions.js +18 -15
  47. package/dist/global-client-N5EIFW6T.js +3 -0
  48. package/dist/history.js +9 -8
  49. package/dist/index.d.ts +172 -133
  50. package/dist/index.js +95 -74
  51. package/dist/{node-detail-view-GAGSLHAO.js → node-detail-view-WIWW5N6N.js} +36 -11
  52. package/dist/nodes.js +5 -3
  53. package/dist/observability.js +10 -8
  54. package/dist/{quick-config-panel-44RBX6YR.js → quick-config-panel-O64TXXK3.js} +10 -8
  55. package/dist/settings.js +11 -9
  56. package/dist/templates.js +15 -14
  57. package/dist/{workflow-builder-BMGVSwpy.d.ts → workflow-builder-7vv9dCdp.d.ts} +413 -2
  58. package/dist/workflow-builder.d.ts +2 -1
  59. package/dist/workflow-builder.js +17 -16
  60. package/dist/workflows-list.js +11 -10
  61. package/package.json +1 -1
  62. package/dist/ExecutionDetailPage-ND2VMU45.js +0 -25
  63. package/dist/ExecutionsListPage-EZ65FXER.js +0 -20
  64. package/dist/WorkflowBuilder-4I44FWII.js +0 -31
  65. package/dist/WorkflowsList-YFD2RKKQ.js +0 -20
  66. package/dist/chunk-6AMNEX2Y.js +0 -214
  67. package/dist/client-EYQDMR4O.js +0 -1
  68. package/dist/config-3DBXLCK3.js +0 -1
  69. package/dist/global-client-O2HCG5CJ.js +0 -1
@@ -1,85 +1,22 @@
1
- import { FailureTriagePanel } from './chunk-SS7Y66P5.js';
2
- import { useDataSinks, useDataSinkData, useInsightsData, useRunReplayActions } from './chunk-MF7JLSJX.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-SALZ3LQB.js';
3
4
  import { proposalCanvasPath } from './chunk-GLYINMOR.js';
4
- import { useContinuousOptimisationEnabled, throwIfResultError } from './chunk-AAIZ6S6E.js';
5
+ import { useContinuousOptimisationEnabled } from './chunk-3QCRKKTI.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
- import { getFluidGridsClient } from './chunk-NTWG3CJX.js';
9
+ import { getFluidGridsClient } from './chunk-3IXU7WUC.js';
10
+ import { throwIfResultError } from './chunk-US5QSLPV.js';
9
11
  import { useState, useMemo, useEffect, useRef, useCallback } from 'react';
10
12
  import { Icon } from '@iconify/react';
11
13
  import { Button } from '@burdenoff/fe-libs/button';
12
14
  import { Input, Button as Button$1, Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, Label, Textarea, DialogFooter } from '@burdenoff/fe-libs/ui';
13
15
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
14
- import { useNavigate } from 'react-router';
16
+ import { useNavigate, Link } from 'react-router';
15
17
  import { toast } from 'sonner';
16
18
  import { Lightbulb, Scissors, Timer, ShieldCheck, DatabaseZap, BadgeDollarSign, RotateCcw } from 'lucide-react';
17
19
 
18
- // src/modules/observability/utils.ts
19
- function percentage(value, total) {
20
- if (total <= 0) return 0;
21
- return Math.round(value / total * 1e3) / 10;
22
- }
23
- function getFailureBreakdown(runs) {
24
- const counts = /* @__PURE__ */ new Map();
25
- for (const run of runs) {
26
- const key = run.errorType || "UNKNOWN";
27
- counts.set(key, (counts.get(key) ?? 0) + 1);
28
- }
29
- return [...counts.entries()].map(([key, count]) => ({ key, count })).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
30
- }
31
- function getTopFailingWorkflows(runs) {
32
- const counts = /* @__PURE__ */ new Map();
33
- for (const run of runs) {
34
- const current = counts.get(run.workflowKey);
35
- if (current) {
36
- current.count += 1;
37
- } else {
38
- counts.set(run.workflowKey, {
39
- workflowKey: run.workflowKey,
40
- label: run.workflow?.name || run.workflowKey,
41
- count: 1
42
- });
43
- }
44
- }
45
- return [...counts.values()].sort(
46
- (a, b) => b.count - a.count || a.label.localeCompare(b.label)
47
- );
48
- }
49
- function getTopFailingNodes(nodeUsageStats) {
50
- return [...nodeUsageStats].filter((stat) => stat.failureCount > 0).sort(
51
- (a, b) => b.failureCount - a.failureCount || a.nodeType.localeCompare(b.nodeType)
52
- );
53
- }
54
- function formatDuration(seconds) {
55
- if (seconds === null || seconds === void 0 || !Number.isFinite(seconds)) {
56
- return "\u2014";
57
- }
58
- if (seconds < 1) return `${Math.round(seconds * 1e3)}ms`;
59
- if (seconds < 60) return `${seconds.toFixed(1)}s`;
60
- const minutes = Math.floor(seconds / 60);
61
- const remainingSeconds = Math.round(seconds % 60);
62
- return `${minutes}m ${remainingSeconds}s`;
63
- }
64
- function formatBytes(bytes) {
65
- if (!Number.isFinite(bytes) || bytes < 0) return "\u2014";
66
- if (bytes < 1024) return `${bytes} B`;
67
- const units = ["KB", "MB", "GB", "TB"];
68
- let value = bytes / 1024;
69
- let unit = units[0];
70
- for (let i = 1; i < units.length && value >= 1024; i += 1) {
71
- value /= 1024;
72
- unit = units[i];
73
- }
74
- return `${value.toFixed(value >= 10 ? 0 : 1)} ${unit}`;
75
- }
76
- function prettyJson(value) {
77
- try {
78
- return JSON.stringify(value, null, 2) ?? "null";
79
- } catch {
80
- return String(value);
81
- }
82
- }
83
20
  function valueLabel(value) {
84
21
  if (value === null || value === void 0) return "\u2014";
85
22
  if (typeof value === "object") return JSON.stringify(value);
@@ -579,132 +516,6 @@ function useContinuousOptimisation(active) {
579
516
  };
580
517
  }
581
518
 
582
- // src/modules/observability/optimisation.ts
583
- var DEFAULT_WINDOW_DAYS = 30;
584
- var DISMISS_REASONS = [
585
- "NOT_APPLICABLE" /* NotApplicable */,
586
- "ACCEPTED_COST" /* AcceptedCost */,
587
- "WILL_FIX_LATER" /* WillFixLater */,
588
- "OTHER" /* Other */
589
- ];
590
- var MAX_DISMISS_NOTE_LENGTH = 500;
591
- function isRecord(value) {
592
- return typeof value === "object" && value !== null && !Array.isArray(value);
593
- }
594
- function evidenceRecord(evidence) {
595
- if (typeof evidence === "string") {
596
- try {
597
- const parsed = JSON.parse(evidence);
598
- return isRecord(parsed) ? parsed : {};
599
- } catch {
600
- return {};
601
- }
602
- }
603
- return isRecord(evidence) ? evidence : {};
604
- }
605
- function numberAt(evidence, key) {
606
- const value = evidence[key];
607
- return typeof value === "number" && Number.isFinite(value) ? value : null;
608
- }
609
- function formatShare(value) {
610
- return `${Math.round(value * 100)}%`;
611
- }
612
- function formatSeconds(ms) {
613
- const seconds = ms / 1e3;
614
- return `${Number.isInteger(seconds) ? seconds : seconds.toFixed(1)} s`;
615
- }
616
- function formatCount(value) {
617
- return Math.round(value).toLocaleString("en-US");
618
- }
619
- function formatMicroUsd(value) {
620
- const dollars = value / 1e6;
621
- if (dollars > 0 && dollars < 0.01) return "< $0.01";
622
- return `$${dollars.toLocaleString("en-US", {
623
- minimumFractionDigits: 2,
624
- maximumFractionDigits: 2
625
- })}`;
626
- }
627
- function monthlySavingLabel(microUsd) {
628
- if (microUsd === null || microUsd === void 0) return null;
629
- if (!Number.isFinite(microUsd) || microUsd <= 0) return null;
630
- return formatMicroUsd(microUsd);
631
- }
632
- function evidenceWindowDays(evidence) {
633
- const days = numberAt(evidenceRecord(evidence), "windowDays");
634
- return days !== null && days > 0 ? days : DEFAULT_WINDOW_DAYS;
635
- }
636
- var FACTS = {
637
- ["RAISE_TIMEOUT" /* RaiseTimeout */]: [
638
- { id: "executions", format: formatCount },
639
- { id: "timeoutRate", format: formatShare },
640
- { id: "currentTimeoutMs", format: formatSeconds },
641
- {
642
- id: "p95CompletedMs",
643
- format: formatSeconds
644
- },
645
- {
646
- id: "suggestedTimeoutMs",
647
- format: formatSeconds
648
- }
649
- ],
650
- ["ADD_RETRY" /* AddRetry */]: [
651
- { id: "executions", format: formatCount },
652
- {
653
- id: "transientFailures",
654
- format: formatCount
655
- },
656
- {
657
- id: "transientFailureRate",
658
- format: formatShare
659
- }
660
- ],
661
- ["UNUSED_NODE" /* UnusedNode */]: [{ id: "completedRuns", format: formatCount }],
662
- ["ENABLE_CACHE" /* EnableCache */]: [
663
- { id: "calls", format: formatCount },
664
- { id: "repeatedCalls", format: formatCount },
665
- { id: "repeatShare", format: formatShare },
666
- { id: "costMicroUsd", format: formatMicroUsd }
667
- ],
668
- ["CHEAPER_MODEL" /* CheaperModel */]: [],
669
- ["MISSING_GUARDRAIL" /* MissingGuardrail */]: []
670
- };
671
- function evidenceFacts(kind, evidence) {
672
- const record = evidenceRecord(evidence);
673
- const facts = [];
674
- for (const spec of FACTS[kind] ?? []) {
675
- const value = numberAt(record, spec.id);
676
- if (value === null) continue;
677
- facts.push({ id: spec.id, value: spec.format(value) });
678
- }
679
- if (kind === "UNUSED_NODE" /* UnusedNode */) {
680
- const removed = record.removedNodeIds;
681
- if (Array.isArray(removed) && removed.length > 0) {
682
- facts.push({
683
- id: "removedNodeIds",
684
- value: removed.map(String).join(", ")
685
- });
686
- }
687
- }
688
- if (kind === "ADD_RETRY" /* AddRetry */) {
689
- const byType = record.failuresByType;
690
- if (isRecord(byType)) {
691
- const parts = Object.entries(byType).filter(
692
- (entry) => typeof entry[1] === "number" && entry[1] > 0
693
- ).map(([type, count]) => `${type} ${formatCount(count)}`);
694
- if (parts.length > 0) {
695
- facts.push({
696
- id: "failuresByType",
697
- value: parts.join(", ")
698
- });
699
- }
700
- }
701
- }
702
- return facts;
703
- }
704
- function shortRunId(runId) {
705
- return runId.length > 8 ? runId.slice(0, 8) : runId;
706
- }
707
-
708
519
  // src/modules/observability/optimisation-labels.ts
709
520
  function kindLabel(tr, kind) {
710
521
  switch (kind) {
@@ -1338,6 +1149,273 @@ function DismissDialog({
1338
1149
  }
1339
1150
  );
1340
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
+ }
1341
1419
  function normalizedRate(summary) {
1342
1420
  const supplied = summary.workflowSuccessRate;
1343
1421
  if (supplied !== null && supplied !== void 0) {
@@ -1526,6 +1604,9 @@ function InsightsPage({ showErrors = false }) {
1526
1604
  )
1527
1605
  }
1528
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, {}) }),
1529
1610
  loading && !summary ? /* @__PURE__ */ jsx(LoadingState, {}) : activeTab === "overview" && summary ? /* @__PURE__ */ jsx(
1530
1611
  OverviewContent,
1531
1612
  {
@@ -1,6 +1,6 @@
1
- import { NodeEndpointConfigsDocument, NodeEndpointConfigDocument, NodeManifestDocument, fetchAllNodeEndpointConfigs } from './chunk-AVHBWJRY.js';
1
+ import { NodeEndpointConfigsDocument, NodeEndpointConfigDocument, NodeManifestDocument, fetchAllNodeEndpointConfigs } from './chunk-Z3ABLERJ.js';
2
2
  import { useTr } from './chunk-UULVIT25.js';
3
- import { getFluidGridsClient } from './chunk-NTWG3CJX.js';
3
+ import { getFluidGridsClient } from './chunk-3IXU7WUC.js';
4
4
  import { useMemo, useState, useCallback, useEffect } from 'react';
5
5
  import { useNavigate } from 'react-router';
6
6
  import { Icon } from '@iconify/react';
@@ -1,7 +1,8 @@
1
- import { throwIfResultError, describeOperationError, signalPayloadCard, components_default, payloadFromCardValues, parseRawSignalPayload } from './chunk-AAIZ6S6E.js';
2
- import { TerminateWorkflowRunDocument, WorkflowRunHistoryDocument, RunEventAppendedDocument, DurableRunFieldsDocument, WorkflowRunDeterminismStatusDocument, WorkflowRunStackTraceDocument, SignalWorkflowRunDocument } from './chunk-AVHBWJRY.js';
1
+ import { signalPayloadCard, components_default, payloadFromCardValues, parseRawSignalPayload } from './chunk-3QCRKKTI.js';
2
+ import { TerminateWorkflowRunDocument, WorkflowRunHistoryDocument, RunEventAppendedDocument, DurableRunFieldsDocument, WorkflowRunDeterminismStatusDocument, WorkflowRunStackTraceDocument, SignalWorkflowRunDocument } from './chunk-Z3ABLERJ.js';
3
3
  import { useTr } from './chunk-UULVIT25.js';
4
- import { getFluidGridsClient, getFluidGridsClientSafe } from './chunk-NTWG3CJX.js';
4
+ import { getFluidGridsClient, getFluidGridsClientSafe } from './chunk-3IXU7WUC.js';
5
+ import { throwIfResultError, describeOperationError } from './chunk-US5QSLPV.js';
5
6
  import { useState, useMemo, useRef, useCallback, useEffect } from 'react';
6
7
  import { Icon } from '@iconify/react';
7
8
  import { Button } from '@burdenoff/fe-libs/button';
@@ -1,8 +1,9 @@
1
- import { useFluidGridsAnalytics, throwIfResultError, emitWorkflowCreated, emitRunStarted, runTriggerFromMode, openRunQuotaUpgradePrompt, emitWorkflowPublished } from './chunk-AAIZ6S6E.js';
2
- import { getFluidGridsConfig } from './chunk-6AMNEX2Y.js';
3
- import { WorkflowDefinitionsDocument, WorkflowDefinitionDocument, WorkflowVersionsDocument, CreateWorkflowDefinitionDocument, ImportWorkflowDocument, PreviewN8nImportDocument, UpdateWorkflowDefinitionDocument, CreateWorkflowVersionDocument, DeleteWorkflowDefinitionDocument, DuplicateWorkflowDocument, RestoreWorkflowVersionDocument, TriggerWorkflowDocument, WorkflowRunsDocument, WorkflowRunDocument, DashboardSummaryDocument, ActivateWorkflowDefinitionDocument, DeactivateWorkflowDefinitionDocument } from './chunk-AVHBWJRY.js';
1
+ import { useFluidGridsAnalytics, emitWorkflowCreated, emitRunStarted, runTriggerFromMode, openRunQuotaUpgradePrompt, emitWorkflowPublished } from './chunk-3QCRKKTI.js';
2
+ import { WorkflowDefinitionsDocument, WorkflowDefinitionDocument, WorkflowVersionsDocument, CreateWorkflowDefinitionDocument, ImportWorkflowDocument, PreviewN8nImportDocument, UpdateWorkflowDefinitionDocument, CreateWorkflowVersionDocument, DeleteWorkflowDefinitionDocument, DuplicateWorkflowDocument, RestoreWorkflowVersionDocument, TriggerWorkflowDocument, WorkflowRunsDocument, WorkflowRunDocument, DashboardSummaryDocument, ActivateWorkflowDefinitionDocument, DeactivateWorkflowDefinitionDocument } from './chunk-Z3ABLERJ.js';
4
3
  import { useTr } from './chunk-UULVIT25.js';
5
- import { getFluidGridsClient } from './chunk-NTWG3CJX.js';
4
+ import { getFluidGridsClient } from './chunk-3IXU7WUC.js';
5
+ import { throwIfResultError, emitFluidGridsEvent, emitOperationErrorEvent } from './chunk-US5QSLPV.js';
6
+ import { getFluidGridsConfig } from './chunk-UEG2WC3G.js';
6
7
  import { useState, useCallback, useEffect, useMemo } from 'react';
7
8
 
8
9
  var WORKFLOW_DEFINITIONS = WorkflowDefinitionsDocument;
@@ -552,10 +553,16 @@ function useTriggerWorkflow() {
552
553
  executionId: data.runId,
553
554
  trigger: runTriggerFromMode(input.mode)
554
555
  });
556
+ emitFluidGridsEvent("run:started", {
557
+ runId: data.runId,
558
+ workflowKey: data.workflowKey,
559
+ mode: input.mode ?? void 0
560
+ });
555
561
  }
556
562
  return data;
557
563
  } catch (err) {
558
564
  setError(err);
565
+ emitOperationErrorEvent("run:start", err);
559
566
  openRunQuotaUpgradePrompt(err, tr, {
560
567
  bus,
561
568
  workspaceId,
@@ -704,10 +711,15 @@ function useActivateWorkflow() {
704
711
  workflowId: activated.id,
705
712
  definition: options?.definition
706
713
  });
714
+ emitFluidGridsEvent("workflow:activated", {
715
+ workflowKey: activated.workflowKey,
716
+ version: activated.version ?? void 0
717
+ });
707
718
  }
708
719
  return activated;
709
720
  } catch (err) {
710
721
  console.error("Error activating workflow:", err);
722
+ emitOperationErrorEvent("workflow:activate", err);
711
723
  throw err;
712
724
  } finally {
713
725
  setLoading(false);
@@ -1,14 +1,15 @@
1
- import { AgentsTab } from './chunk-VDJFTZ2H.js';
2
- import { ActionPoliciesTab } from './chunk-NGXJHYV5.js';
3
- import { LogStreamingTab } from './chunk-MF7JLSJX.js';
1
+ import { AgentsTab } from './chunk-XFH6SLDK.js';
2
+ import { ActionPoliciesTab } from './chunk-AMXJLVZN.js';
3
+ import { LogStreamingTab } from './chunk-SALZ3LQB.js';
4
4
  import { FluidGridsDocsLink } from './chunk-4VJ3MDOV.js';
5
- import { useFlowStore, useFluidGridsFlag, FLUIDGRIDS_FLAGS, useSaveStatusStore, findSaveConflict, useDurableParityEnabled, throwIfResultError, describeOperationError, useConnections } from './chunk-AAIZ6S6E.js';
5
+ import { useFlowStore, useFluidGridsFlag, FLUIDGRIDS_FLAGS, useSaveStatusStore, findSaveConflict, useDurableParityEnabled, useConnections } from './chunk-3QCRKKTI.js';
6
6
  import { useFluidGridsPermission, FLUIDGRIDS_PERMISSIONS } from './chunk-5375NUIO.js';
7
- import { getGlobalStoreClient } from './chunk-PXIFUMLA.js';
8
- import { getFluidGridsConfig } from './chunk-6AMNEX2Y.js';
9
- import { useWorkflowDefinitionQuery, useUpdateWorkflowDefinitionMutation, useWorkflowDefinitionsQuery, DurableHistoryCapsDocument, UpdateDurableHistoryCapsDocument } from './chunk-AVHBWJRY.js';
7
+ import { getGlobalStoreClient } from './chunk-AU7LACOY.js';
8
+ import { useWorkflowDefinitionQuery, useUpdateWorkflowDefinitionMutation, useWorkflowDefinitionsQuery, DurableHistoryCapsDocument, UpdateDurableHistoryCapsDocument } from './chunk-Z3ABLERJ.js';
10
9
  import { useTr } from './chunk-UULVIT25.js';
11
- import { getFluidGridsClient } from './chunk-NTWG3CJX.js';
10
+ import { getFluidGridsClient } from './chunk-3IXU7WUC.js';
11
+ import { throwIfResultError, describeOperationError } from './chunk-US5QSLPV.js';
12
+ import { getFluidGridsConfig } from './chunk-UEG2WC3G.js';
12
13
  import { useState, useMemo, useEffect, useCallback } from 'react';
13
14
  import { useNavigate, useParams } from 'react-router';
14
15
  import { Icon } from '@iconify/react';
@@ -1,7 +1,8 @@
1
1
  import { LOG_STREAM_DESTINATIONS, CREATE_LOG_STREAM_DESTINATION, UPDATE_LOG_STREAM_DESTINATION, DELETE_LOG_STREAM_DESTINATION, TEST_LOG_STREAM_DESTINATION, REPLAY_WORKFLOW_RUN, RETRY_WORKFLOW_RUNS, OBSERVABILITY_DASHBOARD_SUMMARY, OBSERVABILITY_FAILED_RUNS, OBSERVABILITY_DATA_SINKS, OBSERVABILITY_DATA_SINK_DATA } from './chunk-5ERAJC2I.js';
2
- import { throwIfResultError, describeOperationError, useFluidGridsAnalytics, emitRunStarted, openRunQuotaUpgradePrompt, useFluidGridsFlag, FLUIDGRIDS_FLAGS } from './chunk-AAIZ6S6E.js';
2
+ import { useFluidGridsAnalytics, emitRunStarted, openRunQuotaUpgradePrompt, useFluidGridsFlag, FLUIDGRIDS_FLAGS } from './chunk-3QCRKKTI.js';
3
3
  import { useTr } from './chunk-UULVIT25.js';
4
- import { getFluidGridsClient } from './chunk-NTWG3CJX.js';
4
+ import { getFluidGridsClient } from './chunk-3IXU7WUC.js';
5
+ import { throwIfResultError, describeOperationError, emitFluidGridsEvent } from './chunk-US5QSLPV.js';
5
6
  import { useCallback, useState, useMemo, useEffect } from 'react';
6
7
  import { Icon } from '@iconify/react';
7
8
  import { toast } from 'sonner';
@@ -138,6 +139,10 @@ function useRunReplayActions() {
138
139
  executionId: replayed.runId,
139
140
  trigger: "manual"
140
141
  });
142
+ emitFluidGridsEvent("run:started", {
143
+ runId: replayed.runId,
144
+ workflowKey: replayed.workflowKey
145
+ });
141
146
  }
142
147
  return replayed;
143
148
  } catch (cause) {
@@ -173,6 +178,10 @@ function useRunReplayActions() {
173
178
  executionId: run.runId,
174
179
  trigger: "manual"
175
180
  });
181
+ emitFluidGridsEvent("run:started", {
182
+ runId: run.runId,
183
+ workflowKey: run.workflowKey
184
+ });
176
185
  }
177
186
  return retried;
178
187
  } catch (cause) {