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.js CHANGED
@@ -4037,6 +4037,135 @@ var VLinePill = memo4(function VLinePill2({
4037
4037
  /* @__PURE__ */ jsx18("div", { style: { flex: 1, width: 1, background: theme.border } })
4038
4038
  ] });
4039
4039
  });
4040
+ function detectKeyedSteps(data) {
4041
+ if (!data || typeof data !== "object") return null;
4042
+ const obj = data;
4043
+ for (const val of Object.values(obj)) {
4044
+ if (val && typeof val === "object" && !Array.isArray(val)) {
4045
+ const entries = Object.entries(val);
4046
+ if (entries.length === 0) continue;
4047
+ const allObjectsWithNumbers = entries.every(([, v2]) => {
4048
+ if (!v2 || typeof v2 !== "object" || Array.isArray(v2)) return false;
4049
+ return Object.values(v2).some((f) => typeof f === "number");
4050
+ });
4051
+ if (allObjectsWithNumbers) {
4052
+ const keyType = entries.some(([k]) => k.includes("#")) ? "runtimeStageId" : "stageName";
4053
+ return { steps: val, keyType };
4054
+ }
4055
+ }
4056
+ }
4057
+ return null;
4058
+ }
4059
+ function findNumericField(entry) {
4060
+ for (const [k, v2] of Object.entries(entry)) {
4061
+ if (typeof v2 === "number") return { key: k, value: v2 };
4062
+ }
4063
+ return null;
4064
+ }
4065
+ function KeyedRecorderView({
4066
+ data,
4067
+ description,
4068
+ snapshots,
4069
+ selectedIndex
4070
+ }) {
4071
+ const [showAggregate, setShowAggregate] = useState9(false);
4072
+ const detected = useMemo11(() => detectKeyedSteps(data), [data]);
4073
+ const visibleKeys = useMemo11(() => {
4074
+ const keys = /* @__PURE__ */ new Set();
4075
+ for (let i = 0; i <= selectedIndex && i < snapshots.length; i++) {
4076
+ const snap = snapshots[i];
4077
+ if (detected?.keyType === "runtimeStageId") {
4078
+ if (snap.runtimeStageId) keys.add(snap.runtimeStageId);
4079
+ } else {
4080
+ if (snap.stageName) keys.add(snap.stageName);
4081
+ if (snap.stageLabel) keys.add(snap.stageLabel);
4082
+ }
4083
+ }
4084
+ return keys;
4085
+ }, [snapshots, selectedIndex, detected?.keyType]);
4086
+ const isAtEnd = selectedIndex >= snapshots.length - 1;
4087
+ if (!detected) {
4088
+ return /* @__PURE__ */ jsx18("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) });
4089
+ }
4090
+ const steps = detected.steps;
4091
+ const allKeys = Object.keys(steps);
4092
+ const visibleEntries = allKeys.filter((k) => visibleKeys.has(k));
4093
+ const numField = allKeys.length > 0 ? findNumericField(steps[allKeys[0]]) : null;
4094
+ const numFieldKey = numField?.key ?? "";
4095
+ let runningTotal = 0;
4096
+ if (numFieldKey) {
4097
+ for (const k of visibleEntries) {
4098
+ runningTotal += steps[k][numFieldKey] ?? 0;
4099
+ }
4100
+ }
4101
+ let grandTotal = 0;
4102
+ if (numFieldKey) {
4103
+ for (const entry of Object.values(steps)) {
4104
+ grandTotal += entry[numFieldKey] ?? 0;
4105
+ }
4106
+ }
4107
+ return /* @__PURE__ */ jsxs17("div", { style: { overflow: "auto", height: "100%", display: "flex", flexDirection: "column" }, children: [
4108
+ description && /* @__PURE__ */ jsx18("div", { style: { padding: "6px 12px", fontSize: 11, color: theme.textMuted, fontStyle: "italic", borderBottom: `1px solid ${theme.border}`, flexShrink: 0 }, children: description }),
4109
+ /* @__PURE__ */ jsxs17("div", { style: { padding: 12, flex: 1, overflow: "auto" }, children: [
4110
+ visibleEntries.map((key) => {
4111
+ const entry = steps[key];
4112
+ const label = entry.stageName ?? key;
4113
+ const numVal = numFieldKey ? entry[numFieldKey] : void 0;
4114
+ return /* @__PURE__ */ jsxs17("div", { style: { display: "flex", alignItems: "center", padding: "4px 0", fontSize: 12, fontFamily: theme.fontMono, borderBottom: `1px solid ${theme.border}22` }, children: [
4115
+ /* @__PURE__ */ jsx18("span", { style: { color: theme.textMuted, width: 140, flexShrink: 0, fontSize: 10 }, children: key }),
4116
+ /* @__PURE__ */ jsx18("span", { style: { fontWeight: 600, flex: 1 }, children: label }),
4117
+ numVal !== void 0 && /* @__PURE__ */ jsx18("span", { style: { color: theme.primary, fontWeight: 700, marginLeft: 8 }, children: numVal < 1 ? numVal.toFixed(3) : numVal.toFixed(1) })
4118
+ ] }, key);
4119
+ }),
4120
+ visibleEntries.length === 0 && /* @__PURE__ */ jsx18("div", { style: { color: theme.textMuted, fontSize: 11, fontStyle: "italic", padding: "8px 0" }, children: "Scrub the slider to reveal entries..." }),
4121
+ numFieldKey && visibleEntries.length > 0 && /* @__PURE__ */ jsxs17("div", { style: { marginTop: 12, padding: "8px 12px", background: `color-mix(in srgb, ${theme.primary} 8%, transparent)`, borderRadius: 6, fontSize: 12 }, children: [
4122
+ /* @__PURE__ */ jsxs17("span", { style: { color: theme.textMuted }, children: [
4123
+ "Running total (",
4124
+ numFieldKey,
4125
+ "):"
4126
+ ] }),
4127
+ /* @__PURE__ */ jsx18("span", { style: { fontWeight: 700, marginLeft: 8, color: theme.primary }, children: runningTotal < 1 ? runningTotal.toFixed(3) : runningTotal.toFixed(1) }),
4128
+ /* @__PURE__ */ jsxs17("span", { style: { color: theme.textMuted, marginLeft: 8, fontSize: 10 }, children: [
4129
+ "(",
4130
+ visibleEntries.length,
4131
+ " of ",
4132
+ allKeys.length,
4133
+ " steps)"
4134
+ ] })
4135
+ ] }),
4136
+ isAtEnd && numFieldKey && /* @__PURE__ */ jsx18("div", { style: { marginTop: 12 }, children: !showAggregate ? /* @__PURE__ */ jsxs17(
4137
+ "button",
4138
+ {
4139
+ onClick: () => setShowAggregate(true),
4140
+ style: {
4141
+ background: theme.primary,
4142
+ color: "#fff",
4143
+ border: "none",
4144
+ borderRadius: 6,
4145
+ padding: "8px 16px",
4146
+ fontSize: 12,
4147
+ fontWeight: 600,
4148
+ cursor: "pointer",
4149
+ fontFamily: "inherit"
4150
+ },
4151
+ children: [
4152
+ "Aggregate (",
4153
+ numFieldKey,
4154
+ ")"
4155
+ ]
4156
+ }
4157
+ ) : /* @__PURE__ */ jsxs17("div", { style: { padding: "10px 14px", background: `color-mix(in srgb, ${theme.success} 12%, transparent)`, borderRadius: 6, border: `1px solid ${theme.success}44` }, children: [
4158
+ /* @__PURE__ */ jsx18("div", { style: { fontSize: 10, color: theme.textMuted, textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 4 }, children: "Aggregate \u2014 Grand Total" }),
4159
+ /* @__PURE__ */ jsx18("div", { style: { fontSize: 20, fontWeight: 700, color: theme.success }, children: grandTotal < 1 ? grandTotal.toFixed(3) : grandTotal.toFixed(1) }),
4160
+ /* @__PURE__ */ jsxs17("div", { style: { fontSize: 10, color: theme.textMuted, marginTop: 2 }, children: [
4161
+ allKeys.length,
4162
+ " steps \xB7 ",
4163
+ numFieldKey
4164
+ ] })
4165
+ ] }) })
4166
+ ] })
4167
+ ] });
4168
+ }
4040
4169
  var DetailsContent = memo4(function DetailsContent2({
4041
4170
  snapshots,
4042
4171
  selectedIndex,
@@ -4210,10 +4339,10 @@ function ExplainableShell({
4210
4339
  const allTabs = useMemo11(() => {
4211
4340
  const tabs2 = [
4212
4341
  { id: "result", name: "Result", description: "Final output and console logs" },
4213
- { id: "memory", name: "Memory", description: "Accumulated shared state at each stage" }
4342
+ { id: "memory", name: "Memory", description: "Accumulator \u2014 progressive shared state at each stage" }
4214
4343
  ];
4215
4344
  if (hasNarrative) {
4216
- tabs2.push({ id: "narrative", name: "Narrative", description: "What happened, what data flowed, what decisions were made" });
4345
+ tabs2.push({ id: "narrative", name: "Narrative", description: "Translator (SequenceRecorder) \u2014 interleaved flow + data narrative per execution step" });
4217
4346
  }
4218
4347
  for (const v2 of recorderViews ?? []) {
4219
4348
  tabs2.push({ id: v2.id, name: v2.name, description: v2.description });
@@ -4381,17 +4510,15 @@ function ExplainableShell({
4381
4510
  }
4382
4511
  const autoView = autoRecorderViews.find((v2) => v2.id === activeTab);
4383
4512
  if (autoView) {
4384
- return /* @__PURE__ */ jsxs17("div", { style: { overflow: "auto", height: "100%", display: "flex", flexDirection: "column" }, children: [
4385
- autoView.description && /* @__PURE__ */ jsx18("div", { style: {
4386
- padding: "6px 12px",
4387
- fontSize: 11,
4388
- color: theme.textMuted,
4389
- fontStyle: "italic",
4390
- borderBottom: `1px solid ${theme.border}`,
4391
- flexShrink: 0
4392
- }, children: autoView.description }),
4393
- /* @__PURE__ */ jsx18("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) })
4394
- ] });
4513
+ return /* @__PURE__ */ jsx18(
4514
+ KeyedRecorderView,
4515
+ {
4516
+ data: autoView.data,
4517
+ description: autoView.description,
4518
+ snapshots: activeSnapshots,
4519
+ selectedIndex: safeIdx
4520
+ }
4521
+ );
4395
4522
  }
4396
4523
  return null;
4397
4524
  }, [activeTab, resultData, logs, hideConsole, size, activeSnapshots, safeIdx, activeNarrativeEntries, activeNarrative, recorderViews, autoRecorderViews]);