footprint-explainable-ui 0.14.0 → 0.14.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.
package/dist/index.cjs CHANGED
@@ -4086,6 +4086,123 @@ 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 keys = Object.keys(val);
4095
+ if (keys.length > 0 && keys.every((k) => k.includes("#"))) {
4096
+ return val;
4097
+ }
4098
+ }
4099
+ }
4100
+ return null;
4101
+ }
4102
+ function findNumericField(entry) {
4103
+ for (const [k, v2] of Object.entries(entry)) {
4104
+ if (typeof v2 === "number") return { key: k, value: v2 };
4105
+ }
4106
+ return null;
4107
+ }
4108
+ function KeyedRecorderView({
4109
+ data,
4110
+ description,
4111
+ snapshots,
4112
+ selectedIndex
4113
+ }) {
4114
+ const [showAggregate, setShowAggregate] = (0, import_react19.useState)(false);
4115
+ const steps = (0, import_react19.useMemo)(() => detectKeyedSteps(data), [data]);
4116
+ const visibleIds = (0, import_react19.useMemo)(() => {
4117
+ const ids = /* @__PURE__ */ new Set();
4118
+ for (let i = 0; i <= selectedIndex && i < snapshots.length; i++) {
4119
+ const id = snapshots[i].runtimeStageId;
4120
+ if (id) ids.add(id);
4121
+ }
4122
+ return ids;
4123
+ }, [snapshots, selectedIndex]);
4124
+ const isAtEnd = selectedIndex >= snapshots.length - 1;
4125
+ if (!steps) {
4126
+ 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) });
4127
+ }
4128
+ const allKeys = Object.keys(steps);
4129
+ const visibleEntries = allKeys.filter((k) => visibleIds.has(k));
4130
+ const numField = allKeys.length > 0 ? findNumericField(steps[allKeys[0]]) : null;
4131
+ const numFieldKey = numField?.key ?? "";
4132
+ let runningTotal = 0;
4133
+ if (numFieldKey) {
4134
+ for (const k of visibleEntries) {
4135
+ runningTotal += steps[k][numFieldKey] ?? 0;
4136
+ }
4137
+ }
4138
+ let grandTotal = 0;
4139
+ if (numFieldKey) {
4140
+ for (const entry of Object.values(steps)) {
4141
+ grandTotal += entry[numFieldKey] ?? 0;
4142
+ }
4143
+ }
4144
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { overflow: "auto", height: "100%", display: "flex", flexDirection: "column" }, children: [
4145
+ 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 }),
4146
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { padding: 12, flex: 1, overflow: "auto" }, children: [
4147
+ visibleEntries.map((key) => {
4148
+ const entry = steps[key];
4149
+ const label = entry.stageName ?? key;
4150
+ const numVal = numFieldKey ? entry[numFieldKey] : void 0;
4151
+ 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: [
4152
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: { color: theme.textMuted, width: 140, flexShrink: 0, fontSize: 10 }, children: key }),
4153
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: { fontWeight: 600, flex: 1 }, children: label }),
4154
+ 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) })
4155
+ ] }, key);
4156
+ }),
4157
+ 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..." }),
4158
+ 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: [
4159
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { style: { color: theme.textMuted }, children: [
4160
+ "Running total (",
4161
+ numFieldKey,
4162
+ "):"
4163
+ ] }),
4164
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { style: { fontWeight: 700, marginLeft: 8, color: theme.primary }, children: runningTotal < 1 ? runningTotal.toFixed(3) : runningTotal.toFixed(1) }),
4165
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { style: { color: theme.textMuted, marginLeft: 8, fontSize: 10 }, children: [
4166
+ "(",
4167
+ visibleEntries.length,
4168
+ " of ",
4169
+ allKeys.length,
4170
+ " steps)"
4171
+ ] })
4172
+ ] }),
4173
+ isAtEnd && numFieldKey && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { marginTop: 12 }, children: !showAggregate ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
4174
+ "button",
4175
+ {
4176
+ onClick: () => setShowAggregate(true),
4177
+ style: {
4178
+ background: theme.primary,
4179
+ color: "#fff",
4180
+ border: "none",
4181
+ borderRadius: 6,
4182
+ padding: "8px 16px",
4183
+ fontSize: 12,
4184
+ fontWeight: 600,
4185
+ cursor: "pointer",
4186
+ fontFamily: "inherit"
4187
+ },
4188
+ children: [
4189
+ "Aggregate (",
4190
+ numFieldKey,
4191
+ ")"
4192
+ ]
4193
+ }
4194
+ ) : /* @__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: [
4195
+ /* @__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" }),
4196
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { fontSize: 20, fontWeight: 700, color: theme.success }, children: grandTotal < 1 ? grandTotal.toFixed(3) : grandTotal.toFixed(1) }),
4197
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { fontSize: 10, color: theme.textMuted, marginTop: 2 }, children: [
4198
+ allKeys.length,
4199
+ " steps \xB7 ",
4200
+ numFieldKey
4201
+ ] })
4202
+ ] }) })
4203
+ ] })
4204
+ ] });
4205
+ }
4089
4206
  var DetailsContent = (0, import_react19.memo)(function DetailsContent2({
4090
4207
  snapshots,
4091
4208
  selectedIndex,
@@ -4259,10 +4376,10 @@ function ExplainableShell({
4259
4376
  const allTabs = (0, import_react19.useMemo)(() => {
4260
4377
  const tabs2 = [
4261
4378
  { id: "result", name: "Result", description: "Final output and console logs" },
4262
- { id: "memory", name: "Memory", description: "Accumulated shared state at each stage" }
4379
+ { id: "memory", name: "Memory", description: "Accumulator \u2014 progressive shared state at each stage" }
4263
4380
  ];
4264
4381
  if (hasNarrative) {
4265
- tabs2.push({ id: "narrative", name: "Narrative", description: "What happened, what data flowed, what decisions were made" });
4382
+ tabs2.push({ id: "narrative", name: "Narrative", description: "Translator (SequenceRecorder) \u2014 interleaved flow + data narrative per execution step" });
4266
4383
  }
4267
4384
  for (const v2 of recorderViews ?? []) {
4268
4385
  tabs2.push({ id: v2.id, name: v2.name, description: v2.description });
@@ -4430,17 +4547,15 @@ function ExplainableShell({
4430
4547
  }
4431
4548
  const autoView = autoRecorderViews.find((v2) => v2.id === activeTab);
4432
4549
  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
- ] });
4550
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
4551
+ KeyedRecorderView,
4552
+ {
4553
+ data: autoView.data,
4554
+ description: autoView.description,
4555
+ snapshots: activeSnapshots,
4556
+ selectedIndex: safeIdx
4557
+ }
4558
+ );
4444
4559
  }
4445
4560
  return null;
4446
4561
  }, [activeTab, resultData, logs, hideConsole, size, activeSnapshots, safeIdx, activeNarrativeEntries, activeNarrative, recorderViews, autoRecorderViews]);