@runtypelabs/persona 3.5.1 → 3.6.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/index.cjs +30 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.global.js +41 -41
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +29 -29
- package/dist/index.js.map +1 -1
- package/dist/theme-editor.cjs +17728 -0
- package/dist/theme-editor.d.cts +3857 -0
- package/dist/theme-editor.d.ts +3857 -0
- package/dist/theme-editor.js +17623 -0
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +14 -0
- package/dist/theme-reference.d.ts +14 -0
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +29 -25
- package/package.json +9 -7
- package/src/components/artifact-card.ts +1 -1
- package/src/components/composer-builder.ts +16 -29
- package/src/components/demo-carousel.ts +4 -4
- package/src/components/event-stream-view.ts +1 -1
- package/src/components/header-builder.ts +2 -2
- package/src/components/launcher.ts +9 -0
- package/src/components/message-bubble.ts +9 -3
- package/src/components/suggestions.ts +1 -1
- package/src/defaults.ts +9 -9
- package/src/styles/widget.css +29 -25
- package/src/theme-editor/color-utils.ts +252 -0
- package/src/theme-editor/index.ts +130 -0
- package/src/theme-editor/presets.ts +144 -0
- package/src/theme-editor/preview-utils.ts +265 -0
- package/src/theme-editor/preview.ts +445 -0
- package/src/theme-editor/role-mappings.ts +331 -0
- package/src/theme-editor/sections.ts +952 -0
- package/src/theme-editor/state.ts +298 -0
- package/src/theme-editor/types.ts +177 -0
- package/src/theme-editor.ts +2 -0
- package/src/types/theme.ts +1 -0
- package/src/ui.ts +53 -58
- package/src/utils/plugins.ts +1 -1
- package/src/utils/theme.test.ts +10 -8
- package/src/utils/theme.ts +11 -11
- package/src/utils/tokens.ts +88 -41
- package/widget.css +0 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/** Headless state management for the theme editor (no DOM, no localStorage, no side effects) */
|
|
2
|
+
|
|
3
|
+
import type { AgentWidgetConfig } from '../types';
|
|
4
|
+
import type { PersonaTheme } from '../types/theme';
|
|
5
|
+
import { createTheme } from '../utils/theme';
|
|
6
|
+
import { DEFAULT_WIDGET_CONFIG } from '../defaults';
|
|
7
|
+
import type { ConfiguratorSnapshot, ConfigChangeListener } from './types';
|
|
8
|
+
|
|
9
|
+
// ─── Dot-path utilities ─────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
function getByPath(obj: unknown, path: string): unknown {
|
|
12
|
+
const parts = path.split('.');
|
|
13
|
+
let current: unknown = obj;
|
|
14
|
+
for (const part of parts) {
|
|
15
|
+
if (current === undefined || current === null) return undefined;
|
|
16
|
+
current = (current as Record<string, unknown>)[part];
|
|
17
|
+
}
|
|
18
|
+
return current;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function setByPath(obj: unknown, path: string, value: unknown): unknown {
|
|
22
|
+
const parts = path.split('.');
|
|
23
|
+
if (parts.length === 1) {
|
|
24
|
+
return { ...(obj as Record<string, unknown>), [parts[0]]: value };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const [first, ...rest] = parts;
|
|
28
|
+
const current = obj as Record<string, unknown>;
|
|
29
|
+
return {
|
|
30
|
+
...current,
|
|
31
|
+
[first]: setByPath(current?.[first] ?? {}, rest.join('.'), value),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── ThemeEditorState ───────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export class ThemeEditorState {
|
|
38
|
+
private config: AgentWidgetConfig;
|
|
39
|
+
private theme: PersonaTheme;
|
|
40
|
+
private listeners: ConfigChangeListener[] = [];
|
|
41
|
+
private history: ConfiguratorSnapshot[] = [];
|
|
42
|
+
private historyIndex = -1;
|
|
43
|
+
private suppressHistory = false;
|
|
44
|
+
|
|
45
|
+
constructor(
|
|
46
|
+
initialTheme?: Partial<PersonaTheme>,
|
|
47
|
+
initialConfig?: Partial<AgentWidgetConfig>,
|
|
48
|
+
options?: { mergeDefaults?: boolean }
|
|
49
|
+
) {
|
|
50
|
+
const mergeDefaults = options?.mergeDefaults ?? true;
|
|
51
|
+
this.config = (mergeDefaults
|
|
52
|
+
? { ...DEFAULT_WIDGET_CONFIG, ...initialConfig }
|
|
53
|
+
: (initialConfig ?? DEFAULT_WIDGET_CONFIG)
|
|
54
|
+
) as AgentWidgetConfig;
|
|
55
|
+
this.theme = createTheme(initialTheme, { validate: false });
|
|
56
|
+
this.syncThemeIntoConfig();
|
|
57
|
+
this.pushHistorySnapshot(this.exportSnapshot(), true);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Read ───────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get a value using a dot-path.
|
|
64
|
+
* - `theme.*` → reads from the PersonaTheme
|
|
65
|
+
* - `darkTheme.*` → reads from config.darkTheme
|
|
66
|
+
* - everything else → reads from the AgentWidgetConfig
|
|
67
|
+
*/
|
|
68
|
+
get(path: string): unknown {
|
|
69
|
+
if (path.startsWith('theme.')) {
|
|
70
|
+
return getByPath(this.theme, path.replace('theme.', ''));
|
|
71
|
+
}
|
|
72
|
+
if (path.startsWith('darkTheme.')) {
|
|
73
|
+
return getByPath(this.config.darkTheme ?? {}, path.replace('darkTheme.', ''));
|
|
74
|
+
}
|
|
75
|
+
return getByPath(this.config, path);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getTheme(): PersonaTheme {
|
|
79
|
+
return this.theme;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getConfig(): AgentWidgetConfig {
|
|
83
|
+
return this.config;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── Write ──────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Set a value using a dot-path.
|
|
90
|
+
* - `theme.*` → writes into the PersonaTheme
|
|
91
|
+
* - `darkTheme.*` → writes into config.darkTheme
|
|
92
|
+
* - everything else → writes into AgentWidgetConfig
|
|
93
|
+
*/
|
|
94
|
+
set(path: string, value: unknown): void {
|
|
95
|
+
if (path.startsWith('theme.')) {
|
|
96
|
+
const themePath = path.replace('theme.', '');
|
|
97
|
+
this.theme = setByPath(this.theme, themePath, value) as PersonaTheme;
|
|
98
|
+
this.syncThemeIntoConfig();
|
|
99
|
+
} else if (path.startsWith('darkTheme.')) {
|
|
100
|
+
const themePath = path.replace('darkTheme.', '');
|
|
101
|
+
const dark = this.config.darkTheme ?? createTheme();
|
|
102
|
+
this.config = {
|
|
103
|
+
...this.config,
|
|
104
|
+
darkTheme: setByPath(dark, themePath, value) as AgentWidgetConfig['darkTheme'],
|
|
105
|
+
};
|
|
106
|
+
} else {
|
|
107
|
+
this.config = setByPath(this.config, path, value) as AgentWidgetConfig;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.recordHistory();
|
|
111
|
+
this.notifyListeners();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Batch-set multiple paths at once */
|
|
115
|
+
setBatch(updates: Record<string, unknown>): void {
|
|
116
|
+
let themeChanged = false;
|
|
117
|
+
let darkThemeChanged = false;
|
|
118
|
+
let configChanged = false;
|
|
119
|
+
|
|
120
|
+
for (const [path, value] of Object.entries(updates)) {
|
|
121
|
+
if (path.startsWith('theme.')) {
|
|
122
|
+
const themePath = path.replace('theme.', '');
|
|
123
|
+
this.theme = setByPath(this.theme, themePath, value) as PersonaTheme;
|
|
124
|
+
themeChanged = true;
|
|
125
|
+
} else if (path.startsWith('darkTheme.')) {
|
|
126
|
+
const themePath = path.replace('darkTheme.', '');
|
|
127
|
+
const dark = this.config.darkTheme ?? createTheme();
|
|
128
|
+
this.config = {
|
|
129
|
+
...this.config,
|
|
130
|
+
darkTheme: setByPath(dark, themePath, value) as AgentWidgetConfig['darkTheme'],
|
|
131
|
+
};
|
|
132
|
+
darkThemeChanged = true;
|
|
133
|
+
} else {
|
|
134
|
+
this.config = setByPath(this.config, path, value) as AgentWidgetConfig;
|
|
135
|
+
configChanged = true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (themeChanged) {
|
|
140
|
+
this.syncThemeIntoConfig();
|
|
141
|
+
}
|
|
142
|
+
if (themeChanged || darkThemeChanged || configChanged) {
|
|
143
|
+
this.recordHistory();
|
|
144
|
+
this.notifyListeners();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Replace the entire theme */
|
|
149
|
+
setTheme(theme: PersonaTheme): void {
|
|
150
|
+
this.theme = theme;
|
|
151
|
+
this.syncThemeIntoConfig();
|
|
152
|
+
this.recordHistory();
|
|
153
|
+
this.notifyListeners();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Replace the entire config (for preset loading) */
|
|
157
|
+
setFullConfig(config: AgentWidgetConfig, theme?: PersonaTheme): void {
|
|
158
|
+
this.config = { ...config };
|
|
159
|
+
if (theme) {
|
|
160
|
+
this.theme = theme;
|
|
161
|
+
}
|
|
162
|
+
this.syncThemeIntoConfig();
|
|
163
|
+
this.recordHistory();
|
|
164
|
+
this.notifyListeners();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Import a snapshot (v2 or raw theme) */
|
|
168
|
+
importSnapshot(snapshot: unknown): void {
|
|
169
|
+
if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) {
|
|
170
|
+
throw new Error('Snapshot must be a JSON object');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const parsed = snapshot as Partial<ConfiguratorSnapshot> & { config?: unknown; theme?: unknown };
|
|
174
|
+
|
|
175
|
+
if ('config' in parsed || 'theme' in parsed || parsed.version === 2) {
|
|
176
|
+
const config = (parsed.config ?? this.config) as AgentWidgetConfig;
|
|
177
|
+
const theme = createTheme(
|
|
178
|
+
(parsed.theme ?? this.theme) as Partial<PersonaTheme>,
|
|
179
|
+
{ validate: false }
|
|
180
|
+
);
|
|
181
|
+
this.setFullConfig(config, theme);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const theme = createTheme(parsed as Partial<PersonaTheme>, { validate: false });
|
|
186
|
+
this.setTheme(theme);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Reset to defaults */
|
|
190
|
+
resetToDefaults(): void {
|
|
191
|
+
this.config = { ...DEFAULT_WIDGET_CONFIG } as AgentWidgetConfig;
|
|
192
|
+
this.theme = createTheme();
|
|
193
|
+
this.syncThemeIntoConfig();
|
|
194
|
+
this.history = [];
|
|
195
|
+
this.historyIndex = -1;
|
|
196
|
+
this.pushHistorySnapshot(this.exportSnapshot());
|
|
197
|
+
this.notifyListeners();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ─── History ────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
canUndo(): boolean {
|
|
203
|
+
return this.historyIndex > 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
canRedo(): boolean {
|
|
207
|
+
return this.historyIndex >= 0 && this.historyIndex < this.history.length - 1;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
getHistoryLength(): number {
|
|
211
|
+
return this.history.length;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
getHistoryIndex(): number {
|
|
215
|
+
return this.historyIndex;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
undo(): void {
|
|
219
|
+
if (!this.canUndo()) return;
|
|
220
|
+
this.historyIndex -= 1;
|
|
221
|
+
this.restoreSnapshot(this.history[this.historyIndex]);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
redo(): void {
|
|
225
|
+
if (!this.canRedo()) return;
|
|
226
|
+
this.historyIndex += 1;
|
|
227
|
+
this.restoreSnapshot(this.history[this.historyIndex]);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ─── Snapshots ──────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
exportSnapshot(): ConfiguratorSnapshot {
|
|
233
|
+
return {
|
|
234
|
+
version: 2,
|
|
235
|
+
config: { ...this.config, theme: undefined } as unknown as Record<string, unknown>,
|
|
236
|
+
theme: this.theme,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── Listeners ──────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
onChange(listener: ConfigChangeListener): () => void {
|
|
243
|
+
this.listeners.push(listener);
|
|
244
|
+
return () => {
|
|
245
|
+
const idx = this.listeners.indexOf(listener);
|
|
246
|
+
if (idx >= 0) this.listeners.splice(idx, 1);
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ─── Private ────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
private syncThemeIntoConfig(): void {
|
|
253
|
+
this.config = {
|
|
254
|
+
...this.config,
|
|
255
|
+
theme: this.theme,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private notifyListeners(): void {
|
|
260
|
+
for (const listener of this.listeners) {
|
|
261
|
+
listener(this.config, this.theme);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private recordHistory(): void {
|
|
266
|
+
this.pushHistorySnapshot(this.exportSnapshot());
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private pushHistorySnapshot(snapshot: ConfiguratorSnapshot, replaceCurrent = false): void {
|
|
270
|
+
if (this.suppressHistory) return;
|
|
271
|
+
|
|
272
|
+
const serialized = JSON.stringify(snapshot);
|
|
273
|
+
const currentSerialized =
|
|
274
|
+
this.historyIndex >= 0 && this.history[this.historyIndex]
|
|
275
|
+
? JSON.stringify(this.history[this.historyIndex])
|
|
276
|
+
: null;
|
|
277
|
+
|
|
278
|
+
if (replaceCurrent && this.historyIndex >= 0) {
|
|
279
|
+
this.history[this.historyIndex] = snapshot;
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (serialized === currentSerialized) return;
|
|
284
|
+
|
|
285
|
+
this.history = this.history.slice(0, this.historyIndex + 1);
|
|
286
|
+
this.history.push(snapshot);
|
|
287
|
+
this.historyIndex = this.history.length - 1;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private restoreSnapshot(snapshot: ConfiguratorSnapshot): void {
|
|
291
|
+
this.suppressHistory = true;
|
|
292
|
+
this.config = snapshot.config as unknown as AgentWidgetConfig;
|
|
293
|
+
this.theme = createTheme(snapshot.theme, { validate: false });
|
|
294
|
+
this.syncThemeIntoConfig();
|
|
295
|
+
this.suppressHistory = false;
|
|
296
|
+
this.notifyListeners();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/** Field definition types for the declarative configurator system (headless — no DOM) */
|
|
2
|
+
|
|
3
|
+
import type { DeepPartial, PersonaTheme } from '../types/theme';
|
|
4
|
+
import type { AgentWidgetConfig } from '../types';
|
|
5
|
+
|
|
6
|
+
// ─── Field System ────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export type FieldType =
|
|
9
|
+
| 'color'
|
|
10
|
+
| 'slider'
|
|
11
|
+
| 'toggle'
|
|
12
|
+
| 'select'
|
|
13
|
+
| 'text'
|
|
14
|
+
| 'chip-list'
|
|
15
|
+
| 'color-scale'
|
|
16
|
+
| 'token-ref'
|
|
17
|
+
| 'role-assignment';
|
|
18
|
+
|
|
19
|
+
export interface SliderOptions {
|
|
20
|
+
min: number;
|
|
21
|
+
max: number;
|
|
22
|
+
step: number;
|
|
23
|
+
unit?: 'px' | 'rem' | 'none';
|
|
24
|
+
/** Treat max value as 9999px (border-radius: full) */
|
|
25
|
+
isRadiusFull?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SelectOption {
|
|
29
|
+
value: string;
|
|
30
|
+
label: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ColorScaleOptions {
|
|
34
|
+
/** Which palette color family (e.g., 'primary', 'gray') */
|
|
35
|
+
colorFamily: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TokenRefOptions {
|
|
39
|
+
/** Token type to filter available references */
|
|
40
|
+
tokenType: 'color' | 'spacing' | 'radius' | 'shadow' | 'typography';
|
|
41
|
+
/** Available palette families to reference */
|
|
42
|
+
families?: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── Role Assignment System ─────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/** Which kind of value a role target expects */
|
|
48
|
+
export type RoleTargetKind = 'background' | 'foreground' | 'border' | 'accent';
|
|
49
|
+
|
|
50
|
+
/** A single token path that a role assignment writes to */
|
|
51
|
+
export interface RoleTarget {
|
|
52
|
+
/** Theme token path (e.g., 'components.message.user.background') */
|
|
53
|
+
path: string;
|
|
54
|
+
/** What kind of value this target expects */
|
|
55
|
+
kind: RoleTargetKind;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** An intensity preset (e.g., Solid uses .500 shades, Soft uses .100 shades) */
|
|
59
|
+
export interface RoleIntensity {
|
|
60
|
+
id: string;
|
|
61
|
+
label: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Options for a role-assignment field */
|
|
65
|
+
export interface RoleAssignmentOptions {
|
|
66
|
+
/** Unique role identifier */
|
|
67
|
+
roleId: string;
|
|
68
|
+
/** Helper text shown below the role name */
|
|
69
|
+
helper: string;
|
|
70
|
+
/** Token paths this role writes to */
|
|
71
|
+
targets: RoleTarget[];
|
|
72
|
+
/** Available intensity presets */
|
|
73
|
+
intensities: RoleIntensity[];
|
|
74
|
+
/** Which data-persona-theme-zone this role corresponds to (for preview highlighting) */
|
|
75
|
+
previewZone?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface FieldDef {
|
|
79
|
+
id: string;
|
|
80
|
+
label: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
type: FieldType;
|
|
83
|
+
/** Dot-path into the config/theme object */
|
|
84
|
+
path: string;
|
|
85
|
+
defaultValue?: unknown;
|
|
86
|
+
/** Slider-specific options */
|
|
87
|
+
slider?: SliderOptions;
|
|
88
|
+
/** Select-specific options */
|
|
89
|
+
options?: SelectOption[];
|
|
90
|
+
/** Color-scale-specific options */
|
|
91
|
+
colorScale?: ColorScaleOptions;
|
|
92
|
+
/** Token-ref-specific options */
|
|
93
|
+
tokenRef?: TokenRefOptions;
|
|
94
|
+
/** Role-assignment-specific options */
|
|
95
|
+
roleAssignment?: RoleAssignmentOptions;
|
|
96
|
+
/** CSS property hint for value formatting */
|
|
97
|
+
cssProperty?: string;
|
|
98
|
+
/** Whether this is a theme path (vs config path) */
|
|
99
|
+
isThemePath?: boolean;
|
|
100
|
+
/** Convert stored value into a control-friendly value */
|
|
101
|
+
formatValue?: (value: unknown) => unknown;
|
|
102
|
+
/** Convert control input back into the stored value shape */
|
|
103
|
+
parseValue?: (value: unknown) => unknown;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface SectionDef {
|
|
107
|
+
id: string;
|
|
108
|
+
title: string;
|
|
109
|
+
description?: string;
|
|
110
|
+
fields: FieldDef[];
|
|
111
|
+
/** Whether the section starts collapsed */
|
|
112
|
+
collapsed?: boolean;
|
|
113
|
+
/** Preset buttons for this section */
|
|
114
|
+
presets?: SectionPreset[];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface SectionPreset {
|
|
118
|
+
id: string;
|
|
119
|
+
label: string;
|
|
120
|
+
values: Record<string, unknown>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface TabDef {
|
|
124
|
+
id: string;
|
|
125
|
+
label: string;
|
|
126
|
+
icon?: string;
|
|
127
|
+
sections: SectionDef[];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface SubGroupDef {
|
|
131
|
+
label: string;
|
|
132
|
+
sections: SectionDef[];
|
|
133
|
+
/** When true, the sub-group starts collapsed and must be explicitly expanded */
|
|
134
|
+
collapsedByDefault?: boolean;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── Preset System ───────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
/** Extract the toolCall config type from AgentWidgetConfig */
|
|
140
|
+
type AgentWidgetToolCallConfig = NonNullable<AgentWidgetConfig['toolCall']>;
|
|
141
|
+
|
|
142
|
+
export interface ThemeEditorPreset {
|
|
143
|
+
id: string;
|
|
144
|
+
name: string;
|
|
145
|
+
description: string;
|
|
146
|
+
theme: DeepPartial<PersonaTheme>;
|
|
147
|
+
darkTheme?: DeepPartial<PersonaTheme>;
|
|
148
|
+
/** Tool call styling for light mode */
|
|
149
|
+
toolCall?: AgentWidgetToolCallConfig;
|
|
150
|
+
/** Tool call styling for dark mode (falls back to toolCall if not set) */
|
|
151
|
+
darkToolCall?: AgentWidgetToolCallConfig;
|
|
152
|
+
preview: {
|
|
153
|
+
primary: string;
|
|
154
|
+
surface: string;
|
|
155
|
+
accent: string;
|
|
156
|
+
};
|
|
157
|
+
darkPreview?: {
|
|
158
|
+
primary: string;
|
|
159
|
+
surface: string;
|
|
160
|
+
accent: string;
|
|
161
|
+
};
|
|
162
|
+
/** Tags for filtering/categorization */
|
|
163
|
+
tags?: string[];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ─── State ───────────────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
export interface ConfiguratorSnapshot {
|
|
169
|
+
version: 2;
|
|
170
|
+
config: Record<string, unknown>;
|
|
171
|
+
theme: PersonaTheme;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type ConfigChangeListener = (config: AgentWidgetConfig, theme: PersonaTheme) => void;
|
|
175
|
+
|
|
176
|
+
/** Callback for when a control value changes */
|
|
177
|
+
export type OnChangeCallback = (path: string, value: unknown) => void;
|