@runtypelabs/persona 3.16.0 → 3.18.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/README.md +142 -0
- package/dist/animations/glyph-cycle.cjs +279 -0
- package/dist/animations/glyph-cycle.d.cts +5 -0
- package/dist/animations/glyph-cycle.d.ts +5 -0
- package/dist/animations/glyph-cycle.js +252 -0
- package/dist/animations/types-cwY5HaFD.d.cts +307 -0
- package/dist/animations/types-cwY5HaFD.d.ts +307 -0
- package/dist/animations/wipe.cjs +107 -0
- package/dist/animations/wipe.d.cts +5 -0
- package/dist/animations/wipe.d.ts +5 -0
- package/dist/animations/wipe.js +80 -0
- package/dist/index.cjs +49 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +504 -1
- package/dist/index.d.ts +504 -1
- package/dist/index.global.js +143 -88
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +49 -48
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +85 -0
- package/dist/testing.d.cts +39 -0
- package/dist/testing.d.ts +39 -0
- package/dist/testing.js +56 -0
- package/dist/theme-editor.cjs +2095 -207
- package/dist/theme-editor.d.cts +432 -2
- package/dist/theme-editor.d.ts +432 -2
- package/dist/theme-editor.js +2093 -207
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +14 -0
- package/dist/theme-reference.d.ts +14 -0
- package/dist/widget.css +565 -0
- package/package.json +20 -3
- package/src/animations/glyph-cycle.ts +332 -0
- package/src/animations/wipe.ts +66 -0
- package/src/client.test.ts +275 -0
- package/src/client.ts +99 -0
- package/src/components/ask-user-question-bubble.test.ts +583 -0
- package/src/components/ask-user-question-bubble.ts +924 -0
- package/src/components/composer-builder.ts +61 -10
- package/src/components/message-bubble.test.ts +181 -2
- package/src/components/message-bubble.ts +209 -14
- package/src/components/messages.ts +33 -1
- package/src/components/panel.ts +45 -5
- package/src/defaults.ts +37 -0
- package/src/index-global.ts +31 -0
- package/src/index.ts +34 -1
- package/src/plugins/types.ts +57 -0
- package/src/session.test.ts +276 -1
- package/src/session.ts +247 -3
- package/src/styles/widget.css +565 -0
- package/src/testing/index.ts +11 -0
- package/src/testing/mock-stream.test.ts +80 -0
- package/src/testing/mock-stream.ts +94 -0
- package/src/testing.ts +2 -0
- package/src/theme-editor/index.ts +4 -0
- package/src/theme-editor/preview-utils.test.ts +60 -0
- package/src/theme-editor/preview-utils.ts +129 -0
- package/src/theme-editor/sections.test.ts +19 -0
- package/src/theme-editor/sections.ts +84 -1
- package/src/types/theme.ts +15 -0
- package/src/types.ts +360 -0
- package/src/ui.ask-user-question-plugin.test.ts +649 -0
- package/src/ui.stop-button.test.ts +165 -0
- package/src/ui.ts +706 -11
- package/src/utils/message-fingerprint.ts +2 -0
- package/src/utils/morph.ts +7 -0
- package/src/utils/storage.ts +10 -2
- package/src/utils/stream-animation.test.ts +417 -0
- package/src/utils/stream-animation.ts +449 -0
- package/src/utils/theme.test.ts +36 -0
- package/src/utils/tokens.ts +23 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { createAgentExperience } from "./ui";
|
|
6
|
+
|
|
7
|
+
const createMount = () => {
|
|
8
|
+
const mount = document.createElement("div");
|
|
9
|
+
document.body.appendChild(mount);
|
|
10
|
+
return mount;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const flush = async (times = 4) => {
|
|
14
|
+
for (let i = 0; i < times; i += 1) {
|
|
15
|
+
// eslint-disable-next-line no-await-in-loop
|
|
16
|
+
await Promise.resolve();
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe("createAgentExperience stop-streaming submit button", () => {
|
|
21
|
+
const originalFetch = global.fetch;
|
|
22
|
+
let capturedSignals: AbortSignal[] = [];
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
capturedSignals = [];
|
|
26
|
+
vi.stubGlobal("requestAnimationFrame", (cb: (time: number) => void) => {
|
|
27
|
+
cb(0);
|
|
28
|
+
return 1;
|
|
29
|
+
});
|
|
30
|
+
vi.stubGlobal("cancelAnimationFrame", () => {});
|
|
31
|
+
window.scrollTo = vi.fn();
|
|
32
|
+
|
|
33
|
+
// Fetch hangs until the caller aborts the signal — models an in-flight
|
|
34
|
+
// SSE stream so the widget stays in the "streaming" state.
|
|
35
|
+
global.fetch = vi.fn().mockImplementation((_url: string, options: any) => {
|
|
36
|
+
const signal = options.signal as AbortSignal;
|
|
37
|
+
capturedSignals.push(signal);
|
|
38
|
+
return new Promise((_resolve, reject) => {
|
|
39
|
+
signal.addEventListener("abort", () => {
|
|
40
|
+
const err = new Error("aborted");
|
|
41
|
+
err.name = "AbortError";
|
|
42
|
+
reject(err);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}) as any;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
document.body.innerHTML = "";
|
|
50
|
+
global.fetch = originalFetch;
|
|
51
|
+
vi.restoreAllMocks();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("keeps the submit button enabled while streaming and clicking it cancels the stream", async () => {
|
|
55
|
+
const mount = createMount();
|
|
56
|
+
const controller = createAgentExperience(mount, {
|
|
57
|
+
apiUrl: "https://api.example.com/chat",
|
|
58
|
+
launcher: { enabled: false },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const submit = mount.querySelector<HTMLButtonElement>(
|
|
62
|
+
"[data-persona-composer-submit]"
|
|
63
|
+
);
|
|
64
|
+
expect(submit).not.toBeNull();
|
|
65
|
+
|
|
66
|
+
const textarea = mount.querySelector<HTMLTextAreaElement>(
|
|
67
|
+
"[data-persona-composer-input]"
|
|
68
|
+
)!;
|
|
69
|
+
textarea.value = "Hello";
|
|
70
|
+
submit!.click();
|
|
71
|
+
|
|
72
|
+
await flush();
|
|
73
|
+
|
|
74
|
+
// Streaming is active: the button must stay enabled so it can be clicked
|
|
75
|
+
// again to stop the response.
|
|
76
|
+
expect(controller.getState().streaming).toBe(true);
|
|
77
|
+
expect(submit!.disabled).toBe(false);
|
|
78
|
+
expect(capturedSignals).toHaveLength(1);
|
|
79
|
+
expect(capturedSignals[0].aborted).toBe(false);
|
|
80
|
+
|
|
81
|
+
// Second click — acts as "stop generating".
|
|
82
|
+
submit!.click();
|
|
83
|
+
|
|
84
|
+
await flush();
|
|
85
|
+
|
|
86
|
+
expect(controller.getState().streaming).toBe(false);
|
|
87
|
+
expect(capturedSignals[0].aborted).toBe(true);
|
|
88
|
+
// No new request should have been fired by the stop click.
|
|
89
|
+
expect(capturedSignals).toHaveLength(1);
|
|
90
|
+
// Typed text is preserved so the user can resend after stopping.
|
|
91
|
+
expect(textarea.value).toBe("");
|
|
92
|
+
// (The textarea was cleared on the *first* submit, not by the stop click —
|
|
93
|
+
// that's fine because after cancel the user can keep typing.)
|
|
94
|
+
|
|
95
|
+
controller.destroy();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("swaps to the stop icon while streaming and back to the send icon after cancel (icon mode)", async () => {
|
|
99
|
+
const mount = createMount();
|
|
100
|
+
const controller = createAgentExperience(mount, {
|
|
101
|
+
apiUrl: "https://api.example.com/chat",
|
|
102
|
+
launcher: { enabled: false },
|
|
103
|
+
sendButton: { useIcon: true, iconName: "arrow-up" },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const submit = mount.querySelector<HTMLButtonElement>(
|
|
107
|
+
"[data-persona-composer-submit]"
|
|
108
|
+
)!;
|
|
109
|
+
const textarea = mount.querySelector<HTMLTextAreaElement>(
|
|
110
|
+
"[data-persona-composer-input]"
|
|
111
|
+
)!;
|
|
112
|
+
|
|
113
|
+
// Initial state: send icon (aria-label tracks tooltip default).
|
|
114
|
+
expect(submit.getAttribute("aria-label")).toBe("Send message");
|
|
115
|
+
|
|
116
|
+
textarea.value = "Hi";
|
|
117
|
+
submit.click();
|
|
118
|
+
await flush();
|
|
119
|
+
|
|
120
|
+
expect(controller.getState().streaming).toBe(true);
|
|
121
|
+
expect(submit.getAttribute("aria-label")).toBe("Stop generating");
|
|
122
|
+
|
|
123
|
+
submit.click();
|
|
124
|
+
await flush();
|
|
125
|
+
|
|
126
|
+
expect(controller.getState().streaming).toBe(false);
|
|
127
|
+
expect(submit.getAttribute("aria-label")).toBe("Send message");
|
|
128
|
+
|
|
129
|
+
controller.destroy();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("swaps the text label in text mode", async () => {
|
|
133
|
+
const mount = createMount();
|
|
134
|
+
const controller = createAgentExperience(mount, {
|
|
135
|
+
apiUrl: "https://api.example.com/chat",
|
|
136
|
+
launcher: { enabled: false },
|
|
137
|
+
sendButton: { useIcon: false },
|
|
138
|
+
copy: { sendButtonLabel: "Send", stopButtonLabel: "Stop" },
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const submit = mount.querySelector<HTMLButtonElement>(
|
|
142
|
+
"[data-persona-composer-submit]"
|
|
143
|
+
)!;
|
|
144
|
+
const textarea = mount.querySelector<HTMLTextAreaElement>(
|
|
145
|
+
"[data-persona-composer-input]"
|
|
146
|
+
)!;
|
|
147
|
+
|
|
148
|
+
expect(submit.textContent).toBe("Send");
|
|
149
|
+
|
|
150
|
+
textarea.value = "Hi";
|
|
151
|
+
submit.click();
|
|
152
|
+
await flush();
|
|
153
|
+
|
|
154
|
+
expect(controller.getState().streaming).toBe(true);
|
|
155
|
+
expect(submit.textContent).toBe("Stop");
|
|
156
|
+
|
|
157
|
+
submit.click();
|
|
158
|
+
await flush();
|
|
159
|
+
|
|
160
|
+
expect(controller.getState().streaming).toBe(false);
|
|
161
|
+
expect(submit.textContent).toBe("Send");
|
|
162
|
+
|
|
163
|
+
controller.destroy();
|
|
164
|
+
});
|
|
165
|
+
});
|