@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,215 @@
|
|
|
1
|
+
/** A unit of speech handed to a {@link SpeechEngine}. */
|
|
2
|
+
interface SpeechRequest {
|
|
3
|
+
/** Plain text to speak. The widget strips Markdown before calling the engine. */
|
|
4
|
+
text: string;
|
|
5
|
+
/** Preferred voice identifier (engine-specific; browser = voice name). */
|
|
6
|
+
voice?: string;
|
|
7
|
+
/** Speech rate (engine-specific range; browser: 0.1–10, default 1). */
|
|
8
|
+
rate?: number;
|
|
9
|
+
/** Speech pitch (engine-specific range; browser: 0–2, default 1). */
|
|
10
|
+
pitch?: number;
|
|
11
|
+
}
|
|
12
|
+
/** Lifecycle callbacks a {@link SpeechEngine} invokes during playback. */
|
|
13
|
+
interface SpeechCallbacks {
|
|
14
|
+
/** Audio has started playing. */
|
|
15
|
+
onStart?: () => void;
|
|
16
|
+
/** Playback finished naturally (or was canceled). */
|
|
17
|
+
onEnd?: () => void;
|
|
18
|
+
/** Playback failed. */
|
|
19
|
+
onError?: (error: Error) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Pluggable text-to-speech engine behind the "Read aloud" message action.
|
|
23
|
+
*
|
|
24
|
+
* The widget ships a browser Web Speech API engine by default. Provide a hosted
|
|
25
|
+
* engine (Runtype TTS, ElevenLabs, a server proxy, …) by implementing this
|
|
26
|
+
* interface and returning it from {@link TextToSpeechConfig.createEngine}; a
|
|
27
|
+
* server engine can stream audio into the realtime voice
|
|
28
|
+
* {@link VoicePlaybackEngine}.
|
|
29
|
+
*/
|
|
30
|
+
interface SpeechEngine {
|
|
31
|
+
/** Stable identifier, e.g. `"browser"`, `"runtype"`, or a custom id. */
|
|
32
|
+
readonly id: string;
|
|
33
|
+
/**
|
|
34
|
+
* Whether {@link pause}/{@link resume} are supported. When `false`, the UI
|
|
35
|
+
* offers play/stop only (tapping a playing message stops it).
|
|
36
|
+
*/
|
|
37
|
+
readonly supportsPause: boolean;
|
|
38
|
+
/** Begin speaking. Drives the lifecycle through `callbacks`. */
|
|
39
|
+
speak(request: SpeechRequest, callbacks: SpeechCallbacks): void;
|
|
40
|
+
/** Pause playback (no-op if unsupported). */
|
|
41
|
+
pause(): void;
|
|
42
|
+
/** Resume paused playback (no-op if unsupported). */
|
|
43
|
+
resume(): void;
|
|
44
|
+
/** Stop playback and discard any queued audio. */
|
|
45
|
+
stop(): void;
|
|
46
|
+
/** Optional teardown of engine resources. */
|
|
47
|
+
destroy?(): void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Streaming PCM playback engine used by the realtime voice provider.
|
|
51
|
+
*
|
|
52
|
+
* The provider feeds raw PCM16 LE mono @ 24kHz (WAV header already stripped)
|
|
53
|
+
* via `enqueue`, signals end-of-reply with `markStreamEnd`, and learns when
|
|
54
|
+
* playback has fully drained via `onFinished`. The default implementation is
|
|
55
|
+
* `AudioPlaybackManager`; an optional jitter-buffered AudioWorklet engine ships
|
|
56
|
+
* from `@runtypelabs/persona/voice-worklet-player` and can be injected via the
|
|
57
|
+
* `runtype.createPlaybackEngine` config hook.
|
|
58
|
+
*/
|
|
59
|
+
interface VoicePlaybackEngine {
|
|
60
|
+
/** Enqueue a raw PCM16 LE mono @ 24kHz chunk (no WAV header). */
|
|
61
|
+
enqueue(pcm: Uint8Array): void;
|
|
62
|
+
/** Signal that no more chunks will arrive for the current reply. */
|
|
63
|
+
markStreamEnd(): void;
|
|
64
|
+
/** Immediately stop playback and discard queued audio. */
|
|
65
|
+
flush(): void;
|
|
66
|
+
/** Register a callback fired once all queued audio has finished playing. */
|
|
67
|
+
onFinished(callback: () => void): void;
|
|
68
|
+
/** Release all audio resources. */
|
|
69
|
+
destroy(): Promise<void> | void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A {@link VoicePlaybackEngine} that also supports pause/resume. Returned by
|
|
73
|
+
* `createPcmStreamPlayer` (`@runtypelabs/persona/voice-worklet-player`): a
|
|
74
|
+
* jitter-buffered AudioWorklet player for raw PCM16 / 24 kHz / mono streams.
|
|
75
|
+
* Reuse it inside a hosted {@link SpeechEngine} to get gapless playback with a
|
|
76
|
+
* configurable prebuffer and graceful underrun handling — feed each streamed
|
|
77
|
+
* chunk to {@link VoicePlaybackEngine.enqueue} and the worklet does the rest.
|
|
78
|
+
*/
|
|
79
|
+
interface PcmStreamPlayer extends VoicePlaybackEngine {
|
|
80
|
+
/** Pause playback; the audio clock suspends and {@link resume} continues in place. */
|
|
81
|
+
pause(): void;
|
|
82
|
+
/** Resume playback after {@link pause}. */
|
|
83
|
+
resume(): void;
|
|
84
|
+
/**
|
|
85
|
+
* Register a callback fired once audible playback actually begins — i.e. the
|
|
86
|
+
* prebuffer waterline filled and the first sample reached the output. Use this
|
|
87
|
+
* (rather than "first chunk enqueued") to flip a UI from loading to playing, so
|
|
88
|
+
* the spinner holds through the prebuffer. Fires once per playback session
|
|
89
|
+
* (cleared by {@link VoicePlaybackEngine.flush}); a mid-reply underrun re-buffer
|
|
90
|
+
* does not re-fire it.
|
|
91
|
+
*/
|
|
92
|
+
onStarted(callback: () => void): void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface PcmStreamPlayerOptions {
|
|
96
|
+
/**
|
|
97
|
+
* Audio (ms) to buffer before the first sample, and to re-buffer after an
|
|
98
|
+
* underrun. Higher = smoother on bursty/jittery streams, at the cost of a
|
|
99
|
+
* slightly later first sound (latency ↔ smoothness). Default 150 — good for
|
|
100
|
+
* realtime, server-paced audio; HTTP-pulled TTS often wants ~400–600.
|
|
101
|
+
*/
|
|
102
|
+
prebufferMs?: number;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Create a jitter-buffered AudioWorklet PCM player with pause/resume.
|
|
106
|
+
*
|
|
107
|
+
* Feed it raw PCM16 / 24 kHz / mono via `enqueue()`; it handles prebuffering,
|
|
108
|
+
* gapless playback, and graceful underrun. Reuse it inside a hosted
|
|
109
|
+
* {@link SpeechEngine} so streamed TTS plays smoothly:
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* import { createPcmStreamPlayer } from '@runtypelabs/persona/voice-worklet-player'
|
|
113
|
+
* const player = await createPcmStreamPlayer({ prebufferMs: 500 })
|
|
114
|
+
* // for each streamed chunk: player.enqueue(pcmChunk)
|
|
115
|
+
* player.markStreamEnd()
|
|
116
|
+
*/
|
|
117
|
+
declare function createPcmStreamPlayer(options?: PcmStreamPlayerOptions): Promise<PcmStreamPlayer>;
|
|
118
|
+
/**
|
|
119
|
+
* Realtime-named alias of {@link createPcmStreamPlayer} (default prebuffer),
|
|
120
|
+
* typed as a plain {@link VoicePlaybackEngine}. Pass it to the realtime voice
|
|
121
|
+
* provider's `createPlaybackEngine`:
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* import { createWorkletPlaybackEngine } from '@runtypelabs/persona/voice-worklet-player'
|
|
125
|
+
*
|
|
126
|
+
* initAgentWidget({ config: { voiceRecognition: { enabled: true, provider: {
|
|
127
|
+
* type: 'runtype',
|
|
128
|
+
* runtype: { agentId, createPlaybackEngine: createWorkletPlaybackEngine },
|
|
129
|
+
* } } } })
|
|
130
|
+
*/
|
|
131
|
+
declare function createWorkletPlaybackEngine(): Promise<VoicePlaybackEngine>;
|
|
132
|
+
|
|
133
|
+
interface RuntypeSpeechEngineOptions {
|
|
134
|
+
/**
|
|
135
|
+
* Runtype API host, e.g. `https://api.runtype.com` (typically the widget's
|
|
136
|
+
* `apiUrl`). A trailing slash is tolerated.
|
|
137
|
+
*/
|
|
138
|
+
host: string;
|
|
139
|
+
/** Agent whose configured voice synthesizes the text. */
|
|
140
|
+
agentId: string;
|
|
141
|
+
/** Browser-safe client token — the same one the chat widget uses. */
|
|
142
|
+
clientToken: string;
|
|
143
|
+
/**
|
|
144
|
+
* Default voice id, used when a `SpeechRequest` doesn't carry its own. When
|
|
145
|
+
* omitted the agent's configured voice is used.
|
|
146
|
+
*/
|
|
147
|
+
voice?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Audio (ms) the player buffers before the first sample and after an
|
|
150
|
+
* underrun. Runtype streams steadily, so the default (200) keeps first sound
|
|
151
|
+
* close to time-to-first-byte while still riding out small hiccups. Applies to
|
|
152
|
+
* the default {@link AudioPlaybackManager}; a custom `createPlaybackEngine` is
|
|
153
|
+
* responsible for its own prebuffer.
|
|
154
|
+
*/
|
|
155
|
+
prebufferMs?: number;
|
|
156
|
+
/**
|
|
157
|
+
* Factory for the streaming PCM player. Defaults to the in-bundle, main-thread
|
|
158
|
+
* {@link AudioPlaybackManager} (with `prebufferMs`). Pass `createPcmStreamPlayer`
|
|
159
|
+
* from `@runtypelabs/persona/voice-worklet-player` for the jitter-buffered
|
|
160
|
+
* AudioWorklet player (it then ships in your bundle, not Persona's). May be
|
|
161
|
+
* async — it is resolved on first playback, inside the user gesture.
|
|
162
|
+
*/
|
|
163
|
+
createPlaybackEngine?: () => PcmStreamPlayer | Promise<PcmStreamPlayer>;
|
|
164
|
+
/**
|
|
165
|
+
* Optional hook for surfacing fetch/stream failures (a missing endpoint, an
|
|
166
|
+
* expired token, an upstream 4xx) to a log or telemetry. The widget itself
|
|
167
|
+
* only returns the read-aloud button to idle (or, with a fallback engine,
|
|
168
|
+
* silently switches to the browser voice), so without this the reason is
|
|
169
|
+
* invisible.
|
|
170
|
+
*/
|
|
171
|
+
onError?: (error: Error) => void;
|
|
172
|
+
}
|
|
173
|
+
/** Streaming `SpeechEngine` backed by Runtype's `/v1/agents/:id/speak`. */
|
|
174
|
+
declare class RuntypeSpeechEngine implements SpeechEngine {
|
|
175
|
+
private readonly opts;
|
|
176
|
+
readonly id = "runtype-tts";
|
|
177
|
+
readonly supportsPause = true;
|
|
178
|
+
private player;
|
|
179
|
+
private playerPromise;
|
|
180
|
+
private generation;
|
|
181
|
+
constructor(opts: RuntypeSpeechEngineOptions);
|
|
182
|
+
private ensurePlayer;
|
|
183
|
+
speak(request: SpeechRequest, callbacks: SpeechCallbacks): void;
|
|
184
|
+
private run;
|
|
185
|
+
pause(): void;
|
|
186
|
+
resume(): void;
|
|
187
|
+
stop(): void;
|
|
188
|
+
destroy(): void;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
interface FallbackSpeechEngineOptions {
|
|
192
|
+
/**
|
|
193
|
+
* Called once when the primary engine fails before audio starts and the
|
|
194
|
+
* fallback takes over — so a silent downgrade is still observable in dev/
|
|
195
|
+
* telemetry even though the user keeps hearing speech.
|
|
196
|
+
*/
|
|
197
|
+
onFallback?: (error: Error) => void;
|
|
198
|
+
}
|
|
199
|
+
/** A `SpeechEngine` that falls back from `primary` to `fallback` per utterance. */
|
|
200
|
+
declare class FallbackSpeechEngine implements SpeechEngine {
|
|
201
|
+
private readonly primary;
|
|
202
|
+
private readonly fallback;
|
|
203
|
+
private readonly options;
|
|
204
|
+
readonly id = "fallback";
|
|
205
|
+
private active;
|
|
206
|
+
constructor(primary: SpeechEngine, fallback: SpeechEngine, options?: FallbackSpeechEngineOptions);
|
|
207
|
+
get supportsPause(): boolean;
|
|
208
|
+
speak(request: SpeechRequest, callbacks: SpeechCallbacks): void;
|
|
209
|
+
pause(): void;
|
|
210
|
+
resume(): void;
|
|
211
|
+
stop(): void;
|
|
212
|
+
destroy(): void;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export { FallbackSpeechEngine, type FallbackSpeechEngineOptions, type PcmStreamPlayer, type PcmStreamPlayerOptions, RuntypeSpeechEngine, type RuntypeSpeechEngineOptions, type VoicePlaybackEngine, createPcmStreamPlayer, createWorkletPlaybackEngine };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
var P=`
|
|
2
|
+
class PersonaPcmPlayerProcessor extends AudioWorkletProcessor {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
super()
|
|
5
|
+
const opts = (options && options.processorOptions) || {}
|
|
6
|
+
this.waterline = opts.waterlineSamples > 0 ? opts.waterlineSamples : 3600
|
|
7
|
+
this.chunks = []
|
|
8
|
+
this.readOffset = 0
|
|
9
|
+
this.buffered = 0
|
|
10
|
+
this.waiting = true
|
|
11
|
+
// 'drained' must mean "the reply finished playing", not "momentary
|
|
12
|
+
// underrun": a jitter gap empties the buffer mid-reply too, and firing
|
|
13
|
+
// there would flap the UI status and race the audio_end handler. Only
|
|
14
|
+
// report drained once eos has been signalled.
|
|
15
|
+
this.eosSeen = false
|
|
16
|
+
// Fire 'started' once, when the prebuffer first releases into playback, so a
|
|
17
|
+
// consumer can flip UI from loading\u2192playing only when audio is truly audible.
|
|
18
|
+
// A mid-reply underrun re-buffers (waiting=true) but must NOT re-signal.
|
|
19
|
+
this.startedSignaled = false
|
|
20
|
+
this.port.onmessage = (e) => {
|
|
21
|
+
const msg = e.data
|
|
22
|
+
if (msg.type === 'push') {
|
|
23
|
+
this.eosSeen = false
|
|
24
|
+
this.chunks.push(msg.samples)
|
|
25
|
+
this.buffered += msg.samples.length
|
|
26
|
+
if (this.waiting && this.buffered >= this.waterline) {
|
|
27
|
+
this.waiting = false
|
|
28
|
+
this.signalStarted()
|
|
29
|
+
}
|
|
30
|
+
} else if (msg.type === 'eos') {
|
|
31
|
+
this.eosSeen = true
|
|
32
|
+
if (this.waiting && this.buffered > 0) {
|
|
33
|
+
this.waiting = false
|
|
34
|
+
this.signalStarted()
|
|
35
|
+
}
|
|
36
|
+
if (this.buffered === 0) {
|
|
37
|
+
this.eosSeen = false
|
|
38
|
+
this.port.postMessage({ type: 'drained' })
|
|
39
|
+
}
|
|
40
|
+
} else if (msg.type === 'clear') {
|
|
41
|
+
this.chunks = []
|
|
42
|
+
this.readOffset = 0
|
|
43
|
+
this.buffered = 0
|
|
44
|
+
this.waiting = true
|
|
45
|
+
this.eosSeen = false
|
|
46
|
+
this.startedSignaled = false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
signalStarted() {
|
|
51
|
+
if (!this.startedSignaled) {
|
|
52
|
+
this.startedSignaled = true
|
|
53
|
+
this.port.postMessage({ type: 'started' })
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
process(inputs, outputs) {
|
|
57
|
+
const out = outputs[0][0]
|
|
58
|
+
if (!out || this.waiting) return true // outputs are pre-zeroed: silence
|
|
59
|
+
let i = 0
|
|
60
|
+
while (i < out.length && this.buffered > 0) {
|
|
61
|
+
const chunk = this.chunks[0]
|
|
62
|
+
out[i++] = chunk[this.readOffset++]
|
|
63
|
+
this.buffered--
|
|
64
|
+
if (this.readOffset >= chunk.length) {
|
|
65
|
+
this.chunks.shift()
|
|
66
|
+
this.readOffset = 0
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (this.buffered === 0) {
|
|
70
|
+
this.waiting = true // mid-reply underrun: re-buffer silently
|
|
71
|
+
if (this.eosSeen) {
|
|
72
|
+
this.eosSeen = false
|
|
73
|
+
this.port.postMessage({ type: 'drained' })
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
registerProcessor('persona-pcm-player', PersonaPcmPlayerProcessor)
|
|
80
|
+
`;function b(o){let e=o.length>>1,t=new Float32Array(e),r=new DataView(o.buffer,o.byteOffset,o.byteLength);for(let s=0;s<e;s++)t[s]=r.getInt16(s*2,!0)/32768;return t}async function v(o={}){var f;let e=(f=o.prebufferMs)!=null?f:150,t=Math.max(1,Math.round(24e3*e/1e3)),r=window.AudioContext||window.webkitAudioContext,s=new r({sampleRate:24e3});s.state==="suspended"&&await s.resume().catch(()=>{});let a=URL.createObjectURL(new Blob([P],{type:"application/javascript"}));try{await s.audioWorklet.addModule(a)}catch(u){throw s.close().catch(()=>{}),u}finally{URL.revokeObjectURL(a)}let i=new AudioWorkletNode(s,"persona-pcm-player",{numberOfInputs:0,numberOfOutputs:1,outputChannelCount:[1],processorOptions:{waterlineSamples:t}});i.connect(s.destination);let p=[],h=[],l=null;return i.port.onmessage=u=>{var d;let n=(d=u.data)==null?void 0:d.type;if(n==="started"){let c=h.slice();h=[],c.forEach(y=>y())}else if(n==="drained"){let c=p.slice();p=[],c.forEach(y=>y())}},{enqueue(u){let n=u;if(l){let c=new Uint8Array(l.length+u.length);c.set(l),c.set(u,l.length),n=c,l=null}if(n.length%2!==0&&(l=new Uint8Array([n[n.length-1]]),n=n.subarray(0,n.length-1)),n.length===0)return;let d=b(n);d.length!==0&&i.port.postMessage({type:"push",samples:d},[d.buffer])},markStreamEnd(){i.port.postMessage({type:"eos"})},flush(){l=null,p=[],h=[],i.port.postMessage({type:"clear"})},onFinished(u){p.push(u)},onStarted(u){h.push(u)},pause(){s.state==="running"&&s.suspend()},resume(){s.state==="suspended"&&s.resume()},destroy(){i.port.onmessage=null;try{i.disconnect()}catch{}return s.close().catch(()=>{})}}}function w(){return v()}var m=class{constructor(e=24e3,t={}){this.ctx=null;this.nextStartTime=0;this.activeSources=[];this.finishedCallbacks=[];this.startedCallbacks=[];this.playing=!1;this.streamEnded=!1;this.pendingCount=0;this.started=!1;this.userPaused=!1;this.pendingBuffers=[];this.pendingSamples=0;this.remainder=null;var s;this.sampleRate=e;let r=Math.max(0,(s=t.prebufferMs)!=null?s:0);this.waterlineSamples=Math.round(e*r/1e3),this.buffering=this.waterlineSamples>0}ensureContext(){if(!this.ctx){let t=typeof window!="undefined"?window:void 0;if(!t)throw new Error("AudioPlaybackManager requires a browser environment");let r=t.AudioContext||t.webkitAudioContext;this.ctx=new r({sampleRate:this.sampleRate})}let e=this.ctx;return e.state==="suspended"&&!this.userPaused&&e.resume(),e}enqueue(e){if(e.length===0)return;let t=e;if(this.remainder){let s=new Uint8Array(this.remainder.length+e.length);s.set(this.remainder),s.set(e,this.remainder.length),t=s,this.remainder=null}if(t.length%2!==0&&(this.remainder=new Uint8Array([t[t.length-1]]),t=t.subarray(0,t.length-1)),t.length===0)return;let r=this.pcmToFloat32(t);r.length!==0&&(this.buffering?(this.pendingBuffers.push(r),this.pendingSamples+=r.length,this.pendingSamples>=this.waterlineSamples&&this.releaseBuffer()):this.scheduleSamples(r))}markStreamEnd(){this.pendingBuffers.length>0&&this.releaseBuffer(),this.streamEnded=!0,this.checkFinished()}flush(){for(let e of this.activeSources)try{e.stop(),e.disconnect()}catch{}this.activeSources=[],this.pendingCount=0,this.nextStartTime=0,this.playing=!1,this.streamEnded=!1,this.finishedCallbacks=[],this.startedCallbacks=[],this.remainder=null,this.pendingBuffers=[],this.pendingSamples=0,this.buffering=this.waterlineSamples>0,this.started=!1}isPlaying(){return this.playing}onFinished(e){this.finishedCallbacks.push(e)}onStarted(e){this.startedCallbacks.push(e)}pause(){this.userPaused=!0,this.ctx&&this.ctx.state==="running"&&this.ctx.suspend()}resume(){this.userPaused=!1,this.ctx&&this.ctx.state==="suspended"&&this.ctx.resume()}async destroy(){this.flush(),this.ctx&&(await this.ctx.close(),this.ctx=null)}releaseBuffer(){this.buffering=!1;let e=this.pendingBuffers;this.pendingBuffers=[],this.pendingSamples=0;for(let t of e)this.scheduleSamples(t)}scheduleSamples(e){if(e.length===0)return;let t=this.ensureContext(),r=t.createBuffer(1,e.length,this.sampleRate);r.getChannelData(0).set(e);let s=t.createBufferSource();s.buffer=r,s.connect(t.destination);let a=t.currentTime;if(this.nextStartTime===0?this.nextStartTime=a:this.nextStartTime<a&&(this.nextStartTime=a,this.waterlineSamples>0&&(this.buffering=!0)),s.start(this.nextStartTime),this.nextStartTime+=r.duration,this.activeSources.push(s),this.pendingCount++,this.playing=!0,!this.started){this.started=!0;let i=this.startedCallbacks.slice();this.startedCallbacks=[];for(let p of i)p()}s.onended=()=>{let i=this.activeSources.indexOf(s);i!==-1&&this.activeSources.splice(i,1),this.pendingCount--,this.checkFinished()}}checkFinished(){if(this.streamEnded&&this.pendingCount<=0&&this.pendingBuffers.length===0){this.playing=!1,this.streamEnded=!1;let e=this.finishedCallbacks.slice();this.finishedCallbacks=[];for(let t of e)t()}}pcmToFloat32(e){let t=Math.floor(e.length/2),r=new Float32Array(t),s=new DataView(e.buffer,e.byteOffset,e.byteLength);for(let a=0;a<t;a++){let i=s.getInt16(a*2,!0);r[a]=i/32768}return r}};function E(o){return o.replace(/\/+$/,"")}var g=class{constructor(e){this.opts=e;this.id="runtype-tts";this.supportsPause=!0;this.player=null;this.playerPromise=null;this.generation=0}ensurePlayer(){var e,t;return(t=this.playerPromise)!=null?t:this.playerPromise=Promise.resolve(this.opts.createPlaybackEngine?this.opts.createPlaybackEngine():new m(24e3,{prebufferMs:(e=this.opts.prebufferMs)!=null?e:200})).then(r=>this.player=r)}speak(e,t){let r=++this.generation;this.run(r,e,t)}async run(e,t,r){var s,a,i,p;try{let h=await this.ensurePlayer();if(e!==this.generation)return;h.flush(),h.resume(),h.onStarted(()=>{var n;e===this.generation&&((n=r.onStart)==null||n.call(r))}),h.onFinished(()=>{var n;e===this.generation&&((n=r.onEnd)==null||n.call(r))});let l=`${E(this.opts.host)}/v1/agents/${encodeURIComponent(this.opts.agentId)}/speak`,f=await fetch(l,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.opts.clientToken}`},body:JSON.stringify({text:t.text,voice:(s=t.voice)!=null?s:this.opts.voice,format:"pcm"})});if(e!==this.generation)return;if(!f.ok||!f.body)throw new Error(await x(f));let u=f.body.getReader();for(;;){let{done:n,value:d}=await u.read();if(e!==this.generation){await u.cancel().catch(()=>{});return}if(n)break;d&&d.byteLength>0&&h.enqueue(d)}h.markStreamEnd()}catch(h){if(e!==this.generation)return;let l=h instanceof Error?h:new Error(String(h));(i=(a=this.opts).onError)==null||i.call(a,l),(p=r.onError)==null||p.call(r,l)}}pause(){var e;(e=this.player)==null||e.pause()}resume(){var e;(e=this.player)==null||e.resume()}stop(){var e;this.generation++,(e=this.player)==null||e.flush()}destroy(){var e;this.generation++,(e=this.player)==null||e.destroy(),this.player=null,this.playerPromise=null}};async function x(o){var e,t;try{let r=await o.json();return r.detail?`${(e=r.error)!=null?e:`Runtype TTS ${o.status}`}: ${r.detail}`:(t=r.error)!=null?t:`Runtype TTS request failed (${o.status})`}catch{return`Runtype TTS request failed (${o.status})`}}var S=class{constructor(e,t,r={}){this.primary=e;this.fallback=t;this.options=r;this.id="fallback";this.active=e}get supportsPause(){return this.active.supportsPause}speak(e,t){this.active=this.primary;let r=!1;this.primary.speak(e,{onStart:()=>{var s;r=!0,(s=t.onStart)==null||s.call(t)},onEnd:()=>{var s;return(s=t.onEnd)==null?void 0:s.call(t)},onError:s=>{var a,i,p;if(r){(a=t.onError)==null||a.call(t,s);return}(p=(i=this.options).onFallback)==null||p.call(i,s),this.active=this.fallback,this.fallback.speak(e,t)}})}pause(){this.active.pause()}resume(){this.active.resume()}stop(){this.active.stop()}destroy(){var e,t,r,s;(t=(e=this.primary).destroy)==null||t.call(e),(s=(r=this.fallback).destroy)==null||s.call(r)}};export{S as FallbackSpeechEngine,g as RuntypeSpeechEngine,v as createPcmStreamPlayer,w as createWorkletPlaybackEngine};
|
package/dist/widget.css
CHANGED
|
@@ -1027,7 +1027,7 @@
|
|
|
1027
1027
|
opacity: 1;
|
|
1028
1028
|
}
|
|
1029
1029
|
|
|
1030
|
-
/* Hide tooltips on touch devices
|
|
1030
|
+
/* Hide tooltips on touch devices: hover doesn't exist, and they cause clipping issues */
|
|
1031
1031
|
@media (hover: none), (max-width: 500px) {
|
|
1032
1032
|
.persona-send-button-wrapper:hover .persona-send-button-tooltip,
|
|
1033
1033
|
.persona-send-button-wrapper:focus-within .persona-send-button-tooltip {
|
|
@@ -1144,7 +1144,7 @@
|
|
|
1144
1144
|
animation: persona-voice-processing-spin 1.2s linear infinite;
|
|
1145
1145
|
}
|
|
1146
1146
|
|
|
1147
|
-
/* Voice speaking animation (gentle pulse
|
|
1147
|
+
/* Voice speaking animation (gentle pulse: slower/subtler than recording) */
|
|
1148
1148
|
@keyframes persona-voice-speaking-pulse {
|
|
1149
1149
|
0%, 100% { opacity: 1; transform: scale(1); }
|
|
1150
1150
|
50% { opacity: 0.85; transform: scale(1.03); }
|
|
@@ -1759,7 +1759,7 @@
|
|
|
1759
1759
|
animation: persona-message-actions-fade-in 0.3s ease-out forwards;
|
|
1760
1760
|
}
|
|
1761
1761
|
|
|
1762
|
-
/* ask_user_question
|
|
1762
|
+
/* ask_user_question: collapsed answered state.
|
|
1763
1763
|
* When the user picks an option, the interactive card is replaced with a
|
|
1764
1764
|
* plain assistant bubble showing the question text. A short fade + slight
|
|
1765
1765
|
* upward slide sells the "card resolved into history" feel.
|
|
@@ -1948,6 +1948,18 @@
|
|
|
1948
1948
|
opacity: 0.9;
|
|
1949
1949
|
}
|
|
1950
1950
|
|
|
1951
|
+
/* Read-aloud loading state: spin the loader icon while audio is prepared
|
|
1952
|
+
(near-instant for browser TTS; visible for a hosted/server engine fetch). */
|
|
1953
|
+
.persona-message-action-btn.persona-message-action-loading svg {
|
|
1954
|
+
animation: persona-voice-processing-spin 1s linear infinite;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1958
|
+
.persona-message-action-btn.persona-message-action-loading svg {
|
|
1959
|
+
animation: none;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1951
1963
|
/* Vote pop animation */
|
|
1952
1964
|
@keyframes persona-vote-pop {
|
|
1953
1965
|
0% { transform: scale(1); }
|
|
@@ -2228,7 +2240,7 @@
|
|
|
2228
2240
|
background-color: var(--cw-container, #f8fafc);
|
|
2229
2241
|
}
|
|
2230
2242
|
|
|
2231
|
-
/* Collapsed live preview blocks
|
|
2243
|
+
/* Collapsed live preview blocks: match expanded content's 1px border-top with transparent border */
|
|
2232
2244
|
[data-persona-root] [data-persona-collapsed-preview] {
|
|
2233
2245
|
border-top: 1px solid transparent;
|
|
2234
2246
|
}
|
|
@@ -2380,7 +2392,7 @@
|
|
|
2380
2392
|
}
|
|
2381
2393
|
|
|
2382
2394
|
/* ============================================
|
|
2383
|
-
Visibility (Tailwind parity
|
|
2395
|
+
Visibility (Tailwind parity: prefix persona-)
|
|
2384
2396
|
Shipped widget.css is hand-maintained; these utilities are used in TS but not generated by Tailwind at build time.
|
|
2385
2397
|
============================================ */
|
|
2386
2398
|
[data-persona-root] .persona-hidden {
|
|
@@ -2402,7 +2414,7 @@
|
|
|
2402
2414
|
gap: var(--persona-artifact-split-gap, 0.5rem);
|
|
2403
2415
|
}
|
|
2404
2416
|
|
|
2405
|
-
/* Document toolbar preset
|
|
2417
|
+
/* Document toolbar preset: optional layout vars: --persona-artifact-doc-toolbar-icon-color, etc. */
|
|
2406
2418
|
[data-persona-root] .persona-artifact-toolbar-document .persona-artifact-doc-icon-btn {
|
|
2407
2419
|
display: inline-flex;
|
|
2408
2420
|
align-items: center;
|
|
@@ -2495,7 +2507,7 @@
|
|
|
2495
2507
|
|
|
2496
2508
|
/* ── Composable button utilities ── */
|
|
2497
2509
|
|
|
2498
|
-
/* Icon button
|
|
2510
|
+
/* Icon button: base for all icon-only buttons created by createIconButton() */
|
|
2499
2511
|
[data-persona-root] .persona-icon-btn {
|
|
2500
2512
|
display: inline-flex;
|
|
2501
2513
|
align-items: center;
|
|
@@ -2525,7 +2537,7 @@
|
|
|
2525
2537
|
border-color: var(--persona-icon-btn-active-border, var(--persona-border, #e5e7eb));
|
|
2526
2538
|
}
|
|
2527
2539
|
|
|
2528
|
-
/* Label button
|
|
2540
|
+
/* Label button: icon + text button created by createLabelButton() */
|
|
2529
2541
|
[data-persona-root] .persona-label-btn {
|
|
2530
2542
|
display: inline-flex;
|
|
2531
2543
|
align-items: center;
|
|
@@ -2644,7 +2656,7 @@
|
|
|
2644
2656
|
outline-offset: 2px;
|
|
2645
2657
|
}
|
|
2646
2658
|
|
|
2647
|
-
/* Toggle group
|
|
2659
|
+
/* Toggle group: mutually exclusive button set created by createToggleGroup() */
|
|
2648
2660
|
[data-persona-root] .persona-toggle-group {
|
|
2649
2661
|
display: inline-flex;
|
|
2650
2662
|
gap: var(--persona-toggle-group-gap, 0);
|
|
@@ -2664,7 +2676,7 @@
|
|
|
2664
2676
|
border-bottom-right-radius: var(--persona-toggle-group-radius, var(--persona-icon-btn-radius, var(--persona-radius-md, 0.375rem)));
|
|
2665
2677
|
}
|
|
2666
2678
|
|
|
2667
|
-
/* Combo button
|
|
2679
|
+
/* Combo button: label + chevron with dropdown */
|
|
2668
2680
|
[data-persona-root] .persona-combo-btn {
|
|
2669
2681
|
font-size: var(--persona-combo-btn-font-size, 0.8125rem);
|
|
2670
2682
|
font-weight: var(--persona-combo-btn-font-weight, 600);
|
|
@@ -2758,7 +2770,7 @@
|
|
|
2758
2770
|
}
|
|
2759
2771
|
}
|
|
2760
2772
|
|
|
2761
|
-
/* paneBorderRadius
|
|
2773
|
+
/* paneBorderRadius: works on any paneAppearance; overflow clips content to rounded shape */
|
|
2762
2774
|
[data-persona-root] .persona-artifact-pane {
|
|
2763
2775
|
border-radius: var(--persona-artifact-pane-radius, 0);
|
|
2764
2776
|
overflow: hidden;
|
|
@@ -2769,7 +2781,7 @@
|
|
|
2769
2781
|
);
|
|
2770
2782
|
}
|
|
2771
2783
|
|
|
2772
|
-
/* paneAppearance: 'seamless'
|
|
2784
|
+
/* paneAppearance: 'seamless': flush with chat, no border/shadow/gap */
|
|
2773
2785
|
/* `position: relative` for resizer overlay; gap 0; positioning is applied in ui.ts */
|
|
2774
2786
|
[data-persona-root].persona-artifact-appearance-seamless .persona-artifact-split-root {
|
|
2775
2787
|
position: relative;
|
|
@@ -2787,7 +2799,7 @@
|
|
|
2787
2799
|
);
|
|
2788
2800
|
}
|
|
2789
2801
|
|
|
2790
|
-
/* layout.paneBorder / paneBorderLeft
|
|
2802
|
+
/* layout.paneBorder / paneBorderLeft: theme overrides (after appearance defaults) */
|
|
2791
2803
|
[data-persona-root].persona-artifact-border-full .persona-artifact-pane {
|
|
2792
2804
|
border: var(--persona-artifact-pane-border) !important;
|
|
2793
2805
|
}
|
|
@@ -2869,7 +2881,7 @@
|
|
|
2869
2881
|
}
|
|
2870
2882
|
|
|
2871
2883
|
/* ============================================================
|
|
2872
|
-
Stream animations
|
|
2884
|
+
Stream animations: reveal effects for assistant message text
|
|
2873
2885
|
while streaming. Opt-in via `features.streamAnimation.type`.
|
|
2874
2886
|
Units are staggered via `--char-index` / `--word-index`
|
|
2875
2887
|
(set inline on each wrapper span). Timing is configured via
|
|
@@ -2879,7 +2891,7 @@
|
|
|
2879
2891
|
|
|
2880
2892
|
/* Per-char/per-word spans need to be inline-block so `transform` works for
|
|
2881
2893
|
the rise/fade animations. Each span animates from the moment it is first
|
|
2882
|
-
added to the DOM
|
|
2894
|
+
added to the DOM: streaming itself provides the visible stagger, so the
|
|
2883
2895
|
CSS animation has no per-index delay. An index-based delay would compound
|
|
2884
2896
|
with the stream's arrival cadence and leave later chars permanently hidden. */
|
|
2885
2897
|
[data-persona-root] .persona-stream-char,
|
|
@@ -2925,7 +2937,7 @@
|
|
|
2925
2937
|
ease-out both;
|
|
2926
2938
|
}
|
|
2927
2939
|
|
|
2928
|
-
/* The following animations live in subpath plugin modules
|
|
2940
|
+
/* The following animations live in subpath plugin modules: their CSS is
|
|
2929
2941
|
injected by the plugin when activated, not by the core stylesheet:
|
|
2930
2942
|
- `wipe` → @runtypelabs/persona/animations/wipe
|
|
2931
2943
|
- `glyph-cycle` → @runtypelabs/persona/animations/glyph-cycle */
|
|
@@ -2955,6 +2967,13 @@
|
|
|
2955
2967
|
animation: persona-stream-blink 1s steps(1) infinite;
|
|
2956
2968
|
}
|
|
2957
2969
|
|
|
2970
|
+
/* ---------- inline timestamp (trails the last line of message text) ---------- */
|
|
2971
|
+
[data-persona-root] .persona-timestamp-inline {
|
|
2972
|
+
display: inline-block;
|
|
2973
|
+
margin-left: 0.5rem;
|
|
2974
|
+
white-space: nowrap;
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2958
2977
|
/* ---------- skeleton placeholder (pre-first-token) ---------- */
|
|
2959
2978
|
@keyframes persona-stream-skeleton-shimmer {
|
|
2960
2979
|
0% { background-position: 200% 0; }
|
|
@@ -3002,7 +3021,7 @@
|
|
|
3002
3021
|
}
|
|
3003
3022
|
|
|
3004
3023
|
/* ========================================================================
|
|
3005
|
-
* ask_user_question
|
|
3024
|
+
* ask_user_question: built-in answer-pill sheet
|
|
3006
3025
|
* Slides in over the composer when the assistant invokes `ask_user_question`.
|
|
3007
3026
|
* Overridable via `config.features.askUserQuestion.styles`.
|
|
3008
3027
|
* ======================================================================== */
|
|
@@ -3011,7 +3030,7 @@
|
|
|
3011
3030
|
display: none !important;
|
|
3012
3031
|
}
|
|
3013
3032
|
|
|
3014
|
-
/* In-transcript stub
|
|
3033
|
+
/* In-transcript stub: small "Awaiting…" chip. */
|
|
3015
3034
|
[data-persona-root] .persona-ask-stub {
|
|
3016
3035
|
padding: 0.35rem 0.7rem;
|
|
3017
3036
|
border-radius: 999px;
|
|
@@ -3025,7 +3044,7 @@
|
|
|
3025
3044
|
font-weight: 500;
|
|
3026
3045
|
}
|
|
3027
3046
|
|
|
3028
|
-
/* Sheet container
|
|
3047
|
+
/* Sheet container: absolute inside composerOverlay. */
|
|
3029
3048
|
[data-persona-root] .persona-ask-sheet {
|
|
3030
3049
|
position: absolute;
|
|
3031
3050
|
left: 0.75rem;
|
|
@@ -3053,7 +3072,7 @@
|
|
|
3053
3072
|
pointer-events: none;
|
|
3054
3073
|
}
|
|
3055
3074
|
|
|
3056
|
-
/* Header row
|
|
3075
|
+
/* Header row: question + close button */
|
|
3057
3076
|
[data-persona-root] .persona-ask-sheet-header {
|
|
3058
3077
|
margin-bottom: 0.55rem;
|
|
3059
3078
|
}
|
|
@@ -3094,7 +3113,7 @@
|
|
|
3094
3113
|
display: none;
|
|
3095
3114
|
}
|
|
3096
3115
|
|
|
3097
|
-
/* Option list
|
|
3116
|
+
/* Option list: stacked one per row by default ("rows" layout). The "pills"
|
|
3098
3117
|
layout opt-in switches to horizontal wrap with compact pills; see also
|
|
3099
3118
|
horizontalPillsAskPlugin example for a custom variant. */
|
|
3100
3119
|
[data-persona-root] .persona-ask-pills {
|
|
@@ -3111,7 +3130,7 @@
|
|
|
3111
3130
|
gap: 0.4rem;
|
|
3112
3131
|
}
|
|
3113
3132
|
|
|
3114
|
-
/* Pills layout opt-in
|
|
3133
|
+
/* Pills layout opt-in: horizontal wrap, auto-width compact pills. */
|
|
3115
3134
|
[data-persona-root] .persona-ask-sheet--pills .persona-ask-pills:not(.persona-ask-pills--rows) {
|
|
3116
3135
|
flex-direction: row;
|
|
3117
3136
|
flex-wrap: wrap;
|
|
@@ -3188,7 +3207,7 @@
|
|
|
3188
3207
|
100% { background-position: -100% 50%; }
|
|
3189
3208
|
}
|
|
3190
3209
|
|
|
3191
|
-
/* Row layout
|
|
3210
|
+
/* Row layout: full-width stacked rows with always-visible description
|
|
3192
3211
|
and a right-edge affordance (number badge for single-select, check for
|
|
3193
3212
|
multi-select). Layered on top of .persona-ask-pill base styles. */
|
|
3194
3213
|
[data-persona-root] .persona-ask-row {
|
|
@@ -3291,7 +3310,7 @@
|
|
|
3291
3310
|
height: 3rem;
|
|
3292
3311
|
}
|
|
3293
3312
|
|
|
3294
|
-
/* Other row (rows mode)
|
|
3313
|
+
/* Other row (rows mode): composite row that contains the free-text input
|
|
3295
3314
|
* directly. The input fills the row body; the badge sits on the right. */
|
|
3296
3315
|
[data-persona-root] .persona-ask-row--other {
|
|
3297
3316
|
cursor: text;
|
|
@@ -3358,7 +3377,7 @@
|
|
|
3358
3377
|
cursor: not-allowed;
|
|
3359
3378
|
}
|
|
3360
3379
|
|
|
3361
|
-
/* Nav row
|
|
3380
|
+
/* Nav row: Back / Skip / Next-or-Submit buttons for grouped payloads. */
|
|
3362
3381
|
[data-persona-root] .persona-ask-nav-back,
|
|
3363
3382
|
[data-persona-root] .persona-ask-nav-skip {
|
|
3364
3383
|
padding: 0.45rem 0.85rem;
|
|
@@ -3416,11 +3435,11 @@
|
|
|
3416
3435
|
* `applyComposerBarGeometry()` in ui.ts. The pill composer
|
|
3417
3436
|
* (`pill-composer-builder.ts`) ships with its own clean className
|
|
3418
3437
|
* (`persona-pill-composer`, no `persona-flex-col` / `persona-rounded-2xl`
|
|
3419
|
-
* baggage), so layout CSS does not need to fight utility classes
|
|
3438
|
+
* baggage), so layout CSS does not need to fight utility classes: no
|
|
3420
3439
|
* !important. The pill is the visible element in both collapsed and
|
|
3421
3440
|
* expanded states; only the panel above it appears/disappears.
|
|
3422
3441
|
*
|
|
3423
|
-
* Avoid `transform: scale(...)` on the wrapper
|
|
3442
|
+
* Avoid `transform: scale(...)` on the wrapper: it breaks the textarea
|
|
3424
3443
|
* caret/IME.
|
|
3425
3444
|
* ======================================================================= */
|
|
3426
3445
|
.persona-widget-wrapper[data-persona-composer-bar] {
|
|
@@ -3446,14 +3465,14 @@
|
|
|
3446
3465
|
* so `top/left/transform` resolve to their auto/none defaults) to its
|
|
3447
3466
|
* expanded position. The default `transform 220ms ease` transition would
|
|
3448
3467
|
* interpolate `none → translate(...)` and visibly slide the wrapper from
|
|
3449
|
-
* its static-default origin toward its target
|
|
3468
|
+
* its static-default origin toward its target: diagonally for modal,
|
|
3450
3469
|
* horizontally from the right for anchored. Neither mode is morphing
|
|
3451
3470
|
* something the user can see (the wrapper is invisible when collapsed
|
|
3452
3471
|
* because the pill lives in pillRoot, not the wrapper), so the slide
|
|
3453
3472
|
* is pure motion noise. Disable geometry transitions for both; the
|
|
3454
3473
|
* container's opacity fade-in keyframe is the reveal.
|
|
3455
3474
|
*
|
|
3456
|
-
* Fullscreen intentionally keeps its geometry transition
|
|
3475
|
+
* Fullscreen intentionally keeps its geometry transition: that's the
|
|
3457
3476
|
* one mode where the wrapper genuinely morphs (empty → full viewport),
|
|
3458
3477
|
* and the staggered fade-in cascade below is built to mask the
|
|
3459
3478
|
* outer-edge/inner-content desync during that morph. */
|
|
@@ -3489,7 +3508,7 @@
|
|
|
3489
3508
|
padding: 4px 0;
|
|
3490
3509
|
}
|
|
3491
3510
|
|
|
3492
|
-
/* Responsive default width for the collapsed pill
|
|
3511
|
+
/* Responsive default width for the collapsed pill: applied only when
|
|
3493
3512
|
* `applyComposerBarGeometry()` leaves the wrapper's inline width empty
|
|
3494
3513
|
* (i.e., the user did NOT set `composerBar.collapsedMaxWidth`). Inline
|
|
3495
3514
|
* width from a user config still wins via specificity (inline > class). */
|
|
@@ -3543,8 +3562,7 @@
|
|
|
3543
3562
|
* In composer-bar mode the pill (`footer`) and peek banner live in a
|
|
3544
3563
|
* viewport-fixed sibling of the wrapper, NOT inside the wrapper. Reason:
|
|
3545
3564
|
* modal mode applies `transform: translate(-50%, -50%)` to the wrapper,
|
|
3546
|
-
* which establishes a containing block for `position: fixed` descendants
|
|
3547
|
-
* a fixed pill inside a transformed wrapper would be positioned relative
|
|
3565
|
+
* which establishes a containing block for `position: fixed` descendants: * a fixed pill inside a transformed wrapper would be positioned relative
|
|
3548
3566
|
* to the wrapper, not the viewport.
|
|
3549
3567
|
*
|
|
3550
3568
|
* Bottom offset and (optionally) width are written inline by ui.ts based
|
|
@@ -3574,7 +3592,7 @@
|
|
|
3574
3592
|
}
|
|
3575
3593
|
}
|
|
3576
3594
|
|
|
3577
|
-
/* Container hidden when the chat is collapsed
|
|
3595
|
+
/* Container hidden when the chat is collapsed: only the pill is visible. */
|
|
3578
3596
|
[data-persona-composer-bar][data-state="collapsed"] .persona-widget-container {
|
|
3579
3597
|
display: none;
|
|
3580
3598
|
}
|
|
@@ -3614,7 +3632,7 @@
|
|
|
3614
3632
|
transform 150ms ease,
|
|
3615
3633
|
background-color 150ms ease;
|
|
3616
3634
|
text-align: left;
|
|
3617
|
-
/* Full-width row that matches the pill below
|
|
3635
|
+
/* Full-width row that matches the pill below: text takes the middle
|
|
3618
3636
|
* (flex: 1 1 auto) and the chevron pins to the right edge. */
|
|
3619
3637
|
width: 100%;
|
|
3620
3638
|
align-self: stretch;
|
|
@@ -3700,7 +3718,7 @@
|
|
|
3700
3718
|
* (including the area behind the fixed pill) so messages scroll under the
|
|
3701
3719
|
* pill. The body itself drops its bottom padding so its background extends
|
|
3702
3720
|
* to the viewport edge; reachability for the last bubble comes from
|
|
3703
|
-
* padding-bottom on the messages wrapper (body's last child)
|
|
3721
|
+
* padding-bottom on the messages wrapper (body's last child): that
|
|
3704
3722
|
* pushes the final message above the pill area rather than hiding it
|
|
3705
3723
|
* behind. */
|
|
3706
3724
|
.persona-widget-wrapper[data-persona-composer-bar][data-expanded-size="fullscreen"] .persona-widget-body {
|
|
@@ -3726,7 +3744,7 @@
|
|
|
3726
3744
|
* so the box appears to materialize as it grows rather than snap into
|
|
3727
3745
|
* existence at frame 0.
|
|
3728
3746
|
* - Body content (intro card, messages, composer overlay) fades in AFTER
|
|
3729
|
-
* the geometry has settled
|
|
3747
|
+
* the geometry has settled: `animation-delay: 220ms` plus
|
|
3730
3748
|
* `animation-fill-mode: both` keeps it at opacity 0 during the morph.
|
|
3731
3749
|
*
|
|
3732
3750
|
* Because `[data-state="collapsed"] .persona-widget-container { display:
|