@tangle-network/charts 1.0.2 → 2.0.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/README.md +4 -1
- package/dist/{flow-trace-CKk85dAV.d.ts → flow-trace-CF4Q07cA.d.ts} +3 -2
- package/dist/{timeline-B7c-cede.js → flow-trace-k097Ukvs.js} +10 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +14 -17
- package/dist/react/flow-waterfall.css +12 -12
- package/dist/react/index.d.ts +7 -3
- package/dist/react/index.js +199 -154
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@ The React entry also exports `DayBars`, `HBars`, `Histogram`, `Lines`, `Meter`,
|
|
|
6
6
|
|
|
7
7
|
`FlowWaterfall` from `@tangle-network/charts/react` is the shared live trace inspector: an agent timeline (one row per agent, elbow handoffs, hatched waiting) and a virtualized span waterfall over one clock. It owns hierarchy layout, folding, search, playback, zoom, span cards and running-span growth. Producers own identity, `parentId`, explicit `status` and observed totals. Import `@tangle-network/charts/react/flow-waterfall.css`; its `fw-*` classes and six-field summary support hosts without a Tailwind build. Every color, radius and shadow in it is a Brand role (text, card, hairline, well, chart grid, status inks, the categorical tones for agents), so the host loads `@tangle-network/brand` `tokens.css` (or `legacy-light.css`) and sets its theme; the stylesheet has no color of its own. The React-free root exports the `FlowSpan` and `FlowTrace` types.
|
|
8
8
|
|
|
9
|
+
`FlowWaterfall` accepts optional `graph` and `latestEvents` React nodes. Supplying `graph` adds Graph to its existing Agents/Steps view control, including `defaultView="graph"`. The trace clock and controlled selection survive switches. Graph hides waterfall-only controls; `latestEvents` stays mounted in the header across all views.
|
|
10
|
+
|
|
9
11
|
`WaterfallHeader`, `WaterfallTimeAxis`, `WaterfallBar` and `WaterfallRow` remain available for existing consumers that own their own hierarchy and selection. All use the same `waterfallSpanGeometry` and `waterfallTicks`. Existing fixed-width cells, tones, formatting, attribution slots and unordered-time `—` marks are preserved. Optional fluid tracks, inner-bar colour/class, overlay and clipping fields support the shared inspector without changing existing calls. The primitives were extracted from Intelligence under [brand #211](https://github.com/tangle-network/brand/pull/211); the shared inspector extends that presentation ownership while leaving failure interpretation with producers.
|
|
10
12
|
|
|
11
13
|
Five figures cover the decisions a benchmark reader makes:
|
|
@@ -337,7 +339,7 @@ const trace = otlpFlowTrace(spans, { price: catalogPrice(await (await fetch(cata
|
|
|
337
339
|
|
|
338
340
|
`defaultView="agents"` opens the agent timeline: one row per agent with its model, thinking and tool marks, hatched waiting while it delegates, elbow connectors to each agent it spawned and back, retry and failure glyphs, and a headline with the measured speedup over sequential (agent busy time over wall time). Expanding an agent opens the span view with that agent unfolded. The span view virtualizes rows (5,000 spans scroll at 58+ fps in headless Chromium), pins ancestors while their children scroll, and labels each bar with its duration and recorded cost. Hover shows a span card; click pins it and zooms the axis to a span too narrow to read. `onSelect(span)` hears each opened row and pinned bar, so a host can open its own drill-down; `markers={[{ at, label, tone }]}` draws labelled instants, such as releases, across the rows. Give an agent span an `outcome` (`{ tone, label }`) and its lane takes the outcome color and the header a legend. `detail` becomes the agent's subtitle. The card is a table of what the row recorded, every value with its unit: model, duration, tokens in, out and cached (the span's `promptTokens`, `completionTokens` and `cachedTokens`, else the sum of its children's), LLM latency (median and p95 of its model calls), cost, LLM calls, tool calls by tool (the name before " · "), failed steps, and retries and restarts (`retryOf`, `marks` of kind `restart`); a value nobody recorded is left out. `facts` (`[{ label, value }]`) adds the host's rows after it. The card opens at the pointer, above or below its row, whichever side fits the viewport. `onTimeChange` reports where the reader scrubbed (null when live), and markers double as an event track (click, N or P to jump). A partly priced trace shows its floor and coverage (`costCoverage`), and `billedUsd` shows the real charge beside the list price. Ctrl/Cmd+wheel or `+`/`-`/`0` zoom, Shift+wheel pans, the scrub track under the controls shows the whole trace, and arrow keys walk the tree.
|
|
339
341
|
|
|
340
|
-
Live views append spans as they arrive: `traces watch <id|run-dir> --format jsonl --spans` emits a `traces.stream.span` event per new or changed span, and `otlpFlowTrace` keeps the last span per `span_id`. Running agent spans grow to the clock between deltas without React renders. `activityAt` supplies trace-relative event times and `medianEventGapMs` preserves producer cadence before tail truncation; without it, child-step activity supplies the samples. Silence beyond five minutes and three median event gaps hatches the tail and names the quiet duration. The Errors filter expands matching agents and steps; its rug locates each error, marking unreported times separately. Containers below 600px use stacked 44px rows and a Key disclosure. Search supports Cmd/Ctrl+F and keeps ancestor context. Missing cost and authoritative LLM counts stay Unknown; a cost priced from a catalog is labelled list price. Invalid intervals retain a per-row `—` timing mark. The static phase waterfall remains a separate chart for disjoint phases.
|
|
342
|
+
Live views append spans as they arrive: `traces watch <id|run-dir> --format jsonl --spans` emits a `traces.stream.span` event per new or changed span, and `otlpFlowTrace` keeps the last span per `span_id`. Running agent spans grow to the clock between deltas without React renders. `activityAt` supplies trace-relative event times and `medianEventGapMs` preserves producer cadence before tail truncation; without it, child-step activity supplies the samples. Silence beyond five minutes and three median event gaps hatches the tail and names the quiet duration. The Errors filter expands matching agents and steps; its rug locates each error, marking unreported times separately. Containers below 600px use stacked 44px rows and a Key disclosure. Search supports Cmd/Ctrl+F and keeps ancestor context. Token totals are optional: omit unknown counts, retain measured zero, and guard optional token fields when reading a trace. Missing, partial and nonfinite usage displays a dash, including during playback. Missing cost and authoritative LLM counts stay Unknown; a cost priced from a catalog is labelled list price. Invalid intervals retain a per-row `—` timing mark. The static phase waterfall remains a separate chart for disjoint phases.
|
|
341
343
|
|
|
342
344
|
`scripts/flow-waterfall-demo.mjs` serves the demo routes (reference-shaped fixtures, recorded runs, a synthetic load trace and OTLP files from `FW_DATA_DIR`); `scripts/flow-waterfall-capture.mjs` captures frames, hover cards, fps, first paint and recordings, and with `FW_LIVE_RELAY` records a live session through `scripts/flow-waterfall-live-relay.mjs` with every span's event-to-pixel time.
|
|
343
345
|
|
|
@@ -350,3 +352,4 @@ CHARTS_REACT_VERSION=19 CHARTS_PROOF_DIR=/tmp/charts-react19 node scripts/charts
|
|
|
350
352
|
```
|
|
351
353
|
|
|
352
354
|
Consumers: agent-record adapts record provenance; agent-app supplies application trace producers; Creative consumes both. Intelligence already uses the row/axis primitives with its controlled virtualized tree. Tangle Website’s `RecordedRunWaterfall` uses the Header/Row primitives; Blueprint Agent’s execution timeline is a separately owned follow-up. Keep these existing primitives exported. Agent-record’s earlier agent-app dependency increased its installed graph from 134 to 256 package versions (tangle-tools #1674); adopting the lower chart owner removes that framework dependency, with the actual new graph measured by the deployment owner.
|
|
355
|
+
|
|
@@ -391,8 +391,9 @@ interface FlowMarker {
|
|
|
391
391
|
interface FlowTrace {
|
|
392
392
|
spans: FlowSpan[];
|
|
393
393
|
totalMs: number;
|
|
394
|
-
|
|
395
|
-
|
|
394
|
+
/** Recorded totals only; absent or invalid values remain unknown, including during playback. */
|
|
395
|
+
promptTokens?: number;
|
|
396
|
+
completionTokens?: number;
|
|
396
397
|
/** Computed when per-token pricing is supplied. */
|
|
397
398
|
costUsd?: number;
|
|
398
399
|
/** `list`: priced from a model catalog's list prices, not a bill; the summary says so. */
|
|
@@ -1198,4 +1198,13 @@ function axisTicks(t0, t1, px, left, right, label) {
|
|
|
1198
1198
|
return fit([t0, t1]) ? [t0, t1] : [t0];
|
|
1199
1199
|
}
|
|
1200
1200
|
//#endregion
|
|
1201
|
-
|
|
1201
|
+
//#region src/flow-trace.ts
|
|
1202
|
+
/** Internal quantity boundaries shared by construction, aggregation and presentation. */
|
|
1203
|
+
const finiteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1204
|
+
const reportedCount = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
1205
|
+
function sumReportedCounts(values) {
|
|
1206
|
+
if (!values.length || values.some((value) => reportedCount(value) === void 0)) return void 0;
|
|
1207
|
+
return reportedCount(values.reduce((sum, value) => sum + value, 0));
|
|
1208
|
+
}
|
|
1209
|
+
//#endregion
|
|
1210
|
+
export { rect as $, intervalGap as A, tierSummary as B, isRefusal as C, drawable as D, costText as E, pts as F, MARKER_SHAPES as G, usd as H, rateDataGap as I, hatchPath as J, circle as K, rateIntervalGap as L, kn as M, pct as N, duration as O, perPass as P, polyline as Q, rateOrder as R, markName as S, TIERS_BEST_FIRST as T, usdTick as U, tierTotal as V, table as W, line as X, hbar as Y, markerPath as Z, drawMarks as _, CHECKER_HUES as a, esc as at, groupRowHeight as b, OUTCOME_COLORS as c, plural as ct, checkerColor as d, wrap as dt, svgRoot as et, EVENTS_READ as f, drawGroups as g, drawBands as h, timeline as i, capitalize as it, intervalName as j, estimateNote as k, OUTCOME_ORDER as l, textBlock as lt, MARK_VERDICTS as m, reportedCount as n, titled as nt, ENDING_COLORS as o, fitLines as ot, MARK_HEADS as p, hatch as q, sumReportedCounts as r, wideWidth as rt, ENDING_ORDER as s, listJoin as st, finiteNumber as t, text as tt, checkerClass as u, textWidth as ut, eventsGap as v, METHOD_NAME as w, markLabelRows as x, eventsTable as y, tierAtLeast as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as BreakdownInput, B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, H as Tier, I as Interval, L as IntervalMethod, M as CostBasis, N as Estimate, O as YTarget, P as Exclusion, R as MatrixCell, S as ChartMark, T as MarkerShape, U as TierCounts, V as Setup, W as isRefusal, _ as checkerColor, a as TimelineLane, b as BandKind, c as TimelineTick, d as ENDING_COLORS, f as ENDING_ORDER, g as TickOutcome, h as RunEnding, i as FlowTrace, j as ComparisonRow, k as YTick, l as timeline, m as OUTCOME_ORDER, n as FlowOutcome, o as TimelineOptions, p as OUTCOME_COLORS, r as FlowSpan, s as TimelineSpan, t as FlowMarker, u as CHECKER_HUES, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph, z as RateRow } from "./flow-trace-
|
|
1
|
+
import { A as BreakdownInput, B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, H as Tier, I as Interval, L as IntervalMethod, M as CostBasis, N as Estimate, O as YTarget, P as Exclusion, R as MatrixCell, S as ChartMark, T as MarkerShape, U as TierCounts, V as Setup, W as isRefusal, _ as checkerColor, a as TimelineLane, b as BandKind, c as TimelineTick, d as ENDING_COLORS, f as ENDING_ORDER, g as TickOutcome, h as RunEnding, i as FlowTrace, j as ComparisonRow, k as YTick, l as timeline, m as OUTCOME_ORDER, n as FlowOutcome, o as TimelineOptions, p as OUTCOME_COLORS, r as FlowSpan, s as TimelineSpan, t as FlowMarker, u as CHECKER_HUES, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph, z as RateRow } from "./flow-trace-CF4Q07cA.js";
|
|
2
2
|
//#region src/block.d.ts
|
|
3
3
|
interface RenderFigureOptions {
|
|
4
4
|
/** Chart uses a closed data disclosure; plot keeps only the SVG and accessible data. */
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as rect, A as intervalGap, B as tierSummary, C as isRefusal, D as drawable, E as costText, F as pts, G as MARKER_SHAPES, H as usd, I as rateDataGap, K as circle, L as rateIntervalGap, M as kn, N as pct, O as duration, P as perPass, Q as polyline, R as rateOrder, T as TIERS_BEST_FIRST, U as usdTick, V as tierTotal, W as table, X as line, Y as hbar, Z as markerPath, _ as drawMarks, a as CHECKER_HUES, at as esc, b as groupRowHeight, c as OUTCOME_COLORS, ct as plural, d as checkerColor, dt as wrap, et as svgRoot, f as EVENTS_READ, g as drawGroups, h as drawBands, i as timeline, it as capitalize, j as intervalName, k as estimateNote, l as OUTCOME_ORDER, lt as textBlock, m as MARK_VERDICTS, n as reportedCount, nt as titled, o as ENDING_COLORS, ot as fitLines, q as hatch, r as sumReportedCounts, rt as wideWidth, s as ENDING_ORDER, st as listJoin, t as finiteNumber, tt as text, u as checkerClass, ut as textWidth, v as eventsGap, w as METHOD_NAME, x as markLabelRows, y as eventsTable, z as tierAtLeast } from "./flow-trace-k097Ukvs.js";
|
|
2
2
|
//#region src/block.ts
|
|
3
3
|
/**
|
|
4
4
|
* The figure block: one markup for every figure on every surface.
|
|
@@ -3349,8 +3349,9 @@ const CACHE_KEYS = [...CACHE_READ_KEYS, "gen_ai.usage.cache_creation_input_token
|
|
|
3349
3349
|
function catalogPrice(catalog) {
|
|
3350
3350
|
const rates = /* @__PURE__ */ new Map();
|
|
3351
3351
|
for (const model of catalog.data ?? []) {
|
|
3352
|
-
const
|
|
3353
|
-
|
|
3352
|
+
const rate = (value) => typeof value === "string" ? value.trim() ? finiteNumber(Number(value)) : void 0 : finiteNumber(value);
|
|
3353
|
+
const input = rate(model.pricing?.prompt), output = rate(model.pricing?.completion);
|
|
3354
|
+
if (model.id && input !== void 0 && output !== void 0) rates.set(model.id, {
|
|
3354
3355
|
input,
|
|
3355
3356
|
output
|
|
3356
3357
|
});
|
|
@@ -3391,7 +3392,6 @@ function parseOtlpJsonl(text) {
|
|
|
3391
3392
|
};
|
|
3392
3393
|
}
|
|
3393
3394
|
const str = (value) => typeof value === "string" && value ? value : void 0;
|
|
3394
|
-
const num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3395
3395
|
const pick = (attributes, keys) => {
|
|
3396
3396
|
for (const key of keys) {
|
|
3397
3397
|
const value = str(attributes[key]);
|
|
@@ -3474,8 +3474,7 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3474
3474
|
const topModel = (key) => [...models.get(key)?.entries() ?? []].sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
3475
3475
|
const out = [];
|
|
3476
3476
|
let omitted = 0;
|
|
3477
|
-
|
|
3478
|
-
let completionTokens = 0;
|
|
3477
|
+
const usage = [];
|
|
3479
3478
|
let toolCalls = 0;
|
|
3480
3479
|
let modelCalls = 0;
|
|
3481
3480
|
let errors = 0;
|
|
@@ -3530,9 +3529,9 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3530
3529
|
if (open) endMs = Math.max(endMs, last - origin);
|
|
3531
3530
|
const parentId = span.parent_span_id && byId.has(span.parent_span_id) ? span.parent_span_id : groupOf.has(span.span_id) ? `group:${groupOf.get(span.span_id)}` : void 0;
|
|
3532
3531
|
const model = pick(a, MODEL_KEYS);
|
|
3533
|
-
const prompt =
|
|
3534
|
-
const completion =
|
|
3535
|
-
const cached = CACHE_READ_KEYS.map((key) =>
|
|
3532
|
+
const prompt = reportedCount(a["gen_ai.usage.input_tokens"]);
|
|
3533
|
+
const completion = reportedCount(a["gen_ai.usage.output_tokens"]);
|
|
3534
|
+
const cached = CACHE_READ_KEYS.map((key) => reportedCount(a[key])).find((n) => n !== void 0);
|
|
3536
3535
|
let kind = span.kind === "LLM" ? "model" : span.kind === "TOOL" ? "tool" : "pipeline";
|
|
3537
3536
|
if (kind === "tool" && (a["traces.agent.spawn"] === true || spawnTarget.has(span.span_id) || handoffTo.has(span.span_id))) kind = "handoff";
|
|
3538
3537
|
let name = str(a["gen_ai.tool.name"]) ?? span.name;
|
|
@@ -3542,16 +3541,13 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3542
3541
|
if (kind === "model" && !aggregate) modelCalls++;
|
|
3543
3542
|
if (kind === "tool" || kind === "handoff") toolCalls++;
|
|
3544
3543
|
if (failed) errors++;
|
|
3545
|
-
if (!aggregate)
|
|
3546
|
-
|
|
3547
|
-
completionTokens += completion ?? 0;
|
|
3548
|
-
}
|
|
3549
|
-
const listPrice = kind === "model" && !aggregate && model && options.price && (prompt !== void 0 || completion !== void 0) ? options.price(model, prompt ?? 0, completion ?? 0) : void 0;
|
|
3544
|
+
if (!aggregate && (kind === "model" || prompt !== void 0 || completion !== void 0)) usage.push([prompt, completion]);
|
|
3545
|
+
const listPrice = finiteNumber(kind === "model" && !aggregate && model && options.price && prompt !== void 0 && completion !== void 0 ? options.price(model, prompt, completion) : void 0);
|
|
3550
3546
|
if (listPrice !== void 0) {
|
|
3551
3547
|
cost += listPrice;
|
|
3552
3548
|
priced++;
|
|
3553
3549
|
if (CACHE_KEYS.some((key) => a[key] !== void 0)) cacheSplit = true;
|
|
3554
|
-
} else if (kind === "model" && !aggregate
|
|
3550
|
+
} else if (kind === "model" && !aggregate) unpriced++;
|
|
3555
3551
|
out.push({
|
|
3556
3552
|
id: span.span_id,
|
|
3557
3553
|
...parentId ? { parentId } : {},
|
|
@@ -3559,6 +3555,7 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3559
3555
|
name,
|
|
3560
3556
|
startMs,
|
|
3561
3557
|
endMs,
|
|
3558
|
+
...aggregate ? { meta: { "cli_bridge.usage.scope": "sum_of_children" } } : {},
|
|
3562
3559
|
status: open ? "running" : failed ? "failed" : "done",
|
|
3563
3560
|
...endMs === startMs && kind !== "pipeline" ? { approx: true } : {},
|
|
3564
3561
|
...isAgent(span) ? { detail: topModel(span.span_id) } : kind === "model" && model ? { detail: shortModel(model) } : {},
|
|
@@ -3573,8 +3570,8 @@ function otlpFlowTrace(input, options = {}) {
|
|
|
3573
3570
|
return {
|
|
3574
3571
|
spans: out,
|
|
3575
3572
|
totalMs: Number.isFinite(last - origin) ? last - origin : 0,
|
|
3576
|
-
promptTokens,
|
|
3577
|
-
completionTokens,
|
|
3573
|
+
promptTokens: sumReportedCounts(usage.map(([value]) => value)),
|
|
3574
|
+
completionTokens: sumReportedCounts(usage.map(([, value]) => value)),
|
|
3578
3575
|
toolCalls,
|
|
3579
3576
|
modelCalls,
|
|
3580
3577
|
errors,
|
|
@@ -83,11 +83,13 @@
|
|
|
83
83
|
.fw-root .fw-search-input::placeholder { color: var(--fw-muted); }
|
|
84
84
|
.fw-root .fw-search-input::-webkit-search-cancel-button { display: none; }
|
|
85
85
|
|
|
86
|
-
.fw-root .fw-summary { display:
|
|
86
|
+
.fw-root .fw-summary { display: flex; flex-wrap: wrap; align-items: center; border-bottom: 1px solid var(--fw-border); }
|
|
87
87
|
.fw-root .fw-summary > div { padding: 12px 16px; min-width: 0; }
|
|
88
|
-
.fw-root .fw-summary > div
|
|
89
|
-
.fw-root .fw-summary
|
|
90
|
-
.fw-root .fw-
|
|
88
|
+
.fw-root .fw-summary > div:nth-child(-n+2) { flex: 1; }
|
|
89
|
+
.fw-root .fw-summary > div:nth-child(n+3) { display: flex; align-items: baseline; gap: 4px; padding: 8px 6px; color: var(--fw-muted); }
|
|
90
|
+
.fw-root .fw-summary dt { font-size: 14px; line-height: 20px; color: var(--fw-muted); }
|
|
91
|
+
.fw-root .fw-value { margin-top: 4px; font-size: 28px; line-height: 34px; font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
92
|
+
.fw-root .fw-summary > div:nth-child(n+3) .fw-value { margin: 0; font-size: 14px; line-height: 20px; font-weight: 500; letter-spacing: normal; }
|
|
91
93
|
.fw-root .fw-has-errors .fw-value { color: var(--fw-failed); }
|
|
92
94
|
|
|
93
95
|
.fw-root .fw-axis { display: grid; grid-template-columns: var(--fw-label-col) minmax(0, 1fr); height: 32px; color: var(--fw-muted); font-size: 14px; }
|
|
@@ -203,7 +205,7 @@
|
|
|
203
205
|
.fw-root :is(.fw-bar, .fw-seg):hover { filter: brightness(1.18); box-shadow: inset 0 0 0 1px var(--line), 0 0 0 2px color-mix(in oklab, var(--line) 55%, transparent); z-index: 1; }
|
|
204
206
|
.fw-root .fw-bar.fw-k-tool { background: var(--dim); box-shadow: inset 0 0 0 1px var(--dim-line); }
|
|
205
207
|
.fw-root .fw-bar.fw-k-handoff { background: var(--line); height: 8px; top: calc(50% + var(--fw-bar-shift) - 4px); box-shadow: none; }
|
|
206
|
-
.fw-root .fw-bar.fw-k-waiting { background: repeating-linear-gradient(-45deg, var(--fw-hatch) 0 1px, transparent 1px 5px); box-shadow: inset 0 0 0 1px var(--fw-hatch); }
|
|
208
|
+
.fw-root .fw-bar.fw-k-waiting, .fw-root .fw-kinds .fw-swatch-wait { background: repeating-linear-gradient(-45deg, var(--fw-hatch) 0 1px, transparent 1px 5px); box-shadow: inset 0 0 0 1px var(--fw-hatch); }
|
|
207
209
|
.fw-root .fw-bar.fw-k-failed { background: color-mix(in oklab, var(--fw-failed) 22%, var(--fw-surface)); box-shadow: inset 0 0 0 1px var(--fw-failed); color: var(--fw-failed); }
|
|
208
210
|
.fw-root .fw-bar.fw-k-pipeline { background: linear-gradient(90deg, var(--fw-neutral-bar), color-mix(in oklab, var(--fw-neutral-bar) 55%, var(--fw-surface))); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-text) 12%, transparent); }
|
|
209
211
|
.fw-root .fw-bar.fw-k-pipeline.fw-has-marks { background: repeating-linear-gradient(-45deg, var(--fw-hatch) 0 1px, transparent 1px 5px); box-shadow: inset 0 0 0 1px var(--fw-hatch); }
|
|
@@ -227,7 +229,7 @@
|
|
|
227
229
|
.fw-root.fw-fill-agent .fw-kinds .fw-swatch-model { background: var(--model-fill); }
|
|
228
230
|
.fw-root.fw-fill-agent .fw-kinds .fw-swatch-tool { background: var(--tool-fill); }
|
|
229
231
|
.fw-root .fw-kinds .fw-swatch-tool { width: 12px; height: 8px; border-radius: 2px; background: var(--fw-kind-tool); }
|
|
230
|
-
.fw-root .fw-kinds .fw-swatch-wait { width: 12px; height: 8px; border-radius: 2px;
|
|
232
|
+
.fw-root .fw-kinds .fw-swatch-wait { width: 12px; height: 8px; border-radius: 2px; }
|
|
231
233
|
.fw-root .fw-kinds .fw-swatch-event { width: 8px; height: 8px; border-radius: 50%; background: var(--fw-muted); box-shadow: 0 0 0 2px var(--fw-surface); }
|
|
232
234
|
.fw-root .fw-kinds .fw-swatch-handoff { width: 10px; height: 8px; border-left: 1.5px solid var(--fw-muted); border-bottom: 1.5px solid var(--fw-muted); border-bottom-left-radius: 3px; }
|
|
233
235
|
.fw-root .fw-description { margin: 0; color: var(--fw-muted); font-size: 14px; line-height: 20px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -384,10 +386,7 @@
|
|
|
384
386
|
.fw-root .fw-card .fw-agent-dot:not([data-outcome]) { background: transparent; box-shadow: inset 0 0 0 1.5px var(--fw-muted); }
|
|
385
387
|
@container flow-waterfall (max-width: 599px) {
|
|
386
388
|
.fw-root > * { --fw-label-col: 0px; --fw-bar-shift: 8px; --fw-bar-h: 8px; --fw-row-h: 44px; }
|
|
387
|
-
.fw-root .fw-summary {
|
|
388
|
-
.fw-root .fw-summary > div { padding: 8px; }
|
|
389
|
-
.fw-root .fw-summary > div:nth-child(4) { border-left: 0; }
|
|
390
|
-
.fw-root .fw-summary > div:nth-child(n+4) { border-top: 1px solid var(--fw-border); }
|
|
389
|
+
.fw-root .fw-summary > div:nth-child(-n+2) { flex-basis: 50%; padding: 6px 8px; }
|
|
391
390
|
.fw-root :is(.fw-zoom, .fw-branch-controls, .fw-resize, .fw-links, .fw-gridlayer, .fw-marker-layer, .fw-subtitle, .fw-badge, .fw-icon-tile) { display: none; }
|
|
392
391
|
.fw-root .fw-row-state { display: inline; }
|
|
393
392
|
.fw-root .fw-key > summary { display: flex; align-items: center; min-height: 32px; cursor: pointer; }
|
|
@@ -396,6 +395,8 @@
|
|
|
396
395
|
.fw-root .fw-title-line { flex-wrap: wrap; }
|
|
397
396
|
.fw-root .fw-search-input { width: 120px; }
|
|
398
397
|
.fw-root .fw-axis-label { border: 0; padding: 0; }
|
|
398
|
+
.fw-root .fw-tick:is(:nth-child(2), :nth-child(3)) { transform: translate(-50%, -50%); }
|
|
399
|
+
.fw-root .fw-tick:nth-child(4) { transform: translate(calc(-100% - 4px), -50%); }
|
|
399
400
|
.fw-root .fw-row { transform: none; top: var(--row-y); contain: style; }
|
|
400
401
|
.fw-root .fw-label { position: absolute; z-index: 3; height: 20px; top: 0; left: 0; right: 0; padding: 0 calc(var(--fw-end-w, 0px) + 8px) 0 calc(8px + var(--depth) * 12px); border: 0; gap: 4px; background: transparent; pointer-events: none; }
|
|
401
402
|
.fw-root .fw-view-agents .fw-names { flex-direction: row; font-size: 14px; line-height: 20px; }
|
|
@@ -404,12 +405,11 @@
|
|
|
404
405
|
.fw-root .fw-lane { grid-column: 2; }
|
|
405
406
|
.fw-root .fw-end { top: 0; height: 20px; left: auto; right: 4px; }
|
|
406
407
|
.fw-root .fw-duration { font-size: 12px; }
|
|
407
|
-
.fw-root .fw-time { display: block; position: absolute; top: -12px; left:
|
|
408
|
+
.fw-root .fw-time { display: block; position: absolute; top: -12px; left: var(--fw-progress); transform: translateX(calc(-1 * var(--fw-progress))); width: max-content; max-width: 100%; white-space: nowrap; background: var(--fw-surface); }
|
|
408
409
|
.fw-root .fw-playback { position: relative; padding: 12px 8px 8px; }
|
|
409
410
|
.fw-root .fw-overview { margin-top: 12px; }
|
|
410
411
|
}
|
|
411
412
|
|
|
412
|
-
@container flow-waterfall (max-width: 599px) { .fw-root .fw-tick:nth-child(4) { transform: translate(calc(-100% - 4px), -50%); } }
|
|
413
413
|
|
|
414
414
|
.fw-root.fw-fill-agent .fw-view-agents .fw-seg-failed[data-model] { background: var(--model-fill); --fw-seg-ink: light-dark(var(--fw-surface), var(--bg-root)); }
|
|
415
415
|
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, O as YTarget, S as ChartMark, T as MarkerShape, a as TimelineLane, b as BandKind, c as TimelineTick, g as TickOutcome, i as FlowTrace, k as YTick, n as FlowOutcome, o as TimelineOptions, r as FlowSpan, s as TimelineSpan, t as FlowMarker, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph } from "../flow-trace-
|
|
1
|
+
import { B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, O as YTarget, S as ChartMark, T as MarkerShape, a as TimelineLane, b as BandKind, c as TimelineTick, g as TickOutcome, i as FlowTrace, k as YTick, n as FlowOutcome, o as TimelineOptions, r as FlowSpan, s as TimelineSpan, t as FlowMarker, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph } from "../flow-trace-CF4Q07cA.js";
|
|
2
2
|
import { CSSProperties, ReactElement, ReactNode } from "react";
|
|
3
3
|
//#region src/react/sparkline.d.ts
|
|
4
4
|
type SparklineDirection = "rising" | "falling" | "flat";
|
|
@@ -459,7 +459,7 @@ export declare function flowWaterfallRows(trace: FlowTrace, resolutionMs?: numbe
|
|
|
459
459
|
export declare function criticalPathIds(rows: readonly FlowWaterfallRow[]): ReadonlySet<string>;
|
|
460
460
|
//#endregion
|
|
461
461
|
//#region src/react/flow-waterfall-row.d.ts
|
|
462
|
-
type FlowView = 'spans' | 'agents';
|
|
462
|
+
type FlowView = 'spans' | 'agents' | 'graph';
|
|
463
463
|
//#endregion
|
|
464
464
|
//#region src/react/flow-waterfall-time.d.ts
|
|
465
465
|
interface ViewWindow {
|
|
@@ -480,6 +480,10 @@ interface FlowWaterfallProps {
|
|
|
480
480
|
defaultCollapsed?: boolean;
|
|
481
481
|
/** `agents` shows one row per agent (the agent timeline); expanding a row opens the full span tree. */
|
|
482
482
|
defaultView?: FlowView;
|
|
483
|
+
/** Host graph rendered by the same view control, retaining the trace clock and selection. */
|
|
484
|
+
graph?: ReactNode;
|
|
485
|
+
/** Shared header feed, kept mounted across Agents, Steps and Graph. */
|
|
486
|
+
latestEvents?: ReactNode;
|
|
483
487
|
/** Host brand marks by span; the inspector draws kind glyphs without it. */
|
|
484
488
|
renderIcon?: (span: FlowSpan) => ReactNode;
|
|
485
489
|
/** Scroll height of the rows; the tree is virtualized beyond it. Defaults to 20 agent rows, or 640 px of steps. */
|
|
@@ -519,7 +523,7 @@ interface FlowWaterfallProps {
|
|
|
519
523
|
}
|
|
520
524
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
521
525
|
* Data remains producer-owned; playback, zoom and folding change presentation only. */
|
|
522
|
-
export declare function FlowWaterfall({ trace, live, startedAt, label, defaultCollapsed, defaultView, renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill: fillProp, selectedId, onDeselect, description, summary: showSummary, criticalPath, query: queryProp, onQueryChange, collapsed: collapsedProp, onCollapsedChange, zoom: zoomProp, onZoomChange, matchSpan }: FlowWaterfallProps): import("react").JSX.Element;
|
|
526
|
+
export declare function FlowWaterfall({ trace, live, startedAt, label, defaultCollapsed, defaultView, graph, latestEvents, renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill: fillProp, selectedId, onDeselect, description, summary: showSummary, criticalPath, query: queryProp, onQueryChange, collapsed: collapsedProp, onCollapsedChange, zoom: zoomProp, onZoomChange, matchSpan }: FlowWaterfallProps): import("react").JSX.Element;
|
|
523
527
|
//#endregion
|
|
524
528
|
//#region src/react/flow-waterfall-icons.d.ts
|
|
525
529
|
/** Kind glyphs drawn when the host supplies no `renderIcon`. Brand marks belong to the host. */
|
package/dist/react/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as isRefusal, J as hatchPath, O as duration, S as markName, Z as markerPath, i as timeline, n as reportedCount, p as MARK_HEADS, r as sumReportedCounts, t as finiteNumber, v as eventsGap } from "../flow-trace-k097Ukvs.js";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { Fragment as Fragment$1, createContext, createElement, forwardRef, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
//#region src/react/sparkline.tsx
|
|
@@ -6515,12 +6515,14 @@ function formatSpanDuration(ms) {
|
|
|
6515
6515
|
}
|
|
6516
6516
|
/** $0.12, $0.005, $12.40; never rounds a recorded nonzero cost to $0.00. */
|
|
6517
6517
|
function formatCost(usd) {
|
|
6518
|
+
if (finiteNumber(usd) === void 0) return "—";
|
|
6518
6519
|
if (usd === 0) return "$0.00";
|
|
6519
6520
|
if (usd < .01) return `$${usd.toPrecision(1).replace(/0+$/, "")}`;
|
|
6520
6521
|
return `$${usd.toFixed(2)}`;
|
|
6521
6522
|
}
|
|
6522
6523
|
/** 9.8k, 754.4k, 1.2M with the exact count left to a title. */
|
|
6523
6524
|
function formatCount(n) {
|
|
6525
|
+
if (reportedCount(n) === void 0) return "—";
|
|
6524
6526
|
if (n < 1e3) return String(n);
|
|
6525
6527
|
if (n < 1e6) return `${(n / 1e3).toFixed(1)}K`;
|
|
6526
6528
|
return `${(n / 1e6).toFixed(1)}M`;
|
|
@@ -6646,9 +6648,9 @@ function flowWaterfallRows(trace, resolutionMs) {
|
|
|
6646
6648
|
}
|
|
6647
6649
|
Object.assign(row, stepStats(kids));
|
|
6648
6650
|
const tokens = own ? {
|
|
6649
|
-
prompt: row.span.promptTokens,
|
|
6650
|
-
completion: row.span.completionTokens,
|
|
6651
|
-
cached: row.span.cachedTokens
|
|
6651
|
+
prompt: reportedCount(row.span.promptTokens),
|
|
6652
|
+
completion: reportedCount(row.span.completionTokens),
|
|
6653
|
+
cached: reportedCount(row.span.cachedTokens)
|
|
6652
6654
|
} : sumTokens(kids);
|
|
6653
6655
|
if (tokens) row.tokens = tokens;
|
|
6654
6656
|
if (row.hasChildren && costKnown) row.rollupCostUsd = cost;
|
|
@@ -6721,13 +6723,14 @@ function mergeStats(rows) {
|
|
|
6721
6723
|
}
|
|
6722
6724
|
function sumTokens(rows) {
|
|
6723
6725
|
let out;
|
|
6724
|
-
|
|
6726
|
+
const sources = rows.filter((row) => row.kind === "model" || row.tokens !== void 0);
|
|
6727
|
+
for (const key of [
|
|
6725
6728
|
"prompt",
|
|
6726
6729
|
"completion",
|
|
6727
6730
|
"cached"
|
|
6728
6731
|
]) {
|
|
6729
|
-
const
|
|
6730
|
-
if (
|
|
6732
|
+
const total = sumReportedCounts(sources.map((row) => row.tokens?.[key]));
|
|
6733
|
+
if (total !== void 0) (out ??= {})[key] = total;
|
|
6731
6734
|
}
|
|
6732
6735
|
return out;
|
|
6733
6736
|
}
|
|
@@ -6851,39 +6854,40 @@ function busyMs(row, clockMs) {
|
|
|
6851
6854
|
}
|
|
6852
6855
|
return total;
|
|
6853
6856
|
}
|
|
6854
|
-
/**
|
|
6857
|
+
/** Complete per-span usage drives playback; authoritative call/error counts remain trace totals. */
|
|
6855
6858
|
function totalsAt(trace, clockMs) {
|
|
6856
|
-
let cost = 0
|
|
6859
|
+
let cost = 0;
|
|
6857
6860
|
let costs = false, counted = false;
|
|
6861
|
+
const tokens = [];
|
|
6858
6862
|
for (const span of trace.spans) {
|
|
6859
6863
|
if (span.startMs > clockMs) continue;
|
|
6860
|
-
|
|
6861
|
-
if (span.kind === "tool") tools++;
|
|
6864
|
+
const aggregate = span.meta?.["cli_bridge.usage.scope"] === "sum_of_children";
|
|
6862
6865
|
if (span.endMs > clockMs) continue;
|
|
6863
|
-
if (spanFailed(span)) errors++;
|
|
6864
6866
|
if (span.costUsd !== void 0) {
|
|
6865
6867
|
cost += span.costUsd;
|
|
6866
6868
|
costs = true;
|
|
6867
6869
|
}
|
|
6868
|
-
if (span.promptTokens !== void 0 || span.completionTokens !== void 0) {
|
|
6869
|
-
tokens
|
|
6870
|
-
counted
|
|
6870
|
+
if (!aggregate && (span.kind === "model" || span.promptTokens !== void 0 || span.completionTokens !== void 0)) {
|
|
6871
|
+
tokens.push(sumReportedCounts([span.promptTokens, span.completionTokens]));
|
|
6872
|
+
counted ||= span.promptTokens !== void 0 || span.completionTokens !== void 0;
|
|
6871
6873
|
}
|
|
6872
6874
|
}
|
|
6873
6875
|
return {
|
|
6874
6876
|
costUsd: trace.costUsd === void 0 ? void 0 : costs ? cost : trace.costUsd,
|
|
6875
|
-
tokens: counted ? tokens : trace.promptTokens
|
|
6876
|
-
modelCalls: trace.modelCalls
|
|
6877
|
-
toolCalls: trace.toolCalls
|
|
6878
|
-
errors
|
|
6877
|
+
tokens: counted ? sumReportedCounts(tokens) : sumReportedCounts([trace.promptTokens, trace.completionTokens]),
|
|
6878
|
+
modelCalls: reportedCount(trace.modelCalls),
|
|
6879
|
+
toolCalls: reportedCount(trace.toolCalls),
|
|
6880
|
+
errors: reportedCount(trace.errors)
|
|
6879
6881
|
};
|
|
6880
6882
|
}
|
|
6881
6883
|
/** The cost a reader can rely on: a lower bound when some calls are unpriced, a dash only when nothing is priced. */
|
|
6882
6884
|
function costSummary(costUsd, trace) {
|
|
6885
|
+
costUsd = finiteNumber(costUsd);
|
|
6886
|
+
const billedUsd = finiteNumber(trace.billedUsd);
|
|
6883
6887
|
const list = trace.costBasis === "list";
|
|
6884
6888
|
const label = list ? "Cost · list price" : "Cost";
|
|
6885
|
-
if (costUsd === void 0) return
|
|
6886
|
-
value: `${formatCost(
|
|
6889
|
+
if (costUsd === void 0) return billedUsd !== void 0 ? {
|
|
6890
|
+
value: `${formatCost(billedUsd)} billed`,
|
|
6887
6891
|
title: "No list price for these calls; billed is the seat charge",
|
|
6888
6892
|
label
|
|
6889
6893
|
} : {
|
|
@@ -6895,13 +6899,13 @@ function costSummary(costUsd, trace) {
|
|
|
6895
6899
|
const partial = coverage !== void 0 && coverage.priced < coverage.total;
|
|
6896
6900
|
const upper = !partial && trace.costBound === "upper";
|
|
6897
6901
|
return {
|
|
6898
|
-
value: `${partial || trace.costBound === "lower" ? "≥" : upper ? "≤" : list ? "≈" : ""}${formatCost(costUsd)}${
|
|
6902
|
+
value: `${partial || trace.costBound === "lower" ? "≥" : upper ? "≤" : list ? "≈" : ""}${formatCost(costUsd)}${billedUsd !== void 0 ? ` · ${formatCost(billedUsd)} billed` : ""}`,
|
|
6899
6903
|
title: [
|
|
6900
6904
|
partial ? `${coverage.priced} of ${coverage.total} model calls priced` : coverage ? `all ${coverage.total} model calls priced` : "",
|
|
6901
6905
|
list ? "at the model catalog list price; not a bill" : "",
|
|
6902
6906
|
upper ? "cached input is not split out, so it is priced as fresh input: at most this" : "",
|
|
6903
6907
|
trace.costBound === "lower" ? "a floor: the producer reports at least this" : "",
|
|
6904
|
-
|
|
6908
|
+
billedUsd !== void 0 ? `billed ${formatCost(billedUsd)} (the seat charge)` : ""
|
|
6905
6909
|
].filter(Boolean).join("; "),
|
|
6906
6910
|
label
|
|
6907
6911
|
};
|
|
@@ -6930,10 +6934,15 @@ function agentFamilies(agents, open) {
|
|
|
6930
6934
|
const hue = (row) => row.tone <= 0 || ++next > 5 ? 0 : 1 + [...roleOf(row.name)].reduce((hash, c) => hash * 31 + c.charCodeAt(0) >>> 0, 0) % 5;
|
|
6931
6935
|
for (const [role, members] of groups) {
|
|
6932
6936
|
if (members.length < 2) {
|
|
6933
|
-
|
|
6937
|
+
const row = members[0], tone = hue(row);
|
|
6938
|
+
out.push({
|
|
6934
6939
|
...row,
|
|
6935
|
-
tone
|
|
6936
|
-
|
|
6940
|
+
tone,
|
|
6941
|
+
own: row.own.map((mark) => ({
|
|
6942
|
+
...mark,
|
|
6943
|
+
tone
|
|
6944
|
+
}))
|
|
6945
|
+
});
|
|
6937
6946
|
continue;
|
|
6938
6947
|
}
|
|
6939
6948
|
const tone = hue(members[0]);
|
|
@@ -7315,9 +7324,12 @@ function metrics(row, clockMs, full) {
|
|
|
7315
7324
|
["completion", "out"],
|
|
7316
7325
|
["cached", "cached"]
|
|
7317
7326
|
].flatMap(([key, unit]) => tokens[key] === void 0 ? [] : [count(tokens[key], unit)]);
|
|
7318
|
-
out.push(["Tokens", /* @__PURE__ */ jsx("span", {
|
|
7327
|
+
out.push(["Tokens", parts.length ? /* @__PURE__ */ jsx("span", {
|
|
7319
7328
|
className: `fw-card-parts${parts.length > 2 ? " fw-card-stack" : ""}`,
|
|
7320
7329
|
children: parts.map((part, i) => /* @__PURE__ */ jsx("span", { children: part }, i))
|
|
7330
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
7331
|
+
title: "Tokens not reported",
|
|
7332
|
+
children: "—"
|
|
7321
7333
|
})]);
|
|
7322
7334
|
}
|
|
7323
7335
|
const latency = row.modelMs;
|
|
@@ -7953,7 +7965,8 @@ function clampWindow(view, limitMs) {
|
|
|
7953
7965
|
//#region src/react/flow-waterfall-body.tsx
|
|
7954
7966
|
const ROW_HEIGHT = {
|
|
7955
7967
|
spans: 36,
|
|
7956
|
-
agents: 40
|
|
7968
|
+
agents: 40,
|
|
7969
|
+
graph: 40
|
|
7957
7970
|
};
|
|
7958
7971
|
const OVERSCAN = 8;
|
|
7959
7972
|
/** Rows re-render once per CHUNK rows scrolled; overscan covers the rows in between. */
|
|
@@ -8683,11 +8696,13 @@ const AGENT_ROWS = 20;
|
|
|
8683
8696
|
const UNFOLD_ROWS = 12;
|
|
8684
8697
|
const END_LABEL_GUTTER = {
|
|
8685
8698
|
spans: 120,
|
|
8686
|
-
agents: 176
|
|
8699
|
+
agents: 176,
|
|
8700
|
+
graph: 176
|
|
8687
8701
|
};
|
|
8688
8702
|
const NAME_WIDTH = {
|
|
8689
8703
|
spans: 320,
|
|
8690
|
-
agents: 240
|
|
8704
|
+
agents: 240,
|
|
8705
|
+
graph: 240
|
|
8691
8706
|
};
|
|
8692
8707
|
/** State the host may own: its value wins when given, and every change is reported either way. */
|
|
8693
8708
|
function useHostable(value, onChange, initial) {
|
|
@@ -8716,36 +8731,42 @@ function usual(values) {
|
|
|
8716
8731
|
/** The summary strip: as of the playhead while scrubbing, the producer's totals otherwise. */
|
|
8717
8732
|
function summaryCells(trace, at, durationMs) {
|
|
8718
8733
|
const cost = costSummary(at ? at.costUsd : trace.costUsd, trace);
|
|
8719
|
-
const tokens = at ? at.tokens : trace.promptTokens
|
|
8720
|
-
const modelCalls = at ? at.modelCalls : trace.modelCalls;
|
|
8721
|
-
const
|
|
8734
|
+
const tokens = at ? at.tokens : sumReportedCounts([trace.promptTokens, trace.completionTokens]);
|
|
8735
|
+
const modelCalls = reportedCount(at ? at.modelCalls : trace.modelCalls);
|
|
8736
|
+
const toolCalls = reportedCount(at ? at.toolCalls : trace.toolCalls);
|
|
8737
|
+
const observedErrors = trace.spans.filter((span) => spanFailed(span) && span.kind !== "pipeline").length || trace.spans.filter(spanFailed).length;
|
|
8738
|
+
const errors = at ? at.errors : trace.errors === void 0 ? observedErrors || void 0 : reportedCount(trace.errors);
|
|
8722
8739
|
return {
|
|
8723
8740
|
cost,
|
|
8724
8741
|
tokens,
|
|
8725
8742
|
errors,
|
|
8726
8743
|
summary: [
|
|
8727
8744
|
["Duration", formatSpanDuration(durationMs)],
|
|
8728
|
-
|
|
8745
|
+
[
|
|
8729
8746
|
cost.label,
|
|
8730
8747
|
cost.value,
|
|
8731
8748
|
cost.title
|
|
8732
|
-
]
|
|
8749
|
+
],
|
|
8733
8750
|
[
|
|
8734
8751
|
"Tokens",
|
|
8735
|
-
formatCount(tokens),
|
|
8736
|
-
`${tokens.toLocaleString()} tokens`
|
|
8752
|
+
tokens === void 0 ? "—" : formatCount(tokens),
|
|
8753
|
+
tokens === void 0 ? "Tokens not reported" : `${tokens.toLocaleString()} tokens`
|
|
8737
8754
|
],
|
|
8738
8755
|
[
|
|
8739
8756
|
"Model calls",
|
|
8740
8757
|
modelCalls === void 0 ? "—" : modelCalls.toLocaleString(),
|
|
8741
|
-
modelCalls === void 0 ? "Model
|
|
8758
|
+
modelCalls === void 0 ? "Model calls not reported" : at ? "Reported trace total" : void 0
|
|
8742
8759
|
],
|
|
8743
8760
|
[
|
|
8744
8761
|
"Tool calls",
|
|
8745
|
-
|
|
8746
|
-
|
|
8762
|
+
toolCalls?.toLocaleString() ?? "—",
|
|
8763
|
+
toolCalls === void 0 ? "Tool calls not reported" : at ? "Reported trace total" : void 0
|
|
8747
8764
|
],
|
|
8748
|
-
[
|
|
8765
|
+
[
|
|
8766
|
+
"Errors",
|
|
8767
|
+
errors?.toLocaleString() ?? "—",
|
|
8768
|
+
errors === void 0 ? "Errors not reported" : at ? "Reported trace total" : void 0
|
|
8769
|
+
]
|
|
8749
8770
|
]
|
|
8750
8771
|
};
|
|
8751
8772
|
}
|
|
@@ -8758,7 +8779,7 @@ const TONE_ORDER = [
|
|
|
8758
8779
|
"ok"
|
|
8759
8780
|
];
|
|
8760
8781
|
/** What each mark means, as the agent view draws it; a key only for kinds the run has. */
|
|
8761
|
-
function KindLegend({ rows }) {
|
|
8782
|
+
function KindLegend({ rows, fill }) {
|
|
8762
8783
|
const has = (kind) => rows.some((row) => row.own.some((mark) => mark.kind === kind) || row.span.kind === kind);
|
|
8763
8784
|
const shown = [
|
|
8764
8785
|
["model", "Model call"],
|
|
@@ -8767,13 +8788,19 @@ function KindLegend({ rows }) {
|
|
|
8767
8788
|
["handoff", "Handoff"]
|
|
8768
8789
|
].filter(([kind]) => kind === "waiting" ? rows.some((row) => row.kind === "pipeline") : kind === "handoff" ? rows.some((row) => row.span.handoffTo || row.hasAgents) : has(kind));
|
|
8769
8790
|
if (shown.length === 0) return null;
|
|
8770
|
-
|
|
8791
|
+
const example = rows.find((row) => toneKey(row.tone) > 0 && row.own.some((mark) => mark.kind === "model")) ?? rows.find((row) => row.own.length);
|
|
8792
|
+
return /* @__PURE__ */ jsxs("ul", {
|
|
8771
8793
|
className: "fw-legend fw-kinds",
|
|
8772
8794
|
"aria-label": "Key",
|
|
8773
|
-
|
|
8795
|
+
"data-tone": fill === "agent" ? toneKey(example?.tone ?? 0) : void 0,
|
|
8796
|
+
"data-example": example?.id,
|
|
8797
|
+
children: [shown.map(([kind, name]) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", {
|
|
8774
8798
|
className: `fw-swatch-${kind === "waiting" ? "wait" : kind}`,
|
|
8775
8799
|
"aria-hidden": true
|
|
8776
|
-
}), name] }, kind))
|
|
8800
|
+
}), name] }, kind)), /* @__PURE__ */ jsx("li", {
|
|
8801
|
+
className: "fw-kind-rule",
|
|
8802
|
+
children: fill === "agent" ? "Bar color identifies family; shade identifies work." : "Shade identifies work."
|
|
8803
|
+
})]
|
|
8777
8804
|
});
|
|
8778
8805
|
}
|
|
8779
8806
|
/** The axis dots and the dashed lines they drop: one key naming what they mark (a release, or the kinds the host gave). */
|
|
@@ -8873,17 +8900,37 @@ function OutcomeLegend({ agents, selected, onFilter, neutral }) {
|
|
|
8873
8900
|
]
|
|
8874
8901
|
});
|
|
8875
8902
|
}
|
|
8903
|
+
function ViewControl({ view, graph, onChange }) {
|
|
8904
|
+
return /* @__PURE__ */ jsx("div", {
|
|
8905
|
+
className: "fw-views",
|
|
8906
|
+
role: "radiogroup",
|
|
8907
|
+
"aria-label": "Trace view",
|
|
8908
|
+
children: [
|
|
8909
|
+
"agents",
|
|
8910
|
+
"spans",
|
|
8911
|
+
...graph ? ["graph"] : []
|
|
8912
|
+
].map((option) => /* @__PURE__ */ jsx("button", {
|
|
8913
|
+
type: "button",
|
|
8914
|
+
role: "radio",
|
|
8915
|
+
"aria-checked": view === option,
|
|
8916
|
+
disabled: view === option,
|
|
8917
|
+
className: "fw-view-button",
|
|
8918
|
+
onClick: () => onChange(option),
|
|
8919
|
+
children: option === "agents" ? "Agents" : option === "spans" ? "Steps" : "Graph"
|
|
8920
|
+
}, option))
|
|
8921
|
+
});
|
|
8922
|
+
}
|
|
8876
8923
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
8877
8924
|
* Data remains producer-owned; playback, zoom and folding change presentation only. */
|
|
8878
|
-
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill: fillProp, selectedId, onDeselect, description, summary: showSummary = true, criticalPath = false, query: queryProp, onQueryChange, collapsed: collapsedProp, onCollapsedChange, zoom: zoomProp, onZoomChange, matchSpan }) {
|
|
8925
|
+
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", graph, latestEvents, renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill: fillProp, selectedId, onDeselect, description, summary: showSummary = true, criticalPath = false, query: queryProp, onQueryChange, collapsed: collapsedProp, onCollapsedChange, zoom: zoomProp, onZoomChange, matchSpan }) {
|
|
8879
8926
|
const root = useRef(null);
|
|
8880
8927
|
const search = useRef(null);
|
|
8881
8928
|
const slider = useRef(null);
|
|
8882
8929
|
const timeOutput = useRef(null);
|
|
8883
8930
|
const hideTimer = useRef(void 0);
|
|
8884
|
-
const searchId = useId();
|
|
8885
|
-
const
|
|
8886
|
-
const
|
|
8931
|
+
const searchId = useId(), cardId = useId();
|
|
8932
|
+
const [requestedView, setView] = useState(defaultView);
|
|
8933
|
+
const view = requestedView === "graph" && graph == null ? "agents" : requestedView;
|
|
8887
8934
|
const [query, setQuery] = useHostable(queryProp, onQueryChange, "");
|
|
8888
8935
|
const [collapsed, setCollapsed] = useHostable(collapsedProp, onCollapsedChange, () => new Set(defaultCollapsed ? trace.spans.flatMap((span) => span.parentId ? [span.parentId] : []) : []));
|
|
8889
8936
|
const [speed, setSpeed] = useState(1);
|
|
@@ -8999,6 +9046,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
8999
9046
|
}, [view, agents]);
|
|
9000
9047
|
useEffect(() => {
|
|
9001
9048
|
const keydown = (event) => {
|
|
9049
|
+
if (view === "graph") return;
|
|
9002
9050
|
const active = document.activeElement;
|
|
9003
9051
|
const ownsFocus = root.current?.contains(active);
|
|
9004
9052
|
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "f") {
|
|
@@ -9223,7 +9271,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
9223
9271
|
const quietAgents = agents.filter((row) => quietActivity(row, clockMs).quiet).length;
|
|
9224
9272
|
const recordedErrors = trace.spans.filter((span) => spanFailed(span) && span.kind !== "pipeline");
|
|
9225
9273
|
const errorSpans = recordedErrors.length ? recordedErrors : trace.spans.filter(spanFailed);
|
|
9226
|
-
const errorTimes = Array.from({ length: errors }, (_, index) => errorSpans[index]?.endMs);
|
|
9274
|
+
const errorTimes = Array.from({ length: errors ?? 0 }, (_, index) => errorSpans[index]?.endMs);
|
|
9227
9275
|
const done = !isLive && !trace.spans.some(spanRunning);
|
|
9228
9276
|
const shown = (row) => row && drawnRow(row, visible);
|
|
9229
9277
|
const cardRow = shown(card ? byId.get(card.target) : void 0);
|
|
@@ -9313,7 +9361,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
9313
9361
|
done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
|
|
9314
9362
|
]
|
|
9315
9363
|
}),
|
|
9316
|
-
failedAgents > 0 && /* @__PURE__ */ jsxs("button", {
|
|
9364
|
+
view !== "graph" && failedAgents > 0 && /* @__PURE__ */ jsxs("button", {
|
|
9317
9365
|
type: "button",
|
|
9318
9366
|
className: "fw-text-button fw-alert",
|
|
9319
9367
|
"aria-pressed": filter === "__errors",
|
|
@@ -9325,7 +9373,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
9325
9373
|
" failed"
|
|
9326
9374
|
]
|
|
9327
9375
|
}),
|
|
9328
|
-
quietAgents > 0 && /* @__PURE__ */ jsxs("button", {
|
|
9376
|
+
view !== "graph" && quietAgents > 0 && /* @__PURE__ */ jsxs("button", {
|
|
9329
9377
|
type: "button",
|
|
9330
9378
|
className: "fw-text-button fw-quiet",
|
|
9331
9379
|
"aria-pressed": filter === "__quiet",
|
|
@@ -9338,7 +9386,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
9338
9386
|
children: "Live"
|
|
9339
9387
|
})
|
|
9340
9388
|
]
|
|
9341
|
-
}), /* @__PURE__ */ jsxs("details", {
|
|
9389
|
+
}), view !== "graph" && /* @__PURE__ */ jsxs("details", {
|
|
9342
9390
|
className: "fw-key",
|
|
9343
9391
|
open: root.current ? root.current.clientWidth >= 600 : true,
|
|
9344
9392
|
children: [/* @__PURE__ */ jsxs("summary", { children: ["Key", /* @__PURE__ */ jsx(ChevronGlyph, {})] }), /* @__PURE__ */ jsxs("div", {
|
|
@@ -9350,30 +9398,24 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
9350
9398
|
selected: filter,
|
|
9351
9399
|
onFilter: applyFilter
|
|
9352
9400
|
}),
|
|
9353
|
-
view === "agents" && /* @__PURE__ */ jsx(KindLegend, {
|
|
9401
|
+
view === "agents" && /* @__PURE__ */ jsx(KindLegend, {
|
|
9402
|
+
rows: visible,
|
|
9403
|
+
fill: agentFill
|
|
9404
|
+
}),
|
|
9354
9405
|
/* @__PURE__ */ jsx(MarkerLegend, { markers })
|
|
9355
9406
|
]
|
|
9356
9407
|
})]
|
|
9357
9408
|
})]
|
|
9358
9409
|
}), /* @__PURE__ */ jsxs("div", {
|
|
9359
9410
|
className: "fw-tools",
|
|
9360
|
-
children: [/* @__PURE__ */ jsx(
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
|
|
9367
|
-
|
|
9368
|
-
disabled: view === option,
|
|
9369
|
-
className: "fw-view-button",
|
|
9370
|
-
onClick: () => {
|
|
9371
|
-
setView(option);
|
|
9372
|
-
setNameWidth(NAME_WIDTH[option]);
|
|
9373
|
-
setCard(null);
|
|
9374
|
-
},
|
|
9375
|
-
children: option === "agents" ? "Agents" : "Steps"
|
|
9376
|
-
}, option))
|
|
9411
|
+
children: [/* @__PURE__ */ jsx(ViewControl, {
|
|
9412
|
+
view,
|
|
9413
|
+
graph: graph != null,
|
|
9414
|
+
onChange: (option) => {
|
|
9415
|
+
setView(option);
|
|
9416
|
+
setNameWidth(NAME_WIDTH[option]);
|
|
9417
|
+
setCard(null);
|
|
9418
|
+
}
|
|
9377
9419
|
}), view === "spans" && /* @__PURE__ */ jsxs("div", {
|
|
9378
9420
|
className: "fw-search",
|
|
9379
9421
|
children: [
|
|
@@ -9407,91 +9449,94 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
9407
9449
|
})]
|
|
9408
9450
|
})]
|
|
9409
9451
|
}),
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
className:
|
|
9414
|
-
children: [
|
|
9415
|
-
className: "fw-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
trackPx,
|
|
9453
|
-
showCursor,
|
|
9454
|
-
markers,
|
|
9455
|
-
fillByAgent: agentFill !== "outcome",
|
|
9456
|
-
zoomed: zoom !== null,
|
|
9457
|
-
renderIcon,
|
|
9458
|
-
emptyText: rows.length === 0 ? "No spans yet. Waiting for trace events." : `No matching steps for “${query}”.`,
|
|
9459
|
-
onToggle: toggle,
|
|
9460
|
-
onInspect: inspect,
|
|
9461
|
-
onLeave: leave,
|
|
9462
|
-
onPin: pin,
|
|
9463
|
-
onActivate: (row) => setActiveId(row.id),
|
|
9464
|
-
onActiveChange: setActiveId,
|
|
9465
|
-
onScrub: scrubTo,
|
|
9466
|
-
onZoom: zoomBy,
|
|
9467
|
-
onPan: pan,
|
|
9468
|
-
nameWidth,
|
|
9469
|
-
onNameWidth: (px) => {
|
|
9470
|
-
resized.current = true;
|
|
9471
|
-
setNameWidth(Math.max(140, Math.min(560, px)));
|
|
9472
|
-
},
|
|
9473
|
-
onTrack: setLaneElement,
|
|
9474
|
-
onExpandAll: rows.some((row) => row.hasChildren) ? () => setCollapsed(/* @__PURE__ */ new Set()) : void 0,
|
|
9475
|
-
onCollapseAll: () => setCollapsed(new Set(rows.filter((row) => row.hasChildren).map((row) => row.id)))
|
|
9476
|
-
}),
|
|
9477
|
-
card && cardRow && /* @__PURE__ */ jsx("div", {
|
|
9478
|
-
className: `fw-card-anchor${card.above && card.top === void 0 ? " fw-card-above" : ""}`,
|
|
9479
|
-
onPointerEnter: () => clearTimeout(hideTimer.current),
|
|
9480
|
-
onPointerLeave: card.pinned ? void 0 : leave,
|
|
9481
|
-
children: /* @__PURE__ */ jsx(SpanCard, {
|
|
9482
|
-
id: cardId,
|
|
9483
|
-
row: cardRow,
|
|
9484
|
-
agent: cardAgent,
|
|
9452
|
+
latestEvents,
|
|
9453
|
+
view === "graph" ? graph : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
9454
|
+
showSummary && /* @__PURE__ */ jsx("dl", {
|
|
9455
|
+
className: "fw-summary",
|
|
9456
|
+
children: summary.map(([name, value, title]) => /* @__PURE__ */ jsxs("div", {
|
|
9457
|
+
className: name === "Errors" && errors !== void 0 && errors > 0 ? "fw-has-errors" : void 0,
|
|
9458
|
+
children: [/* @__PURE__ */ jsx("dt", { children: name }), /* @__PURE__ */ jsx("dd", {
|
|
9459
|
+
className: "fw-value",
|
|
9460
|
+
title,
|
|
9461
|
+
children: name === "Errors" ? /* @__PURE__ */ jsx("button", {
|
|
9462
|
+
type: "button",
|
|
9463
|
+
"aria-pressed": filter === "__errors",
|
|
9464
|
+
onClick: () => applyFilter("__errors"),
|
|
9465
|
+
children: value
|
|
9466
|
+
}) : value
|
|
9467
|
+
})]
|
|
9468
|
+
}, name))
|
|
9469
|
+
}),
|
|
9470
|
+
view === "agents" && playback,
|
|
9471
|
+
/* @__PURE__ */ jsx(FlowBody, {
|
|
9472
|
+
rows: visible,
|
|
9473
|
+
byId,
|
|
9474
|
+
totalRows: rows.length,
|
|
9475
|
+
view,
|
|
9476
|
+
maxHeight: rowsHeight,
|
|
9477
|
+
collapsed,
|
|
9478
|
+
searching: Boolean(query.trim()),
|
|
9479
|
+
query,
|
|
9480
|
+
matchSpan,
|
|
9481
|
+
pauses: trace.pauses,
|
|
9482
|
+
errorTimes,
|
|
9483
|
+
cursorLine: isLive || position !== null || playing,
|
|
9484
|
+
sharedDetail,
|
|
9485
|
+
sharedOutcome,
|
|
9486
|
+
expandedIds: view === "agents" ? /* @__PURE__ */ new Set([...openFamilies, ...openSteps]) : void 0,
|
|
9487
|
+
holdHeight: isLive,
|
|
9488
|
+
slowestId: slowest?.id ?? callouts.slowestId,
|
|
9489
|
+
costliestId: callouts.costliestId,
|
|
9490
|
+
criticalIds: criticalPath && view === "spans" ? critical : void 0,
|
|
9491
|
+
activeId,
|
|
9492
|
+
selectedId: selection,
|
|
9493
|
+
focusId: cardAgent?.id ?? (view === "agents" ? activeId : void 0),
|
|
9485
9494
|
clockMs,
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
|
|
9489
|
-
|
|
9495
|
+
window: win,
|
|
9496
|
+
trackPx,
|
|
9497
|
+
showCursor,
|
|
9498
|
+
markers,
|
|
9499
|
+
fillByAgent: agentFill !== "outcome",
|
|
9500
|
+
zoomed: zoom !== null,
|
|
9490
9501
|
renderIcon,
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
|
|
9502
|
+
emptyText: rows.length === 0 ? "No spans yet. Waiting for trace events." : `No matching steps for “${query}”.`,
|
|
9503
|
+
onToggle: toggle,
|
|
9504
|
+
onInspect: inspect,
|
|
9505
|
+
onLeave: leave,
|
|
9506
|
+
onPin: pin,
|
|
9507
|
+
onActivate: (row) => setActiveId(row.id),
|
|
9508
|
+
onActiveChange: setActiveId,
|
|
9509
|
+
onScrub: scrubTo,
|
|
9510
|
+
onZoom: zoomBy,
|
|
9511
|
+
onPan: pan,
|
|
9512
|
+
nameWidth,
|
|
9513
|
+
onNameWidth: (px) => {
|
|
9514
|
+
resized.current = true;
|
|
9515
|
+
setNameWidth(Math.max(140, Math.min(560, px)));
|
|
9516
|
+
},
|
|
9517
|
+
onTrack: setLaneElement,
|
|
9518
|
+
onExpandAll: rows.some((row) => row.hasChildren) ? () => setCollapsed(/* @__PURE__ */ new Set()) : void 0,
|
|
9519
|
+
onCollapseAll: () => setCollapsed(new Set(rows.filter((row) => row.hasChildren).map((row) => row.id)))
|
|
9520
|
+
}),
|
|
9521
|
+
card && cardRow && /* @__PURE__ */ jsx("div", {
|
|
9522
|
+
className: `fw-card-anchor${card.above && card.top === void 0 ? " fw-card-above" : ""}`,
|
|
9523
|
+
onPointerEnter: () => clearTimeout(hideTimer.current),
|
|
9524
|
+
onPointerLeave: card.pinned ? void 0 : leave,
|
|
9525
|
+
children: /* @__PURE__ */ jsx(SpanCard, {
|
|
9526
|
+
id: cardId,
|
|
9527
|
+
row: cardRow,
|
|
9528
|
+
agent: cardAgent,
|
|
9529
|
+
clockMs,
|
|
9530
|
+
left: card.left,
|
|
9531
|
+
top: card.top ?? (card.above ? card.anchorTop - 8 : card.anchorBottom + 8),
|
|
9532
|
+
pinned: card.pinned,
|
|
9533
|
+
onClose: closeCard,
|
|
9534
|
+
renderIcon,
|
|
9535
|
+
compact: view === "agents"
|
|
9536
|
+
})
|
|
9537
|
+
}),
|
|
9538
|
+
view === "spans" && playback
|
|
9539
|
+
] })
|
|
9495
9540
|
]
|
|
9496
9541
|
});
|
|
9497
9542
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/charts",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Static SVG and HTML figures for benchmarks and operational timing. Draws from producer data and refuses what it cannot draw honestly.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"check:run-tokens": "node scripts/check-run-tokens.mjs"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@tangle-network/brand": "1.31.1",
|
|
55
|
+
"@tangle-network/brand": "^1.31.1",
|
|
56
56
|
"@testing-library/react": "^16.3.3",
|
|
57
57
|
"@testing-library/user-event": "^14.6.7",
|
|
58
58
|
"@types/node": "^26.6.3",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"tsdown": "^0.23.0",
|
|
67
67
|
"typescript": "^7.0.2",
|
|
68
68
|
"vitest": "^5.0.2",
|
|
69
|
-
"@tangle-network/ui": "11.51.1"
|
|
69
|
+
"@tangle-network/ui": "^11.51.1"
|
|
70
70
|
},
|
|
71
71
|
"keywords": [
|
|
72
72
|
"tangle",
|