footprint-explainable-ui 0.13.3 → 0.14.0

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
@@ -35,10 +35,13 @@ __export(src_exports, {
35
35
  StoryNarrative: () => StoryNarrative,
36
36
  SubflowTree: () => SubflowTree,
37
37
  TimeTravelControls: () => TimeTravelControls,
38
+ buildEntryRangeIndex: () => buildEntryRangeIndex,
39
+ computeRevealedEntryCount: () => computeRevealedEntryCount,
38
40
  coolDark: () => coolDark,
39
41
  coolLight: () => coolLight,
40
42
  createSnapshots: () => createSnapshots,
41
43
  defaultTokens: () => defaultTokens,
44
+ extractSubflowNarrative: () => extractSubflowNarrative,
42
45
  rawDefaults: () => rawDefaults,
43
46
  subflowResultToSnapshots: () => subflowResultToSnapshots,
44
47
  themePresets: () => themePresets,
@@ -2256,6 +2259,81 @@ function TimeTravelControls({
2256
2259
  // src/components/ExplainableShell/ExplainableShell.tsx
2257
2260
  var import_react19 = require("react");
2258
2261
 
2262
+ // src/utils/narrativeSync.ts
2263
+ function buildEntryRangeIndex(entries) {
2264
+ const ranges = /* @__PURE__ */ new Map();
2265
+ let lastId;
2266
+ for (let i = 0; i < entries.length; i++) {
2267
+ const id = entries[i].runtimeStageId;
2268
+ if (id) {
2269
+ const existing = ranges.get(id);
2270
+ if (!existing) {
2271
+ ranges.set(id, { firstIdx: i, endIdx: i + 1 });
2272
+ } else {
2273
+ existing.endIdx = i + 1;
2274
+ }
2275
+ lastId = id;
2276
+ } else if (lastId) {
2277
+ ranges.get(lastId).endIdx = i + 1;
2278
+ }
2279
+ }
2280
+ return ranges;
2281
+ }
2282
+ function computeRevealedEntryCount(narrativeEntries, snapshots, selectedIndex, rangeIndex) {
2283
+ if (!narrativeEntries.length || snapshots.length === 0) return 0;
2284
+ if (rangeIndex) {
2285
+ let maxEndIdx = 0;
2286
+ for (let si = 0; si <= selectedIndex && si < snapshots.length; si++) {
2287
+ const targetId = snapshots[si].runtimeStageId;
2288
+ if (!targetId) continue;
2289
+ const range = rangeIndex.get(targetId);
2290
+ if (range && range.endIdx > maxEndIdx) {
2291
+ maxEndIdx = range.endIdx;
2292
+ }
2293
+ }
2294
+ return maxEndIdx;
2295
+ }
2296
+ let entryIdx = 0;
2297
+ for (let si = 0; si <= selectedIndex && si < snapshots.length; si++) {
2298
+ const targetId = snapshots[si].runtimeStageId;
2299
+ if (!targetId) continue;
2300
+ let found = false;
2301
+ for (let j = entryIdx; j < narrativeEntries.length; j++) {
2302
+ if (narrativeEntries[j].runtimeStageId === targetId) {
2303
+ found = true;
2304
+ entryIdx = j;
2305
+ break;
2306
+ }
2307
+ }
2308
+ if (!found) continue;
2309
+ while (entryIdx < narrativeEntries.length) {
2310
+ const eId = narrativeEntries[entryIdx].runtimeStageId;
2311
+ if (eId && eId !== targetId) break;
2312
+ entryIdx++;
2313
+ }
2314
+ }
2315
+ return entryIdx;
2316
+ }
2317
+ function extractSubflowNarrative(entries, subflowId, subflowName) {
2318
+ const prefix = subflowId + "/";
2319
+ const byPrefix = entries.filter((e) => e.stageName?.startsWith(prefix));
2320
+ if (byPrefix.length > 0) return byPrefix;
2321
+ const byId = entries.filter((e) => e.subflowId === subflowId);
2322
+ if (byId.length > 0) return byId;
2323
+ const result = [];
2324
+ const searchName = subflowName ?? subflowId;
2325
+ let inside = false;
2326
+ for (const entry of entries) {
2327
+ if (entry.type === "subflow" && entry.direction === "entry" && entry.stageName === searchName) {
2328
+ inside = true;
2329
+ continue;
2330
+ }
2331
+ if (inside && entry.type === "subflow" && entry.direction === "exit" && entry.stageName === searchName) break;
2332
+ if (inside) result.push(entry);
2333
+ }
2334
+ return result;
2335
+ }
2336
+
2259
2337
  // src/adapters/fromRuntimeSnapshot.ts
2260
2338
  function toVisualizationSnapshots(runtime, narrativeEntries) {
2261
2339
  const stageNarrativeMap = narrativeEntries?.length ? buildStageNarrativeMap(narrativeEntries) : /* @__PURE__ */ new Map();
@@ -2684,37 +2762,14 @@ function NarrativePanel({
2684
2762
  const endIdx = groupsToShow < stageBoundaries.length ? stageBoundaries[groupsToShow] : narrative.length;
2685
2763
  return Math.max(1, endIdx);
2686
2764
  }, [snapshots.length, selectedIndex, narrative]);
2687
- const revealedEntryCount = (0, import_react12.useMemo)(() => {
2688
- if (!narrativeEntries?.length || snapshots.length === 0) return 0;
2689
- let entryIdx = 0;
2690
- for (let si = 0; si <= selectedIndex && si < snapshots.length; si++) {
2691
- const snap = snapshots[si];
2692
- const keys = /* @__PURE__ */ new Set();
2693
- if (snap.stageLabel) keys.add(snap.stageLabel);
2694
- if (snap.stageName) keys.add(snap.stageName);
2695
- if (snap.subflowId) keys.add(snap.subflowId);
2696
- let found = false;
2697
- for (let j = entryIdx; j < narrativeEntries.length; j++) {
2698
- const e = narrativeEntries[j];
2699
- const eKey = e.stageId ?? e.subflowId ?? e.stageName;
2700
- if (eKey && keys.has(eKey)) {
2701
- found = true;
2702
- entryIdx = j;
2703
- break;
2704
- }
2705
- if (!eKey && !found) {
2706
- }
2707
- }
2708
- if (!found) continue;
2709
- while (entryIdx < narrativeEntries.length) {
2710
- const e = narrativeEntries[entryIdx];
2711
- const eKey = e.stageId ?? e.subflowId ?? e.stageName;
2712
- if (eKey && !keys.has(eKey)) break;
2713
- entryIdx++;
2714
- }
2715
- }
2716
- return entryIdx;
2717
- }, [narrativeEntries, snapshots, selectedIndex]);
2765
+ const rangeIndex = (0, import_react12.useMemo)(
2766
+ () => narrativeEntries?.length ? buildEntryRangeIndex(narrativeEntries) : void 0,
2767
+ [narrativeEntries]
2768
+ );
2769
+ const revealedEntryCount = (0, import_react12.useMemo)(
2770
+ () => narrativeEntries?.length ? computeRevealedEntryCount(narrativeEntries, snapshots, selectedIndex, rangeIndex) : 0,
2771
+ [narrativeEntries, snapshots, selectedIndex, rangeIndex]
2772
+ );
2718
2773
  const hasStructured = narrativeEntries && narrativeEntries.length > 0;
2719
2774
  const [copied, setCopied] = (0, import_react12.useState)(false);
2720
2775
  const buildLLMNarrative = (0, import_react12.useCallback)(() => {
@@ -2730,7 +2785,7 @@ function NarrativePanel({
2730
2785
  root.push(entry);
2731
2786
  } else {
2732
2787
  if (entry.type === "subflow") {
2733
- const isExit = entry.text.startsWith("Done:") || entry.text.startsWith("Exiting");
2788
+ const isExit = entry.direction === "exit";
2734
2789
  if (!isExit) {
2735
2790
  root.push(entry);
2736
2791
  }
@@ -2750,7 +2805,11 @@ function NarrativePanel({
2750
2805
  if (opts?.inSubflow && e.type === "subflow") continue;
2751
2806
  let text = e.text;
2752
2807
  if (opts?.inSubflow) {
2753
- text = text.replace(new RegExp(`\\[${opts.inSubflow}/`), "[");
2808
+ const prefix = `[${opts.inSubflow}/`;
2809
+ const idx = text.indexOf(prefix);
2810
+ if (idx !== -1) {
2811
+ text = text.slice(0, idx) + "[" + text.slice(idx + prefix.length);
2812
+ }
2754
2813
  }
2755
2814
  const isHeading = e.type === "stage" || e.type === "subflow" || e.type === "fork" || e.type === "selector";
2756
2815
  if (isHeading) {
@@ -4050,6 +4109,12 @@ var DetailsContent = (0, import_react19.memo)(function DetailsContent2({
4050
4109
  ];
4051
4110
  const allViews = [...builtInViews, ...extraViews ?? []];
4052
4111
  const [activeViewId, setActiveViewId] = (0, import_react19.useState)(allViews[0]?.id ?? "memory");
4112
+ const viewIds = allViews.map((v2) => v2.id).join(",");
4113
+ (0, import_react19.useEffect)(() => {
4114
+ if (!allViews.find((v2) => v2.id === activeViewId)) {
4115
+ setActiveViewId(allViews[0]?.id ?? "memory");
4116
+ }
4117
+ }, [viewIds]);
4053
4118
  const activeView = allViews.find((v2) => v2.id === activeViewId) ?? allViews[0];
4054
4119
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }, children: [
4055
4120
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { display: "flex", borderBottom: `1px solid ${theme.border}`, flexShrink: 0, overflowX: "auto" }, children: allViews.map((view) => {
@@ -4100,25 +4165,6 @@ function resolveSubflowLevel(parentSpec, parentSnapshots, subflowNodeName, narra
4100
4165
  snapshots: sfSnapshots
4101
4166
  };
4102
4167
  }
4103
- function extractSubflowNarrative(entries, subflowId, subflowName) {
4104
- const prefix = subflowId + "/";
4105
- const byPrefix = entries.filter((e) => e.stageName?.startsWith(prefix));
4106
- if (byPrefix.length > 0) return byPrefix;
4107
- const byId = entries.filter((e) => e.subflowId === subflowId);
4108
- if (byId.length > 0) return byId;
4109
- const result = [];
4110
- const searchName = subflowName ?? subflowId;
4111
- let inside = false;
4112
- for (const entry of entries) {
4113
- if (entry.type === "subflow" && entry.text.includes(searchName) && entry.text.startsWith("Entering")) {
4114
- inside = true;
4115
- continue;
4116
- }
4117
- if (inside && entry.type === "subflow" && entry.text.includes(searchName) && entry.text.startsWith("Exiting")) break;
4118
- if (inside) result.push(entry);
4119
- }
4120
- return result;
4121
- }
4122
4168
  function findSubflowSpecNode(node, name) {
4123
4169
  if ((node.name === name || node.id === name) && node.isSubflowRoot) return node;
4124
4170
  if (node.children) {
@@ -4207,22 +4253,22 @@ function ExplainableShell({
4207
4253
  const recorders = runtimeSnapshot?.recorders;
4208
4254
  if (!recorders?.length) return [];
4209
4255
  const explicitIds = new Set((recorderViews ?? []).map((v2) => v2.id));
4210
- return recorders.filter((r) => !explicitIds.has(r.id)).map((r) => ({ id: r.id, name: r.name, data: r.data }));
4256
+ return recorders.filter((r) => !explicitIds.has(r.id)).map((r) => ({ id: r.id, name: r.name, description: r.description, data: r.data }));
4211
4257
  }, [runtimeSnapshot, recorderViews]);
4212
4258
  const hasNarrative = !!(narrative?.length || narrativeEntries?.length);
4213
4259
  const allTabs = (0, import_react19.useMemo)(() => {
4214
4260
  const tabs2 = [
4215
- { id: "result", name: "Result" },
4216
- { id: "memory", name: "Memory" }
4261
+ { id: "result", name: "Result", description: "Final output and console logs" },
4262
+ { id: "memory", name: "Memory", description: "Accumulated shared state at each stage" }
4217
4263
  ];
4218
4264
  if (hasNarrative) {
4219
- tabs2.push({ id: "narrative", name: "Narrative" });
4265
+ tabs2.push({ id: "narrative", name: "Narrative", description: "What happened, what data flowed, what decisions were made" });
4220
4266
  }
4221
4267
  for (const v2 of recorderViews ?? []) {
4222
- tabs2.push({ id: v2.id, name: v2.name });
4268
+ tabs2.push({ id: v2.id, name: v2.name, description: v2.description });
4223
4269
  }
4224
4270
  for (const v2 of autoRecorderViews) {
4225
- tabs2.push({ id: v2.id, name: v2.name });
4271
+ tabs2.push({ id: v2.id, name: v2.name, description: v2.description });
4226
4272
  }
4227
4273
  const hideSet = new Set(hideTabsProp ?? []);
4228
4274
  return hideSet.size > 0 ? tabs2.filter((t) => !hideSet.has(t.id)) : tabs2;
@@ -4384,7 +4430,17 @@ function ExplainableShell({
4384
4430
  }
4385
4431
  const autoView = autoRecorderViews.find((v2) => v2.id === activeTab);
4386
4432
  if (autoView) {
4387
- 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 autoView.data === "string" ? autoView.data : JSON.stringify(autoView.data, null, 2) });
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
+ ] });
4388
4444
  }
4389
4445
  return null;
4390
4446
  }, [activeTab, resultData, logs, hideConsole, size, activeSnapshots, safeIdx, activeNarrativeEntries, activeNarrative, recorderViews, autoRecorderViews]);
@@ -4401,6 +4457,7 @@ function ExplainableShell({
4401
4457
  "button",
4402
4458
  {
4403
4459
  onClick: () => handleTabChange(tab.id),
4460
+ title: tab.description,
4404
4461
  style: {
4405
4462
  padding: "6px 14px",
4406
4463
  fontSize: 11,
@@ -4535,10 +4592,13 @@ function ExplainableShell({
4535
4592
  StoryNarrative,
4536
4593
  SubflowTree,
4537
4594
  TimeTravelControls,
4595
+ buildEntryRangeIndex,
4596
+ computeRevealedEntryCount,
4538
4597
  coolDark,
4539
4598
  coolLight,
4540
4599
  createSnapshots,
4541
4600
  defaultTokens,
4601
+ extractSubflowNarrative,
4542
4602
  rawDefaults,
4543
4603
  subflowResultToSnapshots,
4544
4604
  themePresets,