@runtypelabs/persona 4.13.0 → 4.14.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 +98 -6
- package/dist/context-mentions.d.ts +98 -6
- package/dist/index.cjs +64 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +183 -8
- package/dist/index.d.ts +183 -8
- package/dist/index.global.js +54 -54
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +67 -67
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +1 -1
- package/dist/launcher.global.js.map +1 -1
- package/dist/smart-dom-reader.d.cts +98 -6
- package/dist/smart-dom-reader.d.ts +98 -6
- package/dist/theme-editor-preview.cjs +66 -66
- package/dist/theme-editor-preview.d.cts +100 -6
- package/dist/theme-editor-preview.d.ts +100 -6
- package/dist/theme-editor-preview.js +75 -75
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +134 -7
- package/dist/theme-editor.d.ts +134 -7
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/widget.css +1 -1
- package/package.json +1 -1
- package/src/artifacts-session.test.ts +106 -0
- package/src/client.test.ts +2 -2
- package/src/client.ts +4 -1
- package/src/components/artifact-card.ts +13 -2
- package/src/components/artifact-inline.ts +11 -2
- package/src/components/composer-builder.test.ts +2 -0
- package/src/components/composer-builder.ts +1 -1
- package/src/components/composer-parts.test.ts +2 -0
- package/src/components/composer-parts.ts +1 -1
- package/src/components/message-bubble.test.ts +7 -1
- package/src/components/message-bubble.ts +11 -8
- package/src/index-core.ts +34 -0
- package/src/session.ts +45 -4
- package/src/styles/widget.css +10 -1
- package/src/theme-editor/index.ts +5 -0
- package/src/theme-editor/presets.test.ts +43 -0
- package/src/theme-editor/presets.ts +101 -87
- package/src/theme-editor/sections.test.ts +39 -7
- package/src/theme-editor/sections.ts +60 -8
- package/src/theme-editor/state-preferences.test.ts +15 -0
- package/src/theme-editor/state.ts +46 -0
- package/src/theme-editor/types.ts +15 -1
- package/src/theme-editor/webmcp/tools.test.ts +13 -0
- package/src/theme-editor/webmcp/tools.ts +5 -0
- package/src/types.ts +139 -9
- package/src/ui.artifact-pane-gating.test.ts +5 -5
- package/src/ui.config-preferences.test.ts +166 -0
- package/src/ui.ts +43 -25
- package/src/ui.typing-indicator.test.ts +135 -0
- package/src/utils/artifact-display.test.ts +180 -16
- package/src/utils/artifact-display.ts +155 -14
- package/src/utils/config-merge.test.ts +60 -0
- package/src/utils/config-merge.ts +6 -1
- package/src/utils/feature-preferences.test.ts +326 -0
- package/src/utils/feature-preferences.ts +498 -0
- package/src/utils/theme.test.ts +92 -0
- package/src/utils/tokens.ts +7 -7
|
@@ -2,100 +2,114 @@
|
|
|
2
2
|
|
|
3
3
|
import type { ThemeEditorPreset } from './types';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
toolbarBackground: 'semantic.colors.container',
|
|
16
|
-
},
|
|
5
|
+
const DEFAULT_LIGHT_PRESET: ThemeEditorPreset = {
|
|
6
|
+
id: 'default-light',
|
|
7
|
+
name: 'Default Light',
|
|
8
|
+
description: 'Clean monochrome light theme',
|
|
9
|
+
theme: {
|
|
10
|
+
components: {
|
|
11
|
+
artifact: {
|
|
12
|
+
pane: {
|
|
13
|
+
background: 'semantic.colors.container',
|
|
14
|
+
toolbarBackground: 'semantic.colors.container',
|
|
17
15
|
},
|
|
18
16
|
},
|
|
19
17
|
},
|
|
20
|
-
preview: { primary: '#171717', surface: '#ffffff', accent: '#0f0f0f' },
|
|
21
|
-
tags: ['light'],
|
|
22
18
|
},
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
800: '#1f2937', 900: '#111827', 950: '#030712',
|
|
39
|
-
},
|
|
40
|
-
secondary: {
|
|
41
|
-
50: '#f5f3ff', 100: '#ede9fe', 200: '#ddd6fe', 300: '#c4b5fd',
|
|
42
|
-
400: '#a78bfa', 500: '#8b5cf6', 600: '#7c3aed', 700: '#6d28d9',
|
|
43
|
-
800: '#5b21b6', 900: '#4c1d95', 950: '#2e1065',
|
|
44
|
-
},
|
|
45
|
-
accent: {
|
|
46
|
-
50: '#ecfeff', 100: '#cffafe', 200: '#a5f3fc', 300: '#67e8f9',
|
|
47
|
-
400: '#22d3ee', 500: '#06b6d4', 600: '#0891b2', 700: '#0e7490',
|
|
48
|
-
800: '#155e75', 900: '#164e63', 950: '#083344',
|
|
49
|
-
},
|
|
50
|
-
success: {
|
|
51
|
-
50: '#f0fdf4', 100: '#dcfce7', 200: '#bbf7d0', 300: '#86efac',
|
|
52
|
-
400: '#4ade80', 500: '#22c55e', 600: '#16a34a', 700: '#15803d',
|
|
53
|
-
800: '#166534', 900: '#14532d',
|
|
54
|
-
},
|
|
55
|
-
warning: {
|
|
56
|
-
50: '#fefce8', 100: '#fef9c3', 200: '#fef08a', 300: '#fde047',
|
|
57
|
-
400: '#facc15', 500: '#eab308', 600: '#ca8a04', 700: '#a16207',
|
|
58
|
-
800: '#854d0e', 900: '#713f12',
|
|
59
|
-
},
|
|
60
|
-
error: {
|
|
61
|
-
50: '#fef2f2', 100: '#fee2e2', 200: '#fecaca', 300: '#fca5a5',
|
|
62
|
-
400: '#f87171', 500: '#ef4444', 600: '#dc2626', 700: '#b91c1c',
|
|
63
|
-
800: '#991b1b', 900: '#7f1d1d',
|
|
64
|
-
},
|
|
19
|
+
preview: { primary: '#171717', surface: '#ffffff', accent: '#0f0f0f' },
|
|
20
|
+
tags: ['light'],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const DEFAULT_DARK_PRESET: ThemeEditorPreset = {
|
|
24
|
+
id: 'default-dark',
|
|
25
|
+
name: 'Default Dark',
|
|
26
|
+
description: 'Monochrome dark theme',
|
|
27
|
+
theme: {
|
|
28
|
+
palette: {
|
|
29
|
+
colors: {
|
|
30
|
+
primary: {
|
|
31
|
+
50: '#ffffff', 100: '#f5f5f5', 200: '#d4d4d4', 300: '#a3a3a3',
|
|
32
|
+
400: '#737373', 500: '#171717', 600: '#0f0f0f', 700: '#0a0a0a',
|
|
33
|
+
800: '#050505', 900: '#030303', 950: '#000000',
|
|
65
34
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
secondary: 'palette.colors.gray.400',
|
|
71
|
-
accent: 'palette.colors.primary.500',
|
|
72
|
-
surface: 'palette.colors.gray.800',
|
|
73
|
-
background: 'palette.colors.gray.900',
|
|
74
|
-
container: 'palette.colors.gray.800',
|
|
75
|
-
text: 'palette.colors.gray.100',
|
|
76
|
-
textMuted: 'palette.colors.gray.400',
|
|
77
|
-
textInverse: 'palette.colors.gray.900',
|
|
78
|
-
border: 'palette.colors.gray.700',
|
|
79
|
-
divider: 'palette.colors.gray.700',
|
|
80
|
-
interactive: {
|
|
81
|
-
default: 'palette.colors.primary.400',
|
|
82
|
-
hover: 'palette.colors.primary.300',
|
|
83
|
-
focus: 'palette.colors.primary.500',
|
|
84
|
-
active: 'palette.colors.primary.600',
|
|
85
|
-
disabled: 'palette.colors.gray.600',
|
|
86
|
-
},
|
|
87
|
-
feedback: {
|
|
88
|
-
success: 'palette.colors.success.400',
|
|
89
|
-
warning: 'palette.colors.warning.400',
|
|
90
|
-
error: 'palette.colors.error.400',
|
|
91
|
-
info: 'palette.colors.primary.400',
|
|
92
|
-
},
|
|
35
|
+
gray: {
|
|
36
|
+
50: '#f9fafb', 100: '#f3f4f6', 200: '#e5e7eb', 300: '#d1d5db',
|
|
37
|
+
400: '#9ca3af', 500: '#6b7280', 600: '#4b5563', 700: '#374151',
|
|
38
|
+
800: '#1f2937', 900: '#111827', 950: '#030712',
|
|
93
39
|
},
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
40
|
+
secondary: {
|
|
41
|
+
50: '#f5f3ff', 100: '#ede9fe', 200: '#ddd6fe', 300: '#c4b5fd',
|
|
42
|
+
400: '#a78bfa', 500: '#8b5cf6', 600: '#7c3aed', 700: '#6d28d9',
|
|
43
|
+
800: '#5b21b6', 900: '#4c1d95', 950: '#2e1065',
|
|
44
|
+
},
|
|
45
|
+
accent: {
|
|
46
|
+
50: '#ecfeff', 100: '#cffafe', 200: '#a5f3fc', 300: '#67e8f9',
|
|
47
|
+
400: '#22d3ee', 500: '#06b6d4', 600: '#0891b2', 700: '#0e7490',
|
|
48
|
+
800: '#155e75', 900: '#164e63', 950: '#083344',
|
|
49
|
+
},
|
|
50
|
+
success: {
|
|
51
|
+
50: '#f0fdf4', 100: '#dcfce7', 200: '#bbf7d0', 300: '#86efac',
|
|
52
|
+
400: '#4ade80', 500: '#22c55e', 600: '#16a34a', 700: '#15803d',
|
|
53
|
+
800: '#166534', 900: '#14532d',
|
|
54
|
+
},
|
|
55
|
+
warning: {
|
|
56
|
+
50: '#fefce8', 100: '#fef9c3', 200: '#fef08a', 300: '#fde047',
|
|
57
|
+
400: '#facc15', 500: '#eab308', 600: '#ca8a04', 700: '#a16207',
|
|
58
|
+
800: '#854d0e', 900: '#713f12',
|
|
59
|
+
},
|
|
60
|
+
error: {
|
|
61
|
+
50: '#fef2f2', 100: '#fee2e2', 200: '#fecaca', 300: '#fca5a5',
|
|
62
|
+
400: '#f87171', 500: '#ef4444', 600: '#dc2626', 700: '#b91c1c',
|
|
63
|
+
800: '#991b1b', 900: '#7f1d1d',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
} as any,
|
|
67
|
+
semantic: {
|
|
68
|
+
colors: {
|
|
69
|
+
primary: 'palette.colors.primary.400',
|
|
70
|
+
secondary: 'palette.colors.gray.400',
|
|
71
|
+
accent: 'palette.colors.primary.500',
|
|
72
|
+
surface: 'palette.colors.gray.800',
|
|
73
|
+
background: 'palette.colors.gray.900',
|
|
74
|
+
container: 'palette.colors.gray.800',
|
|
75
|
+
text: 'palette.colors.gray.100',
|
|
76
|
+
textMuted: 'palette.colors.gray.400',
|
|
77
|
+
textInverse: 'palette.colors.gray.900',
|
|
78
|
+
border: 'palette.colors.gray.700',
|
|
79
|
+
divider: 'palette.colors.gray.700',
|
|
80
|
+
interactive: {
|
|
81
|
+
default: 'palette.colors.primary.400',
|
|
82
|
+
hover: 'palette.colors.primary.300',
|
|
83
|
+
focus: 'palette.colors.primary.500',
|
|
84
|
+
active: 'palette.colors.primary.600',
|
|
85
|
+
disabled: 'palette.colors.gray.600',
|
|
86
|
+
},
|
|
87
|
+
feedback: {
|
|
88
|
+
success: 'palette.colors.success.400',
|
|
89
|
+
warning: 'palette.colors.warning.400',
|
|
90
|
+
error: 'palette.colors.error.400',
|
|
91
|
+
info: 'palette.colors.primary.400',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
} as any,
|
|
95
|
+
},
|
|
96
|
+
preview: { primary: '#171717', surface: '#1f2937', accent: '#0f0f0f' },
|
|
97
|
+
tags: ['dark'],
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const BUILT_IN_PRESETS: ThemeEditorPreset[] = [
|
|
101
|
+
{
|
|
102
|
+
id: 'persona-default',
|
|
103
|
+
name: 'Persona Default',
|
|
104
|
+
description: 'Adaptive Persona theme with light and dark modes',
|
|
105
|
+
theme: DEFAULT_LIGHT_PRESET.theme,
|
|
106
|
+
darkTheme: DEFAULT_DARK_PRESET.theme,
|
|
107
|
+
preview: DEFAULT_LIGHT_PRESET.preview,
|
|
108
|
+
darkPreview: DEFAULT_DARK_PRESET.preview,
|
|
109
|
+
tags: ['light', 'dark', 'adaptive'],
|
|
98
110
|
},
|
|
111
|
+
DEFAULT_LIGHT_PRESET,
|
|
112
|
+
DEFAULT_DARK_PRESET,
|
|
99
113
|
{
|
|
100
114
|
id: 'high-contrast',
|
|
101
115
|
name: 'High Contrast',
|
|
@@ -99,16 +99,45 @@ describe("theme editor scroll-to-bottom controls", () => {
|
|
|
99
99
|
expect(INTERFACE_ROLES_SECTION.fields.some((field) => field.id === "role-scroll-to-bottom")).toBe(true);
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
-
it("
|
|
102
|
+
it("moves persistable stream controls out of developer debug without duplicates", () => {
|
|
103
103
|
const debugSection = CONFIGURE_SECTIONS.find((section) => section.id === "debug-inspection");
|
|
104
|
+
const preferenceSection = CONFIGURE_SECTIONS.find(
|
|
105
|
+
(section) => section.id === "stream-display-preferences"
|
|
106
|
+
);
|
|
104
107
|
|
|
105
|
-
expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.collapsedMode")).toBe(
|
|
108
|
+
expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.collapsedMode")).toBe(false);
|
|
106
109
|
expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.activePreview")).toBe(true);
|
|
107
110
|
expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.previewMaxLines")).toBe(true);
|
|
108
111
|
expect(debugSection?.fields.some((field) => field.path === "features.toolCallDisplay.activeMinHeight")).toBe(true);
|
|
109
|
-
expect(
|
|
110
|
-
expect(
|
|
111
|
-
expect(
|
|
112
|
+
expect(preferenceSection?.fields.some((field) => field.path === "features.toolCallDisplay.collapsedMode")).toBe(true);
|
|
113
|
+
expect(preferenceSection?.fields.some((field) => field.path === "features.toolCallDisplay.expandable")).toBe(true);
|
|
114
|
+
expect(preferenceSection?.fields.some((field) => field.path === "features.toolCallDisplay.grouped")).toBe(true);
|
|
115
|
+
expect(preferenceSection?.fields.some((field) => field.path === "features.toolCallDisplay.groupedMode")).toBe(true);
|
|
116
|
+
|
|
117
|
+
const paths = CONFIGURE_SECTIONS.flatMap((section) =>
|
|
118
|
+
section.fields.map((field) => field.path)
|
|
119
|
+
);
|
|
120
|
+
expect(paths.filter((path) => path === "features.toolCallDisplay.grouped")).toHaveLength(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("exposes inheritable capability-tagged artifact preference fields", () => {
|
|
124
|
+
const section = CONFIGURE_SECTIONS.find(
|
|
125
|
+
(entry) => entry.id === "artifact-display-preferences"
|
|
126
|
+
);
|
|
127
|
+
const paths = section?.fields.map((field) => field.path) ?? [];
|
|
128
|
+
expect(paths).toEqual(
|
|
129
|
+
expect.arrayContaining([
|
|
130
|
+
"features.artifacts.display.files",
|
|
131
|
+
"features.artifacts.display.byKind.component",
|
|
132
|
+
"features.artifacts.display.default",
|
|
133
|
+
"features.artifacts.filePreview.enabled",
|
|
134
|
+
])
|
|
135
|
+
);
|
|
136
|
+
expect(paths).not.toContain("features.artifacts.display.byKind.markdown");
|
|
137
|
+
expect(paths).not.toContain("features.artifacts.display.byCategory.document");
|
|
138
|
+
expect(paths).not.toContain("features.artifacts.display.byCategory.application");
|
|
139
|
+
expect(section?.requiresCapability).toBe("artifacts");
|
|
140
|
+
expect(section?.fields.every((field) => field.unsettable)).toBe(true);
|
|
112
141
|
});
|
|
113
142
|
|
|
114
143
|
it("exposes product-facing artifact toolbar copy", () => {
|
|
@@ -129,9 +158,12 @@ describe("theme editor scroll-to-bottom controls", () => {
|
|
|
129
158
|
|
|
130
159
|
it("exposes collapsed reasoning preview controls", () => {
|
|
131
160
|
const debugSection = CONFIGURE_SECTIONS.find((section) => section.id === "debug-inspection");
|
|
161
|
+
const preferenceSection = CONFIGURE_SECTIONS.find(
|
|
162
|
+
(section) => section.id === "stream-display-preferences"
|
|
163
|
+
);
|
|
132
164
|
|
|
133
|
-
expect(
|
|
134
|
-
expect(
|
|
165
|
+
expect(preferenceSection?.fields.some((field) => field.path === "features.reasoningDisplay.expandable")).toBe(true);
|
|
166
|
+
expect(preferenceSection?.fields.some((field) => field.path === "features.reasoningDisplay.activePreview")).toBe(true);
|
|
135
167
|
expect(debugSection?.fields.some((field) => field.path === "features.reasoningDisplay.previewMaxLines")).toBe(true);
|
|
136
168
|
expect(debugSection?.fields.some((field) => field.path === "features.reasoningDisplay.activeMinHeight")).toBe(true);
|
|
137
169
|
});
|
|
@@ -869,20 +869,71 @@ const apiIntegrationSectionDef: SectionDef = {
|
|
|
869
869
|
],
|
|
870
870
|
};
|
|
871
871
|
|
|
872
|
+
const DISPLAY_MODE_OPTIONS = [
|
|
873
|
+
{ value: 'collapsed', label: 'Collapsed card' },
|
|
874
|
+
{ value: 'panel', label: 'Side panel' },
|
|
875
|
+
{ value: 'inline', label: 'In conversation' },
|
|
876
|
+
];
|
|
877
|
+
|
|
878
|
+
const INHERITABLE_DISPLAY_FIELD = {
|
|
879
|
+
type: 'select' as const,
|
|
880
|
+
defaultValue: 'panel',
|
|
881
|
+
options: DISPLAY_MODE_OPTIONS,
|
|
882
|
+
unsettable: true,
|
|
883
|
+
unsetLabel: 'Use default',
|
|
884
|
+
requiresCapability: 'artifacts' as const,
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
export const ARTIFACT_DISPLAY_PREFERENCE_SECTION: SectionDef = {
|
|
888
|
+
id: 'artifact-display-preferences',
|
|
889
|
+
title: 'Artifact display',
|
|
890
|
+
description: 'Choose where recognizable artifact groups appear.',
|
|
891
|
+
requiresCapability: 'artifacts',
|
|
892
|
+
fields: [
|
|
893
|
+
{ ...INHERITABLE_DISPLAY_FIELD, id: 'artifact-display-files', label: 'Generated files', description: 'Default for HTML, CSV, documents, and code files.', path: 'features.artifacts.display.files' },
|
|
894
|
+
{ ...INHERITABLE_DISPLAY_FIELD, id: 'artifact-display-components', label: 'Registered UI components', description: 'Charts, forms, cards, and custom UI rendered by the host.', path: 'features.artifacts.display.byKind.component' },
|
|
895
|
+
{ ...INHERITABLE_DISPLAY_FIELD, id: 'artifact-display-default', label: 'Other artifacts', description: 'Plain documents and anything without a narrower rule.', path: 'features.artifacts.display.default' },
|
|
896
|
+
{
|
|
897
|
+
id: 'artifact-file-preview',
|
|
898
|
+
label: 'Render supported file previews',
|
|
899
|
+
type: 'toggle',
|
|
900
|
+
path: 'features.artifacts.filePreview.enabled',
|
|
901
|
+
defaultValue: true,
|
|
902
|
+
unsettable: true,
|
|
903
|
+
unsetLabel: 'Use default',
|
|
904
|
+
requiresCapability: 'artifacts',
|
|
905
|
+
},
|
|
906
|
+
],
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
const streamDisplayPreferencesSectionDef: SectionDef = {
|
|
910
|
+
id: 'stream-display-preferences',
|
|
911
|
+
title: 'Tool calls and reasoning',
|
|
912
|
+
description: 'Control how agent activity appears in the conversation.',
|
|
913
|
+
fields: [
|
|
914
|
+
{ id: 'pref-show-tools', label: 'Show tool calls', type: 'toggle', path: 'features.showToolCalls', defaultValue: true, unsettable: true, requiresCapability: 'tools' },
|
|
915
|
+
{ id: 'pref-tool-collapsed-mode', label: 'Tool call summary', type: 'select', path: 'features.toolCallDisplay.collapsedMode', defaultValue: 'tool-call', options: [{ value: 'tool-call', label: 'Tool call' }, { value: 'tool-name', label: 'Tool name' }, { value: 'tool-preview', label: 'Tool preview' }], unsettable: true, requiresCapability: 'tools' },
|
|
916
|
+
{ id: 'pref-tool-expandable', label: 'Tool calls expandable', type: 'toggle', path: 'features.toolCallDisplay.expandable', defaultValue: true, unsettable: true, requiresCapability: 'tools' },
|
|
917
|
+
{ id: 'pref-tool-grouped', label: 'Group sequential tool calls', type: 'toggle', path: 'features.toolCallDisplay.grouped', defaultValue: false, unsettable: true, requiresCapability: 'tools' },
|
|
918
|
+
{ id: 'pref-tool-grouped-mode', label: 'Grouped tool detail', type: 'select', path: 'features.toolCallDisplay.groupedMode', defaultValue: 'stack', options: [{ value: 'stack', label: 'Show steps' }, { value: 'summary', label: 'Summary only' }], unsettable: true, requiresCapability: 'tools' },
|
|
919
|
+
{ id: 'pref-tool-loading-animation', label: 'Tool loading animation', type: 'select', path: 'features.toolCallDisplay.loadingAnimation', defaultValue: 'none', options: [{ value: 'none', label: 'None' }, { value: 'pulse', label: 'Pulse' }, { value: 'shimmer', label: 'Shimmer' }, { value: 'shimmer-color', label: 'Color shimmer' }, { value: 'rainbow', label: 'Rainbow' }], unsettable: true, requiresCapability: 'tools' },
|
|
920
|
+
{ id: 'pref-show-reasoning', label: 'Show reasoning', type: 'toggle', path: 'features.showReasoning', defaultValue: true, unsettable: true, requiresCapability: 'reasoning' },
|
|
921
|
+
{ id: 'pref-reasoning-expandable', label: 'Reasoning expandable', type: 'toggle', path: 'features.reasoningDisplay.expandable', defaultValue: true, unsettable: true, requiresCapability: 'reasoning' },
|
|
922
|
+
{ id: 'pref-reasoning-active-preview', label: 'Reasoning preview while active', type: 'toggle', path: 'features.reasoningDisplay.activePreview', defaultValue: false, unsettable: true, requiresCapability: 'reasoning' },
|
|
923
|
+
],
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
export const STREAM_AND_ARTIFACTS_SUB_GROUP: SubGroupDef = {
|
|
927
|
+
label: 'Stream and artifacts',
|
|
928
|
+
sections: [streamDisplayPreferencesSectionDef, ARTIFACT_DISPLAY_PREFERENCE_SECTION],
|
|
929
|
+
};
|
|
930
|
+
|
|
872
931
|
const debugSectionDef: SectionDef = {
|
|
873
932
|
id: 'debug-inspection', title: 'Debug & Inspection', collapsed: true,
|
|
874
933
|
fields: [
|
|
875
|
-
{ id: 'dev-reasoning', label: 'Show Reasoning', description: 'Display AI reasoning steps', type: 'toggle', path: 'features.showReasoning', defaultValue: false },
|
|
876
|
-
{ id: 'dev-tool-calls', label: 'Show Tool Calls', description: 'Display tool call details', type: 'toggle', path: 'features.showToolCalls', defaultValue: false },
|
|
877
|
-
{ id: 'dev-tool-collapsed-mode', label: 'Tool Call Summary', description: 'Choose what collapsed tool rows show by default', type: 'select', path: 'features.toolCallDisplay.collapsedMode', defaultValue: 'tool-call', options: [{ value: 'tool-call', label: 'Tool Call' }, { value: 'tool-name', label: 'Tool Name' }, { value: 'tool-preview', label: 'Tool Preview' }] },
|
|
878
934
|
{ id: 'dev-tool-active-preview', label: 'Tool Preview While Active', description: 'Show a lightweight preview in collapsed active tool rows', type: 'toggle', path: 'features.toolCallDisplay.activePreview', defaultValue: false },
|
|
879
935
|
{ id: 'dev-tool-preview-lines', label: 'Tool Preview Lines', type: 'select', path: 'features.toolCallDisplay.previewMaxLines', defaultValue: 3, options: [{ value: '1', label: '1' }, { value: '2', label: '2' }, { value: '3', label: '3' }, { value: '4', label: '4' }, { value: '5', label: '5' }], formatValue: (v: unknown) => String(v ?? 3), parseValue: (v: unknown) => Number(v) },
|
|
880
936
|
{ id: 'dev-tool-active-min-height', label: 'Tool Active Min Height', description: 'CSS min-height for collapsed active tool rows (e.g. 5rem)', type: 'text', path: 'features.toolCallDisplay.activeMinHeight', defaultValue: '' },
|
|
881
|
-
{ id: 'dev-tool-expandable', label: 'Tool Calls Expandable', description: 'Allow expanding tool call rows to see full details', type: 'toggle', path: 'features.toolCallDisplay.expandable', defaultValue: true },
|
|
882
|
-
{ id: 'dev-tool-grouped', label: 'Group Sequential Tool Calls', description: 'Render consecutive tool rows inside a grouped container', type: 'toggle', path: 'features.toolCallDisplay.grouped', defaultValue: false },
|
|
883
|
-
{ id: 'dev-tool-grouped-mode', label: 'Grouped Tool Detail', description: 'Show each tool step or one consolidated summary', type: 'select', path: 'features.toolCallDisplay.groupedMode', defaultValue: 'stack', options: [{ value: 'stack', label: 'Show Steps' }, { value: 'summary', label: 'Summary Only' }] },
|
|
884
|
-
{ id: 'dev-reasoning-expandable', label: 'Reasoning Expandable', description: 'Allow expanding reasoning rows to see full details', type: 'toggle', path: 'features.reasoningDisplay.expandable', defaultValue: true },
|
|
885
|
-
{ id: 'dev-reasoning-active-preview', label: 'Reasoning Preview While Active', description: 'Show a lightweight preview in collapsed active reasoning rows', type: 'toggle', path: 'features.reasoningDisplay.activePreview', defaultValue: false },
|
|
886
937
|
{ id: 'dev-reasoning-preview-lines', label: 'Reasoning Preview Lines', type: 'select', path: 'features.reasoningDisplay.previewMaxLines', defaultValue: 3, options: [{ value: '1', label: '1' }, { value: '2', label: '2' }, { value: '3', label: '3' }, { value: '4', label: '4' }, { value: '5', label: '5' }], formatValue: (v: unknown) => String(v ?? 3), parseValue: (v: unknown) => Number(v) },
|
|
887
938
|
{ id: 'dev-reasoning-active-min-height', label: 'Reasoning Active Min Height', description: 'CSS min-height for collapsed active reasoning rows (e.g. 5rem)', type: 'text', path: 'features.reasoningDisplay.activeMinHeight', defaultValue: '' },
|
|
888
939
|
{ id: 'dev-debug', label: 'Debug Mode', description: 'Show debug information', type: 'toggle', path: 'debug', defaultValue: false },
|
|
@@ -906,6 +957,7 @@ export const CONFIGURE_SUB_GROUPS: SubGroupDef[] = [
|
|
|
906
957
|
{ label: 'Layout', sections: [generalLayoutSectionDef, headerLayoutSectionDef, messagesLayoutSectionDef, messageActionsSectionDef] },
|
|
907
958
|
{ label: 'Widget', sections: [launcherBasicsSectionDef, launcherAdvancedSectionDef, sendButtonSectionDef, closeButtonSectionDef, clearChatSectionDef, statusIndicatorSectionDef] },
|
|
908
959
|
{ label: 'Features', sections: [featuresSectionDef, streamAnimationSectionDef, attachmentsSectionDef, artifactsSectionDef, artifactCustomizationSectionDef] },
|
|
960
|
+
STREAM_AND_ARTIFACTS_SUB_GROUP,
|
|
909
961
|
{ label: 'Developer', collapsedByDefault: true, sections: [apiIntegrationSectionDef, debugSectionDef, markdownSectionDef] },
|
|
910
962
|
];
|
|
911
963
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { ThemeEditorState } from "./state";
|
|
3
|
+
|
|
4
|
+
describe("ThemeEditorState preference reset", () => {
|
|
5
|
+
it("unsets a path and prunes empty parent objects", () => {
|
|
6
|
+
const state = new ThemeEditorState(undefined, {}, { mergeDefaults: false });
|
|
7
|
+
state.set("features.artifacts.display.files", "inline");
|
|
8
|
+
expect(state.get("features.artifacts.display.files")).toBe("inline");
|
|
9
|
+
|
|
10
|
+
state.unset("features.artifacts.display.files");
|
|
11
|
+
|
|
12
|
+
expect(state.get("features.artifacts.display.files")).toBeUndefined();
|
|
13
|
+
expect(state.getConfig()).not.toHaveProperty("features");
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -32,6 +32,30 @@ function setByPath(obj: unknown, path: string, value: unknown): unknown {
|
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function unsetByPath(obj: unknown, path: string): unknown {
|
|
36
|
+
const parts = path.split('.');
|
|
37
|
+
if (parts.length === 1) {
|
|
38
|
+
const result = { ...(obj as Record<string, unknown>) };
|
|
39
|
+
delete result[parts[0]];
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
const [first, ...rest] = parts;
|
|
43
|
+
const current = obj as Record<string, unknown>;
|
|
44
|
+
const child = unsetByPath(current?.[first] ?? {}, rest.join('.'));
|
|
45
|
+
const result = { ...current };
|
|
46
|
+
if (
|
|
47
|
+
typeof child === 'object' &&
|
|
48
|
+
child !== null &&
|
|
49
|
+
!Array.isArray(child) &&
|
|
50
|
+
Object.keys(child as Record<string, unknown>).length === 0
|
|
51
|
+
) {
|
|
52
|
+
delete result[first];
|
|
53
|
+
} else {
|
|
54
|
+
result[first] = child;
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
35
59
|
// ─── ThemeEditorState ───────────────────────────────────────────
|
|
36
60
|
|
|
37
61
|
export class ThemeEditorState {
|
|
@@ -111,6 +135,28 @@ export class ThemeEditorState {
|
|
|
111
135
|
this.notifyListeners();
|
|
112
136
|
}
|
|
113
137
|
|
|
138
|
+
/** Delete a dot-path and prune empty parents so sparse preferences inherit. */
|
|
139
|
+
unset(path: string): void {
|
|
140
|
+
if (path.startsWith('theme.')) {
|
|
141
|
+
const themePath = path.replace('theme.', '');
|
|
142
|
+
this.theme = unsetByPath(this.theme, themePath) as PersonaTheme;
|
|
143
|
+
this.syncThemeIntoConfig();
|
|
144
|
+
} else if (path.startsWith('darkTheme.')) {
|
|
145
|
+
const themePath = path.replace('darkTheme.', '');
|
|
146
|
+
this.config = {
|
|
147
|
+
...this.config,
|
|
148
|
+
darkTheme: unsetByPath(
|
|
149
|
+
this.config.darkTheme ?? {},
|
|
150
|
+
themePath
|
|
151
|
+
) as AgentWidgetConfig['darkTheme'],
|
|
152
|
+
};
|
|
153
|
+
} else {
|
|
154
|
+
this.config = unsetByPath(this.config, path) as AgentWidgetConfig;
|
|
155
|
+
}
|
|
156
|
+
this.recordHistory();
|
|
157
|
+
this.notifyListeners();
|
|
158
|
+
}
|
|
159
|
+
|
|
114
160
|
/** Batch-set multiple paths at once */
|
|
115
161
|
setBatch(updates: Record<string, unknown>): void {
|
|
116
162
|
let themeChanged = false;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/** Field definition types for the declarative configurator system (headless: no DOM) */
|
|
2
2
|
|
|
3
3
|
import type { DeepPartial, PersonaTheme } from '../types/theme';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
AgentWidgetConfig,
|
|
6
|
+
WidgetPreferenceCapabilities,
|
|
7
|
+
WidgetPreferenceSlice,
|
|
8
|
+
} from '../types';
|
|
5
9
|
|
|
6
10
|
// ─── Field System ────────────────────────────────────────────────
|
|
7
11
|
|
|
@@ -101,6 +105,12 @@ export interface FieldDef {
|
|
|
101
105
|
formatValue?: (value: unknown) => unknown;
|
|
102
106
|
/** Convert control input back into the stored value shape */
|
|
103
107
|
parseValue?: (value: unknown) => unknown;
|
|
108
|
+
/** Capability required for hosts to render this field. */
|
|
109
|
+
requiresCapability?: keyof WidgetPreferenceCapabilities;
|
|
110
|
+
/** The host may offer an inherit/reset action which deletes this path. */
|
|
111
|
+
unsettable?: boolean;
|
|
112
|
+
/** Label for the inherit/reset action. Default: "Use default". */
|
|
113
|
+
unsetLabel?: string;
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
export interface SectionDef {
|
|
@@ -112,6 +122,8 @@ export interface SectionDef {
|
|
|
112
122
|
collapsed?: boolean;
|
|
113
123
|
/** Preset buttons for this section */
|
|
114
124
|
presets?: SectionPreset[];
|
|
125
|
+
/** Capability required for hosts to render this section. */
|
|
126
|
+
requiresCapability?: keyof WidgetPreferenceCapabilities;
|
|
115
127
|
}
|
|
116
128
|
|
|
117
129
|
export interface SectionPreset {
|
|
@@ -149,6 +161,8 @@ export interface ThemeEditorPreset {
|
|
|
149
161
|
toolCall?: AgentWidgetToolCallConfig;
|
|
150
162
|
/** Tool call styling for dark mode (falls back to toolCall if not set) */
|
|
151
163
|
darkToolCall?: AgentWidgetToolCallConfig;
|
|
164
|
+
/** Sparse, persistable widget behavior layered separately from theme tokens. */
|
|
165
|
+
behavior?: WidgetPreferenceSlice;
|
|
152
166
|
preview: {
|
|
153
167
|
primary: string;
|
|
154
168
|
surface: string;
|
|
@@ -123,6 +123,19 @@ describe('createThemeEditorTools', () => {
|
|
|
123
123
|
);
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
+
it('apply_preset installs both Persona default slots with automatic scheme selection', async () => {
|
|
127
|
+
const out = await call(tools.get('apply_preset')!, { presetId: 'persona-default' });
|
|
128
|
+
|
|
129
|
+
expect(out.applied.appliedPreset.id).toBe('persona-default');
|
|
130
|
+
expect(state.get('colorScheme')).toBe('auto');
|
|
131
|
+
expect(state.get('theme.components.artifact.pane.background')).toBe(
|
|
132
|
+
'semantic.colors.container'
|
|
133
|
+
);
|
|
134
|
+
expect(state.get('darkTheme.semantic.colors.surface')).toBe(
|
|
135
|
+
'palette.colors.gray.800'
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
126
139
|
it('configure_widget writes config paths', async () => {
|
|
127
140
|
await call(tools.get('configure_widget')!, {
|
|
128
141
|
launcherPosition: 'bottom-left',
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { createTheme } from '../../utils/theme';
|
|
13
|
+
import { applyFeaturePreferences } from '../../utils/feature-preferences';
|
|
13
14
|
import type { AgentWidgetConfig } from '../../types';
|
|
14
15
|
import type { PersonaTheme } from '../../types/theme';
|
|
15
16
|
import { generateColorScale, SHADE_KEYS } from '../color-utils';
|
|
@@ -469,8 +470,12 @@ export function createThemeEditorTools(
|
|
|
469
470
|
const config: AgentWidgetConfig = { ...state.getConfig() };
|
|
470
471
|
if (preset.darkTheme) {
|
|
471
472
|
config.darkTheme = createTheme(preset.darkTheme, { validate: false }) as PersonaTheme;
|
|
473
|
+
config.colorScheme = 'auto';
|
|
472
474
|
}
|
|
473
475
|
if (preset.toolCall) config.toolCall = preset.toolCall;
|
|
476
|
+
if (preset.behavior) {
|
|
477
|
+
config.features = applyFeaturePreferences(config.features, [preset.behavior]);
|
|
478
|
+
}
|
|
474
479
|
state.setFullConfig(config, theme);
|
|
475
480
|
const warnings = quickContrastWarnings(state, ['body', 'assistant-message'], 'light');
|
|
476
481
|
return result({ appliedPreset: { id: preset.id, name: preset.name } }, warnings);
|