@runtypelabs/persona 3.34.1 → 3.35.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 +11 -11
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-CthJFfNx.d.cts → types-DgYsuwXL.d.cts} +15 -14
- package/dist/animations/{types-CthJFfNx.d.ts → types-DgYsuwXL.d.ts} +15 -14
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/codegen.cjs +11 -11
- package/dist/codegen.js +10 -10
- package/dist/index.cjs +46 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +514 -158
- package/dist/index.d.ts +514 -158
- package/dist/index.global.js +41 -89
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +46 -44
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/markdown-parsers.js +53 -0
- package/dist/plugin-kit.d.cts +4 -4
- package/dist/plugin-kit.d.ts +4 -4
- package/dist/runtype-tts.js +1 -0
- package/dist/smart-dom-reader.cjs +1 -1
- package/dist/smart-dom-reader.d.cts +352 -103
- package/dist/smart-dom-reader.d.ts +352 -103
- package/dist/smart-dom-reader.js +1 -1
- package/dist/theme-editor-preview.cjs +203 -0
- package/dist/theme-editor-preview.d.cts +5748 -0
- package/dist/theme-editor-preview.d.ts +5748 -0
- package/dist/theme-editor-preview.js +203 -0
- package/dist/theme-editor.cjs +14 -131
- package/dist/theme-editor.d.cts +362 -574
- package/dist/theme-editor.d.ts +362 -574
- package/dist/theme-editor.js +14 -131
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +1 -1
- package/dist/theme-reference.d.ts +1 -1
- package/dist/theme-reference.js +1 -1
- package/dist/voice-worklet-player.cjs +80 -0
- package/dist/voice-worklet-player.d.cts +215 -0
- package/dist/voice-worklet-player.d.ts +215 -0
- package/dist/voice-worklet-player.js +80 -0
- package/dist/widget.css +54 -36
- package/package.json +16 -2
- package/src/animations/glyph-cycle.ts +11 -14
- package/src/animations/wipe.ts +1 -1
- package/src/ask-user-question-tool.test.ts +1 -1
- package/src/ask-user-question-tool.ts +6 -8
- package/src/client.test.ts +12 -12
- package/src/client.ts +30 -31
- package/src/codegen.test.ts +1 -1
- package/src/codegen.ts +1 -1
- package/src/components/approval-bubble.test.ts +1 -1
- package/src/components/approval-bubble.ts +3 -3
- package/src/components/artifact-pane.ts +3 -3
- package/src/components/ask-user-question-bubble.test.ts +14 -14
- package/src/components/ask-user-question-bubble.ts +10 -10
- package/src/components/composer-builder.ts +17 -16
- package/src/components/composer-parts.test.ts +54 -0
- package/src/components/composer-parts.ts +95 -126
- package/src/components/demo-carousel.ts +1 -1
- package/src/components/event-stream-view.test.ts +7 -7
- package/src/components/event-stream-view.ts +5 -5
- package/src/components/header-builder.ts +33 -27
- package/src/components/header-layouts.ts +1 -1
- package/src/components/header-parts.test.ts +90 -0
- package/src/components/header-parts.ts +62 -93
- package/src/components/launcher.test.ts +11 -0
- package/src/components/launcher.ts +1 -1
- package/src/components/message-bubble.test.ts +74 -2
- package/src/components/message-bubble.ts +25 -11
- package/src/components/messages.ts +2 -2
- package/src/components/panel.test.ts +1 -1
- package/src/components/panel.ts +92 -97
- package/src/components/pill-composer-builder.ts +28 -24
- package/src/components/reasoning-bubble.ts +2 -2
- package/src/components/suggestions.ts +1 -1
- package/src/components/tool-bubble.ts +1 -1
- package/src/components/widget-view.test.ts +181 -0
- package/src/components/widget-view.ts +234 -0
- package/src/defaults.ts +54 -51
- package/src/generated/runtype-openapi-contract.ts +5 -0
- package/src/index-core.ts +30 -4
- package/src/index-global.ts +73 -6
- package/src/index.ts +7 -1
- package/src/install.test.ts +11 -11
- package/src/install.ts +28 -18
- package/src/launcher-global.ts +25 -14
- package/src/markdown-parsers-eager.ts +20 -0
- package/src/markdown-parsers-entry.ts +4 -0
- package/src/markdown-parsers-loader.ts +49 -0
- package/src/plugin-kit.test.ts +1 -1
- package/src/plugin-kit.ts +7 -7
- package/src/plugins/types.ts +5 -5
- package/src/postprocessors.ts +27 -14
- package/src/runtime/host-layout.test.ts +1 -1
- package/src/runtime/host-layout.ts +4 -5
- package/src/runtime/init.ts +2 -2
- package/src/session.test.ts +5 -6
- package/src/session.ts +333 -227
- package/src/session.voice.test.ts +146 -0
- package/src/session.webmcp.test.ts +24 -25
- package/src/smart-dom-reader.ts +3 -4
- package/src/styles/widget.css +54 -36
- package/src/suggest-replies-tool.test.ts +3 -3
- package/src/suggest-replies-tool.ts +5 -5
- package/src/testing/index.ts +1 -1
- package/src/theme-editor/color-utils.ts +2 -2
- package/src/theme-editor/index.ts +4 -13
- package/src/theme-editor/presets.ts +1 -1
- package/src/theme-editor/preview-utils.ts +22 -20
- package/src/theme-editor/preview.ts +4 -1
- package/src/theme-editor/role-mappings.ts +3 -3
- package/src/theme-editor/sections.ts +21 -20
- package/src/theme-editor/types.ts +1 -1
- package/src/theme-editor/webmcp/index.ts +1 -1
- package/src/theme-editor/webmcp/summary.ts +1 -2
- package/src/theme-editor/webmcp/tools.ts +30 -30
- package/src/theme-editor/webmcp/types.ts +2 -2
- package/src/theme-editor-preview.ts +10 -0
- package/src/theme-reference.ts +26 -26
- package/src/types.ts +368 -99
- package/src/ui.approval-plugin.test.ts +3 -3
- package/src/ui.ask-user-question-plugin.test.ts +9 -9
- package/src/ui.component-directive.test.ts +4 -4
- package/src/ui.composer-bar.test.ts +10 -11
- package/src/ui.composer-keyboard.test.ts +5 -5
- package/src/ui.header-icon-color.test.ts +59 -0
- package/src/ui.scroll.test.ts +5 -6
- package/src/ui.stop-button.test.ts +3 -4
- package/src/ui.suggest-replies.test.ts +2 -2
- package/src/ui.ts +292 -131
- package/src/utils/buttons.ts +1 -1
- package/src/utils/code-generators.ts +1 -1
- package/src/utils/composer-history.ts +3 -3
- package/src/utils/dom-context.test.ts +2 -2
- package/src/utils/dom-context.ts +7 -7
- package/src/utils/dom.test.ts +67 -0
- package/src/utils/dom.ts +75 -0
- package/src/utils/dropdown.ts +2 -2
- package/src/utils/event-stream-buffer.test.ts +2 -2
- package/src/utils/message-fingerprint.test.ts +9 -0
- package/src/utils/message-fingerprint.ts +5 -0
- package/src/utils/morph.ts +1 -1
- package/src/utils/sanitize.ts +32 -16
- package/src/utils/sequence-buffer.test.ts +8 -8
- package/src/utils/sequence-buffer.ts +4 -4
- package/src/utils/smart-dom-adapter.test.ts +1 -1
- package/src/utils/smart-dom-adapter.ts +4 -5
- package/src/utils/speech-text.test.ts +122 -0
- package/src/utils/speech-text.ts +101 -0
- package/src/utils/stream-animation.test.ts +3 -3
- package/src/utils/stream-animation.ts +6 -6
- package/src/utils/theme.test.ts +1 -1
- package/src/utils/throughput-tracker.test.ts +11 -11
- package/src/utils/throughput-tracker.ts +9 -9
- package/src/utils/tokens.ts +13 -13
- package/src/utils/virtual-scroller.ts +1 -1
- package/src/vendor/smart-dom-reader/README.md +4 -4
- package/src/vendor/smart-dom-reader/index.d.ts +1 -1
- package/src/vendor/smart-dom-reader/index.js +2 -2
- package/src/version.ts +1 -1
- package/src/voice/audio-playback-manager.test.ts +159 -0
- package/src/voice/audio-playback-manager.ts +145 -30
- package/src/voice/browser-speech-engine.ts +130 -0
- package/src/voice/fallback-speech-engine.ts +89 -0
- package/src/voice/index.ts +21 -0
- package/src/voice/read-aloud-controller.test.ts +143 -0
- package/src/voice/read-aloud-controller.ts +136 -0
- package/src/voice/runtype-speech-engine.test.ts +280 -0
- package/src/voice/runtype-speech-engine.ts +211 -0
- package/src/voice/runtype-tts-entry.ts +10 -0
- package/src/voice/runtype-tts-loader.test.ts +47 -0
- package/src/voice/runtype-tts-loader.ts +42 -0
- package/src/voice/runtype-voice-provider.ts +332 -537
- package/src/voice/voice-factory.ts +26 -4
- package/src/voice/voice.test.ts +321 -7
- package/src/voice/worklet-playback-engine.ts +272 -0
- package/src/voice-worklet-player.ts +39 -0
- package/src/webmcp-bridge.test.ts +6 -6
- package/src/webmcp-bridge.ts +24 -25
- package/src/webmcp-polyfill.ts +1 -1
- package/src/voice/voice-activity-detector.ts +0 -90
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { createWidgetView, resolveLauncher } from "./widget-view";
|
|
5
|
+
import type { HeaderElements } from "./header-builder";
|
|
6
|
+
import type { AgentWidgetConfig } from "../types";
|
|
7
|
+
import type { AgentWidgetPlugin } from "../plugins/types";
|
|
8
|
+
|
|
9
|
+
describe("createWidgetView: default assembly", () => {
|
|
10
|
+
const config: AgentWidgetConfig = {
|
|
11
|
+
apiUrl: "/api",
|
|
12
|
+
voiceRecognition: { enabled: true, provider: { type: "runtype" } },
|
|
13
|
+
attachments: { enabled: true },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
it("groups shell, transcript, header, and composer refs over the real nodes", () => {
|
|
17
|
+
const view = createWidgetView({ config, showClose: true });
|
|
18
|
+
|
|
19
|
+
// Shell. The empty `panel` is wired to the `container` later by ui.ts
|
|
20
|
+
// (panel.appendChild(container)); createWidgetView preserves that split.
|
|
21
|
+
expect(view.shell.wrapper.contains(view.shell.panel)).toBe(true);
|
|
22
|
+
expect(view.shell.pillRoot).toBeUndefined();
|
|
23
|
+
|
|
24
|
+
// Transcript subtree is wired by buildPanel
|
|
25
|
+
expect(view.transcript.container.contains(view.transcript.body)).toBe(true);
|
|
26
|
+
expect(view.transcript.body.contains(view.transcript.messagesWrapper)).toBe(true);
|
|
27
|
+
|
|
28
|
+
// Header + composer are mounted in the container
|
|
29
|
+
expect(view.transcript.container.contains(view.header.element)).toBe(true);
|
|
30
|
+
expect(view.transcript.container.contains(view.composer.footer)).toBe(true);
|
|
31
|
+
|
|
32
|
+
// Grouped refs are the same nodes as the raw flat panelElements
|
|
33
|
+
expect(view.composer.textarea).toBe(view.panelElements.textarea);
|
|
34
|
+
expect(view.composer.footer).toBe(view.panelElements.footer);
|
|
35
|
+
expect(view.header.element).toBe(view.panelElements.header);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("exposes the stable composer ref attributes", () => {
|
|
39
|
+
const view = createWidgetView({ config, showClose: true });
|
|
40
|
+
|
|
41
|
+
expect(view.composer.textarea.getAttribute("data-persona-composer-input")).toBe("");
|
|
42
|
+
expect(view.composer.sendButton.getAttribute("data-persona-composer-submit")).toBe("");
|
|
43
|
+
expect(view.composer.statusText.getAttribute("data-persona-composer-status")).toBe("");
|
|
44
|
+
expect(view.composer.suggestions.getAttribute("data-persona-composer-suggestions")).toBe("");
|
|
45
|
+
expect(view.composer.actionsRow.getAttribute("data-persona-composer-actions")).toBe("");
|
|
46
|
+
expect(view.composer.micButton).not.toBeNull();
|
|
47
|
+
expect(
|
|
48
|
+
view.composer.attachmentButton?.getAttribute("data-persona-composer-attachment-button")
|
|
49
|
+
).toBe("");
|
|
50
|
+
expect(
|
|
51
|
+
view.composer.attachmentInput?.getAttribute("data-persona-composer-attachment-input")
|
|
52
|
+
).toBe("");
|
|
53
|
+
expect(
|
|
54
|
+
view.composer.attachmentPreviewsContainer?.getAttribute(
|
|
55
|
+
"data-persona-composer-attachment-previews"
|
|
56
|
+
)
|
|
57
|
+
).toBe("");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("hides header and footer when layout disables them", () => {
|
|
61
|
+
const view = createWidgetView({
|
|
62
|
+
config: { apiUrl: "/api", layout: { showHeader: false, showFooter: false } },
|
|
63
|
+
showClose: true,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(view.header.element.style.display).toBe("none");
|
|
67
|
+
expect(view.composer.footer.style.display).toBe("none");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("createWidgetView: composer-bar assembly", () => {
|
|
72
|
+
const config: AgentWidgetConfig = {
|
|
73
|
+
apiUrl: "/api",
|
|
74
|
+
launcher: { mountMode: "composer-bar" },
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
it("exposes the pill root and peek banner", () => {
|
|
78
|
+
const view = createWidgetView({ config, showClose: true });
|
|
79
|
+
|
|
80
|
+
expect(view.shell.pillRoot).toBeInstanceOf(HTMLElement);
|
|
81
|
+
expect(view.composer.peekBanner).toBeInstanceOf(HTMLElement);
|
|
82
|
+
expect(view.composer.peekTextNode).toBeInstanceOf(HTMLElement);
|
|
83
|
+
// Composer-bar uses a hidden placeholder header (close button is the chrome).
|
|
84
|
+
expect(view.header.element.style.display).toBe("none");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("createWidgetView: replacement helpers", () => {
|
|
89
|
+
it("replaceHeader swaps the mounted element and mirrors sub-refs", () => {
|
|
90
|
+
const view = createWidgetView({ config: { apiUrl: "/api" }, showClose: true });
|
|
91
|
+
const original = view.header.element;
|
|
92
|
+
const parent = original.parentElement;
|
|
93
|
+
|
|
94
|
+
const next: HeaderElements = {
|
|
95
|
+
header: document.createElement("header"),
|
|
96
|
+
iconHolder: document.createElement("span"),
|
|
97
|
+
headerTitle: document.createElement("span"),
|
|
98
|
+
headerSubtitle: document.createElement("span"),
|
|
99
|
+
closeButton: document.createElement("button"),
|
|
100
|
+
closeButtonWrapper: document.createElement("div"),
|
|
101
|
+
clearChatButton: null,
|
|
102
|
+
clearChatButtonWrapper: null,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const returned = view.replaceHeader(next);
|
|
106
|
+
|
|
107
|
+
expect(returned).toBe(next);
|
|
108
|
+
expect(view.header.element).toBe(next.header);
|
|
109
|
+
expect(view.header.iconHolder).toBe(next.iconHolder);
|
|
110
|
+
expect(view.header.closeButton).toBe(next.closeButton);
|
|
111
|
+
expect(parent?.contains(next.header)).toBe(true);
|
|
112
|
+
expect(parent?.contains(original)).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("replaceComposer swaps the mounted footer and updates the ref", () => {
|
|
116
|
+
const view = createWidgetView({ config: { apiUrl: "/api" }, showClose: true });
|
|
117
|
+
const original = view.composer.footer;
|
|
118
|
+
const parent = original.parentElement;
|
|
119
|
+
const nextFooter = document.createElement("div");
|
|
120
|
+
|
|
121
|
+
view.replaceComposer(nextFooter);
|
|
122
|
+
|
|
123
|
+
expect(view.composer.footer).toBe(nextFooter);
|
|
124
|
+
expect(parent?.contains(nextFooter)).toBe(true);
|
|
125
|
+
expect(parent?.contains(original)).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("resolveLauncher", () => {
|
|
130
|
+
const config: AgentWidgetConfig = { apiUrl: "/api" };
|
|
131
|
+
|
|
132
|
+
it("builds the default launcher controller and wires onToggle", () => {
|
|
133
|
+
const onToggle = vi.fn();
|
|
134
|
+
const { instance, element } = resolveLauncher({ config, plugins: [], onToggle });
|
|
135
|
+
|
|
136
|
+
expect(instance).not.toBeNull();
|
|
137
|
+
expect(element).toBe(instance?.element);
|
|
138
|
+
expect(element.tagName).toBe("BUTTON");
|
|
139
|
+
|
|
140
|
+
element.dispatchEvent(new Event("click"));
|
|
141
|
+
expect(onToggle).toHaveBeenCalledTimes(1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("uses a plugin-provided launcher element and returns no controller", () => {
|
|
145
|
+
const custom = document.createElement("div");
|
|
146
|
+
custom.id = "custom-launcher";
|
|
147
|
+
const plugin: AgentWidgetPlugin = {
|
|
148
|
+
id: "custom",
|
|
149
|
+
renderLauncher: ({ defaultRenderer }) => {
|
|
150
|
+
// The default renderer is still callable; the plugin opts to ignore it.
|
|
151
|
+
expect(defaultRenderer().tagName).toBe("BUTTON");
|
|
152
|
+
return custom;
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const { instance, element } = resolveLauncher({
|
|
157
|
+
config,
|
|
158
|
+
plugins: [plugin],
|
|
159
|
+
onToggle: () => {},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(instance).toBeNull();
|
|
163
|
+
expect(element).toBe(custom);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("falls back to the default launcher when the plugin returns null", () => {
|
|
167
|
+
const plugin: AgentWidgetPlugin = {
|
|
168
|
+
id: "passthrough",
|
|
169
|
+
renderLauncher: () => null,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const { instance, element } = resolveLauncher({
|
|
173
|
+
config,
|
|
174
|
+
plugins: [plugin],
|
|
175
|
+
onToggle: () => {},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(instance).not.toBeNull();
|
|
179
|
+
expect(element).toBe(instance?.element);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { AgentWidgetConfig } from "../types";
|
|
2
|
+
import { AgentWidgetPlugin } from "../plugins/types";
|
|
3
|
+
import { createWrapper, buildPanel, PanelElements } from "./panel";
|
|
4
|
+
import { HeaderElements } from "./header-builder";
|
|
5
|
+
import { createLauncherButton, LauncherButton } from "./launcher";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Widget view assembly layer.
|
|
9
|
+
*
|
|
10
|
+
* `ui.ts` owns behavior (session dispatch, streaming, scroll, voice, events);
|
|
11
|
+
* this module owns the one-time structural assembly of the widget's DOM and
|
|
12
|
+
* groups the resulting element references into named regions so callers read
|
|
13
|
+
* `view.composer.textarea` instead of destructuring a ~30-field flat object.
|
|
14
|
+
*
|
|
15
|
+
* The grouped refs are a view over the same nodes returned by `buildPanel`;
|
|
16
|
+
* the raw flat `PanelElements` is still exposed as `view.panelElements` for
|
|
17
|
+
* incremental migration. No framework, no virtual DOM: these are the real
|
|
18
|
+
* elements built by the existing `panel.ts` / `composer-*` / `header-*`
|
|
19
|
+
* builders.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export interface WidgetShellRefs {
|
|
23
|
+
wrapper: HTMLElement;
|
|
24
|
+
panel: HTMLElement;
|
|
25
|
+
/** Composer-bar mode only: viewport-fixed sibling that owns the pill. */
|
|
26
|
+
pillRoot?: HTMLElement;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WidgetTranscriptRefs {
|
|
30
|
+
container: HTMLElement;
|
|
31
|
+
body: HTMLElement;
|
|
32
|
+
messagesWrapper: HTMLElement;
|
|
33
|
+
/** Absolute slot above the composer where interactive sheets mount. */
|
|
34
|
+
composerOverlay: HTMLElement;
|
|
35
|
+
introTitle: HTMLElement;
|
|
36
|
+
introSubtitle: HTMLElement;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WidgetHeaderRefs {
|
|
40
|
+
/** The header root element currently mounted in the container. */
|
|
41
|
+
element: HTMLElement;
|
|
42
|
+
iconHolder: HTMLElement;
|
|
43
|
+
headerTitle: HTMLElement;
|
|
44
|
+
headerSubtitle: HTMLElement;
|
|
45
|
+
closeButton: HTMLButtonElement;
|
|
46
|
+
closeButtonWrapper: HTMLElement | null;
|
|
47
|
+
clearChatButton: HTMLButtonElement | null;
|
|
48
|
+
clearChatButtonWrapper: HTMLElement | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface WidgetComposerRefs {
|
|
52
|
+
/** The footer root element currently mounted in the panel. */
|
|
53
|
+
footer: HTMLElement;
|
|
54
|
+
form: HTMLFormElement;
|
|
55
|
+
textarea: HTMLTextAreaElement;
|
|
56
|
+
sendButton: HTMLButtonElement;
|
|
57
|
+
sendButtonWrapper: HTMLElement;
|
|
58
|
+
micButton: HTMLButtonElement | null;
|
|
59
|
+
micButtonWrapper: HTMLElement | null;
|
|
60
|
+
statusText: HTMLElement;
|
|
61
|
+
suggestions: HTMLElement;
|
|
62
|
+
attachmentButton: HTMLButtonElement | null;
|
|
63
|
+
attachmentButtonWrapper: HTMLElement | null;
|
|
64
|
+
attachmentInput: HTMLInputElement | null;
|
|
65
|
+
attachmentPreviewsContainer: HTMLElement | null;
|
|
66
|
+
actionsRow: HTMLElement;
|
|
67
|
+
leftActions: HTMLElement;
|
|
68
|
+
rightActions: HTMLElement;
|
|
69
|
+
setSendButtonMode: (mode: "send" | "stop") => void;
|
|
70
|
+
/** Composer-bar peek banner (undefined for non-composer-bar modes). */
|
|
71
|
+
peekBanner?: HTMLButtonElement;
|
|
72
|
+
peekTextNode?: HTMLElement;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface WidgetView {
|
|
76
|
+
shell: WidgetShellRefs;
|
|
77
|
+
/** Raw flat refs from `buildPanel`, retained for incremental migration. */
|
|
78
|
+
panelElements: PanelElements;
|
|
79
|
+
transcript: WidgetTranscriptRefs;
|
|
80
|
+
header: WidgetHeaderRefs;
|
|
81
|
+
composer: WidgetComposerRefs;
|
|
82
|
+
/**
|
|
83
|
+
* Swap the mounted header element for `next.header` and mirror the header
|
|
84
|
+
* sub-refs (icon/title/subtitle/close) onto `view.header`. Used by the
|
|
85
|
+
* header-layout rebuild path. Returns the new `HeaderElements` for the
|
|
86
|
+
* caller to mirror into its own locals during incremental migration.
|
|
87
|
+
*/
|
|
88
|
+
replaceHeader: (next: HeaderElements) => HeaderElements;
|
|
89
|
+
/**
|
|
90
|
+
* Swap the mounted footer element for `nextFooter` and update
|
|
91
|
+
* `view.composer.footer`. The remaining composer sub-refs become stale
|
|
92
|
+
* after a plugin-provided composer replaces the footer; callers re-derive
|
|
93
|
+
* them from the new footer (see `bindComposerRefsFromFooter` in ui.ts).
|
|
94
|
+
*/
|
|
95
|
+
replaceComposer: (nextFooter: HTMLElement) => void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface CreateWidgetViewOptions {
|
|
99
|
+
config: AgentWidgetConfig;
|
|
100
|
+
/** Whether the panel renders a close affordance (panel is toggleable). */
|
|
101
|
+
showClose: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Build the widget shell + panel once and group the element references into
|
|
106
|
+
* named regions. Equivalent to the previous `createWrapper(config)` +
|
|
107
|
+
* `buildPanel(config, showClose)` pair, plus the grouping/replacement helpers.
|
|
108
|
+
*/
|
|
109
|
+
export const createWidgetView = ({
|
|
110
|
+
config,
|
|
111
|
+
showClose,
|
|
112
|
+
}: CreateWidgetViewOptions): WidgetView => {
|
|
113
|
+
const { wrapper, panel, pillRoot } = createWrapper(config);
|
|
114
|
+
const panelElements = buildPanel(config, showClose);
|
|
115
|
+
|
|
116
|
+
const shell: WidgetShellRefs = { wrapper, panel, pillRoot };
|
|
117
|
+
|
|
118
|
+
const transcript: WidgetTranscriptRefs = {
|
|
119
|
+
container: panelElements.container,
|
|
120
|
+
body: panelElements.body,
|
|
121
|
+
messagesWrapper: panelElements.messagesWrapper,
|
|
122
|
+
composerOverlay: panelElements.composerOverlay,
|
|
123
|
+
introTitle: panelElements.introTitle,
|
|
124
|
+
introSubtitle: panelElements.introSubtitle,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const header: WidgetHeaderRefs = {
|
|
128
|
+
element: panelElements.header,
|
|
129
|
+
iconHolder: panelElements.iconHolder,
|
|
130
|
+
headerTitle: panelElements.headerTitle,
|
|
131
|
+
headerSubtitle: panelElements.headerSubtitle,
|
|
132
|
+
closeButton: panelElements.closeButton,
|
|
133
|
+
closeButtonWrapper: panelElements.closeButtonWrapper,
|
|
134
|
+
clearChatButton: panelElements.clearChatButton,
|
|
135
|
+
clearChatButtonWrapper: panelElements.clearChatButtonWrapper,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const composer: WidgetComposerRefs = {
|
|
139
|
+
footer: panelElements.footer,
|
|
140
|
+
form: panelElements.composerForm,
|
|
141
|
+
textarea: panelElements.textarea,
|
|
142
|
+
sendButton: panelElements.sendButton,
|
|
143
|
+
sendButtonWrapper: panelElements.sendButtonWrapper,
|
|
144
|
+
micButton: panelElements.micButton,
|
|
145
|
+
micButtonWrapper: panelElements.micButtonWrapper,
|
|
146
|
+
statusText: panelElements.statusText,
|
|
147
|
+
suggestions: panelElements.suggestions,
|
|
148
|
+
attachmentButton: panelElements.attachmentButton,
|
|
149
|
+
attachmentButtonWrapper: panelElements.attachmentButtonWrapper,
|
|
150
|
+
attachmentInput: panelElements.attachmentInput,
|
|
151
|
+
attachmentPreviewsContainer: panelElements.attachmentPreviewsContainer,
|
|
152
|
+
actionsRow: panelElements.actionsRow,
|
|
153
|
+
leftActions: panelElements.leftActions,
|
|
154
|
+
rightActions: panelElements.rightActions,
|
|
155
|
+
setSendButtonMode: panelElements.setSendButtonMode,
|
|
156
|
+
peekBanner: panelElements.peekBanner,
|
|
157
|
+
peekTextNode: panelElements.peekTextNode,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const replaceHeader = (next: HeaderElements): HeaderElements => {
|
|
161
|
+
header.element.replaceWith(next.header);
|
|
162
|
+
header.element = next.header;
|
|
163
|
+
header.iconHolder = next.iconHolder;
|
|
164
|
+
header.headerTitle = next.headerTitle;
|
|
165
|
+
header.headerSubtitle = next.headerSubtitle;
|
|
166
|
+
header.closeButton = next.closeButton;
|
|
167
|
+
header.closeButtonWrapper = next.closeButtonWrapper;
|
|
168
|
+
header.clearChatButton = next.clearChatButton;
|
|
169
|
+
header.clearChatButtonWrapper = next.clearChatButtonWrapper;
|
|
170
|
+
return next;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const replaceComposer = (nextFooter: HTMLElement): void => {
|
|
174
|
+
composer.footer.replaceWith(nextFooter);
|
|
175
|
+
composer.footer = nextFooter;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
shell,
|
|
180
|
+
panelElements,
|
|
181
|
+
transcript,
|
|
182
|
+
header,
|
|
183
|
+
composer,
|
|
184
|
+
replaceHeader,
|
|
185
|
+
replaceComposer,
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export interface ResolveLauncherOptions {
|
|
190
|
+
config: AgentWidgetConfig;
|
|
191
|
+
plugins: AgentWidgetPlugin[];
|
|
192
|
+
/** Toggles the panel open/closed; wired to the launcher's click. */
|
|
193
|
+
onToggle: () => void;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface ResolvedLauncher {
|
|
197
|
+
/**
|
|
198
|
+
* The default launcher controller (with `update`/`destroy`), or null when a
|
|
199
|
+
* plugin supplied a custom launcher element.
|
|
200
|
+
*/
|
|
201
|
+
instance: LauncherButton | null;
|
|
202
|
+
/** The element to mount: either the default button or the plugin's element. */
|
|
203
|
+
element: HTMLElement;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Resolve the launcher element, honoring a `renderLauncher` plugin if present.
|
|
208
|
+
* De-duplicates the identical default-or-plugin logic that previously lived at
|
|
209
|
+
* both the initial-build and re-enable call sites in ui.ts.
|
|
210
|
+
*
|
|
211
|
+
* When a plugin returns a custom element, `instance` is null (the plugin owns
|
|
212
|
+
* its own updates); otherwise the default `LauncherButton` controller is
|
|
213
|
+
* returned so the caller can drive `update()`/`destroy()`.
|
|
214
|
+
*/
|
|
215
|
+
export const resolveLauncher = ({
|
|
216
|
+
config,
|
|
217
|
+
plugins,
|
|
218
|
+
onToggle,
|
|
219
|
+
}: ResolveLauncherOptions): ResolvedLauncher => {
|
|
220
|
+
const launcherPlugin = plugins.find((p) => p.renderLauncher);
|
|
221
|
+
if (launcherPlugin?.renderLauncher) {
|
|
222
|
+
const customLauncher = launcherPlugin.renderLauncher({
|
|
223
|
+
config,
|
|
224
|
+
defaultRenderer: () => createLauncherButton(config, onToggle).element,
|
|
225
|
+
onToggle,
|
|
226
|
+
});
|
|
227
|
+
if (customLauncher) {
|
|
228
|
+
return { instance: null, element: customLauncher };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const instance = createLauncherButton(config, onToggle);
|
|
233
|
+
return { instance, element: instance.element };
|
|
234
|
+
};
|
package/src/defaults.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentWidgetConfig } from "./types";
|
|
1
|
+
import type { AgentWidgetConfig, AgentWidgetLauncherConfig } from "./types";
|
|
2
2
|
import type { DeepPartial, PersonaTheme } from "./types/theme";
|
|
3
3
|
import { deepMerge } from "./utils/deep-merge";
|
|
4
4
|
|
|
@@ -12,6 +12,58 @@ export const DEFAULT_FLOATING_LAUNCHER_WIDTH = "min(440px, calc(100vw - 24px))";
|
|
|
12
12
|
/** Max width cap paired with {@link DEFAULT_FLOATING_LAUNCHER_WIDTH} for theme defaults. */
|
|
13
13
|
export const DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH = "440px";
|
|
14
14
|
|
|
15
|
+
export const DEFAULT_LAUNCHER_CONFIG: AgentWidgetLauncherConfig = {
|
|
16
|
+
enabled: true,
|
|
17
|
+
mountMode: "floating",
|
|
18
|
+
dock: {
|
|
19
|
+
side: "right",
|
|
20
|
+
width: "420px",
|
|
21
|
+
},
|
|
22
|
+
title: "Chat Assistant",
|
|
23
|
+
subtitle: "Here to help you get answers fast",
|
|
24
|
+
agentIconText: "💬",
|
|
25
|
+
agentIconName: "bot",
|
|
26
|
+
headerIconName: "bot",
|
|
27
|
+
position: "bottom-right",
|
|
28
|
+
width: DEFAULT_FLOATING_LAUNCHER_WIDTH,
|
|
29
|
+
heightOffset: 0,
|
|
30
|
+
autoExpand: false,
|
|
31
|
+
callToActionIconHidden: false,
|
|
32
|
+
agentIconSize: "40px",
|
|
33
|
+
headerIconSize: "40px",
|
|
34
|
+
closeButtonSize: "32px",
|
|
35
|
+
// Zero out browser-default <button> padding so the icon gets the full
|
|
36
|
+
// 32x32 content box, matching clearChat.paddingX/Y below. Without this,
|
|
37
|
+
// UA stylesheets add ~1-2px vertical and ~6px horizontal padding that
|
|
38
|
+
// eats into the border-box width and shrinks the rendered icon.
|
|
39
|
+
closeButtonPaddingX: "0px",
|
|
40
|
+
closeButtonPaddingY: "0px",
|
|
41
|
+
callToActionIconName: "arrow-up-right",
|
|
42
|
+
callToActionIconText: "",
|
|
43
|
+
callToActionIconSize: "32px",
|
|
44
|
+
callToActionIconPadding: "5px",
|
|
45
|
+
callToActionIconColor: undefined,
|
|
46
|
+
callToActionIconBackgroundColor: undefined,
|
|
47
|
+
// closeButtonColor / clearChat.iconColor omitted so theme.components.header.actionIconForeground applies.
|
|
48
|
+
closeButtonBackgroundColor: "transparent",
|
|
49
|
+
clearChat: {
|
|
50
|
+
backgroundColor: "transparent",
|
|
51
|
+
borderColor: "transparent",
|
|
52
|
+
enabled: true,
|
|
53
|
+
placement: "inline",
|
|
54
|
+
iconName: "refresh-cw",
|
|
55
|
+
size: "32px",
|
|
56
|
+
showTooltip: true,
|
|
57
|
+
tooltipText: "Clear chat",
|
|
58
|
+
paddingX: "0px",
|
|
59
|
+
paddingY: "0px",
|
|
60
|
+
},
|
|
61
|
+
headerIconHidden: false,
|
|
62
|
+
border: undefined,
|
|
63
|
+
shadow:
|
|
64
|
+
"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
|
|
65
|
+
};
|
|
66
|
+
|
|
15
67
|
/**
|
|
16
68
|
* Default widget configuration
|
|
17
69
|
* Single source of truth for all default values
|
|
@@ -23,56 +75,7 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
|
|
|
23
75
|
theme: undefined,
|
|
24
76
|
darkTheme: undefined,
|
|
25
77
|
colorScheme: "light",
|
|
26
|
-
launcher:
|
|
27
|
-
enabled: true,
|
|
28
|
-
mountMode: "floating",
|
|
29
|
-
dock: {
|
|
30
|
-
side: "right",
|
|
31
|
-
width: "420px",
|
|
32
|
-
},
|
|
33
|
-
title: "Chat Assistant",
|
|
34
|
-
subtitle: "Here to help you get answers fast",
|
|
35
|
-
agentIconText: "💬",
|
|
36
|
-
agentIconName: "bot",
|
|
37
|
-
headerIconName: "bot",
|
|
38
|
-
position: "bottom-right",
|
|
39
|
-
width: DEFAULT_FLOATING_LAUNCHER_WIDTH,
|
|
40
|
-
heightOffset: 0,
|
|
41
|
-
autoExpand: false,
|
|
42
|
-
callToActionIconHidden: false,
|
|
43
|
-
agentIconSize: "40px",
|
|
44
|
-
headerIconSize: "40px",
|
|
45
|
-
closeButtonSize: "32px",
|
|
46
|
-
// Zero out browser-default <button> padding so the icon gets the full
|
|
47
|
-
// 32x32 content box, matching clearChat.paddingX/Y below. Without this,
|
|
48
|
-
// UA stylesheets add ~1-2px vertical and ~6px horizontal padding that
|
|
49
|
-
// eats into the border-box width and shrinks the rendered icon.
|
|
50
|
-
closeButtonPaddingX: "0px",
|
|
51
|
-
closeButtonPaddingY: "0px",
|
|
52
|
-
callToActionIconName: "arrow-up-right",
|
|
53
|
-
callToActionIconText: "",
|
|
54
|
-
callToActionIconSize: "32px",
|
|
55
|
-
callToActionIconPadding: "5px",
|
|
56
|
-
callToActionIconColor: undefined,
|
|
57
|
-
callToActionIconBackgroundColor: undefined,
|
|
58
|
-
// closeButtonColor / clearChat.iconColor omitted so theme.components.header.actionIconForeground applies.
|
|
59
|
-
closeButtonBackgroundColor: "transparent",
|
|
60
|
-
clearChat: {
|
|
61
|
-
backgroundColor: "transparent",
|
|
62
|
-
borderColor: "transparent",
|
|
63
|
-
enabled: true,
|
|
64
|
-
placement: "inline",
|
|
65
|
-
iconName: "refresh-cw",
|
|
66
|
-
size: "32px",
|
|
67
|
-
showTooltip: true,
|
|
68
|
-
tooltipText: "Clear chat",
|
|
69
|
-
paddingX: "0px",
|
|
70
|
-
paddingY: "0px",
|
|
71
|
-
},
|
|
72
|
-
headerIconHidden: false,
|
|
73
|
-
border: undefined,
|
|
74
|
-
shadow: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
|
|
75
|
-
},
|
|
78
|
+
launcher: DEFAULT_LAUNCHER_CONFIG,
|
|
76
79
|
copy: {
|
|
77
80
|
welcomeTitle: "Hello 👋",
|
|
78
81
|
welcomeSubtitle: "Ask anything about your account or products.",
|
|
@@ -285,6 +285,7 @@ export type RuntypeFlowSSEEvent = {
|
|
|
285
285
|
totalSteps?: number;
|
|
286
286
|
type: "flow_start";
|
|
287
287
|
} | {
|
|
288
|
+
claudeManagedAgentId?: string;
|
|
288
289
|
completedAt?: string;
|
|
289
290
|
completedSteps?: number;
|
|
290
291
|
duration?: number;
|
|
@@ -399,6 +400,7 @@ export type RuntypeFlowSSEEvent = {
|
|
|
399
400
|
toolId: string;
|
|
400
401
|
};
|
|
401
402
|
type: "step_complete";
|
|
403
|
+
unresolvedVariables?: Array<string>;
|
|
402
404
|
}) | {
|
|
403
405
|
error: string;
|
|
404
406
|
executionId?: string;
|
|
@@ -836,6 +838,7 @@ export type RuntypeDispatchSSEEvent = {
|
|
|
836
838
|
totalSteps?: number;
|
|
837
839
|
type: "flow_start";
|
|
838
840
|
} | {
|
|
841
|
+
claudeManagedAgentId?: string;
|
|
839
842
|
completedAt?: string;
|
|
840
843
|
completedSteps?: number;
|
|
841
844
|
duration?: number;
|
|
@@ -950,6 +953,7 @@ export type RuntypeDispatchSSEEvent = {
|
|
|
950
953
|
toolId: string;
|
|
951
954
|
};
|
|
952
955
|
type: "step_complete";
|
|
956
|
+
unresolvedVariables?: Array<string>;
|
|
953
957
|
}) | {
|
|
954
958
|
error: string;
|
|
955
959
|
executionId?: string;
|
|
@@ -1253,6 +1257,7 @@ export type RuntypeClientFeedbackRequest = {
|
|
|
1253
1257
|
metadata?: Record<string, unknown>;
|
|
1254
1258
|
rating?: number;
|
|
1255
1259
|
sessionId: string;
|
|
1260
|
+
token: string;
|
|
1256
1261
|
type: "upvote" | "downvote" | "copy" | "csat" | "nps";
|
|
1257
1262
|
};
|
|
1258
1263
|
|
package/src/index-core.ts
CHANGED
|
@@ -82,7 +82,7 @@ export type {
|
|
|
82
82
|
AgentWidgetApproval,
|
|
83
83
|
AgentWidgetApprovalConfig,
|
|
84
84
|
AgentWidgetApprovalDecisionOptions,
|
|
85
|
-
// WebMCP
|
|
85
|
+
// WebMCP: page-discovered tool consumption
|
|
86
86
|
AgentWidgetWebMcpConfig,
|
|
87
87
|
ClientToolDefinition,
|
|
88
88
|
WebMcpConfirmHandler,
|
|
@@ -98,6 +98,7 @@ export type {
|
|
|
98
98
|
EventStreamPayloadRenderContext,
|
|
99
99
|
// Controller event map
|
|
100
100
|
AgentWidgetControllerEventMap,
|
|
101
|
+
AgentWidgetReadAloudEvent,
|
|
101
102
|
// Ask-user-question (built-in answer-pill sheet) types
|
|
102
103
|
AskUserQuestionPayload,
|
|
103
104
|
AskUserQuestionPrompt,
|
|
@@ -245,7 +246,7 @@ export type { AgentWidgetInitHandle };
|
|
|
245
246
|
export type { AgentWidgetPlugin } from "./plugins/types";
|
|
246
247
|
export { pluginRegistry } from "./plugins/registry";
|
|
247
248
|
|
|
248
|
-
// Stream animation plugin API
|
|
249
|
+
// Stream animation plugin API: lets consumers register custom animations
|
|
249
250
|
// that match the built-in surface (typewriter, pop-bubble) and subpath
|
|
250
251
|
// modules (letter-rise, word-fade, wipe, glyph-cycle).
|
|
251
252
|
export {
|
|
@@ -263,7 +264,7 @@ export type {
|
|
|
263
264
|
AgentWidgetStreamAnimationPlaceholder,
|
|
264
265
|
} from "./types";
|
|
265
266
|
|
|
266
|
-
// Action system types
|
|
267
|
+
// Action system types: needed to type the `actionHandlers` / `actionParsers`
|
|
267
268
|
// config options and to author custom handlers/parsers.
|
|
268
269
|
export type {
|
|
269
270
|
AgentWidgetActionHandler,
|
|
@@ -435,7 +436,32 @@ export type {
|
|
|
435
436
|
VoiceProvider,
|
|
436
437
|
VoiceResult,
|
|
437
438
|
VoiceStatus,
|
|
438
|
-
VoiceConfig
|
|
439
|
+
VoiceConfig,
|
|
440
|
+
VoiceMetrics,
|
|
441
|
+
VoicePlaybackEngine,
|
|
442
|
+
PcmStreamPlayer,
|
|
443
|
+
AgentWidgetVoiceStatusEvent,
|
|
444
|
+
ReadAloudState,
|
|
445
|
+
SpeechEngine,
|
|
446
|
+
SpeechRequest,
|
|
447
|
+
SpeechCallbacks
|
|
439
448
|
} from "./types";
|
|
440
449
|
|
|
450
|
+
export {
|
|
451
|
+
BrowserSpeechEngine,
|
|
452
|
+
pickBestVoice,
|
|
453
|
+
ReadAloudController
|
|
454
|
+
} from "./voice";
|
|
455
|
+
export type {
|
|
456
|
+
BrowserSpeechEngineOptions,
|
|
457
|
+
ReadAloudListener,
|
|
458
|
+
FallbackSpeechEngineOptions
|
|
459
|
+
} from "./voice";
|
|
460
|
+
// RuntypeSpeechEngine + FallbackSpeechEngine ship from the
|
|
461
|
+
// `@runtypelabs/persona/voice-worklet-player` subpath, not here: keeping the
|
|
462
|
+
// hosted read-aloud engine out of the main bundle lets the IIFE/CDN build defer
|
|
463
|
+
// it to the `runtype-tts.js` chunk (loaded only for `provider: 'runtype'`). The
|
|
464
|
+
// blessed way to use it is `textToSpeech: { provider: 'runtype' }` — no import
|
|
465
|
+
// needed.
|
|
466
|
+
|
|
441
467
|
export default initAgentWidgetFn;
|