@runtypelabs/persona 3.9.2 → 3.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +45 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +148 -0
- package/dist/index.d.ts +148 -0
- package/dist/index.global.js +67 -64
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +45 -42
- package/dist/index.js.map +1 -1
- package/dist/theme-editor.cjs +959 -214
- package/dist/theme-editor.d.cts +157 -3
- package/dist/theme-editor.d.ts +157 -3
- package/dist/theme-editor.js +955 -214
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +8 -0
- package/dist/theme-reference.d.ts +8 -0
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +154 -0
- package/package.json +1 -1
- package/src/client.test.ts +312 -1
- package/src/client.ts +247 -24
- package/src/components/messages.ts +1 -1
- package/src/components/reasoning-bubble.ts +117 -28
- package/src/components/tool-bubble.ts +161 -27
- package/src/defaults.ts +12 -0
- package/src/styles/widget.css +154 -0
- package/src/theme-editor/index.ts +5 -0
- package/src/theme-editor/preview-utils.test.ts +58 -0
- package/src/theme-editor/preview-utils.ts +220 -4
- package/src/theme-editor/sections.test.ts +20 -0
- package/src/theme-editor/sections.ts +10 -0
- package/src/theme-reference.ts +8 -3
- package/src/tool-call-display-defaults.test.ts +23 -0
- package/src/types.ts +155 -0
- package/src/ui.attachments-drop.test.ts +188 -0
- package/src/ui.scroll.test.ts +150 -0
- package/src/ui.tool-display.test.ts +204 -0
- package/src/ui.ts +275 -7
- package/src/utils/message-fingerprint.test.ts +17 -0
- package/src/utils/message-fingerprint.ts +13 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { createAgentExperience } from "./ui";
|
|
6
|
+
import { AttachmentManager } from "./utils/attachment-manager";
|
|
7
|
+
|
|
8
|
+
const createMount = () => {
|
|
9
|
+
const mount = document.createElement("div");
|
|
10
|
+
document.body.appendChild(mount);
|
|
11
|
+
return mount;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** jsdom does not expose `DataTransfer`; real browsers set `dropEffect` on dragover. */
|
|
15
|
+
function createFileDataTransfer(files: File[]): DataTransfer {
|
|
16
|
+
const list: File[] = [...files];
|
|
17
|
+
const fileList = list as unknown as FileList;
|
|
18
|
+
return {
|
|
19
|
+
dropEffect: "none",
|
|
20
|
+
effectAllowed: "all",
|
|
21
|
+
files: fileList,
|
|
22
|
+
items: {
|
|
23
|
+
add: () => {},
|
|
24
|
+
clear: () => {},
|
|
25
|
+
remove: () => {}
|
|
26
|
+
} as unknown as DataTransferItemList,
|
|
27
|
+
types: files.length > 0 ? ["Files"] : [],
|
|
28
|
+
clearData: () => {},
|
|
29
|
+
getData: () => "",
|
|
30
|
+
setData: () => {},
|
|
31
|
+
setDragImage: () => {}
|
|
32
|
+
} as unknown as DataTransfer;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function createDragEvent(type: string, dataTransfer: DataTransfer): DragEvent {
|
|
36
|
+
const ev = new Event(type, { bubbles: true, cancelable: true }) as unknown as DragEvent;
|
|
37
|
+
Object.defineProperty(ev, "dataTransfer", { value: dataTransfer, enumerable: true });
|
|
38
|
+
return ev;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("createAgentExperience attachment file drop", () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.stubGlobal("requestAnimationFrame", (cb: (time: number) => void) => {
|
|
44
|
+
cb(0);
|
|
45
|
+
return 1;
|
|
46
|
+
});
|
|
47
|
+
vi.stubGlobal("cancelAnimationFrame", () => {});
|
|
48
|
+
window.scrollTo = vi.fn();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
document.body.innerHTML = "";
|
|
53
|
+
vi.restoreAllMocks();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("calls AttachmentManager.handleFiles when files are dropped on the mount", () => {
|
|
57
|
+
const handleFilesSpy = vi.spyOn(AttachmentManager.prototype, "handleFiles");
|
|
58
|
+
|
|
59
|
+
const mount = createMount();
|
|
60
|
+
const controller = createAgentExperience(mount, {
|
|
61
|
+
apiUrl: "https://api.example.com/chat",
|
|
62
|
+
launcher: { enabled: false },
|
|
63
|
+
attachments: { enabled: true, maxFiles: 4 },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const file = new File(["x"], "test.png", { type: "image/png" });
|
|
67
|
+
const dt = createFileDataTransfer([file]);
|
|
68
|
+
|
|
69
|
+
// dragover/drop are on mount so the browser default is suppressed everywhere
|
|
70
|
+
const dragOver = createDragEvent("dragover", dt);
|
|
71
|
+
mount.dispatchEvent(dragOver);
|
|
72
|
+
expect(dragOver.defaultPrevented).toBe(true);
|
|
73
|
+
expect(dt.dropEffect).toBe("copy");
|
|
74
|
+
|
|
75
|
+
const drop = createDragEvent("drop", dt);
|
|
76
|
+
mount.dispatchEvent(drop);
|
|
77
|
+
expect(drop.defaultPrevented).toBe(true);
|
|
78
|
+
|
|
79
|
+
expect(handleFilesSpy).toHaveBeenCalledTimes(1);
|
|
80
|
+
const passed = handleFilesSpy.mock.calls[0]?.[0] as File[];
|
|
81
|
+
expect(passed).toHaveLength(1);
|
|
82
|
+
expect(passed[0]?.name).toBe("test.png");
|
|
83
|
+
|
|
84
|
+
handleFilesSpy.mockRestore();
|
|
85
|
+
controller.destroy();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("shows drop-active highlight on container during dragenter", () => {
|
|
89
|
+
const mount = createMount();
|
|
90
|
+
const controller = createAgentExperience(mount, {
|
|
91
|
+
apiUrl: "https://api.example.com/chat",
|
|
92
|
+
launcher: { enabled: false },
|
|
93
|
+
attachments: { enabled: true, maxFiles: 4 },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const container = mount.querySelector(".persona-widget-container")!;
|
|
97
|
+
const file = new File(["x"], "test.png", { type: "image/png" });
|
|
98
|
+
const dt = createFileDataTransfer([file]);
|
|
99
|
+
|
|
100
|
+
container.dispatchEvent(createDragEvent("dragenter", dt));
|
|
101
|
+
expect(container.classList.contains("persona-attachment-drop-active")).toBe(true);
|
|
102
|
+
|
|
103
|
+
container.dispatchEvent(createDragEvent("dragleave", dt));
|
|
104
|
+
expect(container.classList.contains("persona-attachment-drop-active")).toBe(false);
|
|
105
|
+
|
|
106
|
+
controller.destroy();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("renders drop overlay with icon inside container", () => {
|
|
110
|
+
const mount = createMount();
|
|
111
|
+
const controller = createAgentExperience(mount, {
|
|
112
|
+
apiUrl: "https://api.example.com/chat",
|
|
113
|
+
launcher: { enabled: false },
|
|
114
|
+
attachments: { enabled: true, maxFiles: 4 },
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const overlay = mount.querySelector(".persona-attachment-drop-overlay");
|
|
118
|
+
expect(overlay).not.toBeNull();
|
|
119
|
+
expect(overlay!.querySelector("svg")).not.toBeNull();
|
|
120
|
+
|
|
121
|
+
controller.destroy();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("applies custom dropOverlay config as CSS variables", () => {
|
|
125
|
+
const mount = createMount();
|
|
126
|
+
const controller = createAgentExperience(mount, {
|
|
127
|
+
apiUrl: "https://api.example.com/chat",
|
|
128
|
+
launcher: { enabled: false },
|
|
129
|
+
attachments: {
|
|
130
|
+
enabled: true,
|
|
131
|
+
maxFiles: 4,
|
|
132
|
+
dropOverlay: {
|
|
133
|
+
background: "rgba(255, 0, 0, 0.1)",
|
|
134
|
+
backdropBlur: "12px",
|
|
135
|
+
border: "2px solid red",
|
|
136
|
+
inset: "8px",
|
|
137
|
+
iconName: "image-plus",
|
|
138
|
+
label: "Drop here",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const overlay = mount.querySelector<HTMLElement>(".persona-attachment-drop-overlay")!;
|
|
144
|
+
expect(overlay).not.toBeNull();
|
|
145
|
+
expect(overlay.style.getPropertyValue("--persona-drop-overlay-bg")).toBe("rgba(255, 0, 0, 0.1)");
|
|
146
|
+
expect(overlay.style.getPropertyValue("--persona-drop-overlay-blur")).toBe("12px");
|
|
147
|
+
expect(overlay.style.getPropertyValue("--persona-drop-overlay-border")).toBe("2px solid red");
|
|
148
|
+
expect(overlay.style.getPropertyValue("--persona-drop-overlay-inset")).toBe("8px");
|
|
149
|
+
|
|
150
|
+
const label = overlay.querySelector(".persona-drop-overlay-label");
|
|
151
|
+
expect(label).not.toBeNull();
|
|
152
|
+
expect(label!.textContent).toBe("Drop here");
|
|
153
|
+
|
|
154
|
+
controller.destroy();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("does not render drop overlay when attachments are disabled", () => {
|
|
158
|
+
const mount = createMount();
|
|
159
|
+
const controller = createAgentExperience(mount, {
|
|
160
|
+
apiUrl: "https://api.example.com/chat",
|
|
161
|
+
launcher: { enabled: false },
|
|
162
|
+
attachments: { enabled: false },
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const overlay = mount.querySelector(".persona-attachment-drop-overlay");
|
|
166
|
+
expect(overlay).toBeNull();
|
|
167
|
+
|
|
168
|
+
controller.destroy();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("does not prevent dragover when attachments are disabled", () => {
|
|
172
|
+
const mount = createMount();
|
|
173
|
+
const controller = createAgentExperience(mount, {
|
|
174
|
+
apiUrl: "https://api.example.com/chat",
|
|
175
|
+
launcher: { enabled: false },
|
|
176
|
+
attachments: { enabled: false },
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const file = new File(["x"], "test.png", { type: "image/png" });
|
|
180
|
+
const dt = createFileDataTransfer([file]);
|
|
181
|
+
|
|
182
|
+
const dragOver = createDragEvent("dragover", dt);
|
|
183
|
+
mount.dispatchEvent(dragOver);
|
|
184
|
+
expect(dragOver.defaultPrevented).toBe(false);
|
|
185
|
+
|
|
186
|
+
controller.destroy();
|
|
187
|
+
});
|
|
188
|
+
});
|
package/src/ui.scroll.test.ts
CHANGED
|
@@ -135,6 +135,36 @@ const emitReasoningMessage = (
|
|
|
135
135
|
});
|
|
136
136
|
};
|
|
137
137
|
|
|
138
|
+
const emitToolMessage = (
|
|
139
|
+
controller: ReturnType<typeof createAgentExperience>,
|
|
140
|
+
{
|
|
141
|
+
id = STREAM_MESSAGE_ID,
|
|
142
|
+
status = "running",
|
|
143
|
+
chunks,
|
|
144
|
+
}: {
|
|
145
|
+
id?: string;
|
|
146
|
+
status?: "pending" | "running" | "complete";
|
|
147
|
+
chunks: string[];
|
|
148
|
+
}
|
|
149
|
+
) => {
|
|
150
|
+
controller.injectTestMessage({
|
|
151
|
+
type: "message",
|
|
152
|
+
message: {
|
|
153
|
+
id,
|
|
154
|
+
role: "assistant",
|
|
155
|
+
content: "",
|
|
156
|
+
createdAt: STREAM_CREATED_AT,
|
|
157
|
+
streaming: status !== "complete",
|
|
158
|
+
variant: "tool",
|
|
159
|
+
toolCall: {
|
|
160
|
+
id,
|
|
161
|
+
status,
|
|
162
|
+
chunks,
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
138
168
|
const createCustomComposer = () => {
|
|
139
169
|
const footer = document.createElement("div");
|
|
140
170
|
footer.className = "persona-widget-footer";
|
|
@@ -368,6 +398,126 @@ describe("createAgentExperience streaming scroll", () => {
|
|
|
368
398
|
controller.destroy();
|
|
369
399
|
});
|
|
370
400
|
|
|
401
|
+
it("keeps following collapsed tool preview updates while active", () => {
|
|
402
|
+
const raf = installRafMock();
|
|
403
|
+
const mount = createMount();
|
|
404
|
+
const controller = createAgentExperience(mount, {
|
|
405
|
+
apiUrl: "https://api.example.com/chat",
|
|
406
|
+
launcher: { enabled: false },
|
|
407
|
+
features: {
|
|
408
|
+
toolCallDisplay: {
|
|
409
|
+
activePreview: true,
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
} as any);
|
|
413
|
+
|
|
414
|
+
const scrollContainer = mount.querySelector<HTMLElement>("#persona-scroll-container");
|
|
415
|
+
expect(scrollContainer).not.toBeNull();
|
|
416
|
+
|
|
417
|
+
const metrics = installScrollMetrics(scrollContainer!, {
|
|
418
|
+
scrollHeight: 980,
|
|
419
|
+
clientHeight: 400
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
emitStreamingStatus(controller);
|
|
423
|
+
emitToolMessage(controller, { chunks: ["Loaded tools"] });
|
|
424
|
+
raf.flush();
|
|
425
|
+
|
|
426
|
+
expect(metrics.getScrollTop()).toBe(metrics.getBottomScrollTop());
|
|
427
|
+
|
|
428
|
+
metrics.setScrollHeight(1045);
|
|
429
|
+
emitToolMessage(controller, {
|
|
430
|
+
chunks: ["Loaded tools", "\nFetched platform documentation"]
|
|
431
|
+
});
|
|
432
|
+
raf.flush();
|
|
433
|
+
|
|
434
|
+
expect(metrics.getScrollTop()).toBe(metrics.getBottomScrollTop());
|
|
435
|
+
|
|
436
|
+
controller.destroy();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("keeps following grouped tool sequences as new tool rows arrive", () => {
|
|
440
|
+
const raf = installRafMock();
|
|
441
|
+
const mount = createMount();
|
|
442
|
+
const controller = createAgentExperience(mount, {
|
|
443
|
+
apiUrl: "https://api.example.com/chat",
|
|
444
|
+
launcher: { enabled: false },
|
|
445
|
+
features: {
|
|
446
|
+
toolCallDisplay: {
|
|
447
|
+
grouped: true,
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
} as any);
|
|
451
|
+
|
|
452
|
+
const scrollContainer = mount.querySelector<HTMLElement>("#persona-scroll-container");
|
|
453
|
+
expect(scrollContainer).not.toBeNull();
|
|
454
|
+
|
|
455
|
+
const metrics = installScrollMetrics(scrollContainer!, {
|
|
456
|
+
scrollHeight: 960,
|
|
457
|
+
clientHeight: 400
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
emitStreamingStatus(controller);
|
|
461
|
+
emitToolMessage(controller, { id: "tool-1", chunks: ["Loaded tools"] });
|
|
462
|
+
raf.flush();
|
|
463
|
+
|
|
464
|
+
expect(metrics.getScrollTop()).toBe(metrics.getBottomScrollTop());
|
|
465
|
+
|
|
466
|
+
metrics.setScrollHeight(1030);
|
|
467
|
+
emitToolMessage(controller, { id: "tool-2", chunks: ["Fetched platform documentation"] });
|
|
468
|
+
raf.flush();
|
|
469
|
+
|
|
470
|
+
expect(metrics.getScrollTop()).toBe(metrics.getBottomScrollTop());
|
|
471
|
+
|
|
472
|
+
controller.destroy();
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("ignores layout-driven scroll events before a scheduled auto-scroll starts", () => {
|
|
476
|
+
const raf = installRafMock();
|
|
477
|
+
const mount = createMount();
|
|
478
|
+
const controller = createAgentExperience(mount, {
|
|
479
|
+
apiUrl: "https://api.example.com/chat",
|
|
480
|
+
launcher: { enabled: false },
|
|
481
|
+
features: {
|
|
482
|
+
toolCallDisplay: {
|
|
483
|
+
activePreview: true,
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
} as any);
|
|
487
|
+
|
|
488
|
+
const scrollContainer = mount.querySelector<HTMLElement>("#persona-scroll-container");
|
|
489
|
+
expect(scrollContainer).not.toBeNull();
|
|
490
|
+
|
|
491
|
+
const metrics = installScrollMetrics(scrollContainer!, {
|
|
492
|
+
scrollHeight: 960,
|
|
493
|
+
clientHeight: 400,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
emitStreamingStatus(controller);
|
|
497
|
+
emitToolMessage(controller, { id: "tool-1", chunks: ["Loaded tools"] });
|
|
498
|
+
emitToolMessage(controller, { id: "tool-2", chunks: ["Fetched docs"] });
|
|
499
|
+
raf.flush();
|
|
500
|
+
|
|
501
|
+
expect(metrics.getScrollTop()).toBe(metrics.getBottomScrollTop());
|
|
502
|
+
|
|
503
|
+
metrics.setScrollHeight(1035);
|
|
504
|
+
emitToolMessage(controller, {
|
|
505
|
+
id: "tool-3",
|
|
506
|
+
chunks: ["Compared layouts and noted launcher sizing"],
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// Simulate the browser emitting a scroll event caused by layout/scroll
|
|
510
|
+
// anchoring before the scheduled auto-scroll rAF has started.
|
|
511
|
+
metrics.setScrollTop(metrics.getScrollTop() - 2);
|
|
512
|
+
scrollContainer!.dispatchEvent(new Event("scroll"));
|
|
513
|
+
|
|
514
|
+
raf.flush();
|
|
515
|
+
|
|
516
|
+
expect(metrics.getScrollTop()).toBe(metrics.getBottomScrollTop());
|
|
517
|
+
|
|
518
|
+
controller.destroy();
|
|
519
|
+
});
|
|
520
|
+
|
|
371
521
|
it("uses icon-only arrow-down defaults for the transcript affordance", () => {
|
|
372
522
|
const raf = installRafMock();
|
|
373
523
|
const mount = createMount();
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { createAgentExperience } from "./ui";
|
|
6
|
+
import type { AgentWidgetController } from "./ui";
|
|
7
|
+
|
|
8
|
+
const createMount = () => {
|
|
9
|
+
const mount = document.createElement("div");
|
|
10
|
+
document.body.appendChild(mount);
|
|
11
|
+
return mount;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const injectToolMessage = (
|
|
15
|
+
controller: AgentWidgetController,
|
|
16
|
+
{
|
|
17
|
+
id,
|
|
18
|
+
name,
|
|
19
|
+
status = "running",
|
|
20
|
+
chunks = [],
|
|
21
|
+
}: {
|
|
22
|
+
id: string;
|
|
23
|
+
name?: string;
|
|
24
|
+
status?: "pending" | "running" | "complete";
|
|
25
|
+
chunks?: string[];
|
|
26
|
+
}
|
|
27
|
+
) => {
|
|
28
|
+
controller.injectTestMessage({
|
|
29
|
+
type: "message",
|
|
30
|
+
message: {
|
|
31
|
+
id,
|
|
32
|
+
role: "assistant",
|
|
33
|
+
content: "",
|
|
34
|
+
createdAt: new Date().toISOString(),
|
|
35
|
+
streaming: status !== "complete",
|
|
36
|
+
variant: "tool",
|
|
37
|
+
toolCall: {
|
|
38
|
+
id,
|
|
39
|
+
name,
|
|
40
|
+
status,
|
|
41
|
+
chunks,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const injectReasoningMessage = (
|
|
48
|
+
controller: AgentWidgetController,
|
|
49
|
+
{
|
|
50
|
+
id,
|
|
51
|
+
status = "streaming",
|
|
52
|
+
chunks = [],
|
|
53
|
+
}: {
|
|
54
|
+
id: string;
|
|
55
|
+
status?: "pending" | "streaming" | "complete";
|
|
56
|
+
chunks?: string[];
|
|
57
|
+
}
|
|
58
|
+
) => {
|
|
59
|
+
controller.injectTestMessage({
|
|
60
|
+
type: "message",
|
|
61
|
+
message: {
|
|
62
|
+
id,
|
|
63
|
+
role: "assistant",
|
|
64
|
+
content: "",
|
|
65
|
+
createdAt: new Date().toISOString(),
|
|
66
|
+
streaming: status !== "complete",
|
|
67
|
+
variant: "reasoning",
|
|
68
|
+
reasoning: {
|
|
69
|
+
id,
|
|
70
|
+
status,
|
|
71
|
+
chunks,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
describe("createAgentExperience tool call display modes", () => {
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
vi.stubGlobal("requestAnimationFrame", (cb: (time: number) => void) => {
|
|
80
|
+
cb(0);
|
|
81
|
+
return 1;
|
|
82
|
+
});
|
|
83
|
+
vi.stubGlobal("cancelAnimationFrame", () => {});
|
|
84
|
+
window.scrollTo = vi.fn();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
document.body.innerHTML = "";
|
|
89
|
+
vi.restoreAllMocks();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("keeps collapsed tool rows on the generic summary by default", () => {
|
|
93
|
+
const mount = createMount();
|
|
94
|
+
const controller = createAgentExperience(mount, {
|
|
95
|
+
apiUrl: "https://api.example.com/chat",
|
|
96
|
+
launcher: { enabled: false },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
injectToolMessage(controller, {
|
|
100
|
+
id: "tool-1",
|
|
101
|
+
name: "Get platform documentation",
|
|
102
|
+
chunks: ["Loaded tools, used Runtype integration"],
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const header = mount.querySelector(".persona-tool-bubble button[data-expand-header='true']");
|
|
106
|
+
expect(header?.textContent).toContain("Using tool...");
|
|
107
|
+
expect(header?.textContent).not.toContain("Get platform documentation");
|
|
108
|
+
|
|
109
|
+
controller.destroy();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("shows the tool name in collapsed rows when configured", () => {
|
|
113
|
+
const mount = createMount();
|
|
114
|
+
const controller = createAgentExperience(mount, {
|
|
115
|
+
apiUrl: "https://api.example.com/chat",
|
|
116
|
+
launcher: { enabled: false },
|
|
117
|
+
features: {
|
|
118
|
+
toolCallDisplay: {
|
|
119
|
+
collapsedMode: "tool-name",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
} as any);
|
|
123
|
+
|
|
124
|
+
injectToolMessage(controller, {
|
|
125
|
+
id: "tool-1",
|
|
126
|
+
name: "Get platform documentation",
|
|
127
|
+
chunks: ["Loaded tools, used Runtype integration"],
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const header = mount.querySelector(".persona-tool-bubble button[data-expand-header='true']");
|
|
131
|
+
expect(header?.textContent).toContain("Get platform documentation");
|
|
132
|
+
|
|
133
|
+
controller.destroy();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("renders a collapsed preview for active tool rows when enabled", () => {
|
|
137
|
+
const mount = createMount();
|
|
138
|
+
const controller = createAgentExperience(mount, {
|
|
139
|
+
apiUrl: "https://api.example.com/chat",
|
|
140
|
+
launcher: { enabled: false },
|
|
141
|
+
features: {
|
|
142
|
+
toolCallDisplay: {
|
|
143
|
+
activePreview: true,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
} as any);
|
|
147
|
+
|
|
148
|
+
injectToolMessage(controller, {
|
|
149
|
+
id: "tool-1",
|
|
150
|
+
name: "Get platform documentation",
|
|
151
|
+
chunks: ["Loaded tools, used Runtype integration"],
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const preview = mount.querySelector("[data-persona-collapsed-preview='tool']");
|
|
155
|
+
expect(preview?.textContent).toContain("Loaded tools, used Runtype integration");
|
|
156
|
+
|
|
157
|
+
controller.destroy();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("renders a collapsed preview for active reasoning rows when enabled", () => {
|
|
161
|
+
const mount = createMount();
|
|
162
|
+
const controller = createAgentExperience(mount, {
|
|
163
|
+
apiUrl: "https://api.example.com/chat",
|
|
164
|
+
launcher: { enabled: false },
|
|
165
|
+
features: {
|
|
166
|
+
reasoningDisplay: {
|
|
167
|
+
activePreview: true,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
} as any);
|
|
171
|
+
|
|
172
|
+
injectReasoningMessage(controller, {
|
|
173
|
+
id: "reason-1",
|
|
174
|
+
chunks: ["Now let me get the Persona embed documentation and builtin tools catalog."],
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const preview = mount.querySelector("[data-persona-collapsed-preview='reasoning']");
|
|
178
|
+
expect(preview?.textContent).toContain("Now let me get the Persona embed documentation");
|
|
179
|
+
|
|
180
|
+
controller.destroy();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("groups consecutive tool calls when enabled", () => {
|
|
184
|
+
const mount = createMount();
|
|
185
|
+
const controller = createAgentExperience(mount, {
|
|
186
|
+
apiUrl: "https://api.example.com/chat",
|
|
187
|
+
launcher: { enabled: false },
|
|
188
|
+
features: {
|
|
189
|
+
toolCallDisplay: {
|
|
190
|
+
grouped: true,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
} as any);
|
|
194
|
+
|
|
195
|
+
injectToolMessage(controller, { id: "tool-1", name: "Load tools", chunks: ["Loaded tools"] });
|
|
196
|
+
injectToolMessage(controller, { id: "tool-2", name: "Get docs", chunks: ["Fetched docs"] });
|
|
197
|
+
|
|
198
|
+
const group = mount.querySelector("[data-persona-tool-group='true']");
|
|
199
|
+
expect(group).not.toBeNull();
|
|
200
|
+
expect(group?.textContent).toContain("Called 2 tools");
|
|
201
|
+
|
|
202
|
+
controller.destroy();
|
|
203
|
+
});
|
|
204
|
+
});
|