@runtypelabs/persona 4.15.0 → 4.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/context-mentions.d.cts +283 -9
- package/dist/context-mentions.d.ts +283 -9
- package/dist/index.cjs +68 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +299 -10
- package/dist/index.d.ts +299 -10
- package/dist/index.global.js +57 -57
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +69 -69
- 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 +36 -36
- package/dist/smart-dom-reader.d.cts +283 -9
- package/dist/smart-dom-reader.d.ts +283 -9
- package/dist/testing.cjs +3 -3
- package/dist/testing.d.cts +7 -0
- package/dist/testing.d.ts +7 -0
- package/dist/testing.js +3 -3
- package/dist/theme-editor-preview.cjs +64 -64
- package/dist/theme-editor-preview.d.cts +283 -9
- package/dist/theme-editor-preview.d.ts +283 -9
- package/dist/theme-editor-preview.js +56 -56
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +283 -10
- package/dist/theme-editor.d.ts +283 -10
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +66 -3
- package/dist/theme-reference.d.ts +66 -3
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +2 -2
- package/src/components/artifact-pane.test.ts +27 -0
- package/src/components/artifact-pane.ts +7 -3
- package/src/components/header-builder.ts +27 -2
- package/src/components/header-layouts.test.ts +56 -0
- package/src/components/header-layouts.ts +29 -9
- package/src/components/launcher-surface.test.ts +403 -0
- package/src/components/launcher.ts +297 -2
- package/src/components/panel.ts +69 -74
- package/src/components/suggestions.test.ts +40 -0
- package/src/components/suggestions.ts +213 -153
- package/src/components/welcome.test.ts +121 -0
- package/src/components/welcome.ts +305 -0
- package/src/components/widget-view.test.ts +5 -3
- package/src/components/widget-view.ts +14 -7
- package/src/defaults.ts +2 -2
- package/src/index-core.ts +12 -1
- package/src/install.test.ts +35 -1
- package/src/install.ts +47 -11
- package/src/launcher-global.ts +10 -7
- package/src/plugins/types.ts +66 -1
- package/src/styles/widget-styles.test.ts +220 -0
- package/src/styles/widget.css +353 -9
- package/src/testing/mock-stream.test.ts +19 -0
- package/src/testing/mock-stream.ts +20 -0
- package/src/theme-editor/sections.ts +30 -1
- package/src/theme-reference.ts +26 -4
- package/src/types/theme.ts +40 -3
- package/src/types.ts +164 -6
- package/src/ui.body-top-slot.test.ts +95 -0
- package/src/ui.content-width.test.ts +91 -0
- package/src/ui.docked.test.ts +7 -4
- package/src/ui.launcher-teaser.test.ts +162 -0
- package/src/ui.rebuild-composer.test.ts +491 -0
- package/src/ui.render-welcome.test.ts +505 -0
- package/src/ui.scroll-additive.test.ts +91 -21
- package/src/ui.scroll.test.ts +302 -14
- package/src/ui.suggest-replies.test.ts +46 -0
- package/src/ui.ts +747 -136
- package/src/ui.welcome.test.ts +504 -0
- package/src/utils/artifact-gate.test.ts +27 -0
- package/src/utils/artifact-gate.ts +9 -0
- package/src/utils/attachment-manager.test.ts +37 -0
- package/src/utils/attachment-manager.ts +15 -0
- package/src/utils/auto-follow.test.ts +23 -0
- package/src/utils/auto-follow.ts +11 -1
- package/src/utils/config-merge.ts +2 -0
- package/src/utils/content-width.test.ts +37 -0
- package/src/utils/content-width.ts +17 -0
- package/src/utils/plugin-storage.test.ts +94 -0
- package/src/utils/plugin-storage.ts +98 -0
- package/src/utils/theme.test.ts +137 -0
- package/src/utils/tokens.ts +49 -3
- package/src/welcome.test.ts +213 -0
- package/src/welcome.ts +130 -0
package/src/types/theme.ts
CHANGED
|
@@ -202,6 +202,16 @@ export interface PanelTokens extends ComponentTokenSet {
|
|
|
202
202
|
canvasBackground?: string;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
/** Per-element text styling shared by themable text surfaces. */
|
|
206
|
+
export interface TextStyleTokens {
|
|
207
|
+
fontFamily?: string;
|
|
208
|
+
fontSize?: string;
|
|
209
|
+
fontWeight?: string;
|
|
210
|
+
lineHeight?: string;
|
|
211
|
+
letterSpacing?: string;
|
|
212
|
+
color?: TokenReference<'color'>;
|
|
213
|
+
}
|
|
214
|
+
|
|
205
215
|
export interface HeaderTokens extends ComponentTokenSet {
|
|
206
216
|
background: TokenReference<'color'>;
|
|
207
217
|
border: TokenReference<'color'>;
|
|
@@ -210,10 +220,14 @@ export interface HeaderTokens extends ComponentTokenSet {
|
|
|
210
220
|
iconBackground: TokenReference<'color'>;
|
|
211
221
|
/** Foreground (glyph stroke or emoji text) on the header avatar tile. */
|
|
212
222
|
iconForeground: TokenReference<'color'>;
|
|
213
|
-
/**
|
|
223
|
+
/** Legacy alias of `title.color`; `title.color` wins when both are set. */
|
|
214
224
|
titleForeground: TokenReference<'color'>;
|
|
215
|
-
/**
|
|
225
|
+
/** Legacy alias of `subtitle.color`; `subtitle.color` wins when both are set. */
|
|
216
226
|
subtitleForeground: TokenReference<'color'>;
|
|
227
|
+
/** Header title typography (next to the icon, or minimal layout title). */
|
|
228
|
+
title?: TextStyleTokens;
|
|
229
|
+
/** Header subtitle typography, for the line under the title. */
|
|
230
|
+
subtitle?: TextStyleTokens;
|
|
217
231
|
/** Default color for clear / close icon buttons when launcher overrides are unset. */
|
|
218
232
|
actionIconForeground: TokenReference<'color'>;
|
|
219
233
|
/** Box-shadow on the header (e.g., a fade shadow to replace the default border). */
|
|
@@ -245,7 +259,7 @@ export interface MessageTokens {
|
|
|
245
259
|
|
|
246
260
|
/**
|
|
247
261
|
* Welcome / intro card rendered above the message list when no messages exist.
|
|
248
|
-
* Set `
|
|
262
|
+
* Set `welcome.variant: "none"` to hide it; use `layout.slots["body-top"]`
|
|
249
263
|
* to replace it wholesale.
|
|
250
264
|
*/
|
|
251
265
|
export interface IntroCardTokens extends ComponentTokenSet {
|
|
@@ -254,6 +268,12 @@ export interface IntroCardTokens extends ComponentTokenSet {
|
|
|
254
268
|
padding?: TokenReference<'spacing'>;
|
|
255
269
|
/** Box-shadow on the intro card (token ref or raw CSS, e.g. `none`). */
|
|
256
270
|
shadow?: string;
|
|
271
|
+
/** Full border shorthand on the intro card (raw CSS, e.g. `"1px solid rgba(0,0,0,0.1)"`). @default "none" */
|
|
272
|
+
border?: string;
|
|
273
|
+
/** Welcome title typography. */
|
|
274
|
+
title?: TextStyleTokens;
|
|
275
|
+
/** Welcome subtitle typography. */
|
|
276
|
+
subtitle?: TextStyleTokens;
|
|
257
277
|
}
|
|
258
278
|
|
|
259
279
|
/** Collapsible widget chrome (tool bubbles, reasoning bubbles, approval bubbles). */
|
|
@@ -298,6 +318,8 @@ export interface MarkdownTokens {
|
|
|
298
318
|
background?: TokenReference<'color'>;
|
|
299
319
|
borderColor?: TokenReference<'color'>;
|
|
300
320
|
textColor?: TokenReference<'color'>;
|
|
321
|
+
/** Corner radius; follows `palette.radius.md` by default so square-corner themes get square code blocks. */
|
|
322
|
+
borderRadius?: TokenReference<'radius'>;
|
|
301
323
|
};
|
|
302
324
|
/** Table styling. */
|
|
303
325
|
table?: {
|
|
@@ -361,10 +383,20 @@ export interface ReasoningBubbleTokens {
|
|
|
361
383
|
shadow: string;
|
|
362
384
|
}
|
|
363
385
|
|
|
386
|
+
/** Scrollbar appearance shared by every scroller in the widget. */
|
|
387
|
+
export interface ScrollbarTokens {
|
|
388
|
+
/** Thumb color. @default semantic.colors.border */
|
|
389
|
+
thumb?: TokenReference<'color'>;
|
|
390
|
+
/** Track color. @default "transparent" */
|
|
391
|
+
track?: TokenReference<'color'>;
|
|
392
|
+
}
|
|
393
|
+
|
|
364
394
|
/** Composer (message input) chrome. */
|
|
365
395
|
export interface ComposerChromeTokens {
|
|
366
396
|
/** Box-shadow on the composer form (raw CSS, e.g. `none`). */
|
|
367
397
|
shadow: string;
|
|
398
|
+
/** Border color of the composer form. @default semantic.colors.border */
|
|
399
|
+
borderColor?: TokenReference<'color'>;
|
|
368
400
|
/** Inner padding of the composer form (raw CSS shorthand). @default "0.75rem 1rem" */
|
|
369
401
|
padding?: string;
|
|
370
402
|
/** Gap between the textarea row and the actions row. @default "0.5rem" */
|
|
@@ -534,7 +566,10 @@ export interface ScrollToBottomTokens extends ComponentTokenSet {
|
|
|
534
566
|
|
|
535
567
|
/** Visual tokens shared by one suggestion presentation variant. */
|
|
536
568
|
export interface SuggestionVariantTokens extends ComponentTokenSet {
|
|
569
|
+
/** Space inside one item, between its icon and its copy. */
|
|
537
570
|
gap?: string;
|
|
571
|
+
/** Space between suggestion items in the container. */
|
|
572
|
+
itemGap?: string;
|
|
538
573
|
minHeight?: string;
|
|
539
574
|
fontSize?: string;
|
|
540
575
|
lineHeight?: string;
|
|
@@ -579,6 +614,8 @@ export interface ComponentTokens {
|
|
|
579
614
|
toolBubble: ToolBubbleTokens;
|
|
580
615
|
reasoningBubble: ReasoningBubbleTokens;
|
|
581
616
|
composer: ComposerChromeTokens;
|
|
617
|
+
/** Scrollbar appearance for every scroller in the widget. */
|
|
618
|
+
scrollbar?: ScrollbarTokens;
|
|
582
619
|
/** Icon button styling tokens. */
|
|
583
620
|
iconButton?: IconButtonTokens;
|
|
584
621
|
/** Label button styling tokens. */
|
package/src/types.ts
CHANGED
|
@@ -1251,6 +1251,13 @@ export type AgentWidgetArtifactsLayoutConfig = {
|
|
|
1251
1251
|
* @default 520
|
|
1252
1252
|
*/
|
|
1253
1253
|
narrowHostMaxWidth?: number;
|
|
1254
|
+
/**
|
|
1255
|
+
* Width of the slide-over artifact drawer (the narrow-host and mobile
|
|
1256
|
+
* presentations; `paneWidth`/`paneMaxWidth` only size the desktop split).
|
|
1257
|
+
* CSS length; `"100%"` makes the drawer cover the whole panel.
|
|
1258
|
+
* @default min(calc(100% - 1rem), 22rem)
|
|
1259
|
+
*/
|
|
1260
|
+
drawerWidth?: string;
|
|
1254
1261
|
/**
|
|
1255
1262
|
* When true (default), widen the launcher panel while artifacts are visible and not user-dismissed.
|
|
1256
1263
|
* No-op for inline embed (`launcher.enabled === false`).
|
|
@@ -1894,6 +1901,19 @@ export type AgentWidgetScrollMode = "follow" | "anchor-top" | "none";
|
|
|
1894
1901
|
*/
|
|
1895
1902
|
export type AgentWidgetScrollRestorePosition = "bottom" | "last-user-turn";
|
|
1896
1903
|
|
|
1904
|
+
/**
|
|
1905
|
+
* Scrollbar visibility policy for every scroll surface in the widget.
|
|
1906
|
+
*
|
|
1907
|
+
* - `"on-scroll"` hides bars at rest and reveals them on genuine reader
|
|
1908
|
+
* input, keeping them visible while the reader is away from the resting
|
|
1909
|
+
* position: overlay-scrollbar semantics on every platform.
|
|
1910
|
+
* - `"auto"` keeps the themed bar under native visibility semantics: overlay
|
|
1911
|
+
* platforms still auto-hide, classic-scrollbar platforms (Windows,
|
|
1912
|
+
* always-show macOS) show it persistently.
|
|
1913
|
+
* - `"hidden"` never shows an indicator. Scrolling itself is unaffected.
|
|
1914
|
+
*/
|
|
1915
|
+
export type AgentWidgetScrollbarPolicy = "on-scroll" | "auto" | "hidden";
|
|
1916
|
+
|
|
1897
1917
|
export type AgentWidgetScrollBehaviorFeature = {
|
|
1898
1918
|
/** Scroll behavior during streamed responses. @default "anchor-top" */
|
|
1899
1919
|
mode?: AgentWidgetScrollMode;
|
|
@@ -1938,6 +1958,13 @@ export type AgentWidgetScrollBehaviorFeature = {
|
|
|
1938
1958
|
* @default false
|
|
1939
1959
|
*/
|
|
1940
1960
|
announce?: boolean;
|
|
1961
|
+
/**
|
|
1962
|
+
* Scrollbar visibility policy, applied to the transcript and every inner
|
|
1963
|
+
* scroller through one root attribute. Themable via
|
|
1964
|
+
* `--persona-scrollbar-thumb` and `--persona-scrollbar-track`.
|
|
1965
|
+
* @default "on-scroll"
|
|
1966
|
+
*/
|
|
1967
|
+
scrollbar?: AgentWidgetScrollbarPolicy;
|
|
1941
1968
|
};
|
|
1942
1969
|
|
|
1943
1970
|
export type AgentWidgetScrollToBottomFeature = {
|
|
@@ -2915,6 +2942,58 @@ export type AgentWidgetLauncherConfig = {
|
|
|
2915
2942
|
* @example "min(380px, calc(100vw - 48px))"
|
|
2916
2943
|
*/
|
|
2917
2944
|
collapsedMaxWidth?: string;
|
|
2945
|
+
/**
|
|
2946
|
+
* Proactive nudge bubble anchored above the collapsed floating launcher.
|
|
2947
|
+
* Omit it (the default) and no teaser renders.
|
|
2948
|
+
*/
|
|
2949
|
+
teaser?: AgentWidgetLauncherTeaserConfig;
|
|
2950
|
+
};
|
|
2951
|
+
|
|
2952
|
+
/**
|
|
2953
|
+
* Launcher teaser: a small bubble above the collapsed launcher. Clicking it
|
|
2954
|
+
* opens the panel and consumes the teaser exactly like an explicit dismissal.
|
|
2955
|
+
* Within a single page load the teaser appears at most once under either
|
|
2956
|
+
* frequency.
|
|
2957
|
+
*
|
|
2958
|
+
* @example
|
|
2959
|
+
* ```typescript
|
|
2960
|
+
* launcher: {
|
|
2961
|
+
* teaser: { text: "Need a hand picking a plan?", delayMs: 3000 }
|
|
2962
|
+
* }
|
|
2963
|
+
* ```
|
|
2964
|
+
*/
|
|
2965
|
+
export type AgentWidgetLauncherTeaserConfig = {
|
|
2966
|
+
/** Bubble copy. An empty string renders no teaser. */
|
|
2967
|
+
text: string;
|
|
2968
|
+
/**
|
|
2969
|
+
* Delay before the bubble appears, in milliseconds.
|
|
2970
|
+
* @default 0
|
|
2971
|
+
*/
|
|
2972
|
+
delayMs?: number;
|
|
2973
|
+
/**
|
|
2974
|
+
* How often the teaser may return.
|
|
2975
|
+
*
|
|
2976
|
+
* `"once"`: a dismissal or a click-through is remembered in `localStorage`
|
|
2977
|
+
* under `` `${persistState.keyPrefix ?? "persona-"}teaser-dismissed` `` and
|
|
2978
|
+
* the teaser never returns on that browser. `"always"`: shows on every page
|
|
2979
|
+
* load, tracked in memory only. `persistState: false` downgrades `"once"` to
|
|
2980
|
+
* in-memory too, so the teaser reappears each load.
|
|
2981
|
+
*
|
|
2982
|
+
* Custom `storageAdapter` implementations do not participate in teaser state.
|
|
2983
|
+
*
|
|
2984
|
+
* @default "once"
|
|
2985
|
+
*/
|
|
2986
|
+
frequency?: "once" | "always";
|
|
2987
|
+
/**
|
|
2988
|
+
* Render an x affordance that dismisses the teaser without opening the panel.
|
|
2989
|
+
* @default true
|
|
2990
|
+
*/
|
|
2991
|
+
dismissible?: boolean;
|
|
2992
|
+
/**
|
|
2993
|
+
* Accessible label for the dismiss affordance.
|
|
2994
|
+
* @default "Dismiss message"
|
|
2995
|
+
*/
|
|
2996
|
+
dismissLabel?: string;
|
|
2918
2997
|
};
|
|
2919
2998
|
|
|
2920
2999
|
export type AgentWidgetSendButtonConfig = {
|
|
@@ -3753,7 +3832,10 @@ export type AgentWidgetSuggestion =
|
|
|
3753
3832
|
label: string;
|
|
3754
3833
|
/** Text placed in the composer or sent. Defaults to `label`. */
|
|
3755
3834
|
prompt?: string;
|
|
3756
|
-
/**
|
|
3835
|
+
/**
|
|
3836
|
+
* Optional supporting copy, most useful with the `card` variant. The
|
|
3837
|
+
* `chip` variant ignores it and renders the label alone.
|
|
3838
|
+
*/
|
|
3757
3839
|
description?: string;
|
|
3758
3840
|
/** Optional icon from Persona's bundled Lucide registry. */
|
|
3759
3841
|
icon?: IconName;
|
|
@@ -3800,9 +3882,10 @@ export type AgentWidgetSuggestionSurfaceConfig = {
|
|
|
3800
3882
|
/** Default click behavior, overridable per item. Defaults to `send`. */
|
|
3801
3883
|
behavior?: AgentWidgetSuggestionBehavior;
|
|
3802
3884
|
/**
|
|
3803
|
-
* Layout for items past the surface width. Defaults to `wrap`
|
|
3804
|
-
*
|
|
3805
|
-
* `card` and `list` layouts manage their
|
|
3885
|
+
* Layout for items past the surface width. Defaults to `wrap` on every
|
|
3886
|
+
* surface; `scroll` opts into a horizontal strip for large sets. Applies
|
|
3887
|
+
* to the `chip` variant only; the `card` and `list` layouts manage their
|
|
3888
|
+
* own stacking.
|
|
3806
3889
|
*/
|
|
3807
3890
|
overflow?: "scroll" | "wrap";
|
|
3808
3891
|
/** Maximum visible items. Defaults to 4. */
|
|
@@ -3860,6 +3943,66 @@ export type AgentWidgetSuggestionsConfig = {
|
|
|
3860
3943
|
followUps?: AgentWidgetFollowUpSuggestionsConfig;
|
|
3861
3944
|
};
|
|
3862
3945
|
|
|
3946
|
+
/**
|
|
3947
|
+
* Which welcome surface renders.
|
|
3948
|
+
* `"card"` is the top-of-scroll intro card (default, back-compatible),
|
|
3949
|
+
* `"hero"` centers it in the empty conversation, `"none"` renders no surface.
|
|
3950
|
+
*/
|
|
3951
|
+
export type AgentWidgetWelcomeVariant = "card" | "hero" | "none";
|
|
3952
|
+
|
|
3953
|
+
/**
|
|
3954
|
+
* When the welcome surface goes away. `"never"` is the default for `"card"`;
|
|
3955
|
+
* `"on-first-message"` is the default for `"hero"` and cannot be overridden
|
|
3956
|
+
* there.
|
|
3957
|
+
*/
|
|
3958
|
+
export type AgentWidgetWelcomeDismiss = "never" | "on-first-message";
|
|
3959
|
+
|
|
3960
|
+
/**
|
|
3961
|
+
* Avatar or logo shown above the welcome title. Discriminated union; the
|
|
3962
|
+
* function form is the escape hatch for custom markup. There is deliberately
|
|
3963
|
+
* no raw HTML string variant (sanitization surface plus accessibility hole).
|
|
3964
|
+
*/
|
|
3965
|
+
export type AgentWidgetWelcomeIcon =
|
|
3966
|
+
| { type: "lucide"; name: IconName }
|
|
3967
|
+
/** `alt` is required, and may be `""` for a decorative logo. */
|
|
3968
|
+
| { type: "image"; url: string; alt: string }
|
|
3969
|
+
/** Emoji or short text glyph. */
|
|
3970
|
+
| { type: "text"; text: string }
|
|
3971
|
+
| (() => HTMLElement | SVGElement);
|
|
3972
|
+
|
|
3973
|
+
/**
|
|
3974
|
+
* First-open welcome surface. Consolidates the deprecated
|
|
3975
|
+
* `copy.welcomeTitle` / `copy.welcomeSubtitle` / `copy.showWelcomeCard`
|
|
3976
|
+
* options; a field present here wins over its legacy alias.
|
|
3977
|
+
*
|
|
3978
|
+
* All defaults live in the resolver (`resolveWelcomeConfig`), never in
|
|
3979
|
+
* `DEFAULT_WIDGET_CONFIG`: presence on this object is what distinguishes a
|
|
3980
|
+
* host-set value from a filled-in default.
|
|
3981
|
+
*/
|
|
3982
|
+
export interface AgentWidgetWelcomeConfig {
|
|
3983
|
+
/** Card or hero title. Defaults to "Hello 👋". */
|
|
3984
|
+
title?: string;
|
|
3985
|
+
/** Scope statement in the assistant's voice. Empty string omits it. */
|
|
3986
|
+
subtitle?: string;
|
|
3987
|
+
/** Avatar or logo shown above the title. */
|
|
3988
|
+
icon?: AgentWidgetWelcomeIcon;
|
|
3989
|
+
/** @default "card" */
|
|
3990
|
+
variant?: AgentWidgetWelcomeVariant;
|
|
3991
|
+
/** @default "never" for `card`, always `"on-first-message"` for `hero`. */
|
|
3992
|
+
dismiss?: AgentWidgetWelcomeDismiss;
|
|
3993
|
+
/**
|
|
3994
|
+
* Display-only greeting bubble pinned at transcript position zero. It is UI
|
|
3995
|
+
* chrome derived from config, never a session message: it does not appear in
|
|
3996
|
+
* `getMessages()`, is never persisted or sent to the model, `onMessage` does
|
|
3997
|
+
* not fire for it, and it survives `clearChat()`. Use an assistant
|
|
3998
|
+
* `initialMessages` entry instead when the model should see the greeting.
|
|
3999
|
+
*
|
|
4000
|
+
* Ignored (with a debug-mode warning) when `variant` is `"hero"`: the hero
|
|
4001
|
+
* is the greeting.
|
|
4002
|
+
*/
|
|
4003
|
+
message?: string;
|
|
4004
|
+
}
|
|
4005
|
+
|
|
3863
4006
|
/**
|
|
3864
4007
|
* Interface for pluggable stream parsers that extract text from streaming responses.
|
|
3865
4008
|
* Parsers handle incremental parsing to extract text values from structured formats (JSON, XML, etc.).
|
|
@@ -4152,7 +4295,9 @@ export type AgentWidgetHeaderLayoutConfig = {
|
|
|
4152
4295
|
*/
|
|
4153
4296
|
render?: (context: HeaderRenderContext) => HTMLElement;
|
|
4154
4297
|
/**
|
|
4155
|
-
*
|
|
4298
|
+
* Icon action buttons at the trailing edge of the `minimal` header layout,
|
|
4299
|
+
* clustered with the close button and sharing its chrome. For a menu
|
|
4300
|
+
* affordance next to the title, use `titleMenu` instead.
|
|
4156
4301
|
*/
|
|
4157
4302
|
trailingActions?: AgentWidgetHeaderTrailingAction[];
|
|
4158
4303
|
/** Called when a `trailingActions` button is clicked. */
|
|
@@ -4348,7 +4493,11 @@ export type AgentWidgetLayoutConfig = {
|
|
|
4348
4493
|
/**
|
|
4349
4494
|
* Max width for the content area (messages + composer).
|
|
4350
4495
|
* Applied with `margin: 0 auto` for centering.
|
|
4351
|
-
* Accepts any CSS width value (e.g. "90ch", "720px", "80%")
|
|
4496
|
+
* Accepts any CSS width value (e.g. "90ch", "720px", "80%"), or "none"
|
|
4497
|
+
* for a full-width column. Only engages on panels wider than the cap,
|
|
4498
|
+
* so launcher-width panels are unaffected.
|
|
4499
|
+
* @default "768px" ("720px" in composer-bar mode via
|
|
4500
|
+
* `launcher.composerBar.contentMaxWidth`)
|
|
4352
4501
|
*/
|
|
4353
4502
|
contentMaxWidth?: string;
|
|
4354
4503
|
};
|
|
@@ -5108,7 +5257,9 @@ export type AgentWidgetConfig = {
|
|
|
5108
5257
|
*/
|
|
5109
5258
|
getHeaders?: AgentWidgetHeadersFunction;
|
|
5110
5259
|
copy?: {
|
|
5260
|
+
/** @deprecated Use `welcome.title`. */
|
|
5111
5261
|
welcomeTitle?: string;
|
|
5262
|
+
/** @deprecated Use `welcome.subtitle`. */
|
|
5112
5263
|
welcomeSubtitle?: string;
|
|
5113
5264
|
inputPlaceholder?: string;
|
|
5114
5265
|
sendButtonLabel?: string;
|
|
@@ -5116,6 +5267,7 @@ export type AgentWidgetConfig = {
|
|
|
5116
5267
|
stopButtonLabel?: string;
|
|
5117
5268
|
/**
|
|
5118
5269
|
* When false, the welcome / intro card is not shown above the message list.
|
|
5270
|
+
* @deprecated Use `welcome.variant: "none"`.
|
|
5119
5271
|
* @default true
|
|
5120
5272
|
*/
|
|
5121
5273
|
showWelcomeCard?: boolean;
|
|
@@ -5182,6 +5334,12 @@ export type AgentWidgetConfig = {
|
|
|
5182
5334
|
initialSelectedArtifactId?: string | null;
|
|
5183
5335
|
/** Rich starter and follow-up suggestion surfaces. */
|
|
5184
5336
|
suggestions?: AgentWidgetSuggestionsConfig;
|
|
5337
|
+
/**
|
|
5338
|
+
* First-open welcome surface (card, hero, or none) plus the optional
|
|
5339
|
+
* display-only greeting bubble. Supersedes `copy.welcomeTitle`,
|
|
5340
|
+
* `copy.welcomeSubtitle`, and `copy.showWelcomeCard`.
|
|
5341
|
+
*/
|
|
5342
|
+
welcome?: AgentWidgetWelcomeConfig;
|
|
5185
5343
|
/** @deprecated Prefer `suggestions.starters.items`. */
|
|
5186
5344
|
suggestionChips?: string[];
|
|
5187
5345
|
/** @deprecated Prefer semantic tokens under `theme.components.suggestion`. */
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, 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
|
+
describe("layout.slots['body-top']", () => {
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
document.body.innerHTML = "";
|
|
16
|
+
vi.restoreAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("resolves defaultContent() to the intro card (regression: queried stale utility classes)", () => {
|
|
20
|
+
window.scrollTo = vi.fn();
|
|
21
|
+
const mount = createMount();
|
|
22
|
+
let resolved: HTMLElement | null | undefined;
|
|
23
|
+
|
|
24
|
+
createAgentExperience(mount, {
|
|
25
|
+
apiUrl: "https://api.example.com/chat",
|
|
26
|
+
launcher: { enabled: false },
|
|
27
|
+
layout: {
|
|
28
|
+
slots: {
|
|
29
|
+
"body-top": ({ defaultContent }) => {
|
|
30
|
+
resolved = defaultContent();
|
|
31
|
+
return null;
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(resolved).not.toBeNull();
|
|
38
|
+
expect(resolved!.hasAttribute("data-persona-intro-card")).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("replaces the intro card with the slot element instead of prepending beside it", () => {
|
|
42
|
+
window.scrollTo = vi.fn();
|
|
43
|
+
const mount = createMount();
|
|
44
|
+
|
|
45
|
+
createAgentExperience(mount, {
|
|
46
|
+
apiUrl: "https://api.example.com/chat",
|
|
47
|
+
launcher: { enabled: false },
|
|
48
|
+
layout: {
|
|
49
|
+
slots: {
|
|
50
|
+
"body-top": () => {
|
|
51
|
+
const custom = document.createElement("div");
|
|
52
|
+
custom.setAttribute("data-test-slot", "body-top");
|
|
53
|
+
return custom;
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const body = mount.querySelector("#persona-scroll-container")!;
|
|
60
|
+
expect(body.querySelector("[data-test-slot='body-top']")).not.toBeNull();
|
|
61
|
+
expect(body.querySelector("[data-persona-intro-card]")).toBeNull();
|
|
62
|
+
expect(
|
|
63
|
+
(body.firstElementChild as HTMLElement).getAttribute("data-test-slot")
|
|
64
|
+
).toBe("body-top");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("supports composing around the default intro card", () => {
|
|
68
|
+
window.scrollTo = vi.fn();
|
|
69
|
+
const mount = createMount();
|
|
70
|
+
|
|
71
|
+
createAgentExperience(mount, {
|
|
72
|
+
apiUrl: "https://api.example.com/chat",
|
|
73
|
+
launcher: { enabled: false },
|
|
74
|
+
layout: {
|
|
75
|
+
slots: {
|
|
76
|
+
"body-top": ({ defaultContent }) => {
|
|
77
|
+
const wrapper = document.createElement("div");
|
|
78
|
+
wrapper.setAttribute("data-test-slot", "wrapper");
|
|
79
|
+
const banner = document.createElement("p");
|
|
80
|
+
banner.textContent = "Welcome back";
|
|
81
|
+
wrapper.appendChild(banner);
|
|
82
|
+
const card = defaultContent();
|
|
83
|
+
if (card) wrapper.appendChild(card);
|
|
84
|
+
return wrapper;
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const body = mount.querySelector("#persona-scroll-container")!;
|
|
91
|
+
const wrapper = body.querySelector("[data-test-slot='wrapper']");
|
|
92
|
+
expect(wrapper).not.toBeNull();
|
|
93
|
+
expect(wrapper!.querySelector("[data-persona-intro-card]")).not.toBeNull();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { createAgentExperience } from "./ui";
|
|
6
|
+
|
|
7
|
+
const mounts: HTMLElement[] = [];
|
|
8
|
+
const controllers: ReturnType<typeof createAgentExperience>[] = [];
|
|
9
|
+
|
|
10
|
+
const makeController = (config: Record<string, unknown> = {}) => {
|
|
11
|
+
const mount = document.createElement("div");
|
|
12
|
+
document.body.appendChild(mount);
|
|
13
|
+
mounts.push(mount);
|
|
14
|
+
const controller = createAgentExperience(mount, {
|
|
15
|
+
apiUrl: "https://api.example.com/chat",
|
|
16
|
+
launcher: { enabled: false },
|
|
17
|
+
persistState: false,
|
|
18
|
+
suggestionChips: [],
|
|
19
|
+
...config,
|
|
20
|
+
} as unknown as Parameters<typeof createAgentExperience>[1]);
|
|
21
|
+
controllers.push(controller);
|
|
22
|
+
return { mount, controller };
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const wrapper = (mount: HTMLElement) =>
|
|
26
|
+
mount.querySelector<HTMLElement>("#persona-scroll-container > .persona-widget-messages")!;
|
|
27
|
+
|
|
28
|
+
describe("default content column", () => {
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
controllers.splice(0).forEach((controller) => controller.destroy());
|
|
31
|
+
mounts.splice(0).forEach((mount) => mount.remove());
|
|
32
|
+
document.body.innerHTML = "";
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("keeps the transcript wrapper :empty pre-conversation so CSS drops it from the flex flow", () => {
|
|
36
|
+
// The stylesheet's .persona-widget-messages:empty { display: none } only
|
|
37
|
+
// works if nothing seeds the wrapper with placeholder nodes.
|
|
38
|
+
const { mount, controller } = makeController();
|
|
39
|
+
const messagesWrapper = wrapper(mount);
|
|
40
|
+
expect(messagesWrapper.matches(":empty")).toBe(true);
|
|
41
|
+
|
|
42
|
+
controller.injectUserMessage({ content: "hi" });
|
|
43
|
+
expect(messagesWrapper.matches(":empty")).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("caps and centers the transcript at 768px by default", () => {
|
|
47
|
+
const { mount } = makeController();
|
|
48
|
+
const messagesWrapper = wrapper(mount);
|
|
49
|
+
expect(messagesWrapper.style.maxWidth).toBe("768px");
|
|
50
|
+
expect(messagesWrapper.style.marginLeft).toBe("auto");
|
|
51
|
+
expect(messagesWrapper.style.marginRight).toBe("auto");
|
|
52
|
+
expect(messagesWrapper.style.width).toBe("100%");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("honors an explicit layout.contentMaxWidth", () => {
|
|
56
|
+
const { mount } = makeController({ layout: { contentMaxWidth: "90ch" } });
|
|
57
|
+
expect(wrapper(mount).style.maxWidth).toBe("90ch");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("opts out with \"none\" for a full-width column", () => {
|
|
61
|
+
const { mount } = makeController({ layout: { contentMaxWidth: "none" } });
|
|
62
|
+
expect(wrapper(mount).style.maxWidth).toBe("none");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("caps the composer status text to the same column as the composer form", () => {
|
|
66
|
+
const { mount, controller } = makeController();
|
|
67
|
+
const statusText = mount.querySelector<HTMLElement>("[data-persona-composer-status]")!;
|
|
68
|
+
expect(statusText.style.maxWidth).toBe("768px");
|
|
69
|
+
expect(statusText.style.marginLeft).toBe("auto");
|
|
70
|
+
expect(statusText.style.marginRight).toBe("auto");
|
|
71
|
+
|
|
72
|
+
controller.update({ layout: { contentMaxWidth: "90ch" } });
|
|
73
|
+
expect(statusText.style.maxWidth).toBe("90ch");
|
|
74
|
+
|
|
75
|
+
controller.update({ layout: { contentMaxWidth: "" } });
|
|
76
|
+
expect(statusText.style.maxWidth).toBe("");
|
|
77
|
+
expect(statusText.style.marginLeft).toBe("");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("publishes the resolved column as a CSS var for plugin content", () => {
|
|
81
|
+
const { mount, controller } = makeController();
|
|
82
|
+
expect(
|
|
83
|
+
mount.style.getPropertyValue("--persona-content-max-width")
|
|
84
|
+
).toBe("768px");
|
|
85
|
+
|
|
86
|
+
controller.update({ layout: { contentMaxWidth: "90ch" } });
|
|
87
|
+
expect(
|
|
88
|
+
mount.style.getPropertyValue("--persona-content-max-width")
|
|
89
|
+
).toBe("90ch");
|
|
90
|
+
});
|
|
91
|
+
});
|
package/src/ui.docked.test.ts
CHANGED
|
@@ -27,24 +27,27 @@ describe("createAgentExperience docked mode", () => {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
const wrapper = mount.firstElementChild as HTMLElement | null;
|
|
30
|
-
|
|
30
|
+
// The launcher mounts inside its surface wrapper; visibility is driven on
|
|
31
|
+
// the wrapper so a teaser bubble hides with the button.
|
|
32
|
+
const launcherSurface = mount.lastElementChild as HTMLElement | null;
|
|
33
|
+
const launcherButton = launcherSurface?.querySelector("button") ?? null;
|
|
31
34
|
|
|
32
35
|
expect(wrapper).not.toBeNull();
|
|
33
36
|
expect(wrapper?.style.display).toBe("flex");
|
|
34
37
|
expect(launcherButton).not.toBeNull();
|
|
35
38
|
expect(launcherButton?.tagName).toBe("BUTTON");
|
|
36
|
-
expect(
|
|
39
|
+
expect(launcherSurface?.style.display).toBe("none");
|
|
37
40
|
expect(launcherButton?.className).toContain("persona-mt-4");
|
|
38
41
|
|
|
39
42
|
controller.close();
|
|
40
43
|
|
|
41
44
|
expect(wrapper?.style.display).toBe("none");
|
|
42
|
-
expect(
|
|
45
|
+
expect(launcherSurface?.style.display).toBe("none");
|
|
43
46
|
|
|
44
47
|
controller.open();
|
|
45
48
|
|
|
46
49
|
expect(wrapper?.style.display).toBe("flex");
|
|
47
|
-
expect(
|
|
50
|
+
expect(launcherSurface?.style.display).toBe("none");
|
|
48
51
|
|
|
49
52
|
controller.destroy();
|
|
50
53
|
});
|