jev-devtools 0.0.0-stage → 0.1.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.
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { JevPanel } from "./JevDevtools.js";
4
+ import { JevDrawer } from "./JevDrawer.js";
5
+ function endpointUrl(endpoint) {
6
+ const url = new URL(endpoint, window.location.origin);
7
+ if (url.origin !== window.location.origin ||
8
+ url.username ||
9
+ url.password ||
10
+ url.search ||
11
+ url.hash)
12
+ throw new Error("Use a same-origin inspector endpoint without a query or fragment.");
13
+ return url.href.replace(/\/$/, "");
14
+ }
15
+ export default function ConnectedInspector({ endpoint = "/api/jev-devtools", defaultOpen = true, }) {
16
+ const [traces, setTraces] = useState([]), [connection, setConnection] = useState("connecting");
17
+ useEffect(() => {
18
+ let alive = true, events;
19
+ const abort = new AbortController();
20
+ setTraces([]);
21
+ setConnection("connecting");
22
+ void (async () => {
23
+ try {
24
+ const url = endpointUrl(endpoint);
25
+ const response = await fetch(url, {
26
+ signal: abort.signal,
27
+ credentials: "same-origin",
28
+ });
29
+ if (!response.ok)
30
+ throw new Error(response.status === 404
31
+ ? "Inspector disabled or endpoint missing"
32
+ : `Inspector unavailable (${response.status})`);
33
+ const config = await response.json();
34
+ if (config.version !== 1 || !config.enabled)
35
+ throw new Error("Unsupported inspector endpoint");
36
+ if (!alive)
37
+ return;
38
+ events = new EventSource(url + "/events");
39
+ events.addEventListener("snapshot", (event) => {
40
+ if (!alive)
41
+ return;
42
+ try {
43
+ const data = JSON.parse(event.data);
44
+ if (data.version !== 1 || !Array.isArray(data.traces))
45
+ throw new Error();
46
+ setTraces(data.traces);
47
+ setConnection("connected");
48
+ }
49
+ catch {
50
+ setConnection("Invalid inspector update");
51
+ }
52
+ });
53
+ events.onerror = () => {
54
+ if (alive)
55
+ setConnection("reconnecting");
56
+ };
57
+ }
58
+ catch (error) {
59
+ if (alive)
60
+ setConnection(error instanceof Error ? error.message : "Inspector unavailable");
61
+ }
62
+ })();
63
+ return () => {
64
+ alive = false;
65
+ abort.abort();
66
+ events?.close();
67
+ };
68
+ }, [endpoint]);
69
+ async function clear() {
70
+ const response = await fetch(endpointUrl(endpoint) + "/clear", {
71
+ method: "DELETE",
72
+ credentials: "same-origin",
73
+ });
74
+ if (!response.ok)
75
+ throw new Error(`Cannot clear history (${response.status})`);
76
+ }
77
+ return (_jsx(JevDrawer, { defaultOpen: defaultOpen, children: _jsx(JevPanel, { traces: traces, connection: connection, onClear: clear }) }));
78
+ }
@@ -0,0 +1,6 @@
1
+ import type { Trace } from "./protocol.js";
2
+ export declare function JevPanel({ traces, connection, onClear, }: {
3
+ traces: Trace[];
4
+ connection: string;
5
+ onClear: () => Promise<unknown>;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,58 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { TraceDiagram } from "./TraceDiagram.js";
4
+ const pretty = (value) => JSON.stringify(value, null, 2);
5
+ const ms = (value) => value === undefined ? "—" : `${Math.round(value)} ms`;
6
+ const percent = (value) => `${Math.round(value * 100)}%`;
7
+ function findImage(value) {
8
+ if (!value || typeof value !== "object")
9
+ return;
10
+ if (!Array.isArray(value) &&
11
+ typeof value.preview === "string" &&
12
+ /^data:image\/(png|jpeg);base64,/.test(value.preview))
13
+ return value.preview;
14
+ for (const child of Object.values(value)) {
15
+ const found = findImage(child);
16
+ if (found)
17
+ return found;
18
+ }
19
+ }
20
+ function JsonBlock({ value }) {
21
+ return _jsx("pre", { children: pretty(value) });
22
+ }
23
+ function Answer({ answer, image }) {
24
+ if (!answer)
25
+ return _jsx("p", { className: "muted", children: "No answer received." });
26
+ return (_jsxs("div", { className: "answer", children: [answer.choice !== undefined && (_jsx("strong", { className: "answer-value", children: answer.choice })), answer.score !== undefined && (_jsxs("strong", { className: "answer-value", children: [answer.score.toFixed(2), " ", _jsx("small", { children: "score" })] })), answer.noul !== undefined && (_jsxs(_Fragment, { children: [_jsxs("strong", { className: "answer-value", children: [percent(answer.noul), " ", _jsx("small", { children: "probability of true" })] }), _jsx("div", { className: "bar", children: _jsx("i", { style: { width: percent(answer.noul) } }) })] })), answer.probabilities &&
27
+ Object.entries(answer.probabilities).map(([label, value]) => (_jsxs("div", { className: "probability", children: [_jsxs("div", { children: [_jsx("span", { children: label }), _jsx("b", { children: percent(value) })] }), _jsx("div", { className: "bar", children: _jsx("i", { style: { width: percent(value) } }) })] }, label))), answer.legend && (_jsxs("details", { children: [_jsx("summary", { children: "Score legend" }), _jsx(JsonBlock, { value: answer.legend })] })), answer.box && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "box-preview", children: [image ? (_jsx("img", { src: image, alt: "Captured request image" })) : (_jsx("span", { children: "Image preview unavailable" })), _jsx("div", { className: "bounding-box", style: {
28
+ left: percent(answer.box.left),
29
+ top: percent(answer.box.top),
30
+ width: percent(answer.box.right - answer.box.left),
31
+ height: percent(answer.box.bottom - answer.box.top),
32
+ } })] }), _jsx("code", { className: "coordinates", children: Object.entries(answer.box)
33
+ .map(([k, v]) => `${k} ${v.toFixed(2)}`)
34
+ .join(" · ") })] })), answer.confidence !== undefined && (_jsxs("p", { className: "confidence", children: ["Confidence ", _jsx("b", { children: percent(answer.confidence) })] }))] }));
35
+ }
36
+ export function JevPanel({ traces, connection, onClear, }) {
37
+ const [selected, setSelected] = useState();
38
+ const [tab, setTab] = useState("Flow");
39
+ const [search, setSearch] = useState("");
40
+ const [status, setStatus] = useState("all");
41
+ const [error, setError] = useState("");
42
+ const filtered = traces.filter((t) => (status === "all" || status === t.status) &&
43
+ `${t.label} ${t.request.model} ${t.id}`
44
+ .toLowerCase()
45
+ .includes(search.toLowerCase()));
46
+ const trace = filtered.find((t) => t.id === selected) || filtered[0];
47
+ return (_jsxs("div", { className: "jev-inspector", children: [_jsxs("aside", { className: "request-list", children: [_jsxs("header", { children: [_jsxs("h2", { children: ["Jev requests ", _jsx("span", { children: traces.length })] }), _jsx("button", { onClick: () => void onClear().catch((e) => setError(e.message)), disabled: !traces.length, children: "Clear" })] }), _jsxs("div", { className: "list-filters", children: [_jsx("input", { "aria-label": "Search requests", placeholder: "Search requests\u2026", value: search, onChange: (e) => setSearch(e.target.value) }), _jsx("select", { "aria-label": "Filter request status", value: status, onChange: (e) => setStatus(e.target.value), children: ["all", "pending", "success", "error", "cancelled"].map((s) => (_jsx("option", { children: s }, s))) })] }), _jsxs("div", { className: "request-items", children: [filtered.map((t) => (_jsxs("button", { className: `request-row ${trace?.id === t.id ? "selected" : ""}`, onClick: () => setSelected(t.id), children: [_jsx("span", { className: `status-dot ${t.status}` }), _jsxs("span", { children: [_jsx("strong", { children: t.label }), _jsxs("small", { children: [new Date(t.startedAt).toLocaleTimeString(), " \u00B7 ", t.source, " \u00B7", " ", ms(t.durationMs)] })] }), _jsx("span", { className: "row-status", children: t.status })] }, t.id))), !filtered.length && (_jsx("p", { className: "empty", children: traces.length
48
+ ? "No matching requests."
49
+ : "Run a scenario to capture your first request." }))] }), (connection !== "connected" || error) && (_jsxs("footer", { children: [connection !== "connected" && _jsx("span", { role: "status", children: connection }), error && _jsx("p", { role: "alert", children: error })] }))] }), _jsx("section", { className: "request-detail", children: trace ? (_jsxs(_Fragment, { children: [_jsxs("header", { className: "detail-header", children: [_jsxs("div", { children: [_jsxs("span", { className: "eyebrow", children: [trace.source, " response \u00B7 ", trace.status] }), _jsx("h2", { children: trace.label })] }), _jsxs("div", { className: "metrics", children: [_jsxs("span", { children: [ms(trace.durationMs), _jsx("small", { children: "Total duration" })] }), _jsxs("span", { children: [trace.attempts.length, _jsx("small", { children: "Attempts" })] }), _jsxs("span", { children: [trace.result?.usage
50
+ ? `${trace.result.usage.input_tokens} / ${trace.result.usage.output_tokens}`
51
+ : "—", _jsx("small", { children: "Tokens in / out" })] })] })] }), _jsxs("div", { className: "detail-meta", children: [_jsx("code", { children: trace.result?.model || trace.request.model }), _jsx("code", { children: trace.requestId ||
52
+ (trace.status === "pending"
53
+ ? "Request ID pending"
54
+ : "No request ID") })] }), _jsx("nav", { className: "detail-tabs", "aria-label": "Request views", children: ["Flow", "Questions", "Context", "Transport", "Raw"].map((name) => (_jsx("button", { "aria-pressed": tab === name, onClick: () => setTab(name), children: name }, name))) }), _jsxs("div", { className: "detail-body", children: [trace.error && (_jsxs("div", { role: "alert", className: "error-banner", children: [_jsx("strong", { children: trace.error.name }), " ", trace.error.message] })), trace.captureWarnings.map((w) => (_jsx("p", { className: "warning", children: w }, w))), tab === "Flow" && _jsx(TraceDiagram, { trace: trace }, trace.id), tab === "Questions" &&
55
+ Object.entries(trace.request.questions ?? {})
56
+ .filter(([, q]) => q && typeof q === "object" && typeof q.type === "string")
57
+ .map(([key, q]) => (_jsxs("article", { className: "question-node standalone", children: [_jsxs("header", { children: [_jsx("h3", { children: key }), _jsx("span", { className: "type", children: q.type })] }), _jsx(JsonBlock, { value: q }), _jsx(Answer, { answer: trace.result?.answers[key], image: findImage(trace.request.state) })] }, key))), tab === "Context" && (_jsxs(_Fragment, { children: [_jsx("p", { className: "muted", children: "Captured before dispatch. Known credential fields and email are redacted; captures are size limited." }), findImage(trace.request.state) && (_jsx("img", { className: "context-image", src: findImage(trace.request.state), alt: "Captured context" })), _jsx(JsonBlock, { value: trace.request.state }), trace.request.assets && (_jsx(JsonBlock, { value: trace.request.assets }))] })), tab === "Transport" && (_jsxs(_Fragment, { children: [_jsx("h3", { children: "HTTP attempts" }), _jsx("p", { className: "muted", children: "Attempt duration ends at response headers. Total duration includes SDK processing and retry waits." }), _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Attempt" }), _jsx("th", { children: "Status" }), _jsx("th", { children: "Duration" })] }) }), _jsx("tbody", { children: trace.attempts.map((a) => (_jsxs("tr", { children: [_jsx("td", { children: a.number }), _jsx("td", { children: a.error || a.status || "Pending" }), _jsx("td", { children: ms(a.durationMs) })] }, a.number))) })] }), !trace.attempts.length && _jsx("p", { children: "No HTTP attempt recorded." })] })), tab === "Raw" && _jsx(JsonBlock, { value: trace })] })] })) : (_jsxs("div", { className: "empty-detail", children: [_jsx("span", { className: "empty-symbol", children: "\u21B3" }), _jsx("h2", { children: "Follow a Jev request" }), _jsxs("p", { children: ["Context, parallel questions, typed answers.", _jsx("br", {}), "Run a sample above to see the complete flow."] })] })) })] }));
58
+ }
@@ -0,0 +1,6 @@
1
+ import { type ReactNode } from "react";
2
+ /** Non-modal drawer: the app remains usable and children stay mounted when closed. */
3
+ export declare function JevDrawer({ children, defaultOpen, }: {
4
+ children: ReactNode;
5
+ defaultOpen?: boolean;
6
+ }): import("react").ReactPortal;
@@ -0,0 +1,111 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef, useState } from "react";
3
+ import { TypeSafeMark } from "./TypeSafeMark.js";
4
+ import { createPortal } from "react-dom";
5
+ const heightKey = "jev-inspector-drawer-height";
6
+ const viewportHeight = () => window.visualViewport?.height ?? window.innerHeight;
7
+ const limits = (viewport) => ({
8
+ min: Math.min(260, viewport - 16),
9
+ max: Math.max(80, viewport - 16),
10
+ });
11
+ const clamp = (height, viewport) => {
12
+ const { min, max } = limits(viewport);
13
+ return Math.max(min, Math.min(max, height));
14
+ };
15
+ function initialHeight() {
16
+ try {
17
+ const saved = Number(localStorage.getItem(heightKey));
18
+ if (Number.isFinite(saved) && saved > 0)
19
+ return clamp(saved, viewportHeight());
20
+ }
21
+ catch {
22
+ /* Layout persistence is optional. */
23
+ }
24
+ return clamp(Math.min(420, viewportHeight() * 0.56), viewportHeight());
25
+ }
26
+ /** Non-modal drawer: the app remains usable and children stay mounted when closed. */
27
+ export function JevDrawer({ children, defaultOpen = true, }) {
28
+ const id = useId();
29
+ const [open, setOpen] = useState(defaultOpen);
30
+ const [expanded, setExpanded] = useState(false);
31
+ const [viewport, setViewport] = useState(viewportHeight);
32
+ const [height, setHeight] = useState(initialHeight);
33
+ const trigger = useRef(null);
34
+ const closeButton = useRef(null);
35
+ const panel = useRef(null);
36
+ const interacted = useRef(false);
37
+ const drag = useRef(null);
38
+ const { min, max } = limits(viewport);
39
+ const visibleHeight = expanded ? max : clamp(height, viewport);
40
+ useEffect(() => {
41
+ const resize = () => setViewport(viewportHeight());
42
+ window.addEventListener("resize", resize);
43
+ window.visualViewport?.addEventListener("resize", resize);
44
+ return () => {
45
+ window.removeEventListener("resize", resize);
46
+ window.visualViewport?.removeEventListener("resize", resize);
47
+ };
48
+ }, []);
49
+ useEffect(() => {
50
+ try {
51
+ localStorage.setItem(heightKey, String(height));
52
+ }
53
+ catch {
54
+ /* Private browsing may disable storage. */
55
+ }
56
+ }, [height]);
57
+ useEffect(() => {
58
+ if (interacted.current)
59
+ (open ? closeButton.current : trigger.current)?.focus();
60
+ }, [open]);
61
+ const toggle = (value) => {
62
+ interacted.current = true;
63
+ setOpen(value);
64
+ };
65
+ const resizeTo = (value) => {
66
+ setExpanded(false);
67
+ setHeight(clamp(value, viewport));
68
+ };
69
+ return createPortal(_jsxs(_Fragment, { children: [_jsxs("button", { ref: trigger, className: "jev-devtools jev-drawer-trigger", hidden: open, "aria-label": "Open Jev DevTools", "aria-expanded": open, "aria-controls": id, onClick: () => toggle(true), children: [_jsx(TypeSafeMark, {}), " Jev DevTools"] }), _jsxs("section", { ref: panel, id: id, className: "jev-devtools jev-drawer", hidden: !open, "aria-label": "Jev DevTools", style: { height: visibleHeight }, onKeyDown: (event) => {
70
+ if (event.key === "Escape" && !event.defaultPrevented) {
71
+ event.stopPropagation();
72
+ toggle(false);
73
+ }
74
+ }, children: [_jsx("div", { className: "jev-drawer-resize", role: "separator", tabIndex: 0, "aria-label": "Resize Jev DevTools", "aria-orientation": "horizontal", "aria-valuemin": Math.round(min), "aria-valuemax": Math.round(max), "aria-valuenow": Math.round(visibleHeight), "aria-valuetext": `${Math.round(visibleHeight)} pixels high`, "aria-controls": id, onPointerDown: (event) => {
75
+ if (event.button !== 0)
76
+ return;
77
+ event.preventDefault();
78
+ event.currentTarget.focus();
79
+ event.currentTarget.setPointerCapture(event.pointerId);
80
+ drag.current = {
81
+ y: event.clientY,
82
+ height: panel.current.getBoundingClientRect().height,
83
+ };
84
+ }, onPointerMove: (event) => {
85
+ if (drag.current)
86
+ resizeTo(drag.current.height + drag.current.y - event.clientY);
87
+ }, onPointerUp: (event) => {
88
+ drag.current = null;
89
+ if (event.currentTarget.hasPointerCapture(event.pointerId))
90
+ event.currentTarget.releasePointerCapture(event.pointerId);
91
+ }, onPointerCancel: () => {
92
+ drag.current = null;
93
+ }, onLostPointerCapture: () => {
94
+ drag.current = null;
95
+ }, onKeyDown: (event) => {
96
+ const step = event.shiftKey ? 80 : 20;
97
+ const next = event.key === "ArrowUp"
98
+ ? visibleHeight + step
99
+ : event.key === "ArrowDown"
100
+ ? visibleHeight - step
101
+ : event.key === "Home"
102
+ ? min
103
+ : event.key === "End"
104
+ ? max
105
+ : undefined;
106
+ if (next !== undefined) {
107
+ event.preventDefault();
108
+ resizeTo(next);
109
+ }
110
+ }, children: _jsx("span", {}) }), _jsxs("header", { className: "jev-drawer-header", children: [_jsxs("strong", { children: [_jsx(TypeSafeMark, {}), "Jev DevTools"] }), _jsxs("div", { children: [_jsx("button", { "aria-label": expanded ? "Restore Jev drawer" : "Expand Jev drawer", "aria-pressed": expanded, onClick: () => setExpanded(!expanded), children: expanded ? "Restore" : "Expand" }), _jsx("button", { ref: closeButton, className: "jev-drawer-close", "aria-label": "Close Jev DevTools", title: "Close (Esc)", onClick: () => toggle(false), children: "\u00D7" })] })] }), _jsx("div", { className: "jev-drawer-content", children: children })] })] }), document.body);
111
+ }
@@ -0,0 +1,4 @@
1
+ import type { Trace } from "./protocol.js";
2
+ export declare function TraceDiagram({ trace }: {
3
+ trace: Trace;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,73 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { findImage, flowAnswer, stateText } from "./flow-model.js";
4
+ const pct = (v) => `${Math.round(v * 100)}%`;
5
+ function Result({ answer, image }) {
6
+ if (!answer)
7
+ return _jsx("p", { className: "lab-copy", children: "No answer received." });
8
+ return (_jsxs(_Fragment, { children: [answer.probabilities &&
9
+ Object.entries(answer.probabilities).map(([key, value]) => (_jsxs("div", { className: "lab-probability", children: [_jsxs("div", { children: [_jsx("span", { title: typeof answer.legend?.[key] === "string"
10
+ ? answer.legend[key]
11
+ : key, children: answer.type === "score" && answer.legend?.[key]
12
+ ? `${key} · ${answer.legend[key]}`
13
+ : key }), _jsx("code", { children: pct(value) })] }), _jsx("div", { className: "lab-track", children: _jsx("i", { className: answer.type === "choice" && answer.choice !== key
14
+ ? "lab-alternative"
15
+ : "", style: { width: pct(Math.max(0, Math.min(1, value))) } }) })] }, key))), answer.choice !== undefined && (_jsxs("p", { className: "lab-result", children: ["Selected: ", answer.choice] })), answer.score !== undefined && (_jsxs(_Fragment, { children: [_jsxs("p", { className: "lab-result", children: ["Score ", answer.score.toFixed(2)] }), _jsxs("div", { className: "lab-scale", children: [_jsx("span", { style: {
16
+ left: pct(Math.max(0, Math.min(1, answer.score /
17
+ Math.max(1, Object.keys(answer.probabilities ?? {}).length - 1)))),
18
+ } }), Object.keys(answer.probabilities ?? {}).map((k) => (_jsx("small", { children: k }, k)))] })] })), answer.noul !== undefined && (_jsxs(_Fragment, { children: [_jsx("strong", { className: "lab-big", children: pct(answer.noul) }), _jsx("p", { className: "lab-small", children: "probability of true" }), _jsx("div", { className: "lab-track", children: _jsx("i", { style: { width: pct(answer.noul) } }) }), _jsxs("div", { className: "lab-endpoints", children: [_jsx("span", { children: "False" }), _jsx("span", { children: "True" })] })] })), answer.box && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "lab-image", children: [image ? (_jsx("img", { src: image, alt: "Captured image with returned Box overlay" })) : (_jsx("p", { children: "Image preview unavailable" })), image && (_jsx("div", { className: "lab-box", style: {
19
+ left: pct(answer.box.left),
20
+ top: pct(answer.box.top),
21
+ width: pct(answer.box.right - answer.box.left),
22
+ height: pct(answer.box.bottom - answer.box.top),
23
+ } }))] }), _jsxs("p", { className: "lab-small lab-coordinates", children: ["left ", answer.box.left.toFixed(2), " \u00B7 top ", answer.box.top.toFixed(2), _jsx("br", {}), "right ", answer.box.right.toFixed(2), " \u00B7 bottom", " ", answer.box.bottom.toFixed(2)] })] })), answer.confidence !== undefined && (_jsxs("p", { className: "lab-small", children: ["Confidence ", pct(answer.confidence)] }))] }));
24
+ }
25
+ export function TraceDiagram({ trace }) {
26
+ const [selected, setSelected] = useState(""), [zoom, setZoom] = useState(0.8), [flow, setFlow] = useState(true);
27
+ const viewport = useRef(null);
28
+ const entries = Object.entries(trace.request.questions ?? {}).filter(([, q]) => q && typeof q === "object" && typeof q.type === "string"), [key, question] = entries.find(([key]) => key === selected) ??
29
+ entries[0] ?? ["", undefined];
30
+ const answer = trace.result?.answers[key], image = findImage(trace.request.state) ||
31
+ (trace.request.assets ? findImage(trace.request.assets) : undefined), outputs = flowAnswer(answer);
32
+ const resultHeight = Math.max(280, 166 +
33
+ Object.keys(answer?.probabilities ?? {}).length * 44 +
34
+ (answer?.score !== undefined ? 50 : 0), answer?.box ? 390 : 0);
35
+ const height = Math.max(520, resultHeight + 90, outputs.length * 136 + 60), ry = Math.max(40, (height - resultHeight) / 2), mid = ry + resultHeight / 2;
36
+ const fit = () => {
37
+ if (viewport.current)
38
+ setZoom(Math.min(1, (viewport.current.clientWidth - 24) / 1120));
39
+ };
40
+ useEffect(() => {
41
+ if (viewport.current)
42
+ setZoom(Math.min(1, Math.max(0.75, (viewport.current.clientWidth - 24) / 1120)));
43
+ }, []);
44
+ if (!question)
45
+ return _jsx("p", { className: "muted", children: "No questions available in this capture." });
46
+ const sourcePath = (y) => `M260 ${y} C325 ${y},325 ${mid},390 ${mid}`;
47
+ const paths = [
48
+ { id: "state", d: sourcePath(122), active: true },
49
+ { id: "question", d: sourcePath(378), active: true },
50
+ ...outputs.map((output, i) => ({
51
+ id: `output-${i}`,
52
+ d: `M710 ${mid} C790 ${mid},790 ${92 + i * 136},870 ${92 + i * 136}`,
53
+ active: output.active,
54
+ })),
55
+ ];
56
+ return (_jsxs("div", { className: "lab-flow", children: [_jsxs("div", { className: "lab-flow-toolbar", children: [_jsxs("label", { children: ["Question", _jsx("select", { "aria-label": "Diagram question", value: key, onChange: (event) => setSelected(event.target.value), children: entries.map(([name, q]) => (_jsxs("option", { value: name, children: [name, " \u00B7 ", q.type] }, name))) })] }), _jsxs("div", { children: [_jsx("button", { "aria-label": "Zoom diagram out", onClick: () => setZoom((v) => Math.max(0.4, v - 0.1)), children: "\u2212" }), _jsxs("output", { "aria-label": "Diagram zoom", children: [Math.round(zoom * 100), "%"] }), _jsx("button", { "aria-label": "Zoom diagram in", onClick: () => setZoom((v) => Math.min(1.5, v + 0.1)), children: "+" }), _jsx("button", { onClick: fit, children: "Fit" }), _jsx("button", { "aria-pressed": flow, onClick: () => setFlow(!flow), children: flow ? "Pause flow" : "Play flow" })] })] }), _jsx("div", { className: "lab-flow-viewport", ref: viewport, tabIndex: 0, "aria-label": "Jev diagram, scroll to explore", children: _jsx("div", { style: { width: 1120 * zoom, height: height * zoom }, children: _jsxs("div", { className: "lab-flow-canvas", style: { width: 1120, height, transform: `scale(${zoom})` }, children: [_jsxs("svg", { className: "lab-connections", width: "1120", height: height, "aria-hidden": "true", children: [paths.map((path) => (_jsxs("g", { children: [_jsx("path", { d: path.d, className: path.active ? "active" : "" }), flow &&
57
+ path.active &&
58
+ !["error", "cancelled"].includes(trace.status) && (_jsx("circle", { r: "3", className: "lab-flow-dot", children: _jsx("animateMotion", { dur: "3.2s", repeatCount: "indefinite", path: path.d }) }))] }, path.id))), _jsx("text", { x: "305", y: mid - 65, children: "state" }), _jsx("text", { x: "300", y: mid + 65, children: "question" })] }), _jsxs("article", { className: "lab-card", style: { left: 20, top: 32, width: 240, height: 180 }, children: [_jsx("span", { className: "lab-eyebrow", children: image ? "IMAGE STATE" : "STATE" }), _jsx("h3", { children: "Shared context" }), image ? (_jsx("img", { className: "lab-source-image", src: image, alt: "Captured image state" })) : (_jsx("p", { className: "lab-copy", title: stateText(trace), children: stateText(trace) }))] }), _jsxs("article", { className: "lab-card", style: { left: 20, top: 288, width: 240, height: 180 }, children: [_jsx("span", { className: "lab-eyebrow", children: "INSTRUCTIONS" }), _jsxs("h3", { children: ["Question \u00B7 ", question.type] }), _jsx("p", { className: "lab-copy", title: typeof question.instructions === "string"
59
+ ? question.instructions
60
+ : JSON.stringify(question.instructions), children: typeof question.instructions === "string"
61
+ ? question.instructions
62
+ : JSON.stringify(question.instructions) })] }), _jsxs("article", { className: "lab-card lab-answer-card", style: {
63
+ left: 390,
64
+ top: ry,
65
+ width: 320,
66
+ minHeight: resultHeight,
67
+ }, children: [_jsxs("span", { className: "lab-eyebrow", children: ["JEV \u00B7 ", question.type.toUpperCase()] }), _jsx("h3", { title: key, children: key }), _jsx(Result, { answer: answer, image: image })] }), outputs.map((output, i) => (_jsxs("article", { className: `lab-card lab-output ${output.active ? "" : "lab-idle"}`, style: {
68
+ left: 870,
69
+ top: 40 + i * 136,
70
+ width: 230,
71
+ minHeight: 104,
72
+ }, children: [_jsx("span", { className: "lab-eyebrow", children: output.eyebrow }), _jsx("h3", { title: output.label, children: output.label }), _jsx("p", { children: output.detail })] }, i)))] }) }) }), _jsxs("p", { className: "muted", children: [entries.length, " parallel question", entries.length === 1 ? "" : "s", " in one request. Connections show data flow, not model timing. Application routes and thresholds are not captured. Full inputs and criteria are in Questions and Context."] })] }));
73
+ }
@@ -0,0 +1 @@
1
+ export declare function TypeSafeMark(): import("react").JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // TypeSafe logo reused from the public docs asset in Diagram Motion Lab dist/docs/assets/logo-dark.png.
3
+ // Embedded so the installed component needs no host asset route.
4
+ export function TypeSafeMark() {
5
+ return (_jsx("span", { className: "typesafe-mark", "aria-hidden": "true", children: _jsx("img", { alt: "", src: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAXIAAABSCAMAAAB+Ia+5AAAC+lBMVEVHcEz///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////90Tt8gAAAA/XRSTlMAJSaYRwKiBc9wTeAz5J5pRmSAHJN+ZkycILIECivDknb9+gHBOakG/gj3zg8V8M0LWvT1hbUQA/wYY3f4ExvYX5rsJNc3GtAJ5Q3ofHFI9t/APzAM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" }) }));
6
+ }
@@ -0,0 +1,9 @@
1
+ import type { AnswerView, Json, Trace } from "./protocol.js";
2
+ export declare function findImage(value: Json): string | undefined;
3
+ export declare function flowAnswer(answer?: AnswerView): {
4
+ label: string;
5
+ detail: string;
6
+ active: boolean;
7
+ eyebrow: string;
8
+ }[];
9
+ export declare function stateText(trace: Trace): string;
@@ -0,0 +1,69 @@
1
+ export function findImage(value) {
2
+ if (!value || typeof value !== "object")
3
+ return;
4
+ if (!Array.isArray(value) &&
5
+ typeof value.preview === "string" &&
6
+ /^data:image\/(png|jpeg);base64,/.test(value.preview))
7
+ return value.preview;
8
+ for (const child of Object.values(value)) {
9
+ const result = findImage(child);
10
+ if (result)
11
+ return result;
12
+ }
13
+ }
14
+ export function flowAnswer(answer) {
15
+ if (!answer)
16
+ return [];
17
+ if (answer.type === "choice")
18
+ return Object.entries(answer.probabilities ?? {}).map(([label, probability]) => ({
19
+ label,
20
+ detail: `${Math.round(probability * 100)}% probability`,
21
+ active: label === answer.choice,
22
+ eyebrow: label === answer.choice ? "RETURNED CHOICE" : "ALTERNATIVE",
23
+ }));
24
+ if (answer.type === "score")
25
+ return [
26
+ {
27
+ label: `Score ${answer.score ?? "unavailable"}`,
28
+ detail: "Returned by Jev",
29
+ active: true,
30
+ eyebrow: "RETURNED SCORE",
31
+ },
32
+ ];
33
+ if (answer.type === "noul" && answer.noul !== undefined)
34
+ return [
35
+ {
36
+ label: "True",
37
+ detail: `${Math.round(answer.noul * 100)}% probability`,
38
+ active: true,
39
+ eyebrow: "P(TRUE)",
40
+ },
41
+ {
42
+ label: "False",
43
+ detail: `${Math.round((1 - answer.noul) * 100)}% probability`,
44
+ active: true,
45
+ eyebrow: "P(FALSE)",
46
+ },
47
+ ];
48
+ if (answer.type === "box")
49
+ return [
50
+ {
51
+ label: "Region coordinates",
52
+ detail: "Your application decides how to use this region.",
53
+ active: true,
54
+ eyebrow: "RETURNED BOX",
55
+ },
56
+ ];
57
+ return [];
58
+ }
59
+ export function stateText(trace) {
60
+ const state = trace.request.state;
61
+ if (typeof state === "string")
62
+ return state;
63
+ if (state && typeof state === "object" && !Array.isArray(state)) {
64
+ for (const key of ["message", "text", "description"])
65
+ if (typeof state[key] === "string")
66
+ return state[key];
67
+ }
68
+ return JSON.stringify(state, null, 2) ?? "";
69
+ }
@@ -0,0 +1,9 @@
1
+ export type { Trace, Snapshot, Json, AnswerView, QuestionView, } from "./protocol.js";
2
+ export type JevDevtoolsProps = {
3
+ endpoint?: string;
4
+ /** Opt in explicitly, normally with import.meta.env.DEV. */
5
+ enabled?: boolean;
6
+ defaultOpen?: boolean;
7
+ };
8
+ /** SSR-safe entry. No requests or browser globals are accessed while disabled. */
9
+ export declare function JevDevtools({ enabled, ...props }: JevDevtoolsProps): import("react").JSX.Element | null;
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { lazy, Suspense, useEffect, useState } from "react";
4
+ const ConnectedInspector = lazy(() => import("./ConnectedInspector.js"));
5
+ /** SSR-safe entry. No requests or browser globals are accessed while disabled. */
6
+ export function JevDevtools({ enabled = false, ...props }) {
7
+ const [mounted, setMounted] = useState(false);
8
+ useEffect(() => {
9
+ setMounted(true);
10
+ }, []);
11
+ return enabled && mounted ? (_jsx(Suspense, { fallback: null, children: _jsx(ConnectedInspector, { ...props }) })) : null;
12
+ }