footprint-explainable-ui 0.14.0 → 0.14.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.
package/dist/index.cjs CHANGED
@@ -4086,6 +4086,135 @@ var VLinePill = (0, import_react19.memo)(function VLinePill2({
4086
4086
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { flex: 1, width: 1, background: theme.border } })
4087
4087
  ] });
4088
4088
  });
4089
+ function detectKeyedSteps(data) {
4090
+ if (!data || typeof data !== "object") return null;
4091
+ const obj = data;
4092
+ for (const val of Object.values(obj)) {
4093
+ if (val && typeof val === "object" && !Array.isArray(val)) {
4094
+ const entries = Object.entries(val);
4095
+ if (entries.length === 0) continue;
4096
+ const allObjectsWithNumbers = entries.every(([, v2]) => {
4097
+ if (!v2 || typeof v2 !== "object" || Array.isArray(v2)) return false;
4098
+ return Object.values(v2).some((f) => typeof f === "number");
4099
+ });
4100
+ if (allObjectsWithNumbers) {
4101
+ const keyType = entries.some(([k]) => k.includes("#")) ? "runtimeStageId" : "stageName";
4102
+ return { steps: val, keyType };
4103
+ }
4104
+ }
4105
+ }
4106
+ return null;
4107
+ }
4108
+ function findNumericField(entry) {
4109
+ for (const [k, v2] of Object.entries(entry)) {
4110
+ if (typeof v2 === "number") return { key: k, value: v2 };
4111
+ }
4112
+ return null;
4113
+ }
4114
+ function KeyedRecorderView({
4115
+ data,
4116
+ description,
4117
+ snapshots,
4118
+ selectedIndex
4119
+ }) {
4120
+ const [showAggregate, setShowAggregate] = (0, import_react19.useState)(false);
4121
+ const detected = (0, import_react19.useMemo)(() => detectKeyedSteps(data), [data]);
4122
+ const visibleKeys = (0, import_react19.useMemo)(() => {
4123
+ const keys = /* @__PURE__ */ new Set();
4124
+ for (let i = 0; i <= selectedIndex && i < snapshots.length; i++) {
4125
+ const snap = snapshots[i];
4126
+ if (detected?.keyType === "runtimeStageId") {
4127
+ if (snap.runtimeStageId) keys.add(snap.runtimeStageId);
4128
+ } else {
4129
+ if (snap.stageName) keys.add(snap.stageName);
4130
+ if (snap.stageLabel) keys.add(snap.stageLabel);
4131
+ }
4132
+ }
4133
+ return keys;
4134
+ }, [snapshots, selectedIndex, detected?.keyType]);
4135
+ const isAtEnd = selectedIndex >= snapshots.length - 1;
4136
+ if (!detected) {
4137
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { padding: 12, fontFamily: theme.fontMono, fontSize: 11, whiteSpace: "pre-wrap", overflow: "auto", height: "100%" }, children: typeof data === "string" ? data : JSON.stringify(data, null, 2) });
4138
+ }
4139
+ const steps = detected.steps;
4140
+ const allKeys = Object.keys(steps);
4141
+ const visibleEntries = allKeys.filter((k) => visibleKeys.has(k));
4142
+ const numField = allKeys.length > 0 ? findNumericField(steps[allKeys[0]]) : null;
4143
+ const numFieldKey = numField?.key ?? "";
4144
+ let runningTotal = 0;
4145
+ if (numFieldKey) {
4146
+ for (const k of visibleEntries) {
4147
+ runningTotal += steps[k][numFieldKey] ?? 0;
4148
+ }
4149
+ }
4150
+ let grandTotal = 0;
4151
+ if (numFieldKey) {
4152
+ for (const entry of Object.values(steps)) {
4153
+ grandTotal += entry[numFieldKey] ?? 0;
4154
+ }
4155
+ }
4156
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { overflow: "auto", height: "100%", display: "flex", flexDirection: "column" }, children: [
4157
+ description && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { padding: "6px 12px", fontSize: 11, color: theme.textMuted, fontStyle: "italic", borderBottom: `1px solid ${theme.border}`, flexShrink: 0 }, children: description }),
4158
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { padding: 12, flex: 1, overflow: "auto" }, children: [
4159
+ visibleEntries.map((key) => {
4160
+ const entry = steps[key];
4161
+ const label = entry.stageName ?? key;
4162
+ const numVal = numFieldKey ? entry[numFieldKey] : void 0;
4163
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { display: "flex", alignItems: "center", padding: "4px 0", fontSize: 12, fontFamily: theme.fontMono, borderBottom: `1px solid ${theme.border}22` }, children: [
4164
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: { color: theme.textMuted, width: 140, flexShrink: 0, fontSize: 10 }, children: key }),
4165
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: { fontWeight: 600, flex: 1 }, children: label }),
4166
+ numVal !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: { color: theme.primary, fontWeight: 700, marginLeft: 8 }, children: numVal < 1 ? numVal.toFixed(3) : numVal.toFixed(1) })
4167
+ ] }, key);
4168
+ }),
4169
+ visibleEntries.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { color: theme.textMuted, fontSize: 11, fontStyle: "italic", padding: "8px 0" }, children: "Scrub the slider to reveal entries..." }),
4170
+ numFieldKey && visibleEntries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { marginTop: 12, padding: "8px 12px", background: `color-mix(in srgb, ${theme.primary} 8%, transparent)`, borderRadius: 6, fontSize: 12 }, children: [
4171
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { style: { color: theme.textMuted }, children: [
4172
+ "Running total (",
4173
+ numFieldKey,
4174
+ "):"
4175
+ ] }),
4176
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: { fontWeight: 700, marginLeft: 8, color: theme.primary }, children: runningTotal < 1 ? runningTotal.toFixed(3) : runningTotal.toFixed(1) }),
4177
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { style: { color: theme.textMuted, marginLeft: 8, fontSize: 10 }, children: [
4178
+ "(",
4179
+ visibleEntries.length,
4180
+ " of ",
4181
+ allKeys.length,
4182
+ " steps)"
4183
+ ] })
4184
+ ] }),
4185
+ isAtEnd && numFieldKey && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { marginTop: 12 }, children: !showAggregate ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4186
+ "button",
4187
+ {
4188
+ onClick: () => setShowAggregate(true),
4189
+ style: {
4190
+ background: theme.primary,
4191
+ color: "#fff",
4192
+ border: "none",
4193
+ borderRadius: 6,
4194
+ padding: "8px 16px",
4195
+ fontSize: 12,
4196
+ fontWeight: 600,
4197
+ cursor: "pointer",
4198
+ fontFamily: "inherit"
4199
+ },
4200
+ children: [
4201
+ "Aggregate (",
4202
+ numFieldKey,
4203
+ ")"
4204
+ ]
4205
+ }
4206
+ ) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { padding: "10px 14px", background: `color-mix(in srgb, ${theme.success} 12%, transparent)`, borderRadius: 6, border: `1px solid ${theme.success}44` }, children: [
4207
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { fontSize: 10, color: theme.textMuted, textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 4 }, children: "Aggregate \u2014 Grand Total" }),
4208
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { fontSize: 20, fontWeight: 700, color: theme.success }, children: grandTotal < 1 ? grandTotal.toFixed(3) : grandTotal.toFixed(1) }),
4209
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { fontSize: 10, color: theme.textMuted, marginTop: 2 }, children: [
4210
+ allKeys.length,
4211
+ " steps \xB7 ",
4212
+ numFieldKey
4213
+ ] })
4214
+ ] }) })
4215
+ ] })
4216
+ ] });
4217
+ }
4089
4218
  var DetailsContent = (0, import_react19.memo)(function DetailsContent2({
4090
4219
  snapshots,
4091
4220
  selectedIndex,
@@ -4259,10 +4388,10 @@ function ExplainableShell({
4259
4388
  const allTabs = (0, import_react19.useMemo)(() => {
4260
4389
  const tabs2 = [
4261
4390
  { id: "result", name: "Result", description: "Final output and console logs" },
4262
- { id: "memory", name: "Memory", description: "Accumulated shared state at each stage" }
4391
+ { id: "memory", name: "Memory", description: "Accumulator \u2014 progressive shared state at each stage" }
4263
4392
  ];
4264
4393
  if (hasNarrative) {
4265
- tabs2.push({ id: "narrative", name: "Narrative", description: "What happened, what data flowed, what decisions were made" });
4394
+ tabs2.push({ id: "narrative", name: "Narrative", description: "Translator (SequenceRecorder) \u2014 interleaved flow + data narrative per execution step" });
4266
4395
  }
4267
4396
  for (const v2 of recorderViews ?? []) {
4268
4397
  tabs2.push({ id: v2.id, name: v2.name, description: v2.description });
@@ -4430,17 +4559,15 @@ function ExplainableShell({
4430
4559
  }
4431
4560
  const autoView = autoRecorderViews.find((v2) => v2.id === activeTab);
4432
4561
  if (autoView) {
4433
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { overflow: "auto", height: "100%", display: "flex", flexDirection: "column" }, children: [
4434
- autoView.description && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: {
4435
- padding: "6px 12px",
4436
- fontSize: 11,
4437
- color: theme.textMuted,
4438
- fontStyle: "italic",
4439
- borderBottom: `1px solid ${theme.border}`,
4440
- flexShrink: 0
4441
- }, children: autoView.description }),
4442
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { padding: 12, fontFamily: theme.fontMono, fontSize: 11, whiteSpace: "pre-wrap", overflow: "auto", flex: 1 }, children: typeof autoView.data === "string" ? autoView.data : JSON.stringify(autoView.data, null, 2) })
4443
- ] });
4562
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4563
+ KeyedRecorderView,
4564
+ {
4565
+ data: autoView.data,
4566
+ description: autoView.description,
4567
+ snapshots: activeSnapshots,
4568
+ selectedIndex: safeIdx
4569
+ }
4570
+ );
4444
4571
  }
4445
4572
  return null;
4446
4573
  }, [activeTab, resultData, logs, hideConsole, size, activeSnapshots, safeIdx, activeNarrativeEntries, activeNarrative, recorderViews, autoRecorderViews]);