@unofficialbox/box-open-elements 0.19.0 → 0.20.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/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 +71 -0
- 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/element-maps.d.ts +39 -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/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/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 +33 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/patterns/agent-chat/agent-chat.d.ts +9 -0
- package/dist/patterns/agent-chat/agent-chat.js +135 -21
- 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 +17 -0
- package/dist/patterns/call-console/call-console.js +107 -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-uploader/content-uploader.js +2 -1
- 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/package.json +13 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { boeMotionDuration, boeMotionEasing } from "./tokens.js";
|
|
2
|
+
export const boeMotionStagger = "var(--boe-profile-motion-stagger, 50ms)";
|
|
3
|
+
export const boeEntranceKeyframes = `
|
|
4
|
+
@keyframes boe-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
|
|
5
|
+
@keyframes boe-pop { from { opacity: 0; transform: scale(.8); } to { opacity: 1; transform: none; } }
|
|
6
|
+
@keyframes boe-fade-in { from { opacity: 0; } to { opacity: 1; } }
|
|
7
|
+
@keyframes boe-status-check { from { stroke-dashoffset: 20; } to { stroke-dashoffset: 0; } }
|
|
8
|
+
@keyframes boe-status-spin { to { transform: rotate(360deg); } }
|
|
9
|
+
`;
|
|
10
|
+
export const boeEntrance = (name = "rise") => `animation: boe-${name} ${boeMotionDuration.arrival} ${boeMotionEasing.enter} both;`;
|
|
11
|
+
export const boeStaggerStyles = `animation-delay: calc(var(--i, 0) * ${boeMotionStagger});`;
|
|
12
|
+
/** Put padding on a grandchild, not the collapsible child. Pair with inert. */
|
|
13
|
+
export const boeDisclosureStyles = (selector = ".boe-disclosure") => `
|
|
14
|
+
${selector} { display: grid; grid-template-rows: 0fr; transition: grid-template-rows ${boeMotionDuration.panel} ${boeMotionEasing.enter}; }
|
|
15
|
+
${selector}[data-open] { grid-template-rows: 1fr; }
|
|
16
|
+
${selector} > * { min-height: 0; overflow: hidden; padding-block: 0; }
|
|
17
|
+
`;
|
|
18
|
+
/** Include in document and shadow styles: media rules do not cross shadow roots. */
|
|
19
|
+
export const boeReducedMotionPolicy = `
|
|
20
|
+
@media (prefers-reduced-motion: reduce) {
|
|
21
|
+
*, *::before, *::after { animation-duration: 1ms !important; animation-iteration-count: 1 !important;
|
|
22
|
+
animation-delay: 0ms !important; transition-duration: 1ms !important; transition-delay: 0ms !important; scroll-behavior: auto !important; }
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Scroll following is changed by user intent, never by resize-induced scroll events. */
|
|
2
|
+
export declare class ScrollPinController {
|
|
3
|
+
private readonly viewport;
|
|
4
|
+
private readonly onChange;
|
|
5
|
+
pinned: boolean;
|
|
6
|
+
private cleanup;
|
|
7
|
+
private observer?;
|
|
8
|
+
private pointer;
|
|
9
|
+
private lastTop;
|
|
10
|
+
private touchY;
|
|
11
|
+
constructor(viewport: HTMLElement, onChange?: (showJump: boolean) => void);
|
|
12
|
+
connect(content?: Element): void;
|
|
13
|
+
private atBottom;
|
|
14
|
+
private notify;
|
|
15
|
+
unpin(): void;
|
|
16
|
+
contentChanged(newMessage?: boolean): void;
|
|
17
|
+
jump(): void;
|
|
18
|
+
disconnect(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Scroll following is changed by user intent, never by resize-induced scroll events. */
|
|
2
|
+
export class ScrollPinController {
|
|
3
|
+
viewport;
|
|
4
|
+
onChange;
|
|
5
|
+
pinned = true;
|
|
6
|
+
cleanup = [];
|
|
7
|
+
observer;
|
|
8
|
+
pointer = false;
|
|
9
|
+
lastTop = 0;
|
|
10
|
+
touchY = 0;
|
|
11
|
+
constructor(viewport, onChange = () => { }) {
|
|
12
|
+
this.viewport = viewport;
|
|
13
|
+
this.onChange = onChange;
|
|
14
|
+
}
|
|
15
|
+
connect(content = this.viewport) {
|
|
16
|
+
this.disconnect();
|
|
17
|
+
const on = (kind, fn) => {
|
|
18
|
+
this.viewport.addEventListener(kind, fn);
|
|
19
|
+
this.cleanup.push(() => this.viewport.removeEventListener(kind, fn));
|
|
20
|
+
};
|
|
21
|
+
on("wheel", e => { if (e.deltaY < 0)
|
|
22
|
+
this.unpin(); });
|
|
23
|
+
on("keydown", e => { if (["ArrowUp", "PageUp", "Home"].includes(e.key))
|
|
24
|
+
this.unpin(); });
|
|
25
|
+
on("touchstart", e => { this.touchY = e.touches[0]?.clientY ?? 0; });
|
|
26
|
+
on("touchmove", e => { const y = e.touches[0]?.clientY ?? this.touchY; if (y > this.touchY)
|
|
27
|
+
this.unpin(); this.touchY = y; });
|
|
28
|
+
on("pointerdown", () => { this.pointer = true; this.lastTop = this.viewport.scrollTop; });
|
|
29
|
+
const release = () => { this.pointer = false; };
|
|
30
|
+
this.viewport.ownerDocument.addEventListener("pointerup", release);
|
|
31
|
+
this.cleanup.push(() => this.viewport.ownerDocument.removeEventListener("pointerup", release));
|
|
32
|
+
on("scroll", () => {
|
|
33
|
+
if (this.pointer && this.viewport.scrollTop < this.lastTop)
|
|
34
|
+
this.unpin();
|
|
35
|
+
this.lastTop = this.viewport.scrollTop;
|
|
36
|
+
if (this.atBottom())
|
|
37
|
+
this.pinned = true;
|
|
38
|
+
this.notify();
|
|
39
|
+
});
|
|
40
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
41
|
+
this.observer = new ResizeObserver(() => this.contentChanged());
|
|
42
|
+
this.observer.observe(this.viewport);
|
|
43
|
+
if (content !== this.viewport)
|
|
44
|
+
this.observer.observe(content);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
atBottom() { return this.viewport.scrollHeight - this.viewport.clientHeight - this.viewport.scrollTop < 40; }
|
|
48
|
+
notify() { this.onChange(!this.pinned && !this.atBottom()); }
|
|
49
|
+
unpin() { this.pinned = false; this.notify(); }
|
|
50
|
+
contentChanged(newMessage = false) { if (newMessage)
|
|
51
|
+
this.pinned = true; if (this.pinned)
|
|
52
|
+
this.viewport.scrollTop = this.viewport.scrollHeight; this.notify(); }
|
|
53
|
+
jump() {
|
|
54
|
+
this.pinned = true;
|
|
55
|
+
const reduced = this.viewport.ownerDocument.defaultView?.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
56
|
+
this.viewport.scrollTo?.({ top: this.viewport.scrollHeight, behavior: reduced ? "instant" : "smooth" });
|
|
57
|
+
this.onChange(false);
|
|
58
|
+
}
|
|
59
|
+
disconnect() { this.cleanup.forEach(fn => fn()); this.cleanup = []; this.observer?.disconnect(); }
|
|
60
|
+
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* prefers-reduced-motion policy is one place to update.
|
|
7
7
|
*/
|
|
8
8
|
export declare const boeMotionDuration: {
|
|
9
|
+
readonly arrival: "var(--boe-profile-motion-arrival, 200ms)";
|
|
10
|
+
readonly panel: "var(--boe-profile-motion-panel, 320ms)";
|
|
9
11
|
/** Quick accent transitions (charts, micro-feedback) */
|
|
10
12
|
readonly fast: "var(--boe-profile-motion-fast, 120ms)";
|
|
11
13
|
/**
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* prefers-reduced-motion policy is one place to update.
|
|
7
7
|
*/
|
|
8
8
|
export const boeMotionDuration = {
|
|
9
|
+
arrival: "var(--boe-profile-motion-arrival, 200ms)",
|
|
10
|
+
panel: "var(--boe-profile-motion-panel, 320ms)",
|
|
9
11
|
/** Quick accent transitions (charts, micro-feedback) */
|
|
10
12
|
fast: "var(--boe-profile-motion-fast, 120ms)",
|
|
11
13
|
/**
|
|
@@ -52,6 +52,9 @@ export declare const DESIGN_PROFILE_VARIABLES: {
|
|
|
52
52
|
readonly primaryFocus: "--boe-profile-shadow-primary-focus";
|
|
53
53
|
};
|
|
54
54
|
readonly motion: {
|
|
55
|
+
readonly arrival: "--boe-profile-motion-arrival";
|
|
56
|
+
readonly panel: "--boe-profile-motion-panel";
|
|
57
|
+
readonly stagger: "--boe-profile-motion-stagger";
|
|
55
58
|
readonly fast: "--boe-profile-motion-fast";
|
|
56
59
|
readonly interactive: "--boe-profile-motion-interactive";
|
|
57
60
|
readonly medium: "--boe-profile-motion-medium";
|
|
@@ -51,6 +51,9 @@ export const DESIGN_PROFILE_VARIABLES = {
|
|
|
51
51
|
primaryFocus: "--boe-profile-shadow-primary-focus",
|
|
52
52
|
},
|
|
53
53
|
motion: {
|
|
54
|
+
arrival: "--boe-profile-motion-arrival",
|
|
55
|
+
panel: "--boe-profile-motion-panel",
|
|
56
|
+
stagger: "--boe-profile-motion-stagger",
|
|
54
57
|
fast: "--boe-profile-motion-fast",
|
|
55
58
|
interactive: "--boe-profile-motion-interactive",
|
|
56
59
|
medium: "--boe-profile-motion-medium",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type StatusKind = "pending" | "active" | "done" | "warning" | "failed" | "skipped";
|
|
2
|
+
export declare const toStatusKind: (status: string) => StatusKind;
|
|
3
|
+
export declare const statusLabel: (kind: StatusKind) => string;
|
|
4
|
+
/** Decorative markup. Pair with visible words or the StatusIcon component. */
|
|
5
|
+
export declare const boeStatusGlyph: (kind: StatusKind) => string;
|
|
6
|
+
export declare const boeStatusStyles = "\n.boe-status { flex: none; vertical-align: middle; fill: none; stroke: currentColor; stroke-width: 1.5; }\n.boe-status[data-kind=\"active\"] { color: var(--boe-token-surface-surface-brand, #0061d5); animation: boe-status-spin .8s linear infinite; }\n.boe-status[data-kind=\"active\"] circle { opacity: .25; }\n.boe-status[data-kind=\"done\"] { color: var(--boe-token-text-status-text-success, #187657); }\n.boe-status[data-kind=\"warning\"] { color: var(--boe-token-text-status-text-warning, #946400); }\n.boe-status[data-kind=\"failed\"] { color: var(--boe-token-text-status-text-error, #c52a46); }\n.boe-status[data-kind=\"skipped\"] circle { stroke-dasharray: 2 2; }\n.boe-status:is([data-kind=\"done\"], [data-kind=\"warning\"], [data-kind=\"failed\"]) circle { fill: currentColor; }\n.boe-status:is([data-kind=\"done\"], [data-kind=\"warning\"], [data-kind=\"failed\"]) path { stroke: var(--boe-token-surface-surface, #fff); stroke-linecap: round; }\n.boe-status[data-kind=\"done\"] { animation: boe-pop 200ms ease-out both; }\n.boe-status[data-kind=\"done\"] path { stroke-dasharray: 20; animation: boe-status-check 200ms ease-out both; }\n";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const mappings = {
|
|
2
|
+
pending: "pending", info: "pending", active: "active", running: "active", in_progress: "active",
|
|
3
|
+
done: "done", succeeded: "done", completed: "done", pass: "done", approved: "done",
|
|
4
|
+
warning: "warning", warn: "warning", failed: "failed", fail: "failed", error: "failed",
|
|
5
|
+
skipped: "skipped", rejected: "skipped",
|
|
6
|
+
};
|
|
7
|
+
export const toStatusKind = (status) => mappings[status] ?? "pending";
|
|
8
|
+
export const statusLabel = (kind) => ({
|
|
9
|
+
pending: "Pending", active: "In progress", done: "Done", warning: "Done with a warning",
|
|
10
|
+
failed: "Failed", skipped: "Skipped",
|
|
11
|
+
})[kind];
|
|
12
|
+
/** Decorative markup. Pair with visible words or the StatusIcon component. */
|
|
13
|
+
export const boeStatusGlyph = (kind) => {
|
|
14
|
+
const state = toStatusKind(kind);
|
|
15
|
+
const path = state === "done" ? '<path d="m4 8 3 3 5-6"/>'
|
|
16
|
+
: state === "failed" ? '<path d="m5 5 6 6m0-6-6 6"/>'
|
|
17
|
+
: state === "warning" ? '<path d="M5 8h6"/>'
|
|
18
|
+
: state === "active" ? '<path d="M8 1a7 7 0 0 1 7 7"/>' : "";
|
|
19
|
+
return `<svg class="boe-status" data-kind="${state}" aria-hidden="true" viewBox="0 0 16 16" width="16" height="16"><circle cx="8" cy="8" r="7"/>${path}</svg>`;
|
|
20
|
+
};
|
|
21
|
+
export const boeStatusStyles = `
|
|
22
|
+
.boe-status { flex: none; vertical-align: middle; fill: none; stroke: currentColor; stroke-width: 1.5; }
|
|
23
|
+
.boe-status[data-kind="active"] { color: var(--boe-token-surface-surface-brand, #0061d5); animation: boe-status-spin .8s linear infinite; }
|
|
24
|
+
.boe-status[data-kind="active"] circle { opacity: .25; }
|
|
25
|
+
.boe-status[data-kind="done"] { color: var(--boe-token-text-status-text-success, #187657); }
|
|
26
|
+
.boe-status[data-kind="warning"] { color: var(--boe-token-text-status-text-warning, #946400); }
|
|
27
|
+
.boe-status[data-kind="failed"] { color: var(--boe-token-text-status-text-error, #c52a46); }
|
|
28
|
+
.boe-status[data-kind="skipped"] circle { stroke-dasharray: 2 2; }
|
|
29
|
+
.boe-status:is([data-kind="done"], [data-kind="warning"], [data-kind="failed"]) circle { fill: currentColor; }
|
|
30
|
+
.boe-status:is([data-kind="done"], [data-kind="warning"], [data-kind="failed"]) path { stroke: var(--boe-token-surface-surface, #fff); stroke-linecap: round; }
|
|
31
|
+
.boe-status[data-kind="done"] { animation: boe-pop 200ms ease-out both; }
|
|
32
|
+
.boe-status[data-kind="done"] path { stroke-dasharray: 20; animation: boe-status-check 200ms ease-out both; }
|
|
33
|
+
`;
|
package/dist/index.d.ts
CHANGED
|
@@ -134,3 +134,13 @@ export * from "./patterns/task/index.js";
|
|
|
134
134
|
export * from "./patterns/governance/index.js";
|
|
135
135
|
export * from "./patterns/insights/index.js";
|
|
136
136
|
export * from "./element-maps.js";
|
|
137
|
+
export * from "./components/feedback/status-icon.js";
|
|
138
|
+
export * from "./components/collections/fact-list.js";
|
|
139
|
+
export * from "./components/collections/check-list.js";
|
|
140
|
+
export * from "./components/collections/document-list.js";
|
|
141
|
+
export * from "./components/collections/result-blocks.js";
|
|
142
|
+
export * from "./patterns/run/run-summary.js";
|
|
143
|
+
export * from "./patterns/agent-workspace/index.js";
|
|
144
|
+
export * from "./patterns/call-console/index.js";
|
|
145
|
+
export * from "./foundations/status/index.js";
|
|
146
|
+
export * from "./patterns/run/progress.js";
|
package/dist/index.js
CHANGED
|
@@ -148,3 +148,13 @@ export * from "./patterns/task/index.js";
|
|
|
148
148
|
export * from "./patterns/governance/index.js";
|
|
149
149
|
export * from "./patterns/insights/index.js";
|
|
150
150
|
export * from "./element-maps.js";
|
|
151
|
+
export * from "./components/feedback/status-icon.js";
|
|
152
|
+
export * from "./components/collections/fact-list.js";
|
|
153
|
+
export * from "./components/collections/check-list.js";
|
|
154
|
+
export * from "./components/collections/document-list.js";
|
|
155
|
+
export * from "./components/collections/result-blocks.js";
|
|
156
|
+
export * from "./patterns/run/run-summary.js";
|
|
157
|
+
export * from "./patterns/agent-workspace/index.js";
|
|
158
|
+
export * from "./patterns/call-console/index.js";
|
|
159
|
+
export * from "./foundations/status/index.js";
|
|
160
|
+
export * from "./patterns/run/progress.js";
|
|
@@ -16,6 +16,15 @@ export declare class AgentChat extends BaseElement {
|
|
|
16
16
|
static readonly tagName: string;
|
|
17
17
|
static get observedAttributes(): string[];
|
|
18
18
|
private controller;
|
|
19
|
+
private readonly scope;
|
|
20
|
+
private composing;
|
|
21
|
+
private scrollPin?;
|
|
22
|
+
private resizeObserver?;
|
|
23
|
+
private messageCount;
|
|
24
|
+
private jumpEl;
|
|
25
|
+
focusComposer(): void;
|
|
26
|
+
proposalAnchor(messageId: string, proposalId: string): string;
|
|
27
|
+
focusProposal(messageId: string, proposalId: string): void;
|
|
19
28
|
private ownsController;
|
|
20
29
|
private pendingStart;
|
|
21
30
|
private unsubscribeFns;
|
|
@@ -3,6 +3,11 @@ import { isSafeHref } from "../internal/safe-href.js";
|
|
|
3
3
|
import { BaseElement } from "../../core/index.js";
|
|
4
4
|
import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
|
|
5
5
|
import { boePanel, boeRadius } from "../../foundations/geometry/index.js";
|
|
6
|
+
import { ScrollPinController, boeEntranceKeyframes, boeReducedMotionPolicy } from "../../foundations/motion/index.js";
|
|
7
|
+
import { boeStatusGlyph, boeStatusStyles } from "../../foundations/status/index.js";
|
|
8
|
+
import { ResultBlocks, resultDocumentIds } from "../../components/collections/result-blocks.js";
|
|
9
|
+
import { RunSummary } from "../run/run-summary.js";
|
|
10
|
+
let chatInstance = 0;
|
|
6
11
|
const DEFAULT_TAG_NAME = "box-agent-chat";
|
|
7
12
|
const escapeHtml = (value) => value
|
|
8
13
|
.replaceAll("&", "&")
|
|
@@ -365,9 +370,22 @@ const elementStyles = `
|
|
|
365
370
|
export class AgentChat extends BaseElement {
|
|
366
371
|
static tagName = DEFAULT_TAG_NAME;
|
|
367
372
|
static get observedAttributes() {
|
|
368
|
-
return ["agent-name", "heading", "placeholder", "token"];
|
|
373
|
+
return ["agent-name", "heading", "placeholder", "token", "hidden", "hide-citations"];
|
|
369
374
|
}
|
|
370
375
|
controller = null;
|
|
376
|
+
scope = `boe-chat-${++chatInstance}`;
|
|
377
|
+
composing = false;
|
|
378
|
+
scrollPin;
|
|
379
|
+
resizeObserver;
|
|
380
|
+
messageCount = 0;
|
|
381
|
+
jumpEl;
|
|
382
|
+
focusComposer() { this.inputEl?.focus({ preventScroll: true }); }
|
|
383
|
+
proposalAnchor(messageId, proposalId) { return `${this.scope}-${encodeURIComponent(messageId)}-${encodeURIComponent(proposalId)}`; }
|
|
384
|
+
focusProposal(messageId, proposalId) {
|
|
385
|
+
const card = this.shadowRoot?.getElementById(this.proposalAnchor(messageId, proposalId));
|
|
386
|
+
card?.scrollIntoView({ block: "nearest" });
|
|
387
|
+
card?.focus({ preventScroll: true });
|
|
388
|
+
}
|
|
371
389
|
ownsController = false;
|
|
372
390
|
pendingStart = false;
|
|
373
391
|
unsubscribeFns = [];
|
|
@@ -425,6 +443,8 @@ export class AgentChat extends BaseElement {
|
|
|
425
443
|
this.adoptController(value);
|
|
426
444
|
}
|
|
427
445
|
attributeChangedCallback(name, oldValue, newValue) {
|
|
446
|
+
if (name === "hidden" && newValue === null && this.isRendered)
|
|
447
|
+
this.focusComposer();
|
|
428
448
|
if (name === "token") {
|
|
429
449
|
this.scheduleStart();
|
|
430
450
|
}
|
|
@@ -442,10 +462,26 @@ export class AgentChat extends BaseElement {
|
|
|
442
462
|
}
|
|
443
463
|
connectedCallback() {
|
|
444
464
|
super.connectedCallback();
|
|
465
|
+
if (this.controller && !this.ownsController && this.unsubscribeFns.length === 0)
|
|
466
|
+
this.subscribeToController(this.controller);
|
|
467
|
+
this.scrollPin?.connect();
|
|
468
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
469
|
+
this.resizeObserver = new ResizeObserver(() => this.scrollPin?.contentChanged());
|
|
470
|
+
this.resizeObserver.observe(this.threadEl);
|
|
471
|
+
for (const child of Array.from(this.threadEl.children))
|
|
472
|
+
this.resizeObserver.observe(child);
|
|
473
|
+
}
|
|
445
474
|
this.scheduleStart();
|
|
446
475
|
}
|
|
447
476
|
disconnectedCallback() {
|
|
448
|
-
this.
|
|
477
|
+
this.scrollPin?.disconnect();
|
|
478
|
+
this.resizeObserver?.disconnect();
|
|
479
|
+
if (this.ownsController)
|
|
480
|
+
this.teardownController();
|
|
481
|
+
else {
|
|
482
|
+
this.unsubscribeFns.forEach(fn => fn());
|
|
483
|
+
this.unsubscribeFns = [];
|
|
484
|
+
}
|
|
449
485
|
}
|
|
450
486
|
/** Send the composer's contents (or an explicit body) as a user turn. */
|
|
451
487
|
async send(body) {
|
|
@@ -455,11 +491,13 @@ export class AgentChat extends BaseElement {
|
|
|
455
491
|
}
|
|
456
492
|
// Clear only once the controller has accepted the turn, so a refused
|
|
457
493
|
// send (no controller, or a disconnected session) never loses typing.
|
|
458
|
-
const
|
|
459
|
-
|
|
494
|
+
const accepted = this.controller?.getState().connected;
|
|
495
|
+
const sending = this.controller?.send(text);
|
|
496
|
+
if (accepted && body === undefined && this.inputEl) {
|
|
460
497
|
this.inputEl.value = "";
|
|
461
498
|
this.syncComposer();
|
|
462
499
|
}
|
|
500
|
+
await sending;
|
|
463
501
|
}
|
|
464
502
|
/** Stop the in-flight generation; the partial reply is kept. */
|
|
465
503
|
stop() {
|
|
@@ -558,6 +596,13 @@ export class AgentChat extends BaseElement {
|
|
|
558
596
|
const canResolve = Boolean(this.controller?.config.transport.resolveAction);
|
|
559
597
|
return message.proposals
|
|
560
598
|
.map(proposal => {
|
|
599
|
+
if (proposal.decision) {
|
|
600
|
+
const failed = proposal.outcome === "failed";
|
|
601
|
+
return `<div part="proposal" tabindex="-1" id="${this.proposalAnchor(message.id, proposal.id)}" data-proposal-id="${escapeHtml(proposal.id)}" data-decision="${escapeHtml(proposal.decision)}" ${failed ? 'role="alert"' : ""}>
|
|
602
|
+
<span part="decision">${boeStatusGlyph(failed ? "failed" : proposal.decision === "approved" ? "done" : "skipped")}${proposal.decision === "approved" ? failed ? "Approved · didn't complete" : "Approved" : "Rejected"} · <span part="proposal-title">${escapeHtml(proposal.title)}</span></span>
|
|
603
|
+
${proposal.note ? `<p part="proposal-note">${escapeHtml(proposal.note)}</p>` : ""}
|
|
604
|
+
${failed ? `<button part="retry" data-message-id="${escapeHtml(message.id)}">Try again</button>` : ""}</div>`;
|
|
605
|
+
}
|
|
561
606
|
const params = (proposal.params ?? [])
|
|
562
607
|
.map(param => `
|
|
563
608
|
<dt part="param-label">${escapeHtml(param.label)}</dt>
|
|
@@ -567,20 +612,22 @@ export class AgentChat extends BaseElement {
|
|
|
567
612
|
const actions = canResolve && !proposal.decision
|
|
568
613
|
? `
|
|
569
614
|
<div part="proposal-actions">
|
|
570
|
-
<button type="button" part="proposal-action" data-action="approve" data-proposal-id="${escapeHtml(proposal.id)}"
|
|
571
|
-
<button type="button" part="proposal-action" data-action="reject" data-proposal-id="${escapeHtml(proposal.id)}"
|
|
572
|
-
<button type="button" part="proposal-action" data-action="modify" data-proposal-id="${escapeHtml(proposal.id)}">Modify</button>
|
|
615
|
+
<button type="button" part="proposal-action" data-action="approve" data-proposal-id="${escapeHtml(proposal.id)}" ${proposal.resolving ? "disabled" : ""} aria-busy="${proposal.resolving === "approved"}">${proposal.resolving === "approved" ? boeStatusGlyph("active") : ""}Approve</button>
|
|
616
|
+
<button type="button" part="proposal-action" data-action="reject" data-proposal-id="${escapeHtml(proposal.id)}" ${proposal.resolving ? "disabled" : ""} aria-busy="${proposal.resolving === "rejected"}">${proposal.resolving === "rejected" ? boeStatusGlyph("active") : ""}Reject</button>
|
|
617
|
+
<button type="button" part="proposal-action" data-action="modify" data-proposal-id="${escapeHtml(proposal.id)}" ${proposal.resolving ? "disabled" : ""}>Modify</button>
|
|
573
618
|
</div>
|
|
574
619
|
`
|
|
575
620
|
: "";
|
|
576
621
|
return `
|
|
577
|
-
<div part="proposal"
|
|
622
|
+
<div part="proposal" tabindex="-1" id="${this.proposalAnchor(message.id, proposal.id)}" data-proposal-id="${escapeHtml(proposal.id)}">
|
|
623
|
+
<span>${boeStatusGlyph("pending")} Needs your approval</span>
|
|
578
624
|
<span part="proposal-title">${escapeHtml(proposal.title)}</span>
|
|
579
625
|
${proposal.summary ? `<p part="proposal-summary">${escapeHtml(proposal.summary)}</p>` : ""}
|
|
580
626
|
${params ? `<dl part="proposal-params">${params}</dl>` : ""}
|
|
581
627
|
${proposal.decision ? `<span part="decision">${proposal.decision === "approved" ? "Approved" : "Rejected"}</span>` : ""}
|
|
582
628
|
${proposal.note ? `<p part="proposal-note">${escapeHtml(proposal.note)}</p>` : ""}
|
|
583
629
|
${actions}
|
|
630
|
+
<p part="proposal-note">To change it, reply with the new values.</p>
|
|
584
631
|
</div>
|
|
585
632
|
`;
|
|
586
633
|
})
|
|
@@ -589,7 +636,8 @@ export class AgentChat extends BaseElement {
|
|
|
589
636
|
messageInnerHtml(message) {
|
|
590
637
|
const name = message.role === "user" ? "You" : (message.actor?.name ?? this.agentName);
|
|
591
638
|
const initials = message.actor?.initials ?? initialsOf(name);
|
|
592
|
-
const
|
|
639
|
+
const documentIds = resultDocumentIds(message.blocks ?? []);
|
|
640
|
+
const citations = (this.hasAttribute("hide-citations") ? [] : message.citations.filter(c => !documentIds.includes(c.id)))
|
|
593
641
|
.map(citation => this.citationHtml(message.id, citation))
|
|
594
642
|
.join("");
|
|
595
643
|
return `
|
|
@@ -597,10 +645,14 @@ export class AgentChat extends BaseElement {
|
|
|
597
645
|
<span part="avatar" aria-hidden="true">${escapeHtml(initials)}</span>
|
|
598
646
|
<span part="author">${escapeHtml(name)}</span>
|
|
599
647
|
</div>
|
|
600
|
-
|
|
648
|
+
${message.role === "agent" ? '<box-run-summary></box-run-summary>' : ""}
|
|
649
|
+
<p part="body">${escapeHtml(message.body)}${message.status === "streaming" && !message.blocks?.length ? `<span part="caret" aria-hidden="true"></span>` : ""}</p>
|
|
650
|
+
<box-result-blocks></box-result-blocks>
|
|
651
|
+
${message.completeness?.status === "incomplete" ? '<p part="incomplete" role="status">This reply may be incomplete.</p>' : ""}
|
|
601
652
|
${message.status === "error" && message.errorMessage ? `<p part="error" role="alert">${escapeHtml(message.errorMessage)}</p>` : ""}
|
|
602
653
|
${citations ? `<div part="citations">${citations}</div>` : ""}
|
|
603
654
|
${this.proposalHtml(message)}
|
|
655
|
+
${message.proposals.some(p => p.outcome === "failed") ? "" : (message.options ?? []).map((o, i) => `<button part="option" data-option="${i}">${escapeHtml(o.label)}</button>`).join("")}
|
|
604
656
|
`;
|
|
605
657
|
}
|
|
606
658
|
/**
|
|
@@ -620,7 +672,12 @@ export class AgentChat extends BaseElement {
|
|
|
620
672
|
},
|
|
621
673
|
agentName: this.agentName,
|
|
622
674
|
citations: message.citations.map(({ id, href, label }) => ({ id, href, label })),
|
|
623
|
-
|
|
675
|
+
hideCitations: this.hasAttribute("hide-citations"),
|
|
676
|
+
blocks: message.blocks,
|
|
677
|
+
completeness: message.completeness?.status,
|
|
678
|
+
options: message.options,
|
|
679
|
+
proposals: message.proposals.map(({ id, title, summary, params, decision, note, outcome, resolving }) => ({
|
|
680
|
+
outcome, resolving,
|
|
624
681
|
id,
|
|
625
682
|
title,
|
|
626
683
|
summary,
|
|
@@ -656,21 +713,38 @@ export class AgentChat extends BaseElement {
|
|
|
656
713
|
body.insertBefore(document.createTextNode(message.body), body.firstChild);
|
|
657
714
|
}
|
|
658
715
|
renderTemplate() {
|
|
716
|
+
ResultBlocks.register();
|
|
717
|
+
RunSummary.register();
|
|
659
718
|
if (!this.shadowRoot) {
|
|
660
719
|
return;
|
|
661
720
|
}
|
|
662
721
|
// The composer is part of the stable shell: a streaming reply patches
|
|
663
722
|
// only [part="thread"], so typing is never interrupted.
|
|
664
723
|
this.shadowRoot.innerHTML = `
|
|
665
|
-
<style>${elementStyles}
|
|
724
|
+
<style>${elementStyles}
|
|
725
|
+
[part="message"][data-role="agent"]{border:0;background:transparent;padding-inline:0;max-width:75ch;min-width:0}
|
|
726
|
+
[part="message"][data-role="user"]{background:var(--boe-token-surface-surface-secondary,#f4f4f4)}
|
|
727
|
+
[part="proposal"]{border:1px solid var(--boe-token-stroke-stroke,#ddd);background:var(--boe-token-surface-surface,#fff);box-shadow:var(--boe-profile-shadow-overlay,0 3px 14px #0001)}
|
|
728
|
+
[part="proposal"][data-decision]{border:0;background:transparent;box-shadow:none;padding-inline:0}
|
|
729
|
+
[part="decision"]{background:none;border-radius:0;padding:0;gap:.4rem;font:inherit}
|
|
730
|
+
[part="proposal-action"][data-action="approve"],[part="proposal-action"][aria-busy="true"]{background:var(--boe-token-surface-surface-brand,#0061d5);color:var(--boe-token-text-text-on-brand,#fff);border-color:transparent}
|
|
731
|
+
[part="proposal-action"]:disabled:not([aria-busy="true"]){opacity:.5}
|
|
732
|
+
[part="composer"]{position:relative;border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:12px;padding:.4rem}
|
|
733
|
+
[part="input"]{border:0;resize:none;max-height:180px;padding-right:5rem;box-sizing:border-box}
|
|
734
|
+
[part="composer-actions"]{position:absolute;right:.5rem;bottom:.5rem}
|
|
735
|
+
[part="jump"]{justify-self:center;border:1px solid var(--boe-token-stroke-stroke,#ddd);border-radius:999px;padding:.4rem .8rem;background:var(--boe-token-surface-surface,#fff);color:inherit;cursor:pointer}
|
|
736
|
+
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
|
|
737
|
+
${boeEntranceKeyframes}${boeStatusStyles}${boeReducedMotionPolicy}</style>
|
|
666
738
|
<section part="panel">
|
|
667
739
|
<div part="header">
|
|
668
740
|
<h2 part="title"></h2>
|
|
669
741
|
<span part="status" role="status" aria-live="polite"></span>
|
|
670
742
|
</div>
|
|
671
|
-
<ol part="thread" role="log" aria-live="polite" aria-relevant="additions"></ol>
|
|
743
|
+
<ol part="thread" role="log" aria-live="polite" aria-relevant="additions" tabindex="0"></ol>
|
|
744
|
+
<button part="jump" hidden>Jump to latest</button>
|
|
672
745
|
<div part="composer">
|
|
673
|
-
<
|
|
746
|
+
<label class="sr-only" id="${this.scope}-label">Message</label>
|
|
747
|
+
<textarea part="input" rows="2" aria-labelledby="${this.scope}-label"></textarea>
|
|
674
748
|
<div part="composer-actions">
|
|
675
749
|
<button type="button" part="stop" hidden>Stop</button>
|
|
676
750
|
<button type="button" part="send">Send</button>
|
|
@@ -685,6 +759,11 @@ export class AgentChat extends BaseElement {
|
|
|
685
759
|
this.statusEl = this.shadowRoot.querySelector('[part="status"]');
|
|
686
760
|
}
|
|
687
761
|
setupListeners() {
|
|
762
|
+
this.jumpEl = this.shadowRoot.querySelector('[part="jump"]');
|
|
763
|
+
this.scrollPin = new ScrollPinController(this.threadEl, show => { this.jumpEl.hidden = !show; });
|
|
764
|
+
this.jumpEl.addEventListener("click", () => this.scrollPin?.jump());
|
|
765
|
+
this.inputEl.addEventListener("compositionstart", () => { this.composing = true; });
|
|
766
|
+
this.inputEl.addEventListener("compositionend", () => { this.composing = false; });
|
|
688
767
|
this.sendEl.addEventListener("click", () => {
|
|
689
768
|
void this.send();
|
|
690
769
|
});
|
|
@@ -693,7 +772,7 @@ export class AgentChat extends BaseElement {
|
|
|
693
772
|
});
|
|
694
773
|
this.inputEl.addEventListener("keydown", event => {
|
|
695
774
|
// Enter sends, Shift+Enter makes a newline — the conversational default.
|
|
696
|
-
if (event.key === "Enter" && !event.shiftKey) {
|
|
775
|
+
if (event.key === "Enter" && !event.shiftKey && !event.isComposing && !this.composing && event.keyCode !== 229) {
|
|
697
776
|
event.preventDefault();
|
|
698
777
|
void this.send();
|
|
699
778
|
}
|
|
@@ -703,15 +782,31 @@ export class AgentChat extends BaseElement {
|
|
|
703
782
|
});
|
|
704
783
|
this.threadEl.addEventListener("click", event => {
|
|
705
784
|
const target = event.target;
|
|
785
|
+
const messageId = target.closest('[part="message"]')?.getAttribute("data-message-id") ?? "";
|
|
786
|
+
const option = target.closest("[data-option]");
|
|
787
|
+
if (option) {
|
|
788
|
+
const prompt = this.controller?.getMessage(messageId)?.options?.[Number(option.dataset.option)]?.prompt;
|
|
789
|
+
if (prompt)
|
|
790
|
+
void this.send(prompt);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (target.closest('[part="retry"]')) {
|
|
794
|
+
const messages = this.controller?.getState().messages ?? [];
|
|
795
|
+
const index = messages.findIndex(m => m.id === messageId);
|
|
796
|
+
const request = messages.slice(0, index).reverse().find(m => m.role === "user");
|
|
797
|
+
if (request)
|
|
798
|
+
void this.send(request.body);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
706
801
|
const proposalButton = target.closest('[part="proposal-action"]');
|
|
707
802
|
if (proposalButton && this.threadEl.contains(proposalButton)) {
|
|
708
803
|
const proposalId = proposalButton.getAttribute("data-proposal-id") ?? "";
|
|
709
804
|
const action = proposalButton.getAttribute("data-action") ?? "";
|
|
710
805
|
if (action === "approve") {
|
|
711
|
-
void this.controller?.resolveAction(proposalId, "approved");
|
|
806
|
+
void this.controller?.resolveAction(proposalId, "approved", undefined, messageId);
|
|
712
807
|
}
|
|
713
808
|
else if (action === "reject") {
|
|
714
|
-
void this.controller?.resolveAction(proposalId, "rejected");
|
|
809
|
+
void this.controller?.resolveAction(proposalId, "rejected", undefined, messageId);
|
|
715
810
|
}
|
|
716
811
|
else if (action === "modify") {
|
|
717
812
|
// Modifying needs the host's own editor, so this surfaces intent.
|
|
@@ -744,6 +839,9 @@ export class AgentChat extends BaseElement {
|
|
|
744
839
|
const streaming = this.controller?.getState().streaming ?? false;
|
|
745
840
|
this.inputEl.placeholder = this.placeholder;
|
|
746
841
|
this.sendEl.disabled = streaming || this.inputEl.value.trim().length === 0;
|
|
842
|
+
this.sendEl.hidden = streaming;
|
|
843
|
+
this.inputEl.style.height = "auto";
|
|
844
|
+
this.inputEl.style.height = `${Math.min(180, Math.max(56, this.inputEl.scrollHeight))}px`;
|
|
747
845
|
this.stopEl.hidden = !streaming;
|
|
748
846
|
this.statusEl.textContent = streaming ? "Agent is responding…" : "";
|
|
749
847
|
}
|
|
@@ -762,7 +860,8 @@ export class AgentChat extends BaseElement {
|
|
|
762
860
|
: null;
|
|
763
861
|
const state = this.controller?.getState() ?? null;
|
|
764
862
|
const messages = state?.messages ?? [];
|
|
765
|
-
const
|
|
863
|
+
const newMessage = messages.length > this.messageCount;
|
|
864
|
+
this.messageCount = messages.length;
|
|
766
865
|
this.shadowRoot.querySelector('[part="title"]').textContent = this.heading;
|
|
767
866
|
if (messages.length === 0) {
|
|
768
867
|
if (!this.threadEl.querySelector('[part="empty"]')) {
|
|
@@ -793,7 +892,13 @@ export class AgentChat extends BaseElement {
|
|
|
793
892
|
else {
|
|
794
893
|
node.setAttribute("data-signature", signature);
|
|
795
894
|
node.setAttribute("data-status", message.status);
|
|
895
|
+
const summary = node.querySelector("box-run-summary");
|
|
896
|
+
const results = node.querySelector("box-result-blocks");
|
|
796
897
|
node.innerHTML = this.messageInnerHtml(message);
|
|
898
|
+
if (summary)
|
|
899
|
+
node.querySelector("box-run-summary")?.replaceWith(summary);
|
|
900
|
+
if (results)
|
|
901
|
+
node.querySelector("box-result-blocks")?.replaceWith(results);
|
|
797
902
|
}
|
|
798
903
|
}
|
|
799
904
|
else {
|
|
@@ -806,8 +911,19 @@ export class AgentChat extends BaseElement {
|
|
|
806
911
|
this.threadEl.insertBefore(node, anchor);
|
|
807
912
|
}
|
|
808
913
|
previous = node;
|
|
914
|
+
this.resizeObserver?.observe(node);
|
|
915
|
+
const blocks = node.querySelector("box-result-blocks");
|
|
916
|
+
if (blocks && blocks.blocks !== message.blocks)
|
|
917
|
+
blocks.blocks = message.blocks ?? [];
|
|
918
|
+
const summary = node.querySelector("box-run-summary");
|
|
919
|
+
if (summary) {
|
|
920
|
+
summary.toggleAttribute("failed", message.status === "error");
|
|
921
|
+
summary.turn = { steps: message.trace ?? [], todos: message.todos ?? [], startedAt: message.startedAt ?? 0,
|
|
922
|
+
...(message.endedAt !== undefined ? { endedAt: message.endedAt } : {}), incomplete: message.completeness?.status === "incomplete" };
|
|
923
|
+
}
|
|
809
924
|
}
|
|
810
925
|
for (const stale of existing.values()) {
|
|
926
|
+
this.resizeObserver?.unobserve(stale);
|
|
811
927
|
stale.remove();
|
|
812
928
|
}
|
|
813
929
|
if (focusKey?.part) {
|
|
@@ -817,9 +933,7 @@ export class AgentChat extends BaseElement {
|
|
|
817
933
|
target?.focus();
|
|
818
934
|
}
|
|
819
935
|
// Follow the stream only when the reader is already at the bottom.
|
|
820
|
-
|
|
821
|
-
this.threadEl.scrollTop = this.threadEl.scrollHeight;
|
|
822
|
-
}
|
|
936
|
+
this.scrollPin?.contentChanged(newMessage);
|
|
823
937
|
this.syncComposer();
|
|
824
938
|
}
|
|
825
939
|
}
|
|
@@ -11,6 +11,8 @@ export declare class AgentChatController extends Controller<AgentChatState, Agen
|
|
|
11
11
|
readonly config: AgentChatSessionConfig;
|
|
12
12
|
private sendCounter;
|
|
13
13
|
private activeAbortController;
|
|
14
|
+
private finishActive?;
|
|
15
|
+
private readonly resolving;
|
|
14
16
|
constructor(config: AgentChatSessionConfig);
|
|
15
17
|
connect(): void;
|
|
16
18
|
disconnect(): void;
|
|
@@ -23,5 +25,5 @@ export declare class AgentChatController extends Controller<AgentChatState, Agen
|
|
|
23
25
|
*/
|
|
24
26
|
send(body: string): Promise<AgentChatMessage | null>;
|
|
25
27
|
/** Resolve a HITL proposal through the transport capability. */
|
|
26
|
-
resolveAction(proposalId: string, decision: AgentActionDecision, note?: string): Promise<AgentActionProposal | null>;
|
|
28
|
+
resolveAction(proposalId: string, decision: AgentActionDecision, note?: string, messageId?: string): Promise<AgentActionProposal | null>;
|
|
27
29
|
}
|