footprint-explainable-ui 0.13.2 → 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/flowchart.d.cts +2 -0
- package/dist/flowchart.d.ts +2 -0
- package/dist/index.cjs +120 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -2
- package/dist/index.d.ts +75 -2
- package/dist/index.js +117 -59
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/flowchart.d.cts
CHANGED
|
@@ -9,6 +9,8 @@ interface StageSnapshot {
|
|
|
9
9
|
stageName: string;
|
|
10
10
|
/** Human-readable label */
|
|
11
11
|
stageLabel: string;
|
|
12
|
+
/** Unique per-execution-step identifier. Format: [subflowPath/]stageId#executionIndex. Key for recorder Map lookup. */
|
|
13
|
+
runtimeStageId?: string;
|
|
12
14
|
/** Accumulated memory state after this stage ran */
|
|
13
15
|
memory: Record<string, unknown>;
|
|
14
16
|
/** Narrative text describing what happened */
|
package/dist/flowchart.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ interface StageSnapshot {
|
|
|
9
9
|
stageName: string;
|
|
10
10
|
/** Human-readable label */
|
|
11
11
|
stageLabel: string;
|
|
12
|
+
/** Unique per-execution-step identifier. Format: [subflowPath/]stageId#executionIndex. Key for recorder Map lookup. */
|
|
13
|
+
runtimeStageId?: string;
|
|
12
14
|
/** Accumulated memory state after this stage ran */
|
|
13
15
|
memory: Record<string, unknown>;
|
|
14
16
|
/** Narrative text describing what happened */
|
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();
|
|
@@ -2331,6 +2409,7 @@ function flattenTree(node, out, sharedState, accumulatedMs = 0, subflowResults,
|
|
|
2331
2409
|
out.push({
|
|
2332
2410
|
stageName: displayName,
|
|
2333
2411
|
stageLabel: stageId,
|
|
2412
|
+
runtimeStageId: node.runtimeStageId ?? void 0,
|
|
2334
2413
|
memory,
|
|
2335
2414
|
narrative,
|
|
2336
2415
|
startMs,
|
|
@@ -2683,37 +2762,14 @@ function NarrativePanel({
|
|
|
2683
2762
|
const endIdx = groupsToShow < stageBoundaries.length ? stageBoundaries[groupsToShow] : narrative.length;
|
|
2684
2763
|
return Math.max(1, endIdx);
|
|
2685
2764
|
}, [snapshots.length, selectedIndex, narrative]);
|
|
2686
|
-
const
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
if (snap.subflowId) keys.add(snap.subflowId);
|
|
2695
|
-
let found = false;
|
|
2696
|
-
for (let j = entryIdx; j < narrativeEntries.length; j++) {
|
|
2697
|
-
const e = narrativeEntries[j];
|
|
2698
|
-
const eKey = e.stageId ?? e.subflowId ?? e.stageName;
|
|
2699
|
-
if (eKey && keys.has(eKey)) {
|
|
2700
|
-
found = true;
|
|
2701
|
-
entryIdx = j;
|
|
2702
|
-
break;
|
|
2703
|
-
}
|
|
2704
|
-
if (!eKey && !found) {
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
if (!found) continue;
|
|
2708
|
-
while (entryIdx < narrativeEntries.length) {
|
|
2709
|
-
const e = narrativeEntries[entryIdx];
|
|
2710
|
-
const eKey = e.stageId ?? e.subflowId ?? e.stageName;
|
|
2711
|
-
if (eKey && !keys.has(eKey)) break;
|
|
2712
|
-
entryIdx++;
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2715
|
-
return entryIdx;
|
|
2716
|
-
}, [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
|
+
);
|
|
2717
2773
|
const hasStructured = narrativeEntries && narrativeEntries.length > 0;
|
|
2718
2774
|
const [copied, setCopied] = (0, import_react12.useState)(false);
|
|
2719
2775
|
const buildLLMNarrative = (0, import_react12.useCallback)(() => {
|
|
@@ -2729,7 +2785,7 @@ function NarrativePanel({
|
|
|
2729
2785
|
root.push(entry);
|
|
2730
2786
|
} else {
|
|
2731
2787
|
if (entry.type === "subflow") {
|
|
2732
|
-
const isExit = entry.
|
|
2788
|
+
const isExit = entry.direction === "exit";
|
|
2733
2789
|
if (!isExit) {
|
|
2734
2790
|
root.push(entry);
|
|
2735
2791
|
}
|
|
@@ -2749,7 +2805,11 @@ function NarrativePanel({
|
|
|
2749
2805
|
if (opts?.inSubflow && e.type === "subflow") continue;
|
|
2750
2806
|
let text = e.text;
|
|
2751
2807
|
if (opts?.inSubflow) {
|
|
2752
|
-
|
|
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
|
+
}
|
|
2753
2813
|
}
|
|
2754
2814
|
const isHeading = e.type === "stage" || e.type === "subflow" || e.type === "fork" || e.type === "selector";
|
|
2755
2815
|
if (isHeading) {
|
|
@@ -4049,6 +4109,12 @@ var DetailsContent = (0, import_react19.memo)(function DetailsContent2({
|
|
|
4049
4109
|
];
|
|
4050
4110
|
const allViews = [...builtInViews, ...extraViews ?? []];
|
|
4051
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]);
|
|
4052
4118
|
const activeView = allViews.find((v2) => v2.id === activeViewId) ?? allViews[0];
|
|
4053
4119
|
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { style: { flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }, children: [
|
|
4054
4120
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { style: { display: "flex", borderBottom: `1px solid ${theme.border}`, flexShrink: 0, overflowX: "auto" }, children: allViews.map((view) => {
|
|
@@ -4099,25 +4165,6 @@ function resolveSubflowLevel(parentSpec, parentSnapshots, subflowNodeName, narra
|
|
|
4099
4165
|
snapshots: sfSnapshots
|
|
4100
4166
|
};
|
|
4101
4167
|
}
|
|
4102
|
-
function extractSubflowNarrative(entries, subflowId, subflowName) {
|
|
4103
|
-
const prefix = subflowId + "/";
|
|
4104
|
-
const byPrefix = entries.filter((e) => e.stageName?.startsWith(prefix));
|
|
4105
|
-
if (byPrefix.length > 0) return byPrefix;
|
|
4106
|
-
const byId = entries.filter((e) => e.subflowId === subflowId);
|
|
4107
|
-
if (byId.length > 0) return byId;
|
|
4108
|
-
const result = [];
|
|
4109
|
-
const searchName = subflowName ?? subflowId;
|
|
4110
|
-
let inside = false;
|
|
4111
|
-
for (const entry of entries) {
|
|
4112
|
-
if (entry.type === "subflow" && entry.text.includes(searchName) && entry.text.startsWith("Entering")) {
|
|
4113
|
-
inside = true;
|
|
4114
|
-
continue;
|
|
4115
|
-
}
|
|
4116
|
-
if (inside && entry.type === "subflow" && entry.text.includes(searchName) && entry.text.startsWith("Exiting")) break;
|
|
4117
|
-
if (inside) result.push(entry);
|
|
4118
|
-
}
|
|
4119
|
-
return result;
|
|
4120
|
-
}
|
|
4121
4168
|
function findSubflowSpecNode(node, name) {
|
|
4122
4169
|
if ((node.name === name || node.id === name) && node.isSubflowRoot) return node;
|
|
4123
4170
|
if (node.children) {
|
|
@@ -4206,22 +4253,22 @@ function ExplainableShell({
|
|
|
4206
4253
|
const recorders = runtimeSnapshot?.recorders;
|
|
4207
4254
|
if (!recorders?.length) return [];
|
|
4208
4255
|
const explicitIds = new Set((recorderViews ?? []).map((v2) => v2.id));
|
|
4209
|
-
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 }));
|
|
4210
4257
|
}, [runtimeSnapshot, recorderViews]);
|
|
4211
4258
|
const hasNarrative = !!(narrative?.length || narrativeEntries?.length);
|
|
4212
4259
|
const allTabs = (0, import_react19.useMemo)(() => {
|
|
4213
4260
|
const tabs2 = [
|
|
4214
|
-
{ id: "result", name: "Result" },
|
|
4215
|
-
{ 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" }
|
|
4216
4263
|
];
|
|
4217
4264
|
if (hasNarrative) {
|
|
4218
|
-
tabs2.push({ id: "narrative", name: "Narrative" });
|
|
4265
|
+
tabs2.push({ id: "narrative", name: "Narrative", description: "What happened, what data flowed, what decisions were made" });
|
|
4219
4266
|
}
|
|
4220
4267
|
for (const v2 of recorderViews ?? []) {
|
|
4221
|
-
tabs2.push({ id: v2.id, name: v2.name });
|
|
4268
|
+
tabs2.push({ id: v2.id, name: v2.name, description: v2.description });
|
|
4222
4269
|
}
|
|
4223
4270
|
for (const v2 of autoRecorderViews) {
|
|
4224
|
-
tabs2.push({ id: v2.id, name: v2.name });
|
|
4271
|
+
tabs2.push({ id: v2.id, name: v2.name, description: v2.description });
|
|
4225
4272
|
}
|
|
4226
4273
|
const hideSet = new Set(hideTabsProp ?? []);
|
|
4227
4274
|
return hideSet.size > 0 ? tabs2.filter((t) => !hideSet.has(t.id)) : tabs2;
|
|
@@ -4383,7 +4430,17 @@ function ExplainableShell({
|
|
|
4383
4430
|
}
|
|
4384
4431
|
const autoView = autoRecorderViews.find((v2) => v2.id === activeTab);
|
|
4385
4432
|
if (autoView) {
|
|
4386
|
-
return /* @__PURE__ */ (0, import_jsx_runtime18.
|
|
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
|
+
] });
|
|
4387
4444
|
}
|
|
4388
4445
|
return null;
|
|
4389
4446
|
}, [activeTab, resultData, logs, hideConsole, size, activeSnapshots, safeIdx, activeNarrativeEntries, activeNarrative, recorderViews, autoRecorderViews]);
|
|
@@ -4400,6 +4457,7 @@ function ExplainableShell({
|
|
|
4400
4457
|
"button",
|
|
4401
4458
|
{
|
|
4402
4459
|
onClick: () => handleTabChange(tab.id),
|
|
4460
|
+
title: tab.description,
|
|
4403
4461
|
style: {
|
|
4404
4462
|
padding: "6px 14px",
|
|
4405
4463
|
fontSize: 11,
|
|
@@ -4534,10 +4592,13 @@ function ExplainableShell({
|
|
|
4534
4592
|
StoryNarrative,
|
|
4535
4593
|
SubflowTree,
|
|
4536
4594
|
TimeTravelControls,
|
|
4595
|
+
buildEntryRangeIndex,
|
|
4596
|
+
computeRevealedEntryCount,
|
|
4537
4597
|
coolDark,
|
|
4538
4598
|
coolLight,
|
|
4539
4599
|
createSnapshots,
|
|
4540
4600
|
defaultTokens,
|
|
4601
|
+
extractSubflowNarrative,
|
|
4541
4602
|
rawDefaults,
|
|
4542
4603
|
subflowResultToSnapshots,
|
|
4543
4604
|
themePresets,
|