@runtypelabs/persona 2.1.0 → 2.2.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/LICENSE +21 -0
- package/dist/index.cjs +39 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -1
- package/dist/index.d.ts +99 -1
- package/dist/index.global.js +84 -84
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +39 -39
- package/dist/index.js.map +1 -1
- package/dist/widget.css +40 -10
- package/package.json +2 -1
- package/src/client.ts +0 -1
- package/src/components/artifact-pane.ts +2 -0
- package/src/components/composer-builder.ts +1 -0
- package/src/components/header-builder.ts +1 -0
- package/src/components/header-layouts.ts +41 -1
- package/src/components/message-bubble.ts +2 -0
- package/src/components/panel.ts +2 -0
- package/src/index.ts +14 -1
- package/src/presets.ts +127 -0
- package/src/styles/widget.css +40 -10
- package/src/types/theme.ts +41 -0
- package/src/types.ts +14 -0
- package/src/ui.ts +14 -1
- package/src/utils/tokens.ts +54 -0
package/dist/widget.css
CHANGED
|
@@ -2064,9 +2064,9 @@
|
|
|
2064
2064
|
display: inline-flex;
|
|
2065
2065
|
align-items: center;
|
|
2066
2066
|
justify-content: center;
|
|
2067
|
-
padding: 0.25rem;
|
|
2068
|
-
border-radius: var(--persona-radius-md, 0.375rem);
|
|
2069
|
-
border: 1px solid var(--persona-border, #e5e7eb);
|
|
2067
|
+
padding: var(--persona-artifact-toolbar-icon-padding, 0.25rem);
|
|
2068
|
+
border-radius: var(--persona-artifact-toolbar-icon-radius, var(--persona-radius-md, 0.375rem));
|
|
2069
|
+
border: var(--persona-artifact-toolbar-icon-border, 1px solid var(--persona-border, #e5e7eb));
|
|
2070
2070
|
background: var(--persona-surface, #ffffff);
|
|
2071
2071
|
color: var(--persona-artifact-doc-toolbar-icon-color, var(--persona-text, #111827));
|
|
2072
2072
|
cursor: pointer;
|
|
@@ -2074,7 +2074,8 @@
|
|
|
2074
2074
|
}
|
|
2075
2075
|
|
|
2076
2076
|
#persona-root .persona-artifact-toolbar-document .persona-artifact-doc-icon-btn:hover {
|
|
2077
|
-
|
|
2077
|
+
color: var(--persona-artifact-toolbar-icon-hover-color, inherit);
|
|
2078
|
+
background: var(--persona-artifact-toolbar-icon-hover-bg, var(--persona-container, #f3f4f6));
|
|
2078
2079
|
}
|
|
2079
2080
|
|
|
2080
2081
|
#persona-root .persona-artifact-toolbar-document .persona-artifact-doc-icon-btn[aria-pressed="true"] {
|
|
@@ -2086,24 +2087,53 @@
|
|
|
2086
2087
|
display: inline-flex;
|
|
2087
2088
|
align-items: center;
|
|
2088
2089
|
gap: 0.35rem;
|
|
2089
|
-
padding: 0.25rem 0.5rem;
|
|
2090
|
-
border-radius: var(--persona-radius-md, 0.375rem);
|
|
2091
|
-
border: 1px solid var(--persona-border, #e5e7eb);
|
|
2092
|
-
background: var(--persona-surface, #ffffff);
|
|
2093
|
-
color: var(--persona-artifact-doc-toolbar-icon-color, var(--persona-text, #111827));
|
|
2090
|
+
padding: var(--persona-artifact-toolbar-copy-padding, 0.25rem 0.5rem);
|
|
2091
|
+
border-radius: var(--persona-artifact-toolbar-copy-radius, var(--persona-radius-md, 0.375rem));
|
|
2092
|
+
border: var(--persona-artifact-toolbar-copy-border, 1px solid var(--persona-border, #e5e7eb));
|
|
2093
|
+
background: var(--persona-artifact-toolbar-copy-bg, var(--persona-surface, #ffffff));
|
|
2094
|
+
color: var(--persona-artifact-toolbar-copy-color, var(--persona-artifact-doc-toolbar-icon-color, var(--persona-text, #111827)));
|
|
2094
2095
|
cursor: pointer;
|
|
2095
2096
|
font-size: 0.75rem;
|
|
2096
2097
|
line-height: 1.25;
|
|
2097
2098
|
}
|
|
2098
2099
|
|
|
2099
2100
|
#persona-root .persona-artifact-toolbar-document .persona-artifact-doc-copy-btn:hover {
|
|
2100
|
-
background: var(--persona-container, #f3f4f6);
|
|
2101
|
+
background: var(--persona-artifact-toolbar-icon-hover-bg, var(--persona-container, #f3f4f6));
|
|
2101
2102
|
}
|
|
2102
2103
|
|
|
2103
2104
|
#persona-root .persona-artifact-toolbar-document .persona-artifact-doc-copy-label {
|
|
2104
2105
|
font-weight: 500;
|
|
2105
2106
|
}
|
|
2106
2107
|
|
|
2108
|
+
/* Copy menu dropdown theming */
|
|
2109
|
+
#persona-root .persona-artifact-doc-copy-menu {
|
|
2110
|
+
background: var(--persona-artifact-toolbar-copy-menu-bg, var(--persona-surface, #fff));
|
|
2111
|
+
border: var(--persona-artifact-toolbar-copy-menu-border, 1px solid var(--persona-border, #e5e7eb));
|
|
2112
|
+
box-shadow: var(--persona-artifact-toolbar-copy-menu-shadow, 0 4px 6px -1px rgba(0,0,0,.1));
|
|
2113
|
+
border-radius: var(--persona-artifact-toolbar-copy-menu-radius, 0.375rem);
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
#persona-root .persona-artifact-doc-copy-menu button:hover {
|
|
2117
|
+
background: var(--persona-artifact-toolbar-copy-menu-item-hover-bg, var(--persona-container, #f3f4f6));
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
/* Artifact tab theming */
|
|
2121
|
+
#persona-root .persona-artifact-tab {
|
|
2122
|
+
background: var(--persona-artifact-tab-bg, transparent);
|
|
2123
|
+
border-radius: var(--persona-artifact-tab-radius, 0.5rem);
|
|
2124
|
+
color: var(--persona-artifact-tab-color, inherit);
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
#persona-root .persona-artifact-tab.persona-bg-persona-container {
|
|
2128
|
+
background: var(--persona-artifact-tab-active-bg, var(--persona-container, #f3f4f6));
|
|
2129
|
+
border-color: var(--persona-artifact-tab-active-border, var(--persona-border, #e5e7eb));
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
/* Artifact toolbar background theming */
|
|
2133
|
+
#persona-root .persona-artifact-toolbar {
|
|
2134
|
+
background: var(--persona-artifact-toolbar-bg, var(--persona-surface, #fff));
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2107
2137
|
/* Draggable split handle (desktop split only; hidden in drawer / narrow host / small viewport) */
|
|
2108
2138
|
#persona-root .persona-artifact-split-handle {
|
|
2109
2139
|
width: 6px;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/persona",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"engines": {
|
|
48
48
|
"node": ">=18.17.0"
|
|
49
49
|
},
|
|
50
|
+
"author": "Runtype",
|
|
50
51
|
"license": "MIT",
|
|
51
52
|
"keywords": [
|
|
52
53
|
"ai",
|
package/src/client.ts
CHANGED
|
@@ -45,7 +45,6 @@ const DEFAULT_CLIENT_API_BASE = "https://api.runtype.com";
|
|
|
45
45
|
* Check if a message has valid (non-empty) content for sending to the API.
|
|
46
46
|
* Filters out messages with empty content that would cause validation errors.
|
|
47
47
|
*
|
|
48
|
-
* @see https://github.com/anthropics/claude-code/issues/XXX - Empty assistant messages from failed requests
|
|
49
48
|
*/
|
|
50
49
|
const hasValidContent = (message: AgentWidgetMessage): boolean => {
|
|
51
50
|
// Check contentParts (multi-modal content)
|
|
@@ -127,6 +127,7 @@ export function createArtifactPane(
|
|
|
127
127
|
"aside",
|
|
128
128
|
"persona-artifact-pane persona-flex persona-flex-col persona-min-h-0 persona-min-w-0 persona-bg-persona-surface persona-text-persona-primary persona-border-l persona-border-persona-border"
|
|
129
129
|
);
|
|
130
|
+
shell.setAttribute("data-persona-theme-zone", "artifact-pane");
|
|
130
131
|
if (documentChrome) {
|
|
131
132
|
shell.classList.add("persona-artifact-pane-document");
|
|
132
133
|
}
|
|
@@ -135,6 +136,7 @@ export function createArtifactPane(
|
|
|
135
136
|
"div",
|
|
136
137
|
"persona-artifact-toolbar persona-flex persona-items-center persona-justify-between persona-gap-2 persona-px-2 persona-py-2 persona-border-b persona-border-persona-border persona-shrink-0"
|
|
137
138
|
);
|
|
139
|
+
toolbar.setAttribute("data-persona-theme-zone", "artifact-toolbar");
|
|
138
140
|
if (documentChrome) {
|
|
139
141
|
toolbar.classList.add("persona-artifact-toolbar-document");
|
|
140
142
|
}
|
|
@@ -68,6 +68,7 @@ export const buildComposer = (context: ComposerBuildContext): ComposerElements =
|
|
|
68
68
|
"div",
|
|
69
69
|
"persona-widget-footer persona-border-t-persona-divider persona-bg-persona-surface persona-px-6 persona-py-4"
|
|
70
70
|
);
|
|
71
|
+
footer.setAttribute("data-persona-theme-zone", "composer");
|
|
71
72
|
|
|
72
73
|
const suggestions = createElement(
|
|
73
74
|
"div",
|
|
@@ -31,6 +31,7 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
31
31
|
"div",
|
|
32
32
|
"persona-widget-header persona-flex persona-items-center persona-gap-3 persona-px-6 persona-py-5"
|
|
33
33
|
);
|
|
34
|
+
header.setAttribute("data-persona-theme-zone", "header");
|
|
34
35
|
header.style.backgroundColor = 'var(--persona-header-bg, var(--persona-surface, #ffffff))';
|
|
35
36
|
header.style.borderBottomWidth = '1px';
|
|
36
37
|
header.style.borderBottomStyle = 'solid';
|
|
@@ -24,12 +24,32 @@ export type HeaderLayoutRenderer = (context: HeaderLayoutContext) => HeaderEleme
|
|
|
24
24
|
* Full header with icon, title, subtitle, clear chat, and close button
|
|
25
25
|
*/
|
|
26
26
|
export const buildDefaultHeader: HeaderLayoutRenderer = (context) => {
|
|
27
|
-
|
|
27
|
+
const elements = buildHeader({
|
|
28
28
|
config: context.config,
|
|
29
29
|
showClose: context.showClose,
|
|
30
30
|
onClose: context.onClose,
|
|
31
31
|
onClearChat: context.onClearChat
|
|
32
32
|
});
|
|
33
|
+
|
|
34
|
+
// Make the title/subtitle area clickable when onTitleClick is provided
|
|
35
|
+
const onTitleClick = context.layoutHeaderConfig?.onTitleClick;
|
|
36
|
+
if (onTitleClick) {
|
|
37
|
+
const headerCopy = elements.headerTitle.parentElement;
|
|
38
|
+
if (headerCopy) {
|
|
39
|
+
headerCopy.style.cursor = "pointer";
|
|
40
|
+
headerCopy.setAttribute("role", "button");
|
|
41
|
+
headerCopy.setAttribute("tabindex", "0");
|
|
42
|
+
headerCopy.addEventListener("click", () => onTitleClick());
|
|
43
|
+
headerCopy.addEventListener("keydown", (e) => {
|
|
44
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
onTitleClick();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return elements;
|
|
33
53
|
};
|
|
34
54
|
|
|
35
55
|
/**
|
|
@@ -68,6 +88,7 @@ export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
|
|
|
68
88
|
"div",
|
|
69
89
|
"persona-flex persona-items-center persona-justify-between persona-bg-persona-surface persona-px-6 persona-py-4 persona-border-b-persona-divider"
|
|
70
90
|
);
|
|
91
|
+
header.setAttribute("data-persona-theme-zone", "header");
|
|
71
92
|
|
|
72
93
|
const titleRow = createElement(
|
|
73
94
|
"div",
|
|
@@ -85,6 +106,25 @@ export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
|
|
|
85
106
|
layoutHeaderConfig?.onAction ?? onHeaderAction
|
|
86
107
|
);
|
|
87
108
|
|
|
109
|
+
// Make title row clickable when onTitleClick is provided
|
|
110
|
+
if (layoutHeaderConfig?.onTitleClick) {
|
|
111
|
+
titleRow.style.cursor = "pointer";
|
|
112
|
+
titleRow.setAttribute("role", "button");
|
|
113
|
+
titleRow.setAttribute("tabindex", "0");
|
|
114
|
+
const handleTitleClick = layoutHeaderConfig.onTitleClick;
|
|
115
|
+
titleRow.addEventListener("click", (e) => {
|
|
116
|
+
// Skip if the click was on a trailing action button
|
|
117
|
+
if ((e.target as HTMLElement).closest("button")) return;
|
|
118
|
+
handleTitleClick();
|
|
119
|
+
});
|
|
120
|
+
titleRow.addEventListener("keydown", (e) => {
|
|
121
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
handleTitleClick();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
88
128
|
header.appendChild(titleRow);
|
|
89
129
|
|
|
90
130
|
// Close button
|
|
@@ -470,6 +470,8 @@ export const createStandardBubble = (
|
|
|
470
470
|
bubble.id = `bubble-${message.id}`;
|
|
471
471
|
bubble.setAttribute("data-message-id", message.id);
|
|
472
472
|
|
|
473
|
+
bubble.setAttribute("data-persona-theme-zone", message.role === "user" ? "user-message" : "assistant-message");
|
|
474
|
+
|
|
473
475
|
// Apply component-level color overrides via CSS variables
|
|
474
476
|
if (message.role === "user") {
|
|
475
477
|
bubble.style.backgroundColor = 'var(--persona-message-user-bg, var(--persona-accent))';
|
package/src/components/panel.ts
CHANGED
|
@@ -116,6 +116,7 @@ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelE
|
|
|
116
116
|
"div",
|
|
117
117
|
"persona-widget-container persona-flex persona-h-full persona-w-full persona-flex-1 persona-min-h-0 persona-flex-col persona-bg-persona-surface persona-text-persona-primary persona-rounded-2xl persona-overflow-hidden persona-border persona-border-persona-border"
|
|
118
118
|
);
|
|
119
|
+
container.setAttribute("data-persona-theme-zone", "container");
|
|
119
120
|
|
|
120
121
|
// Build header using layout config if available, otherwise use standard builder
|
|
121
122
|
const headerLayoutConfig = config?.layout?.header;
|
|
@@ -130,6 +131,7 @@ export const buildPanel = (config?: AgentWidgetConfig, showClose = true): PanelE
|
|
|
130
131
|
"persona-widget-body persona-flex persona-flex-1 persona-min-h-0 persona-flex-col persona-gap-6 persona-overflow-y-auto persona-bg-persona-container persona-px-6 persona-py-6"
|
|
131
132
|
);
|
|
132
133
|
body.id = "persona-scroll-container";
|
|
134
|
+
body.setAttribute("data-persona-theme-zone", "messages");
|
|
133
135
|
|
|
134
136
|
const introCard = createElement(
|
|
135
137
|
"div",
|
package/src/index.ts
CHANGED
|
@@ -189,8 +189,10 @@ export {
|
|
|
189
189
|
DEFAULT_PALETTE,
|
|
190
190
|
DEFAULT_SEMANTIC,
|
|
191
191
|
DEFAULT_COMPONENTS,
|
|
192
|
-
validateTheme
|
|
192
|
+
validateTheme,
|
|
193
|
+
THEME_ZONES
|
|
193
194
|
} from "./utils/tokens";
|
|
195
|
+
export type { ThemeZone } from "./utils/tokens";
|
|
194
196
|
export {
|
|
195
197
|
accessibilityPlugin,
|
|
196
198
|
animationsPlugin,
|
|
@@ -219,6 +221,9 @@ export type {
|
|
|
219
221
|
SemanticSpacing,
|
|
220
222
|
SemanticTypography,
|
|
221
223
|
ComponentTokens,
|
|
224
|
+
ArtifactToolbarTokens,
|
|
225
|
+
ArtifactTabTokens,
|
|
226
|
+
ArtifactPaneTokens,
|
|
222
227
|
ThemeValidationResult,
|
|
223
228
|
ThemeValidationError
|
|
224
229
|
} from "./types/theme";
|
|
@@ -245,6 +250,14 @@ export {
|
|
|
245
250
|
DEFAULT_DARK_THEME,
|
|
246
251
|
mergeWithDefaults
|
|
247
252
|
} from "./defaults";
|
|
253
|
+
export {
|
|
254
|
+
PRESETS,
|
|
255
|
+
getPreset,
|
|
256
|
+
PRESET_SHOP,
|
|
257
|
+
PRESET_MINIMAL,
|
|
258
|
+
PRESET_FULLSCREEN
|
|
259
|
+
} from "./presets";
|
|
260
|
+
export type { WidgetPreset } from "./presets";
|
|
248
261
|
|
|
249
262
|
// Layout system exports
|
|
250
263
|
export {
|
package/src/presets.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { AgentWidgetConfig } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A named preset containing partial widget configuration.
|
|
5
|
+
* Apply with: `createAgentExperience(el, { ...PRESET_SHOP.config, apiUrl: '...' })`
|
|
6
|
+
* or via IIFE: `{ ...AgentWidget.PRESETS.shop.config, apiUrl: '...' }`
|
|
7
|
+
*/
|
|
8
|
+
export interface WidgetPreset {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
config: Partial<AgentWidgetConfig>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Shopping / e-commerce preset.
|
|
16
|
+
* Dark header, rounded launchers, shopping-oriented copy.
|
|
17
|
+
*/
|
|
18
|
+
export const PRESET_SHOP: WidgetPreset = {
|
|
19
|
+
id: 'shop',
|
|
20
|
+
label: 'Shopping Assistant',
|
|
21
|
+
config: {
|
|
22
|
+
theme: {
|
|
23
|
+
primary: '#111827',
|
|
24
|
+
accent: '#1d4ed8',
|
|
25
|
+
surface: '#ffffff',
|
|
26
|
+
muted: '#6b7280',
|
|
27
|
+
container: '#f8fafc',
|
|
28
|
+
border: '#f1f5f9',
|
|
29
|
+
divider: '#f1f5f9',
|
|
30
|
+
messageBorder: '#f1f5f9',
|
|
31
|
+
inputBackground: '#ffffff',
|
|
32
|
+
callToAction: '#000000',
|
|
33
|
+
callToActionBackground: '#ffffff',
|
|
34
|
+
sendButtonBackgroundColor: '#111827',
|
|
35
|
+
sendButtonTextColor: '#ffffff',
|
|
36
|
+
radiusSm: '0.75rem',
|
|
37
|
+
radiusMd: '1rem',
|
|
38
|
+
radiusLg: '1.5rem',
|
|
39
|
+
launcherRadius: '9999px',
|
|
40
|
+
buttonRadius: '9999px',
|
|
41
|
+
},
|
|
42
|
+
launcher: {
|
|
43
|
+
title: 'Shopping Assistant',
|
|
44
|
+
subtitle: 'Here to help you find what you need',
|
|
45
|
+
agentIconText: '🛍️',
|
|
46
|
+
position: 'bottom-right',
|
|
47
|
+
width: 'min(400px, calc(100vw - 24px))',
|
|
48
|
+
},
|
|
49
|
+
copy: {
|
|
50
|
+
welcomeTitle: 'Welcome to our shop!',
|
|
51
|
+
welcomeSubtitle: 'I can help you find products and answer questions',
|
|
52
|
+
inputPlaceholder: 'Ask me anything...',
|
|
53
|
+
sendButtonLabel: 'Send',
|
|
54
|
+
},
|
|
55
|
+
suggestionChips: [
|
|
56
|
+
'What can you help me with?',
|
|
57
|
+
'Tell me about your features',
|
|
58
|
+
'How does this work?',
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Minimal preset.
|
|
65
|
+
* Stripped-down header, no launcher button, suitable for inline embeds.
|
|
66
|
+
*/
|
|
67
|
+
export const PRESET_MINIMAL: WidgetPreset = {
|
|
68
|
+
id: 'minimal',
|
|
69
|
+
label: 'Minimal',
|
|
70
|
+
config: {
|
|
71
|
+
launcher: {
|
|
72
|
+
enabled: false,
|
|
73
|
+
fullHeight: true,
|
|
74
|
+
},
|
|
75
|
+
layout: {
|
|
76
|
+
header: {
|
|
77
|
+
layout: 'minimal',
|
|
78
|
+
showCloseButton: false,
|
|
79
|
+
},
|
|
80
|
+
messages: {
|
|
81
|
+
layout: 'minimal',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
theme: {
|
|
85
|
+
panelBorderRadius: '0',
|
|
86
|
+
panelShadow: 'none',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Fullscreen assistant preset.
|
|
93
|
+
* No launcher, content-max-width constrained, minimal header.
|
|
94
|
+
*/
|
|
95
|
+
export const PRESET_FULLSCREEN: WidgetPreset = {
|
|
96
|
+
id: 'fullscreen',
|
|
97
|
+
label: 'Fullscreen Assistant',
|
|
98
|
+
config: {
|
|
99
|
+
launcher: {
|
|
100
|
+
enabled: false,
|
|
101
|
+
fullHeight: true,
|
|
102
|
+
},
|
|
103
|
+
layout: {
|
|
104
|
+
header: {
|
|
105
|
+
layout: 'minimal',
|
|
106
|
+
showCloseButton: false,
|
|
107
|
+
},
|
|
108
|
+
contentMaxWidth: '72ch',
|
|
109
|
+
},
|
|
110
|
+
theme: {
|
|
111
|
+
panelBorderRadius: '0',
|
|
112
|
+
panelShadow: 'none',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** All named presets keyed by ID. */
|
|
118
|
+
export const PRESETS: Record<string, WidgetPreset> = {
|
|
119
|
+
shop: PRESET_SHOP,
|
|
120
|
+
minimal: PRESET_MINIMAL,
|
|
121
|
+
fullscreen: PRESET_FULLSCREEN,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** Look up a preset by ID. */
|
|
125
|
+
export function getPreset(id: string): WidgetPreset | undefined {
|
|
126
|
+
return PRESETS[id];
|
|
127
|
+
}
|
package/src/styles/widget.css
CHANGED
|
@@ -2064,9 +2064,9 @@
|
|
|
2064
2064
|
display: inline-flex;
|
|
2065
2065
|
align-items: center;
|
|
2066
2066
|
justify-content: center;
|
|
2067
|
-
padding: 0.25rem;
|
|
2068
|
-
border-radius: var(--persona-radius-md, 0.375rem);
|
|
2069
|
-
border: 1px solid var(--persona-border, #e5e7eb);
|
|
2067
|
+
padding: var(--persona-artifact-toolbar-icon-padding, 0.25rem);
|
|
2068
|
+
border-radius: var(--persona-artifact-toolbar-icon-radius, var(--persona-radius-md, 0.375rem));
|
|
2069
|
+
border: var(--persona-artifact-toolbar-icon-border, 1px solid var(--persona-border, #e5e7eb));
|
|
2070
2070
|
background: var(--persona-surface, #ffffff);
|
|
2071
2071
|
color: var(--persona-artifact-doc-toolbar-icon-color, var(--persona-text, #111827));
|
|
2072
2072
|
cursor: pointer;
|
|
@@ -2074,7 +2074,8 @@
|
|
|
2074
2074
|
}
|
|
2075
2075
|
|
|
2076
2076
|
#persona-root .persona-artifact-toolbar-document .persona-artifact-doc-icon-btn:hover {
|
|
2077
|
-
|
|
2077
|
+
color: var(--persona-artifact-toolbar-icon-hover-color, inherit);
|
|
2078
|
+
background: var(--persona-artifact-toolbar-icon-hover-bg, var(--persona-container, #f3f4f6));
|
|
2078
2079
|
}
|
|
2079
2080
|
|
|
2080
2081
|
#persona-root .persona-artifact-toolbar-document .persona-artifact-doc-icon-btn[aria-pressed="true"] {
|
|
@@ -2086,24 +2087,53 @@
|
|
|
2086
2087
|
display: inline-flex;
|
|
2087
2088
|
align-items: center;
|
|
2088
2089
|
gap: 0.35rem;
|
|
2089
|
-
padding: 0.25rem 0.5rem;
|
|
2090
|
-
border-radius: var(--persona-radius-md, 0.375rem);
|
|
2091
|
-
border: 1px solid var(--persona-border, #e5e7eb);
|
|
2092
|
-
background: var(--persona-surface, #ffffff);
|
|
2093
|
-
color: var(--persona-artifact-doc-toolbar-icon-color, var(--persona-text, #111827));
|
|
2090
|
+
padding: var(--persona-artifact-toolbar-copy-padding, 0.25rem 0.5rem);
|
|
2091
|
+
border-radius: var(--persona-artifact-toolbar-copy-radius, var(--persona-radius-md, 0.375rem));
|
|
2092
|
+
border: var(--persona-artifact-toolbar-copy-border, 1px solid var(--persona-border, #e5e7eb));
|
|
2093
|
+
background: var(--persona-artifact-toolbar-copy-bg, var(--persona-surface, #ffffff));
|
|
2094
|
+
color: var(--persona-artifact-toolbar-copy-color, var(--persona-artifact-doc-toolbar-icon-color, var(--persona-text, #111827)));
|
|
2094
2095
|
cursor: pointer;
|
|
2095
2096
|
font-size: 0.75rem;
|
|
2096
2097
|
line-height: 1.25;
|
|
2097
2098
|
}
|
|
2098
2099
|
|
|
2099
2100
|
#persona-root .persona-artifact-toolbar-document .persona-artifact-doc-copy-btn:hover {
|
|
2100
|
-
background: var(--persona-container, #f3f4f6);
|
|
2101
|
+
background: var(--persona-artifact-toolbar-icon-hover-bg, var(--persona-container, #f3f4f6));
|
|
2101
2102
|
}
|
|
2102
2103
|
|
|
2103
2104
|
#persona-root .persona-artifact-toolbar-document .persona-artifact-doc-copy-label {
|
|
2104
2105
|
font-weight: 500;
|
|
2105
2106
|
}
|
|
2106
2107
|
|
|
2108
|
+
/* Copy menu dropdown theming */
|
|
2109
|
+
#persona-root .persona-artifact-doc-copy-menu {
|
|
2110
|
+
background: var(--persona-artifact-toolbar-copy-menu-bg, var(--persona-surface, #fff));
|
|
2111
|
+
border: var(--persona-artifact-toolbar-copy-menu-border, 1px solid var(--persona-border, #e5e7eb));
|
|
2112
|
+
box-shadow: var(--persona-artifact-toolbar-copy-menu-shadow, 0 4px 6px -1px rgba(0,0,0,.1));
|
|
2113
|
+
border-radius: var(--persona-artifact-toolbar-copy-menu-radius, 0.375rem);
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
#persona-root .persona-artifact-doc-copy-menu button:hover {
|
|
2117
|
+
background: var(--persona-artifact-toolbar-copy-menu-item-hover-bg, var(--persona-container, #f3f4f6));
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
/* Artifact tab theming */
|
|
2121
|
+
#persona-root .persona-artifact-tab {
|
|
2122
|
+
background: var(--persona-artifact-tab-bg, transparent);
|
|
2123
|
+
border-radius: var(--persona-artifact-tab-radius, 0.5rem);
|
|
2124
|
+
color: var(--persona-artifact-tab-color, inherit);
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
#persona-root .persona-artifact-tab.persona-bg-persona-container {
|
|
2128
|
+
background: var(--persona-artifact-tab-active-bg, var(--persona-container, #f3f4f6));
|
|
2129
|
+
border-color: var(--persona-artifact-tab-active-border, var(--persona-border, #e5e7eb));
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
/* Artifact toolbar background theming */
|
|
2133
|
+
#persona-root .persona-artifact-toolbar {
|
|
2134
|
+
background: var(--persona-artifact-toolbar-bg, var(--persona-surface, #fff));
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2107
2137
|
/* Draggable split handle (desktop split only; hidden in drawer / narrow host / small viewport) */
|
|
2108
2138
|
#persona-root .persona-artifact-split-handle {
|
|
2109
2139
|
width: 6px;
|
package/src/types/theme.ts
CHANGED
|
@@ -298,6 +298,41 @@ export interface ComposerChromeTokens {
|
|
|
298
298
|
shadow: string;
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
/** Artifact toolbar chrome. */
|
|
302
|
+
export interface ArtifactToolbarTokens {
|
|
303
|
+
iconHoverColor?: string;
|
|
304
|
+
iconHoverBackground?: string;
|
|
305
|
+
iconPadding?: string;
|
|
306
|
+
iconBorderRadius?: string;
|
|
307
|
+
iconBorder?: string;
|
|
308
|
+
toggleGroupGap?: string;
|
|
309
|
+
toggleBorderRadius?: string;
|
|
310
|
+
copyBackground?: string;
|
|
311
|
+
copyBorder?: string;
|
|
312
|
+
copyColor?: string;
|
|
313
|
+
copyBorderRadius?: string;
|
|
314
|
+
copyPadding?: string;
|
|
315
|
+
copyMenuBackground?: string;
|
|
316
|
+
copyMenuBorder?: string;
|
|
317
|
+
copyMenuShadow?: string;
|
|
318
|
+
copyMenuBorderRadius?: string;
|
|
319
|
+
copyMenuItemHoverBackground?: string;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Artifact tab strip chrome. */
|
|
323
|
+
export interface ArtifactTabTokens {
|
|
324
|
+
background?: string;
|
|
325
|
+
activeBackground?: string;
|
|
326
|
+
activeBorder?: string;
|
|
327
|
+
borderRadius?: string;
|
|
328
|
+
textColor?: string;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Artifact pane chrome. */
|
|
332
|
+
export interface ArtifactPaneTokens {
|
|
333
|
+
toolbarBackground?: string;
|
|
334
|
+
}
|
|
335
|
+
|
|
301
336
|
export interface ComponentTokens {
|
|
302
337
|
button: ButtonTokens;
|
|
303
338
|
input: InputTokens;
|
|
@@ -313,6 +348,12 @@ export interface ComponentTokens {
|
|
|
313
348
|
toolBubble: ToolBubbleTokens;
|
|
314
349
|
reasoningBubble: ReasoningBubbleTokens;
|
|
315
350
|
composer: ComposerChromeTokens;
|
|
351
|
+
/** Artifact toolbar, tab strip, and pane chrome. */
|
|
352
|
+
artifact?: {
|
|
353
|
+
toolbar?: ArtifactToolbarTokens;
|
|
354
|
+
tab?: ArtifactTabTokens;
|
|
355
|
+
pane?: ArtifactPaneTokens;
|
|
356
|
+
};
|
|
316
357
|
}
|
|
317
358
|
|
|
318
359
|
export interface PaletteExtras {
|
package/src/types.ts
CHANGED
|
@@ -525,6 +525,14 @@ export type AgentWidgetArtifactsFeature = {
|
|
|
525
525
|
allowedTypes?: PersonaArtifactKind[];
|
|
526
526
|
/** Split / drawer dimensions and launcher widen behavior */
|
|
527
527
|
layout?: AgentWidgetArtifactsLayoutConfig;
|
|
528
|
+
/**
|
|
529
|
+
* Called when an artifact card action is triggered (open, download).
|
|
530
|
+
* Return `true` to prevent the default behavior.
|
|
531
|
+
*/
|
|
532
|
+
onArtifactAction?: (action: {
|
|
533
|
+
type: 'open' | 'download';
|
|
534
|
+
artifactId: string;
|
|
535
|
+
}) => boolean | void;
|
|
528
536
|
};
|
|
529
537
|
|
|
530
538
|
export type AgentWidgetFeatureFlags = {
|
|
@@ -1452,6 +1460,12 @@ export type AgentWidgetHeaderLayoutConfig = {
|
|
|
1452
1460
|
trailingActions?: AgentWidgetHeaderTrailingAction[];
|
|
1453
1461
|
/** Called when a `trailingActions` button is clicked. */
|
|
1454
1462
|
onAction?: (actionId: string) => void;
|
|
1463
|
+
/**
|
|
1464
|
+
* Called when the header title row is clicked.
|
|
1465
|
+
* Useful for dropdown menus or navigation triggered from the header.
|
|
1466
|
+
* When set, the title row becomes visually interactive (cursor: pointer).
|
|
1467
|
+
*/
|
|
1468
|
+
onTitleClick?: () => void;
|
|
1455
1469
|
};
|
|
1456
1470
|
|
|
1457
1471
|
/**
|
package/src/ui.ts
CHANGED
|
@@ -1142,6 +1142,9 @@ export const createAgentExperience = (
|
|
|
1142
1142
|
event.stopPropagation();
|
|
1143
1143
|
const artifactId = dlBtn.getAttribute('data-download-artifact');
|
|
1144
1144
|
if (!artifactId) return;
|
|
1145
|
+
// Let integrator intercept
|
|
1146
|
+
const dlPrevented = config.features?.artifacts?.onArtifactAction?.({ type: 'download', artifactId });
|
|
1147
|
+
if (dlPrevented === true) return;
|
|
1145
1148
|
// Try session state first, fall back to content stored in the card's rawContent props
|
|
1146
1149
|
const artifact = session.getArtifactById(artifactId);
|
|
1147
1150
|
let markdown = artifact?.markdown;
|
|
@@ -1180,6 +1183,9 @@ export const createAgentExperience = (
|
|
|
1180
1183
|
if (!card) return;
|
|
1181
1184
|
const artifactId = card.getAttribute('data-open-artifact');
|
|
1182
1185
|
if (!artifactId) return;
|
|
1186
|
+
// Let integrator intercept
|
|
1187
|
+
const openPrevented = config.features?.artifacts?.onArtifactAction?.({ type: 'open', artifactId });
|
|
1188
|
+
if (openPrevented === true) return;
|
|
1183
1189
|
event.preventDefault();
|
|
1184
1190
|
event.stopPropagation();
|
|
1185
1191
|
session.selectArtifact(artifactId);
|
|
@@ -3560,6 +3566,8 @@ export const createAgentExperience = (
|
|
|
3560
3566
|
const previousMessageActions = config.messageActions;
|
|
3561
3567
|
const previousLayoutMessages = config.layout?.messages;
|
|
3562
3568
|
const previousColorScheme = config.colorScheme;
|
|
3569
|
+
const previousLoadingIndicator = config.loadingIndicator;
|
|
3570
|
+
const previousIterationDisplay = config.iterationDisplay;
|
|
3563
3571
|
config = { ...config, ...nextConfig };
|
|
3564
3572
|
// applyFullHeightStyles resets mount.style.cssText, so call it before applyThemeVariables
|
|
3565
3573
|
applyFullHeightStyles();
|
|
@@ -3790,7 +3798,12 @@ export const createAgentExperience = (
|
|
|
3790
3798
|
const toolCallConfigChanged = JSON.stringify(nextConfig.toolCall) !== JSON.stringify(previousToolCallConfig);
|
|
3791
3799
|
const messageActionsChanged = JSON.stringify(config.messageActions) !== JSON.stringify(previousMessageActions);
|
|
3792
3800
|
const layoutMessagesChanged = JSON.stringify(config.layout?.messages) !== JSON.stringify(previousLayoutMessages);
|
|
3793
|
-
const
|
|
3801
|
+
const loadingIndicatorChanged = config.loadingIndicator?.render !== previousLoadingIndicator?.render
|
|
3802
|
+
|| config.loadingIndicator?.renderIdle !== previousLoadingIndicator?.renderIdle
|
|
3803
|
+
|| config.loadingIndicator?.showBubble !== previousLoadingIndicator?.showBubble;
|
|
3804
|
+
const iterationDisplayChanged = config.iterationDisplay !== previousIterationDisplay;
|
|
3805
|
+
const messagesConfigChanged = toolCallConfigChanged || messageActionsChanged || layoutMessagesChanged
|
|
3806
|
+
|| loadingIndicatorChanged || iterationDisplayChanged;
|
|
3794
3807
|
if (messagesConfigChanged && session) {
|
|
3795
3808
|
configVersion++;
|
|
3796
3809
|
renderMessagesWithPlugins(messagesWrapper, session.getMessages(), postprocess);
|
package/src/utils/tokens.ts
CHANGED
|
@@ -689,6 +689,42 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
|
|
|
689
689
|
cssVars['--persona-md-prose-font-family'] = mdProseFont;
|
|
690
690
|
}
|
|
691
691
|
|
|
692
|
+
// Artifact tokens
|
|
693
|
+
const components = theme.components;
|
|
694
|
+
const artifact = components?.artifact;
|
|
695
|
+
if (artifact?.toolbar) {
|
|
696
|
+
const t = artifact.toolbar;
|
|
697
|
+
if (t.iconHoverColor) cssVars['--persona-artifact-toolbar-icon-hover-color'] = t.iconHoverColor;
|
|
698
|
+
if (t.iconHoverBackground) cssVars['--persona-artifact-toolbar-icon-hover-bg'] = t.iconHoverBackground;
|
|
699
|
+
if (t.iconPadding) cssVars['--persona-artifact-toolbar-icon-padding'] = t.iconPadding;
|
|
700
|
+
if (t.iconBorderRadius) cssVars['--persona-artifact-toolbar-icon-radius'] = t.iconBorderRadius;
|
|
701
|
+
if (t.iconBorder) cssVars['--persona-artifact-toolbar-icon-border'] = t.iconBorder;
|
|
702
|
+
if (t.toggleGroupGap) cssVars['--persona-artifact-toolbar-toggle-group-gap'] = t.toggleGroupGap;
|
|
703
|
+
if (t.toggleBorderRadius) cssVars['--persona-artifact-toolbar-toggle-radius'] = t.toggleBorderRadius;
|
|
704
|
+
if (t.copyBackground) cssVars['--persona-artifact-toolbar-copy-bg'] = t.copyBackground;
|
|
705
|
+
if (t.copyBorder) cssVars['--persona-artifact-toolbar-copy-border'] = t.copyBorder;
|
|
706
|
+
if (t.copyColor) cssVars['--persona-artifact-toolbar-copy-color'] = t.copyColor;
|
|
707
|
+
if (t.copyBorderRadius) cssVars['--persona-artifact-toolbar-copy-radius'] = t.copyBorderRadius;
|
|
708
|
+
if (t.copyPadding) cssVars['--persona-artifact-toolbar-copy-padding'] = t.copyPadding;
|
|
709
|
+
if (t.copyMenuBackground) cssVars['--persona-artifact-toolbar-copy-menu-bg'] = t.copyMenuBackground;
|
|
710
|
+
if (t.copyMenuBorder) cssVars['--persona-artifact-toolbar-copy-menu-border'] = t.copyMenuBorder;
|
|
711
|
+
if (t.copyMenuShadow) cssVars['--persona-artifact-toolbar-copy-menu-shadow'] = t.copyMenuShadow;
|
|
712
|
+
if (t.copyMenuBorderRadius) cssVars['--persona-artifact-toolbar-copy-menu-radius'] = t.copyMenuBorderRadius;
|
|
713
|
+
if (t.copyMenuItemHoverBackground) cssVars['--persona-artifact-toolbar-copy-menu-item-hover-bg'] = t.copyMenuItemHoverBackground;
|
|
714
|
+
}
|
|
715
|
+
if (artifact?.tab) {
|
|
716
|
+
const t = artifact.tab;
|
|
717
|
+
if (t.background) cssVars['--persona-artifact-tab-bg'] = t.background;
|
|
718
|
+
if (t.activeBackground) cssVars['--persona-artifact-tab-active-bg'] = t.activeBackground;
|
|
719
|
+
if (t.activeBorder) cssVars['--persona-artifact-tab-active-border'] = t.activeBorder;
|
|
720
|
+
if (t.borderRadius) cssVars['--persona-artifact-tab-radius'] = t.borderRadius;
|
|
721
|
+
if (t.textColor) cssVars['--persona-artifact-tab-color'] = t.textColor;
|
|
722
|
+
}
|
|
723
|
+
if (artifact?.pane) {
|
|
724
|
+
const t = artifact.pane;
|
|
725
|
+
if (t.toolbarBackground) cssVars['--persona-artifact-toolbar-bg'] = t.toolbarBackground;
|
|
726
|
+
}
|
|
727
|
+
|
|
692
728
|
return cssVars;
|
|
693
729
|
}
|
|
694
730
|
|
|
@@ -699,3 +735,21 @@ export function applyThemeVariables(element: HTMLElement, theme: PersonaTheme):
|
|
|
699
735
|
element.style.setProperty(name, value);
|
|
700
736
|
}
|
|
701
737
|
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Stable `data-persona-theme-zone` values applied to key widget regions.
|
|
741
|
+
* Visual editors should use `[data-persona-theme-zone="header"]` selectors
|
|
742
|
+
* rather than internal class names.
|
|
743
|
+
*/
|
|
744
|
+
export const THEME_ZONES = {
|
|
745
|
+
header: 'Widget header bar',
|
|
746
|
+
messages: 'Message list area',
|
|
747
|
+
'user-message': 'User message bubble',
|
|
748
|
+
'assistant-message': 'Assistant message bubble',
|
|
749
|
+
composer: 'Footer / composer area',
|
|
750
|
+
container: 'Main widget container',
|
|
751
|
+
'artifact-pane': 'Artifact sidebar',
|
|
752
|
+
'artifact-toolbar': 'Artifact toolbar',
|
|
753
|
+
} as const;
|
|
754
|
+
|
|
755
|
+
export type ThemeZone = keyof typeof THEME_ZONES;
|