@unofficialbox/box-open-elements 0.19.0 → 0.21.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/components/actions/menu-item.d.ts +2 -0
- package/dist/components/actions/menu-item.js +4 -0
- package/dist/components/collections/card.js +6 -0
- package/dist/components/collections/check-list.d.ts +10 -0
- package/dist/components/collections/check-list.js +8 -0
- package/dist/components/collections/document-list.d.ts +10 -0
- package/dist/components/collections/document-list.js +8 -0
- package/dist/components/collections/fact-list.d.ts +10 -0
- package/dist/components/collections/fact-list.js +8 -0
- package/dist/components/collections/result-blocks.d.ts +54 -0
- package/dist/components/collections/result-blocks.js +74 -0
- package/dist/components/feedback/alert.d.ts +2 -0
- package/dist/components/feedback/alert.js +17 -13
- package/dist/components/feedback/progress-bar.d.ts +9 -0
- package/dist/components/feedback/progress-bar.js +29 -3
- package/dist/components/feedback/status-icon.d.ts +10 -0
- package/dist/components/feedback/status-icon.js +26 -0
- package/dist/components/feedback/toast.d.ts +10 -1
- package/dist/components/feedback/toast.js +69 -27
- package/dist/components/forms/number-input.d.ts +1 -0
- package/dist/components/forms/number-input.js +9 -6
- package/dist/components/forms/search-field.js +16 -0
- package/dist/components/overlays/context-menu.d.ts +2 -0
- package/dist/components/overlays/context-menu.js +7 -0
- package/dist/components/overlays/dialog.js +19 -3
- package/dist/components/overlays/popover.js +6 -1
- package/dist/element-maps.d.ts +49 -19
- package/dist/entries/agent-workspace.d.ts +1 -0
- package/dist/entries/agent-workspace.js +1 -0
- package/dist/entries/call-console.d.ts +1 -0
- package/dist/entries/call-console.js +1 -0
- package/dist/entries/check-list.d.ts +1 -0
- package/dist/entries/check-list.js +1 -0
- package/dist/entries/document-list.d.ts +1 -0
- package/dist/entries/document-list.js +1 -0
- package/dist/entries/fact-list.d.ts +1 -0
- package/dist/entries/fact-list.js +1 -0
- package/dist/entries/result-blocks.d.ts +1 -0
- package/dist/entries/result-blocks.js +1 -0
- package/dist/entries/run-summary.d.ts +1 -0
- package/dist/entries/run-summary.js +1 -0
- package/dist/entries/status-icon.d.ts +1 -0
- package/dist/entries/status-icon.js +1 -0
- package/dist/foundations/a11y/announcer.d.ts +12 -0
- package/dist/foundations/a11y/announcer.js +75 -0
- package/dist/foundations/a11y/focus.js +46 -25
- package/dist/foundations/a11y/index.d.ts +2 -0
- package/dist/foundations/a11y/index.js +1 -0
- package/dist/foundations/icons/box-iconography.d.ts +472 -472
- package/dist/foundations/icons/box-iconography.generated.d.ts +1151 -679
- package/dist/foundations/icons/box-iconography.generated.js +1006 -3588
- package/dist/foundations/icons/glyphs/index.d.ts +1 -0
- package/dist/foundations/icons/glyphs/index.js +2 -0
- package/dist/foundations/icons/glyphs/package.json +1 -0
- package/dist/foundations/motion/behaviors.d.ts +8 -0
- package/dist/foundations/motion/behaviors.js +24 -0
- package/dist/foundations/motion/index.d.ts +2 -0
- package/dist/foundations/motion/index.js +2 -0
- package/dist/foundations/motion/scroll-pin.d.ts +19 -0
- package/dist/foundations/motion/scroll-pin.js +60 -0
- package/dist/foundations/motion/tokens.d.ts +2 -0
- package/dist/foundations/motion/tokens.js +2 -0
- package/dist/foundations/overlay/positioning.js +7 -0
- package/dist/foundations/profiles/registry.d.ts +3 -0
- package/dist/foundations/profiles/registry.js +3 -0
- package/dist/foundations/profiles/types.d.ts +3 -0
- package/dist/foundations/status/index.d.ts +6 -0
- package/dist/foundations/status/index.js +35 -0
- package/dist/foundations/tokens/box-dark.js +4 -1
- package/dist/foundations/tokens/box-defaults.js +3 -0
- package/dist/foundations/tokens/registry.d.ts +3 -0
- package/dist/foundations/tokens/registry.js +3 -0
- package/dist/foundations/tokens/types.d.ts +3 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/patterns/agent-chat/agent-chat.d.ts +9 -0
- package/dist/patterns/agent-chat/agent-chat.js +146 -24
- package/dist/patterns/agent-chat/controller.d.ts +3 -1
- package/dist/patterns/agent-chat/controller.js +92 -16
- package/dist/patterns/agent-chat/index.d.ts +1 -0
- package/dist/patterns/agent-chat/index.js +1 -0
- package/dist/patterns/agent-chat/ndjson.d.ts +5 -0
- package/dist/patterns/agent-chat/ndjson.js +41 -0
- package/dist/patterns/agent-chat/types.d.ts +44 -1
- package/dist/patterns/agent-workspace/agent-workspace.d.ts +28 -0
- package/dist/patterns/agent-workspace/agent-workspace.js +215 -0
- package/dist/patterns/agent-workspace/controller.d.ts +38 -0
- package/dist/patterns/agent-workspace/controller.js +65 -0
- package/dist/patterns/agent-workspace/index.d.ts +2 -0
- package/dist/patterns/agent-workspace/index.js +2 -0
- package/dist/patterns/call-console/call-console.d.ts +20 -0
- package/dist/patterns/call-console/call-console.js +201 -0
- package/dist/patterns/call-console/controller.d.ts +23 -0
- package/dist/patterns/call-console/controller.js +50 -0
- package/dist/patterns/call-console/http-transport.d.ts +3 -0
- package/dist/patterns/call-console/http-transport.js +26 -0
- package/dist/patterns/call-console/index.d.ts +4 -0
- package/dist/patterns/call-console/index.js +4 -0
- package/dist/patterns/call-console/types.d.ts +32 -0
- package/dist/patterns/call-console/types.js +8 -0
- package/dist/patterns/content-explorer/adapters/action-menu.js +7 -5
- package/dist/patterns/content-explorer/adapters/toolbar.js +6 -2
- package/dist/patterns/content-uploader/content-uploader.js +2 -1
- package/dist/patterns/file-request/file-request-builder.js +3 -0
- package/dist/patterns/flow-builder/flow-builder.d.ts +48 -0
- package/dist/patterns/flow-builder/flow-builder.js +200 -0
- package/dist/patterns/flow-builder/index.d.ts +3 -0
- package/dist/patterns/flow-builder/index.js +3 -0
- package/dist/patterns/flow-builder/model.d.ts +33 -0
- package/dist/patterns/flow-builder/model.js +28 -0
- package/dist/patterns/flow-builder/primitives.d.ts +47 -0
- package/dist/patterns/flow-builder/primitives.js +192 -0
- package/dist/patterns/lineage/lineage-graph.js +26 -6
- package/dist/patterns/notifications/notification-inbox.js +19 -3
- package/dist/patterns/preview/annotation-inspector.js +2 -2
- package/dist/patterns/run/index.d.ts +2 -0
- package/dist/patterns/run/index.js +2 -0
- package/dist/patterns/run/progress.d.ts +24 -0
- package/dist/patterns/run/progress.js +32 -0
- package/dist/patterns/run/run-summary.d.ts +21 -0
- package/dist/patterns/run/run-summary.js +82 -0
- package/dist/patterns/run/run-trace.d.ts +1 -0
- package/dist/patterns/run/run-trace.js +14 -3
- package/dist/patterns/undo/index.d.ts +19 -0
- package/dist/patterns/undo/index.js +106 -0
- package/package.json +23 -2
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { BaseElement } from "../../core/element.js";
|
|
2
|
+
import { SplitView } from "../../components/layout/split-view.js";
|
|
3
|
+
import { Button } from "../../components/actions/button.js";
|
|
4
|
+
import { CodeBlock } from "../../components/output/code-block.js";
|
|
5
|
+
import { boeControl, boeRadius } from "../../foundations/geometry/index.js";
|
|
6
|
+
import { boeStatusGlyph, boeStatusStyles } from "../../foundations/status/index.js";
|
|
7
|
+
import { boeEntrance, boeEntranceKeyframes, boeReducedMotionPolicy } from "../../foundations/motion/index.js";
|
|
8
|
+
import { callFailed, callStatus, formatCallHttp } from "./types.js";
|
|
9
|
+
const DEFAULT_TAG_NAME = "box-call-console";
|
|
10
|
+
export async function copyCallText(text, doc = document) {
|
|
11
|
+
try {
|
|
12
|
+
await doc.defaultView?.navigator.clipboard.writeText(text);
|
|
13
|
+
if (doc.defaultView?.navigator.clipboard)
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch { }
|
|
17
|
+
const active = doc.activeElement;
|
|
18
|
+
const textarea = doc.createElement("textarea");
|
|
19
|
+
textarea.value = text;
|
|
20
|
+
textarea.style.cssText = "position:fixed;opacity:0";
|
|
21
|
+
doc.body.append(textarea);
|
|
22
|
+
textarea.select();
|
|
23
|
+
try {
|
|
24
|
+
return doc.execCommand?.("copy") ?? false;
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
textarea.remove();
|
|
28
|
+
active?.focus({ preventScroll: true });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class CallConsole extends BaseElement {
|
|
32
|
+
static tagName = DEFAULT_TAG_NAME;
|
|
33
|
+
model = null;
|
|
34
|
+
unsubscribe;
|
|
35
|
+
selected = "";
|
|
36
|
+
clearing = false;
|
|
37
|
+
rowSignatures = new WeakMap();
|
|
38
|
+
get callController() { return this.model; }
|
|
39
|
+
set callController(value) { this.unsubscribe?.(); this.model = value; if (this.isConnected)
|
|
40
|
+
this.listen(); if (this.isRendered)
|
|
41
|
+
this.update(); }
|
|
42
|
+
listen() { this.unsubscribe?.(); this.unsubscribe = this.model?.subscribe(() => this.update()); }
|
|
43
|
+
connectedCallback() { super.connectedCallback(); this.listen(); }
|
|
44
|
+
disconnectedCallback() { this.unsubscribe?.(); }
|
|
45
|
+
renderTemplate() {
|
|
46
|
+
SplitView.register();
|
|
47
|
+
Button.register();
|
|
48
|
+
CodeBlock.register();
|
|
49
|
+
this.shadowRoot.innerHTML = `<style>:host{display:block;min-width:0;color:var(--boe-token-text-text,#222);font:inherit}:host([hidden]){display:none !important}
|
|
50
|
+
:host{container-type:inline-size;font-size:14px;line-height:1.5}
|
|
51
|
+
*{box-sizing:border-box} [hidden]{display:none!important}
|
|
52
|
+
[part="console"]{border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:${boeRadius.large};background:var(--boe-token-surface-surface,#fff);overflow:hidden}
|
|
53
|
+
header,.filters,.pane-heading{display:flex;gap:.75rem;flex-wrap:wrap;align-items:center;padding:1rem}
|
|
54
|
+
header{justify-content:space-between} h2,h3,p{margin:0} h2{font-size:16px} h3{font-size:14px} small,.muted{color:var(--boe-token-text-text-secondary,#666);font-size:12px}
|
|
55
|
+
#connection{display:inline-flex;align-items:center;gap:.4rem;font-size:12px}
|
|
56
|
+
.filters{border-block:1px solid var(--boe-token-stroke-stroke,#ddd);background:var(--boe-token-surface-surface-secondary,#f7f7f7);padding:.75rem 1rem}
|
|
57
|
+
label{display:flex;align-items:center;gap:.5rem;font-size:13px} .search{flex:1;min-width:10rem} .search input{width:100%}
|
|
58
|
+
input[type="search"],select{min-height:${boeControl.height};border:1px solid var(--boe-token-stroke-stroke,#ccc);border-radius:${boeRadius.control};background:var(--boe-token-surface-surface,#fff);color:inherit;font:inherit;padding:.35rem .65rem;min-width:0}
|
|
59
|
+
input[type="checkbox"]{accent-color:var(--boe-token-surface-surface-brand,#0061d5)}
|
|
60
|
+
button:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--boe-token-surface-surface-brand,#0061d5);outline-offset:-2px}
|
|
61
|
+
.pane-heading{justify-content:space-between;padding:.75rem 1rem;border-bottom:1px solid var(--boe-token-stroke-stroke,#ddd)}
|
|
62
|
+
#list{overflow:auto;max-height:34rem;min-height:20rem;padding:.35rem}
|
|
63
|
+
[part="call"]{font:inherit;color:inherit;border:1px solid transparent;background:transparent;border-radius:${boeRadius.med};width:100%;display:grid;grid-template-columns:minmax(0,1fr) auto;text-align:left;gap:.3rem .75rem;padding:.75rem;cursor:pointer;${boeEntrance()}}
|
|
64
|
+
[part="call"]:hover{background:var(--boe-token-surface-surface-secondary,#f5f5f5)}
|
|
65
|
+
[part="call"][aria-current="true"]{background:var(--boe-token-surface-item-surface-selected,#eaf3ff);border-color:var(--boe-token-surface-surface-brand,#0061d5)}
|
|
66
|
+
[part="call"] span{min-width:0;overflow-wrap:anywhere} [part="call"] .state{display:flex;gap:.4rem;align-items:center;font-size:12px} [part="call"] .summary{font-weight:600} [part="call"] .duration{text-align:right;font-variant-numeric:tabular-nums;font-size:12px}
|
|
67
|
+
#inspector{padding:1rem;min-width:0;max-height:38rem;overflow:auto} #summary{font-size:15px;overflow-wrap:anywhere} #metadata{margin:.25rem 0 1rem;overflow-wrap:anywhere} .code-heading{margin:1rem 0 .5rem}
|
|
68
|
+
#outcome{padding:.6rem .75rem;margin:.75rem 0;border-radius:${boeRadius.med};background:var(--boe-token-surface-surface-secondary,#f5f5f5);overflow-wrap:anywhere}
|
|
69
|
+
#copy-status{font-size:12px;color:var(--boe-token-text-text-secondary,#666)}
|
|
70
|
+
#list-empty,#detail-empty{padding:2.5rem 1rem;text-align:center;color:var(--boe-token-text-text-secondary,#666)}
|
|
71
|
+
[part="skeleton"]{height:3rem;margin:.5rem;background:var(--boe-token-surface-surface-secondary,#eee);border-radius:${boeRadius.med}}
|
|
72
|
+
box-code-block::part(pre){max-height:18rem;overflow:auto}
|
|
73
|
+
@container(max-width:640px){box-split-view::part(split-view){grid-template-columns:minmax(0,1fr)!important}box-split-view::part(separator){display:none}#list{min-height:0;max-height:17rem}#inspector{border-top:1px solid var(--boe-token-stroke-stroke,#ddd)}.search{flex-basis:100%}}
|
|
74
|
+
${boeEntranceKeyframes}${boeStatusStyles}${boeReducedMotionPolicy}</style>
|
|
75
|
+
<section part="console" aria-label="API call console">
|
|
76
|
+
<header><div><h2>API calls</h2><small>Inspect requests, responses, and tool outcomes</small></div><span id="connection" role="status"></span><box-button id="retry" label="Reconnect" tone="neutral" hidden></box-button></header>
|
|
77
|
+
<div class="filters"><label class="search">Search <input id="search" type="search" placeholder="Method, URL, or summary"></label><label>Service <select id="service" aria-label="Service"><option value="">All services</option></select></label><label><input type="checkbox" id="errors"> Errors only</label><box-button id="clear" label="Clear calls" tone="neutral"></box-button></div>
|
|
78
|
+
<box-split-view resizable ratio="0.42" label="Calls and request inspector"><section slot="primary" aria-label="Calls"><div class="pane-heading"><h3>Requests</h3><span id="count" class="muted" role="status"></span></div><div id="list"></div><p id="list-empty" role="status" hidden></p></section><section aria-label="Request inspector"><div class="pane-heading"><h3>Inspector</h3><box-button data-copy="both" label="Copy both" tone="neutral"></box-button><span id="copy-status" role="status"></span></div><p id="detail-empty">Select a request to inspect its details.</p><div id="inspector" hidden><h3 id="summary"></h3><p id="metadata" class="muted"></p><p id="outcome" role="status" hidden></p><h3 class="code-heading">Request</h3><box-code-block id="request" copy-label="Copy request" wrap></box-code-block><h3 class="code-heading">Response</h3><box-code-block id="response" copy-label="Copy response" wrap></box-code-block></div></section></box-split-view></section>`;
|
|
79
|
+
}
|
|
80
|
+
setupListeners() {
|
|
81
|
+
this.shadowRoot.querySelector("#list").addEventListener("keydown", event => {
|
|
82
|
+
const e = event;
|
|
83
|
+
const rows = Array.from(this.shadowRoot.querySelectorAll('[part="call"]'));
|
|
84
|
+
const current = rows.indexOf(e.target);
|
|
85
|
+
if (current < 0 || !["ArrowDown", "ArrowUp", "Home", "End"].includes(e.key))
|
|
86
|
+
return;
|
|
87
|
+
e.preventDefault();
|
|
88
|
+
const next = e.key === "Home" ? 0 : e.key === "End" ? rows.length - 1 : (current + (e.key === "ArrowDown" ? 1 : -1) + rows.length) % rows.length;
|
|
89
|
+
rows[next].focus();
|
|
90
|
+
rows[next].click();
|
|
91
|
+
});
|
|
92
|
+
const filter = () => { this.update(); this.dispatchEvent(new CustomEvent("filters-changed", { bubbles: true, composed: true, detail: this.filters })); };
|
|
93
|
+
this.shadowRoot.querySelector("#service").addEventListener("change", filter);
|
|
94
|
+
this.shadowRoot.querySelector("#errors").addEventListener("change", filter);
|
|
95
|
+
this.shadowRoot.querySelector("#search").addEventListener("input", filter);
|
|
96
|
+
this.shadowRoot.querySelector("#retry").addEventListener("click", () => { void this.model?.connect(); });
|
|
97
|
+
this.shadowRoot.querySelector("#clear").addEventListener("click", async () => {
|
|
98
|
+
if (!this.model || this.clearing)
|
|
99
|
+
return;
|
|
100
|
+
this.clearing = true;
|
|
101
|
+
this.update();
|
|
102
|
+
try {
|
|
103
|
+
await this.model.clear();
|
|
104
|
+
this.dispatchEvent(new CustomEvent("calls-cleared", { bubbles: true, composed: true }));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
this.shadowRoot.querySelector("#copy-status").textContent = "Unable to clear calls. Try again.";
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
this.clearing = false;
|
|
111
|
+
this.update();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
this.shadowRoot.querySelectorAll("[data-copy]").forEach(b => b.addEventListener("click", async () => {
|
|
115
|
+
const entry = this.model?.entries.find(e => e.id === this.selected);
|
|
116
|
+
if (!entry)
|
|
117
|
+
return;
|
|
118
|
+
const ok = await copyCallText(formatCallHttp(entry, b.dataset.copy));
|
|
119
|
+
this.shadowRoot.querySelector("#copy-status").textContent = ok ? "Copied" : "Copy unavailable; select the text to copy";
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
get filters() {
|
|
123
|
+
return { service: this.shadowRoot.querySelector("#service").value, errorsOnly: this.shadowRoot.querySelector("#errors").checked, query: this.shadowRoot.querySelector("#search").value };
|
|
124
|
+
}
|
|
125
|
+
update() {
|
|
126
|
+
if (!this.isRendered)
|
|
127
|
+
return;
|
|
128
|
+
const state = this.model?.connection ?? "connecting";
|
|
129
|
+
const connection = this.shadowRoot.querySelector("#connection");
|
|
130
|
+
connection.textContent = this.model ? ({ live: "Live", connecting: "Connecting…", reconnecting: "Reconnecting…", unavailable: "Endpoint unavailable" }[state]) : "Not connected";
|
|
131
|
+
this.shadowRoot.querySelector("#retry").hidden = !this.model || state === "live" || state === "connecting";
|
|
132
|
+
const clear = this.shadowRoot.querySelector("#clear");
|
|
133
|
+
clear.disabled = this.clearing || !this.model?.entries.length;
|
|
134
|
+
clear.label = this.clearing ? "Clearing…" : "Clear calls";
|
|
135
|
+
const service = this.shadowRoot.querySelector("#service");
|
|
136
|
+
const services = [...new Set(this.model?.entries.map(e => e.service) ?? [])];
|
|
137
|
+
for (const name of services)
|
|
138
|
+
if (!Array.from(service.options).some(o => o.value === name)) {
|
|
139
|
+
const option = document.createElement("option");
|
|
140
|
+
option.value = name;
|
|
141
|
+
option.textContent = name;
|
|
142
|
+
service.append(option);
|
|
143
|
+
}
|
|
144
|
+
const errors = this.shadowRoot.querySelector("#errors").checked;
|
|
145
|
+
const entries = this.model?.entries.filter(e => (!service.value || e.service === service.value) && (!errors || callFailed(e))) ?? [];
|
|
146
|
+
const query = this.filters.query.trim().toLowerCase();
|
|
147
|
+
const filtered = entries.filter(e => `${e.method} ${e.url} ${e.summary} ${e.service}`.toLowerCase().includes(query));
|
|
148
|
+
entries.splice(0, entries.length, ...filtered);
|
|
149
|
+
if (!entries.some(e => e.id === this.selected))
|
|
150
|
+
this.selected = entries[0]?.id ?? "";
|
|
151
|
+
const list = this.shadowRoot.querySelector("#list");
|
|
152
|
+
const focused = this.shadowRoot.activeElement?.getAttribute("data-call-id");
|
|
153
|
+
list.querySelectorAll('[part="skeleton"]').forEach(el => el.remove());
|
|
154
|
+
const existing = new Map(Array.from(list.children).map(el => [el.getAttribute("data-call-id"), el]));
|
|
155
|
+
for (const [index, entry] of entries.entries()) {
|
|
156
|
+
let button = existing.get(entry.id);
|
|
157
|
+
existing.delete(entry.id);
|
|
158
|
+
if (!button) {
|
|
159
|
+
button = document.createElement("button");
|
|
160
|
+
button.type = "button";
|
|
161
|
+
button.setAttribute("part", "call");
|
|
162
|
+
button.dataset.callId = entry.id;
|
|
163
|
+
button.addEventListener("click", () => { this.selected = entry.id; this.update(); this.dispatchEvent(new CustomEvent("call-selected", { bubbles: true, composed: true, detail: { id: entry.id } })); });
|
|
164
|
+
}
|
|
165
|
+
button.setAttribute("aria-current", String(entry.id === this.selected));
|
|
166
|
+
const labels = [callStatus(entry), entry.service, entry.summary, entry.pending ? "In progress" : `${Math.round(entry.durationMs)} ms`];
|
|
167
|
+
const signature = JSON.stringify(labels);
|
|
168
|
+
if (this.rowSignatures.get(button) !== signature) {
|
|
169
|
+
button.replaceChildren(...labels.map((text, index) => { const span = document.createElement("span"); span.textContent = text; span.className = ["state", "muted", "summary", "duration"][index]; if (index === 0) {
|
|
170
|
+
const icon = document.createElement("span");
|
|
171
|
+
icon.innerHTML = boeStatusGlyph(entry.pending ? "active" : callFailed(entry) ? "failed" : entry.expected ? "skipped" : "done");
|
|
172
|
+
span.prepend(icon);
|
|
173
|
+
} return span; }));
|
|
174
|
+
this.rowSignatures.set(button, signature);
|
|
175
|
+
}
|
|
176
|
+
if (list.children[index] !== button)
|
|
177
|
+
list.insertBefore(button, list.children[index] ?? null);
|
|
178
|
+
if (entry.id === focused)
|
|
179
|
+
button.focus({ preventScroll: true });
|
|
180
|
+
}
|
|
181
|
+
existing.forEach(el => el.remove());
|
|
182
|
+
if (!entries.length && state === "connecting" && this.model)
|
|
183
|
+
list.innerHTML = '<div part="skeleton"></div><div part="skeleton"></div><div part="skeleton"></div>';
|
|
184
|
+
this.shadowRoot.querySelector("#count").textContent = `${entries.length} of ${this.model?.entries.length ?? 0}`;
|
|
185
|
+
const empty = this.shadowRoot.querySelector("#list-empty");
|
|
186
|
+
empty.hidden = entries.length > 0 || !!this.model && state === "connecting";
|
|
187
|
+
empty.textContent = !this.model ? "Connect a call controller to inspect requests." : state === "unavailable" ? "The /calls endpoint is unavailable. Start the agent, then reconnect." : state === "reconnecting" ? "Connection interrupted. Reconnect to load calls." : this.model.entries.length ? "No requests match these filters." : "No calls yet. New requests will appear here.";
|
|
188
|
+
const entry = entries.find(e => e.id === this.selected);
|
|
189
|
+
this.shadowRoot.querySelector('[data-copy="both"]').disabled = !entry;
|
|
190
|
+
this.shadowRoot.querySelector("#inspector").hidden = !entry;
|
|
191
|
+
this.shadowRoot.querySelector("#detail-empty").hidden = !!entry;
|
|
192
|
+
this.shadowRoot.querySelector("#summary").textContent = entry?.summary ?? "";
|
|
193
|
+
this.shadowRoot.querySelector("#metadata").textContent = entry ? `${entry.service} · ${entry.method} · ${entry.pending ? "In progress" : `${entry.durationMs} ms`} · ${new Date(entry.startedAt).toLocaleTimeString()}` : "";
|
|
194
|
+
const outcome = this.shadowRoot.querySelector("#outcome");
|
|
195
|
+
outcome.hidden = !entry || !(entry.error || entry.rpcError || entry.expected || entry.pending);
|
|
196
|
+
outcome.textContent = entry?.rpcError ? `Tool failed despite HTTP ${entry.status}: ${entry.rpcError}` : entry?.error ?? entry?.expected ?? (entry?.pending ? "Waiting for the response…" : "");
|
|
197
|
+
this.shadowRoot.querySelector("#request").code = entry ? formatCallHttp(entry, "request") : "";
|
|
198
|
+
this.shadowRoot.querySelector("#response").code = entry ? entry.pending ? "Response pending" : formatCallHttp(entry, "response") : "";
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
CallConsole.register();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CallConnection, CallEntry, CallEvent } from "./types.js";
|
|
2
|
+
export interface CallConsoleTransport {
|
|
3
|
+
snapshot(signal: AbortSignal): Promise<CallEntry[]>;
|
|
4
|
+
subscribe(onEvent: (event: CallEvent) => void, onConnection: (state: CallConnection) => void): () => void;
|
|
5
|
+
clear(): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
/** Transport injection keeps networking out of the component. */
|
|
8
|
+
export declare class CallConsoleController {
|
|
9
|
+
readonly transport: CallConsoleTransport;
|
|
10
|
+
entries: CallEntry[];
|
|
11
|
+
connection: CallConnection;
|
|
12
|
+
private listeners;
|
|
13
|
+
private abort?;
|
|
14
|
+
private close?;
|
|
15
|
+
constructor(transport: CallConsoleTransport);
|
|
16
|
+
subscribe(fn: () => void): () => void;
|
|
17
|
+
private emit;
|
|
18
|
+
apply(event: CallEvent): void;
|
|
19
|
+
connect(): Promise<void>;
|
|
20
|
+
clear(): Promise<void>;
|
|
21
|
+
disconnect(): void;
|
|
22
|
+
destroy(): void;
|
|
23
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Transport injection keeps networking out of the component. */
|
|
2
|
+
export class CallConsoleController {
|
|
3
|
+
transport;
|
|
4
|
+
entries = [];
|
|
5
|
+
connection = "connecting";
|
|
6
|
+
listeners = new Set();
|
|
7
|
+
abort;
|
|
8
|
+
close;
|
|
9
|
+
constructor(transport) {
|
|
10
|
+
this.transport = transport;
|
|
11
|
+
}
|
|
12
|
+
subscribe(fn) { this.listeners.add(fn); return () => this.listeners.delete(fn); }
|
|
13
|
+
emit() { this.listeners.forEach(fn => fn()); }
|
|
14
|
+
apply(event) {
|
|
15
|
+
if (event.type === "snapshot")
|
|
16
|
+
this.entries = event.entries;
|
|
17
|
+
else if (event.type === "clear")
|
|
18
|
+
this.entries = [];
|
|
19
|
+
else
|
|
20
|
+
this.entries = this.entries.some(e => e.id === event.entry.id) ? this.entries.map(e => e.id === event.entry.id ? event.entry : e) : [event.entry, ...this.entries];
|
|
21
|
+
this.emit();
|
|
22
|
+
}
|
|
23
|
+
async connect() {
|
|
24
|
+
this.disconnect();
|
|
25
|
+
const abort = new AbortController();
|
|
26
|
+
this.abort = abort;
|
|
27
|
+
this.connection = "connecting";
|
|
28
|
+
this.emit();
|
|
29
|
+
try {
|
|
30
|
+
const entries = await this.transport.snapshot(abort.signal);
|
|
31
|
+
if (abort.signal.aborted)
|
|
32
|
+
return;
|
|
33
|
+
this.apply({ type: "snapshot", entries });
|
|
34
|
+
this.close = this.transport.subscribe(event => { if (!abort.signal.aborted)
|
|
35
|
+
this.apply(event); }, state => { if (!abort.signal.aborted) {
|
|
36
|
+
this.connection = state;
|
|
37
|
+
this.emit();
|
|
38
|
+
} });
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (!abort.signal.aborted) {
|
|
42
|
+
this.connection = error instanceof Error && error.message === "404" ? "unavailable" : "reconnecting";
|
|
43
|
+
this.emit();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async clear() { await this.transport.clear(); this.apply({ type: "clear" }); }
|
|
48
|
+
disconnect() { this.abort?.abort(); this.close?.(); this.close = undefined; }
|
|
49
|
+
destroy() { this.disconnect(); this.listeners.clear(); }
|
|
50
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Optional browser adapter for the documented snapshot/SSE/DELETE contract. */
|
|
2
|
+
export function createCallConsoleTransport(base = "/calls") {
|
|
3
|
+
return {
|
|
4
|
+
async snapshot(signal) { const response = await fetch(base, { signal }); if (!response.ok)
|
|
5
|
+
throw new Error(String(response.status)); return await response.json(); },
|
|
6
|
+
subscribe(onEvent, onConnection) {
|
|
7
|
+
const events = new EventSource(`${base}/stream`);
|
|
8
|
+
events.onopen = () => onConnection("live");
|
|
9
|
+
events.onerror = () => onConnection("reconnecting");
|
|
10
|
+
for (const type of ["snapshot", "call", "clear"])
|
|
11
|
+
events.addEventListener(type, event => {
|
|
12
|
+
try {
|
|
13
|
+
const data = JSON.parse(event.data);
|
|
14
|
+
if (data.type === type)
|
|
15
|
+
onEvent(data);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
onConnection("reconnecting");
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return () => events.close();
|
|
22
|
+
},
|
|
23
|
+
async clear() { const response = await fetch(base, { method: "DELETE" }); if (!response.ok)
|
|
24
|
+
throw new Error(String(response.status)); },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface CallEntry {
|
|
2
|
+
id: string;
|
|
3
|
+
startedAt: number;
|
|
4
|
+
durationMs: number;
|
|
5
|
+
pending: boolean;
|
|
6
|
+
service: string;
|
|
7
|
+
summary: string;
|
|
8
|
+
method: string;
|
|
9
|
+
url: string;
|
|
10
|
+
requestHeaders: Record<string, string>;
|
|
11
|
+
requestBody?: string;
|
|
12
|
+
status: number;
|
|
13
|
+
statusText: string;
|
|
14
|
+
responseHeaders: Record<string, string>;
|
|
15
|
+
responseBody?: string;
|
|
16
|
+
error?: string;
|
|
17
|
+
expected?: string;
|
|
18
|
+
rpcError?: string;
|
|
19
|
+
}
|
|
20
|
+
export type CallEvent = {
|
|
21
|
+
type: "snapshot";
|
|
22
|
+
entries: CallEntry[];
|
|
23
|
+
} | {
|
|
24
|
+
type: "call";
|
|
25
|
+
entry: CallEntry;
|
|
26
|
+
} | {
|
|
27
|
+
type: "clear";
|
|
28
|
+
};
|
|
29
|
+
export type CallConnection = "connecting" | "live" | "reconnecting" | "unavailable";
|
|
30
|
+
export declare const callFailed: (entry: CallEntry) => boolean;
|
|
31
|
+
export declare const callStatus: (entry: CallEntry) => string;
|
|
32
|
+
export declare const formatCallHttp: (entry: CallEntry, side?: "request" | "response" | "both") => string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const callFailed = (entry) => !entry.pending && Boolean(entry.error || entry.rpcError || (!entry.expected && (entry.status < 200 || entry.status >= 300)));
|
|
2
|
+
export const callStatus = (entry) => entry.pending ? "Pending" : entry.rpcError ? "Tool error" : entry.error ? "Failed" : entry.expected ? "Expected" : callFailed(entry) ? "Failed" : "Done";
|
|
3
|
+
export const formatCallHttp = (entry, side = "both") => {
|
|
4
|
+
const headers = (h) => Object.entries(h).map(([k, v]) => `${k}: ${v}`).join("\n");
|
|
5
|
+
const request = `${entry.method} ${entry.url}\n${headers(entry.requestHeaders)}\n\n${entry.requestBody ?? ""}`;
|
|
6
|
+
const response = `HTTP ${entry.status} ${entry.statusText}\n${headers(entry.responseHeaders)}\n\n${entry.responseBody ?? ""}${entry.rpcError || entry.error ? `\n${entry.rpcError ?? entry.error}` : ""}`;
|
|
7
|
+
return side === "request" ? request : side === "response" ? response : `${request}\n\n${response}`;
|
|
8
|
+
};
|
|
@@ -206,6 +206,8 @@ export class ExplorerActionMenu extends BaseElement {
|
|
|
206
206
|
if (this.itemIdValue && actionId) {
|
|
207
207
|
const item = this.controllerValue?.getState().items.find(entry => entry.id === this.itemIdValue) ?? null;
|
|
208
208
|
const action = this.controllerValue?.getItemActions(this.itemIdValue).find(entry => entry.id === actionId) ?? null;
|
|
209
|
+
if (!action || action.disabled)
|
|
210
|
+
return;
|
|
209
211
|
this.controllerValue?.invokeItemAction(this.itemIdValue, actionId);
|
|
210
212
|
if (item && action) {
|
|
211
213
|
this.dispatchEvent(new CustomEvent(toKebabCase("itemActionInvoked"), {
|
|
@@ -249,7 +251,7 @@ export class ExplorerActionMenu extends BaseElement {
|
|
|
249
251
|
});
|
|
250
252
|
return;
|
|
251
253
|
}
|
|
252
|
-
const menuItems = Array.from(this.shadowRoot.querySelectorAll('[part="menu-item"]'));
|
|
254
|
+
const menuItems = Array.from(this.shadowRoot.querySelectorAll('[part="menu-item"]:not(:disabled)'));
|
|
253
255
|
handleRovingKeydown(keyboardEvent, menuItems, { orientation: "vertical" });
|
|
254
256
|
});
|
|
255
257
|
}
|
|
@@ -269,7 +271,7 @@ export class ExplorerActionMenu extends BaseElement {
|
|
|
269
271
|
}
|
|
270
272
|
const menuMarkup = this.open
|
|
271
273
|
? `<div id="${this.menuId}" part="menu" role="menu">${actions
|
|
272
|
-
.map(action => `<button type="button" part="menu-item" role="menuitem" data-action-id="${escapeHtml(action.id)}">${escapeHtml(action.label)}</button>`)
|
|
274
|
+
.map(action => `<button type="button" part="menu-item" role="menuitem" data-action-id="${escapeHtml(action.id)}" ${action.disabled ? 'disabled aria-disabled="true" tabindex="-1"' : ''}>${escapeHtml(action.label)}</button>`)
|
|
273
275
|
.join("")}</div>`
|
|
274
276
|
: "";
|
|
275
277
|
this.hostEl.innerHTML = `
|
|
@@ -286,12 +288,12 @@ export class ExplorerActionMenu extends BaseElement {
|
|
|
286
288
|
</div>
|
|
287
289
|
`;
|
|
288
290
|
if (this.open) {
|
|
289
|
-
const menuItems = Array.from(this.shadowRoot?.querySelectorAll('[part="menu-item"]') ?? []);
|
|
291
|
+
const menuItems = Array.from(this.shadowRoot?.querySelectorAll('[part="menu-item"]:not(:disabled)') ?? []);
|
|
290
292
|
applyRovingTabindex(menuItems, 0);
|
|
291
293
|
}
|
|
292
294
|
if (justOpened) {
|
|
293
295
|
queueMicrotask(() => {
|
|
294
|
-
this.shadowRoot?.querySelector('[part="menu-item"]')?.focus();
|
|
296
|
+
this.shadowRoot?.querySelector('[part="menu-item"]:not(:disabled)')?.focus();
|
|
295
297
|
});
|
|
296
298
|
return;
|
|
297
299
|
}
|
|
@@ -302,7 +304,7 @@ export class ExplorerActionMenu extends BaseElement {
|
|
|
302
304
|
});
|
|
303
305
|
return;
|
|
304
306
|
}
|
|
305
|
-
if (focusedTrigger) {
|
|
307
|
+
if (focusedTrigger || (focusedActionId && !this.open)) {
|
|
306
308
|
queueMicrotask(() => {
|
|
307
309
|
this.shadowRoot?.querySelector('[part="trigger"]')?.focus();
|
|
308
310
|
});
|
|
@@ -9,6 +9,7 @@ const toKebabCase = (value) => value.replace(/[A-Z]/g, letter => `-${letter.toLo
|
|
|
9
9
|
const elementStyles = `
|
|
10
10
|
:host {
|
|
11
11
|
display: block;
|
|
12
|
+
min-inline-size: 0;
|
|
12
13
|
color: inherit;
|
|
13
14
|
font: inherit;
|
|
14
15
|
}
|
|
@@ -33,14 +34,15 @@ const elementStyles = `
|
|
|
33
34
|
|
|
34
35
|
[part="search"] {
|
|
35
36
|
flex: 1 1 14rem;
|
|
36
|
-
min-width:
|
|
37
|
+
min-width: 0;
|
|
38
|
+
max-width: 100%;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
[part="status"],
|
|
40
42
|
[part="selection-count"] {
|
|
41
43
|
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
42
44
|
font-size: 0.85rem;
|
|
43
|
-
|
|
45
|
+
overflow-wrap: anywhere;
|
|
44
46
|
padding-bottom: 0.45rem;
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -54,6 +56,8 @@ const elementStyles = `
|
|
|
54
56
|
|
|
55
57
|
[part="refresh"],
|
|
56
58
|
[part="clear-selection"] {
|
|
59
|
+
max-inline-size: 100%;
|
|
60
|
+
overflow-wrap: anywhere;
|
|
57
61
|
appearance: none;
|
|
58
62
|
font: inherit;
|
|
59
63
|
font-size: 0.88rem;
|
|
@@ -728,7 +728,8 @@ export class ContentUploader extends BaseElement {
|
|
|
728
728
|
</div>
|
|
729
729
|
<box-progress-bar
|
|
730
730
|
part="row-progress"
|
|
731
|
-
label
|
|
731
|
+
hide-label
|
|
732
|
+
label="${escapeHtml(item.name)}"
|
|
732
733
|
max="100"
|
|
733
734
|
value="${String(Math.round(item.progress * 100))}"
|
|
734
735
|
></box-progress-bar>
|
|
@@ -302,6 +302,7 @@ export class FileRequestBuilder extends BaseElement {
|
|
|
302
302
|
if (!host) {
|
|
303
303
|
return;
|
|
304
304
|
}
|
|
305
|
+
const focusedSetting = this.shadowRoot.activeElement?.getAttribute("data-setting-id");
|
|
305
306
|
host.innerHTML = `
|
|
306
307
|
<section part="builder">
|
|
307
308
|
<header part="header">
|
|
@@ -318,6 +319,8 @@ export class FileRequestBuilder extends BaseElement {
|
|
|
318
319
|
</section>
|
|
319
320
|
`;
|
|
320
321
|
this.shadowRoot.querySelectorAll("[data-setting-id]").forEach(input => {
|
|
322
|
+
if (focusedSetting === input.dataset.settingId)
|
|
323
|
+
input.focus({ preventScroll: true });
|
|
321
324
|
input.addEventListener("change", () => {
|
|
322
325
|
const settingId = input.dataset.settingId;
|
|
323
326
|
if (!settingId) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { BaseElement } from "../../core/index.js";
|
|
2
|
+
import { type FlowKind, type FlowNode, type NodePath } from "./model.js";
|
|
3
|
+
export type InspectorRenderer = (node: FlowNode, container: HTMLElement) => void | (() => void);
|
|
4
|
+
/** Host-owned document editing with accessible navigation and responsive inspection. */
|
|
5
|
+
export declare class FlowBuilder extends BaseElement {
|
|
6
|
+
static readonly tagName = "box-flow-builder";
|
|
7
|
+
private nodesValue;
|
|
8
|
+
private catalogValue;
|
|
9
|
+
private selectedValue;
|
|
10
|
+
private invalidValue;
|
|
11
|
+
private renderer?;
|
|
12
|
+
private cleanupInspector?;
|
|
13
|
+
private inspected;
|
|
14
|
+
private renderedRenderer?;
|
|
15
|
+
private observer?;
|
|
16
|
+
private narrow;
|
|
17
|
+
private insertion?;
|
|
18
|
+
private invoker?;
|
|
19
|
+
private spine;
|
|
20
|
+
private palette;
|
|
21
|
+
private picker;
|
|
22
|
+
private popup;
|
|
23
|
+
private aside;
|
|
24
|
+
private inspector;
|
|
25
|
+
private drawer;
|
|
26
|
+
private error;
|
|
27
|
+
get nodes(): FlowNode[];
|
|
28
|
+
set nodes(value: FlowNode[]);
|
|
29
|
+
get catalog(): readonly FlowKind[];
|
|
30
|
+
set catalog(value: readonly FlowKind[]);
|
|
31
|
+
get selected(): FlowNode | null;
|
|
32
|
+
set selected(node: FlowNode | null);
|
|
33
|
+
get renderInspector(): InspectorRenderer | undefined;
|
|
34
|
+
set renderInspector(renderer: InspectorRenderer | undefined);
|
|
35
|
+
connectedCallback(): void;
|
|
36
|
+
disconnectedCallback(): void;
|
|
37
|
+
private setNarrow;
|
|
38
|
+
protected renderTemplate(): void;
|
|
39
|
+
protected setupListeners(): void;
|
|
40
|
+
private closePicker;
|
|
41
|
+
private insert;
|
|
42
|
+
select(node: FlowNode | null): void;
|
|
43
|
+
/** Mark validation without opening a sheet over the error on a narrow screen. */
|
|
44
|
+
setValidation(message: string, path?: NodePath, document?: unknown): void;
|
|
45
|
+
refresh(): void;
|
|
46
|
+
protected update(): void;
|
|
47
|
+
private updateInspector;
|
|
48
|
+
}
|