@runtypelabs/persona 4.14.0 → 4.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-DveIaNx6.d.cts → types-BpWGm7_6.d.cts} +17 -0
- package/dist/animations/{types-DveIaNx6.d.ts → types-BpWGm7_6.d.ts} +17 -0
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/chunk-QJ4LV2KW.js +1 -0
- package/dist/codegen.cjs +12 -11
- package/dist/codegen.js +14 -13
- package/dist/{context-mentions-ONG7A7M6.js → context-mentions-BTSTIBFC.js} +1 -1
- package/dist/context-mentions.cjs +11 -8
- package/dist/context-mentions.d.cts +241 -16
- package/dist/context-mentions.d.ts +241 -16
- package/dist/context-mentions.js +11 -8
- package/dist/index.cjs +67 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +301 -41
- package/dist/index.d.ts +301 -41
- package/dist/index.global.js +59 -56
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +67 -66
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +5 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/smart-dom-reader.d.cts +241 -16
- package/dist/smart-dom-reader.d.ts +241 -16
- package/dist/theme-editor-preview.cjs +66 -66
- package/dist/theme-editor-preview.d.cts +255 -17
- package/dist/theme-editor-preview.d.ts +255 -17
- package/dist/theme-editor-preview.js +56 -56
- package/dist/theme-editor.cjs +5 -5
- package/dist/theme-editor.d.cts +242 -17
- package/dist/theme-editor.d.ts +242 -17
- package/dist/theme-editor.js +7 -7
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +38 -0
- package/dist/theme-reference.d.ts +38 -0
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +1 -1
- package/src/ask-user-question-tool.ts +10 -7
- package/src/components/artifact-preview.ts +1 -1
- package/src/components/composer-parts.test.ts +103 -4
- package/src/components/composer-parts.ts +53 -15
- package/src/components/messages.ts +5 -2
- package/src/components/panel.ts +31 -5
- package/src/components/pill-composer-builder.test.ts +18 -0
- package/src/components/suggestions.test.ts +233 -0
- package/src/components/suggestions.ts +463 -81
- package/src/components/widget-view.ts +4 -0
- package/src/defaults.ts +5 -3
- package/src/generated/runtype-openapi-contract.ts +17 -0
- package/src/index-core.ts +18 -2
- package/src/launcher-global.ts +1 -1
- package/src/plugins/types.ts +71 -3
- package/src/presets.ts +3 -3
- package/src/session.ts +10 -8
- package/src/styles/widget-styles.test.ts +162 -0
- package/src/styles/widget.css +430 -0
- package/src/suggest-replies-tool.test.ts +233 -2
- package/src/suggest-replies-tool.ts +115 -39
- package/src/theme-editor/preview-utils.test.ts +34 -0
- package/src/theme-editor/preview-utils.ts +77 -2
- package/src/theme-editor/sections.test.ts +33 -0
- package/src/theme-editor/sections.ts +53 -5
- package/src/theme-reference.ts +9 -2
- package/src/types/theme.ts +32 -0
- package/src/types.ts +152 -16
- package/src/ui.detached-panel.test.ts +1 -1
- package/src/ui.suggest-replies.test.ts +1066 -1
- package/src/ui.ts +268 -20
- package/src/utils/artifact-file.test.ts +1 -1
- package/src/utils/code-generators.test.ts +40 -0
- package/src/utils/code-generators.ts +22 -0
- package/src/utils/config-merge.test.ts +24 -0
- package/src/utils/icons.test.ts +26 -0
- package/src/utils/icons.ts +8 -0
- package/src/utils/theme.test.ts +79 -0
- package/src/utils/tokens.ts +64 -0
- package/dist/chunk-MMUPR2JW.js +0 -1
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
* Built-in `suggest_replies` client tool.
|
|
3
3
|
*
|
|
4
4
|
* The widget can advertise this tool to the agent on every dispatch via
|
|
5
|
-
* `clientTools[]` (set `
|
|
5
|
+
* `clientTools[]` (set `suggestions.followUps.expose: true`): the same
|
|
6
6
|
* wire surface as `ask_user_question` and WebMCP page tools. When the model
|
|
7
|
-
* calls it, the execution pauses with a `step_await`
|
|
8
|
-
*
|
|
9
|
-
* widget resolves it FIRE-AND-FORGET: it renders the
|
|
10
|
-
*
|
|
11
|
-
* canned "shown" result, so the
|
|
12
|
-
* user. Tapping a chip sends its text verbatim as the user's next message.
|
|
7
|
+
* calls it, the execution pauses with a `step_await` (a local-tool pause
|
|
8
|
+
* carries no `awaitReason`; that discriminator is internal). Unlike
|
|
9
|
+
* `ask_user_question`, the widget resolves it FIRE-AND-FORGET: it renders the
|
|
10
|
+
* configured follow-up surface and immediately resumes the execution with a
|
|
11
|
+
* canned "shown" result, so the turn completes without waiting on the user.
|
|
13
12
|
*
|
|
14
13
|
* Chip visibility is DERIVED state, not imperative show/hide: the chips of
|
|
15
14
|
* the last `suggest_replies` tool message with no user message after it are
|
|
@@ -21,6 +20,7 @@
|
|
|
21
20
|
import type {
|
|
22
21
|
AgentWidgetConfig,
|
|
23
22
|
AgentWidgetMessage,
|
|
23
|
+
AgentWidgetSuggestion,
|
|
24
24
|
ClientToolDefinition,
|
|
25
25
|
} from "./types";
|
|
26
26
|
|
|
@@ -30,9 +30,10 @@ export const SUGGEST_REPLIES_TOOL_NAME = "suggest_replies";
|
|
|
30
30
|
export const SUGGEST_REPLIES_MAX = 4;
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
* JSON Schema for the tool's parameters.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* JSON Schema for the tool's parameters. The model owns semantics only: id,
|
|
34
|
+
* icon, behavior, and emphasis are presentation, and hosts set them with the
|
|
35
|
+
* `transformSuggestions` plugin hook. A single object type is used because
|
|
36
|
+
* strict structured-output modes reject or flatten `oneOf`.
|
|
36
37
|
*/
|
|
37
38
|
export const SUGGEST_REPLIES_PARAMETERS_SCHEMA = {
|
|
38
39
|
type: "object",
|
|
@@ -43,7 +44,16 @@ export const SUGGEST_REPLIES_PARAMETERS_SCHEMA = {
|
|
|
43
44
|
maxItems: SUGGEST_REPLIES_MAX,
|
|
44
45
|
description:
|
|
45
46
|
"1-4 short, distinct follow-up replies, phrased in the user's voice.",
|
|
46
|
-
items: {
|
|
47
|
+
items: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
label: { type: "string", minLength: 1, maxLength: 80 },
|
|
51
|
+
prompt: { type: "string", minLength: 1, maxLength: 500 },
|
|
52
|
+
description: { type: "string", minLength: 1, maxLength: 160 },
|
|
53
|
+
},
|
|
54
|
+
required: ["label"],
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
},
|
|
47
57
|
},
|
|
48
58
|
},
|
|
49
59
|
required: ["suggestions"],
|
|
@@ -52,7 +62,7 @@ export const SUGGEST_REPLIES_PARAMETERS_SCHEMA = {
|
|
|
52
62
|
|
|
53
63
|
/**
|
|
54
64
|
* The `ClientToolDefinition` shipped on `dispatch.clientTools[]` when
|
|
55
|
-
* `
|
|
65
|
+
* `suggestions.followUps.expose` is on. Exported so integrators who prefer
|
|
56
66
|
* declaring the tool server-side (a flow's `runtimeTools`) can reuse the same
|
|
57
67
|
* description and schema instead of hand-writing them.
|
|
58
68
|
*/
|
|
@@ -60,12 +70,15 @@ export const SUGGEST_REPLIES_CLIENT_TOOL: ClientToolDefinition = {
|
|
|
60
70
|
name: SUGGEST_REPLIES_TOOL_NAME,
|
|
61
71
|
description:
|
|
62
72
|
"Offer the user tappable quick-reply suggestions for their next message. " +
|
|
63
|
-
"Call at most once per turn, as the
|
|
64
|
-
"complete.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
"
|
|
73
|
+
"Call at most once per turn, as the last action after your reply text is " +
|
|
74
|
+
"complete. Give each suggestion a `label` with the short visible text, an " +
|
|
75
|
+
"optional `prompt` with the fuller text placed as the user's message " +
|
|
76
|
+
"(defaults to the label), and an optional `description` with one line of " +
|
|
77
|
+
"supporting copy. Suggestions are sent as the user's next message, so " +
|
|
78
|
+
'phrase prompts in the user\'s voice (e.g. "Tell me more about pricing"). ' +
|
|
79
|
+
"Keep them short and distinct. The result only confirms the suggestions " +
|
|
80
|
+
"were shown: do not add further commentary after calling this tool; end " +
|
|
81
|
+
"your turn.",
|
|
69
82
|
parametersSchema: SUGGEST_REPLIES_PARAMETERS_SCHEMA,
|
|
70
83
|
origin: "sdk",
|
|
71
84
|
annotations: { readOnlyHint: true },
|
|
@@ -90,11 +103,15 @@ export const isSuggestRepliesMessage = (
|
|
|
90
103
|
message.toolCall?.name === SUGGEST_REPLIES_TOOL_NAME;
|
|
91
104
|
|
|
92
105
|
/**
|
|
93
|
-
* Tolerant parse of a `suggest_replies` tool call's args into
|
|
94
|
-
*
|
|
95
|
-
*
|
|
106
|
+
* Tolerant parse of a `suggest_replies` tool call's args into suggestion
|
|
107
|
+
* items. Plain strings stay strings for older flows that emit
|
|
108
|
+
* `suggestions: string[]`; objects keep only label, prompt, and description,
|
|
109
|
+
* so a model that ignores `additionalProperties` cannot set presentation.
|
|
110
|
+
* Hosts add id, icon, behavior, and emphasis via `transformSuggestions`.
|
|
96
111
|
*/
|
|
97
|
-
export const parseSuggestRepliesPayload = (
|
|
112
|
+
export const parseSuggestRepliesPayload = (
|
|
113
|
+
args: unknown
|
|
114
|
+
): AgentWidgetSuggestion[] => {
|
|
98
115
|
let parsed: unknown = args;
|
|
99
116
|
if (typeof parsed === "string") {
|
|
100
117
|
try {
|
|
@@ -106,17 +123,32 @@ export const parseSuggestRepliesPayload = (args: unknown): string[] => {
|
|
|
106
123
|
const raw = (parsed as { suggestions?: unknown } | null | undefined)
|
|
107
124
|
?.suggestions;
|
|
108
125
|
if (!Array.isArray(raw)) return [];
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
const suggestions = raw.flatMap<AgentWidgetSuggestion>((item) => {
|
|
127
|
+
if (typeof item === "string") {
|
|
128
|
+
const value = item.trim();
|
|
129
|
+
return value ? [value] : [];
|
|
130
|
+
}
|
|
131
|
+
if (!item || typeof item !== "object") return [];
|
|
132
|
+
const value = item as Record<string, unknown>;
|
|
133
|
+
if (typeof value.label !== "string" || !value.label.trim()) return [];
|
|
134
|
+
const suggestion: Exclude<AgentWidgetSuggestion, string> = {
|
|
135
|
+
label: value.label.trim(),
|
|
136
|
+
};
|
|
137
|
+
if (typeof value.prompt === "string" && value.prompt.trim()) {
|
|
138
|
+
suggestion.prompt = value.prompt.trim();
|
|
139
|
+
}
|
|
140
|
+
if (typeof value.description === "string" && value.description.trim()) {
|
|
141
|
+
suggestion.description = value.description.trim();
|
|
142
|
+
}
|
|
143
|
+
return [suggestion];
|
|
144
|
+
});
|
|
145
|
+
if (suggestions.length > SUGGEST_REPLIES_MAX) {
|
|
114
146
|
console.warn(
|
|
115
|
-
`[persona] suggest_replies: ${
|
|
147
|
+
`[persona] suggest_replies: ${suggestions.length} suggestions exceeds the cap of ${SUGGEST_REPLIES_MAX}; extra suggestions dropped.`,
|
|
116
148
|
);
|
|
117
|
-
return
|
|
149
|
+
return suggestions.slice(0, SUGGEST_REPLIES_MAX);
|
|
118
150
|
}
|
|
119
|
-
return
|
|
151
|
+
return suggestions;
|
|
120
152
|
};
|
|
121
153
|
|
|
122
154
|
/**
|
|
@@ -127,7 +159,7 @@ export const parseSuggestRepliesPayload = (args: unknown): string[] => {
|
|
|
127
159
|
*/
|
|
128
160
|
export const latestAgentSuggestions = (
|
|
129
161
|
messages: AgentWidgetMessage[],
|
|
130
|
-
):
|
|
162
|
+
): AgentWidgetSuggestion[] | null => {
|
|
131
163
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
132
164
|
const message = messages[i];
|
|
133
165
|
if (message.role === "user") return null;
|
|
@@ -138,15 +170,59 @@ export const latestAgentSuggestions = (
|
|
|
138
170
|
return null;
|
|
139
171
|
};
|
|
140
172
|
|
|
173
|
+
export type ResolvedFollowUpsFeature = {
|
|
174
|
+
/** Widget renders follow-up chips and auto-resumes the tool call. */
|
|
175
|
+
enabled: boolean;
|
|
176
|
+
/** Tool ships on `dispatch.clientTools[]`. */
|
|
177
|
+
expose: boolean;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Keyed on the conflict itself, not the config object: `controller.update()`
|
|
181
|
+
// rebuilds the config on every call, so an object-keyed dedupe re-warns
|
|
182
|
+
// forever. Trade-off: two widgets with the identical conflict warn once total
|
|
183
|
+
// per page load.
|
|
184
|
+
const conflictWarnings = new Set<string>();
|
|
185
|
+
|
|
186
|
+
/** Always warns, debug or not: a config conflict is a developer error. */
|
|
187
|
+
const warnConflict = (
|
|
188
|
+
key: "enabled" | "expose",
|
|
189
|
+
legacyValue: boolean,
|
|
190
|
+
newValue: boolean,
|
|
191
|
+
): void => {
|
|
192
|
+
const signature = `${key}:${legacyValue}:${newValue}`;
|
|
193
|
+
if (conflictWarnings.has(signature)) return;
|
|
194
|
+
conflictWarnings.add(signature);
|
|
195
|
+
console.warn(
|
|
196
|
+
`[persona] suggestions.followUps.${key} and features.suggestReplies.${key} disagree; suggestions.followUps.${key} wins.`,
|
|
197
|
+
);
|
|
198
|
+
};
|
|
199
|
+
|
|
141
200
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
201
|
+
* Single source of truth for the follow-ups capability flags. Resolution is
|
|
202
|
+
* per key so a `features.suggestReplies` embed that adds presentation-only
|
|
203
|
+
* `suggestions.followUps` keys is not silently re-enabled. `expose` is forced
|
|
204
|
+
* off when `enabled` resolves false: advertising the tool without the widget
|
|
205
|
+
* auto-resolving it parks the execution on a generic tool bubble.
|
|
146
206
|
*/
|
|
147
|
-
export const
|
|
207
|
+
export const resolveFollowUpsFeature = (
|
|
148
208
|
config: AgentWidgetConfig | undefined,
|
|
149
|
-
):
|
|
150
|
-
const
|
|
151
|
-
|
|
209
|
+
): ResolvedFollowUpsFeature => {
|
|
210
|
+
const followUps = config?.suggestions?.followUps;
|
|
211
|
+
const legacy = config?.features?.suggestReplies;
|
|
212
|
+
const warnIfConflicting = (key: "enabled" | "expose"): void => {
|
|
213
|
+
const newValue = followUps?.[key];
|
|
214
|
+
const legacyValue = legacy?.[key];
|
|
215
|
+
if (newValue === undefined || legacyValue === undefined) return;
|
|
216
|
+
if (newValue === legacyValue) return;
|
|
217
|
+
warnConflict(key, legacyValue, newValue);
|
|
218
|
+
};
|
|
219
|
+
if (config) {
|
|
220
|
+
warnIfConflicting("enabled");
|
|
221
|
+
warnIfConflicting("expose");
|
|
222
|
+
}
|
|
223
|
+
const enabled = followUps?.enabled ?? legacy?.enabled ?? true;
|
|
224
|
+
const expose = enabled
|
|
225
|
+
? followUps?.expose ?? legacy?.expose ?? false
|
|
226
|
+
: false;
|
|
227
|
+
return { enabled, expose };
|
|
152
228
|
};
|
|
@@ -9,6 +9,40 @@ import {
|
|
|
9
9
|
} from "./preview-utils";
|
|
10
10
|
|
|
11
11
|
describe("theme editor preview demo data", () => {
|
|
12
|
+
it("keeps the home scene in a true pre-conversation state", () => {
|
|
13
|
+
const config = buildPreviewConfig({ scene: "home" });
|
|
14
|
+
|
|
15
|
+
expect(config.initialMessages).toEqual([]);
|
|
16
|
+
expect(config.suggestionChips?.length).toBeGreaterThan(0);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("seeds rich agent suggestions in the follow-ups scene", () => {
|
|
20
|
+
const config = buildPreviewConfig({ scene: "follow-ups" });
|
|
21
|
+
const messages = config.initialMessages ?? [];
|
|
22
|
+
const suggestionMessage = messages.find(
|
|
23
|
+
(message) =>
|
|
24
|
+
message.variant === "tool" &&
|
|
25
|
+
message.toolCall?.name === "suggest_replies",
|
|
26
|
+
);
|
|
27
|
+
const suggestions = (
|
|
28
|
+
suggestionMessage?.toolCall?.args as
|
|
29
|
+
| { suggestions?: Array<Record<string, unknown>> }
|
|
30
|
+
| undefined
|
|
31
|
+
)?.suggestions;
|
|
32
|
+
|
|
33
|
+
expect(messages.some((message) => message.role === "user")).toBe(true);
|
|
34
|
+
expect(suggestionMessage?.toolCall?.status).toBe("complete");
|
|
35
|
+
expect(suggestions).toHaveLength(4);
|
|
36
|
+
expect(suggestions?.[0]).toEqual(
|
|
37
|
+
expect.objectContaining({
|
|
38
|
+
label: "Show me an implementation",
|
|
39
|
+
description: expect.any(String),
|
|
40
|
+
icon: "code-2",
|
|
41
|
+
emphasis: "primary",
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
12
46
|
it("seeds tool call preview messages when advanced tool display modes are enabled", () => {
|
|
13
47
|
const config = buildPreviewConfig({
|
|
14
48
|
scene: "conversation",
|
|
@@ -195,7 +195,12 @@ export function buildSrcdoc(
|
|
|
195
195
|
|
|
196
196
|
// ─── Preview Messages ───────────────────────────────────────────
|
|
197
197
|
|
|
198
|
-
export type PreviewScene =
|
|
198
|
+
export type PreviewScene =
|
|
199
|
+
| 'home'
|
|
200
|
+
| 'conversation'
|
|
201
|
+
| 'follow-ups'
|
|
202
|
+
| 'minimized'
|
|
203
|
+
| 'artifact';
|
|
199
204
|
|
|
200
205
|
export type PreviewTranscriptEntryPreset =
|
|
201
206
|
| 'user-message'
|
|
@@ -543,8 +548,78 @@ export function createPreviewMessages(
|
|
|
543
548
|
config?: Partial<AgentWidgetConfig>,
|
|
544
549
|
appendedMessages: AgentWidgetMessage[] = []
|
|
545
550
|
): AgentWidgetMessage[] {
|
|
551
|
+
if (scene === 'follow-ups') {
|
|
552
|
+
const now = Date.now();
|
|
553
|
+
return [
|
|
554
|
+
{
|
|
555
|
+
id: 'preview-follow-up-user',
|
|
556
|
+
role: 'user',
|
|
557
|
+
content: 'What can Persona help me build?',
|
|
558
|
+
createdAt: new Date(now - 120000).toISOString(),
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
id: 'preview-follow-up-assistant',
|
|
562
|
+
role: 'assistant',
|
|
563
|
+
content:
|
|
564
|
+
'Persona gives you a themeable streaming chat interface with flexible integrations and interaction patterns. Where would you like to go next?',
|
|
565
|
+
createdAt: new Date(now - 60000).toISOString(),
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
id: 'preview-follow-up-suggestions',
|
|
569
|
+
role: 'assistant',
|
|
570
|
+
content: '',
|
|
571
|
+
createdAt: new Date(now - 59000).toISOString(),
|
|
572
|
+
streaming: false,
|
|
573
|
+
variant: 'tool',
|
|
574
|
+
toolCall: {
|
|
575
|
+
id: 'preview-follow-up-suggestions',
|
|
576
|
+
name: 'suggest_replies',
|
|
577
|
+
status: 'complete',
|
|
578
|
+
args: {
|
|
579
|
+
suggestions: [
|
|
580
|
+
{
|
|
581
|
+
id: 'preview-implementation',
|
|
582
|
+
label: 'Show me an implementation',
|
|
583
|
+
prompt: 'Show me a practical implementation',
|
|
584
|
+
description: 'See a complete integration example',
|
|
585
|
+
icon: 'code-2',
|
|
586
|
+
emphasis: 'primary',
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
id: 'preview-theming',
|
|
590
|
+
label: 'Explore theming',
|
|
591
|
+
prompt: 'Tell me how theming works',
|
|
592
|
+
description: 'Customize colors, type, and component styles',
|
|
593
|
+
icon: 'palette',
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
id: 'preview-security',
|
|
597
|
+
label: 'Review security',
|
|
598
|
+
prompt: 'Explain the security model',
|
|
599
|
+
description: 'Learn about isolation and safe rendering',
|
|
600
|
+
icon: 'shield-check',
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
id: 'preview-support',
|
|
604
|
+
label: 'Talk to support',
|
|
605
|
+
prompt: 'Help me contact support',
|
|
606
|
+
description: 'Get help choosing the right approach',
|
|
607
|
+
icon: 'messages-square',
|
|
608
|
+
},
|
|
609
|
+
],
|
|
610
|
+
},
|
|
611
|
+
chunks: [],
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
...appendedMessages,
|
|
615
|
+
];
|
|
616
|
+
}
|
|
546
617
|
if (scene === 'home') {
|
|
547
|
-
|
|
618
|
+
// Home is the pre-conversation state: welcome copy + starter suggestions
|
|
619
|
+
// already provide the greeting and entry points. Seeding an assistant
|
|
620
|
+
// bubble here creates a duplicate greeting that the default runtime never
|
|
621
|
+
// enters unless a host explicitly configures its own initialMessages.
|
|
622
|
+
return [];
|
|
548
623
|
}
|
|
549
624
|
if (scene === 'minimized') {
|
|
550
625
|
return [{ id: 'preview-min-1', role: 'assistant', content: 'We are here whenever you are ready.', createdAt: new Date().toISOString() }];
|
|
@@ -60,6 +60,39 @@ describe("theme editor scroll-to-bottom controls", () => {
|
|
|
60
60
|
expect(fieldPaths).toContain("theme.components.scrollToBottom.iconSize");
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
it("exposes suggestion behavior and component token controls", () => {
|
|
64
|
+
const suggestions = CONFIGURE_SECTIONS.find(
|
|
65
|
+
(section) => section.id === "suggestions"
|
|
66
|
+
);
|
|
67
|
+
const configPaths =
|
|
68
|
+
suggestions?.fields.map((field) => field.path) ?? [];
|
|
69
|
+
expect(configPaths).toEqual(
|
|
70
|
+
expect.arrayContaining([
|
|
71
|
+
"suggestions.starters.variant",
|
|
72
|
+
"suggestions.starters.placement",
|
|
73
|
+
"suggestions.starters.behavior",
|
|
74
|
+
"suggestions.followUps.variant",
|
|
75
|
+
"suggestions.followUps.placement",
|
|
76
|
+
"suggestions.followUps.behavior",
|
|
77
|
+
"suggestions.followUps.overflow",
|
|
78
|
+
])
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const componentPaths = COMPONENTS_SECTIONS.flatMap((section) =>
|
|
82
|
+
section.fields.map((field) => field.path)
|
|
83
|
+
);
|
|
84
|
+
expect(componentPaths).toEqual(
|
|
85
|
+
expect.arrayContaining([
|
|
86
|
+
"theme.components.suggestion.chip.background",
|
|
87
|
+
"theme.components.suggestion.chip.hoverBackground",
|
|
88
|
+
"theme.components.suggestion.chip.focusRing",
|
|
89
|
+
"theme.components.suggestion.card.background",
|
|
90
|
+
"theme.components.suggestion.card.shadow",
|
|
91
|
+
"theme.components.suggestion.list.background",
|
|
92
|
+
])
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
63
96
|
it("exposes a shadow control for every themeable component", () => {
|
|
64
97
|
const fieldPaths = COMPONENTS_SECTIONS.flatMap((section) => section.fields.map((field) => field.path));
|
|
65
98
|
|
|
@@ -369,6 +369,18 @@ const inputShapeSectionDef: SectionDef = {
|
|
|
369
369
|
],
|
|
370
370
|
};
|
|
371
371
|
|
|
372
|
+
const composerSpacingSectionDef: SectionDef = {
|
|
373
|
+
id: 'comp-composer-spacing',
|
|
374
|
+
title: 'Composer Spacing',
|
|
375
|
+
collapsed: true,
|
|
376
|
+
fields: [
|
|
377
|
+
{ id: 'composer-padding', label: 'Padding', type: 'text', path: 'theme.components.composer.padding', defaultValue: '0.75rem 1rem' },
|
|
378
|
+
{ id: 'composer-gap', label: 'Row Gap', description: 'Space between the textarea row and the actions row', type: 'text', path: 'theme.components.composer.gap', defaultValue: '0.5rem' },
|
|
379
|
+
{ id: 'composer-font-size', label: 'Font Size', type: 'text', path: 'theme.components.composer.fontSize', defaultValue: '0.875rem' },
|
|
380
|
+
{ id: 'composer-line-height', label: 'Line Height', type: 'text', path: 'theme.components.composer.lineHeight', defaultValue: '1.25rem' },
|
|
381
|
+
],
|
|
382
|
+
};
|
|
383
|
+
|
|
372
384
|
const buttonShapeSectionDef: SectionDef = {
|
|
373
385
|
id: 'comp-button-shape',
|
|
374
386
|
title: 'Button Shape',
|
|
@@ -466,6 +478,32 @@ const scrollToBottomSectionDef: SectionDef = {
|
|
|
466
478
|
],
|
|
467
479
|
};
|
|
468
480
|
|
|
481
|
+
const suggestionStyleSectionDef: SectionDef = {
|
|
482
|
+
id: 'comp-suggestions',
|
|
483
|
+
title: 'Suggestions',
|
|
484
|
+
description: 'Style starter cards, follow-up chips, and list rows.',
|
|
485
|
+
collapsed: true,
|
|
486
|
+
fields: [
|
|
487
|
+
{ id: 'suggestion-chip-bg', label: 'Chip Background', type: 'token-ref', path: 'theme.components.suggestion.chip.background', defaultValue: 'semantic.colors.surface', tokenRef: { tokenType: 'color' } },
|
|
488
|
+
{ id: 'suggestion-chip-fg', label: 'Chip Text', type: 'token-ref', path: 'theme.components.suggestion.chip.foreground', defaultValue: 'semantic.colors.text', tokenRef: { tokenType: 'color' } },
|
|
489
|
+
{ id: 'suggestion-chip-border', label: 'Chip Border', type: 'token-ref', path: 'theme.components.suggestion.chip.border', defaultValue: 'semantic.colors.border', tokenRef: { tokenType: 'color' } },
|
|
490
|
+
{ id: 'suggestion-chip-hover', label: 'Chip Hover', type: 'token-ref', path: 'theme.components.suggestion.chip.hoverBackground', defaultValue: 'palette.colors.gray.100', tokenRef: { tokenType: 'color' } },
|
|
491
|
+
{ id: 'suggestion-chip-focus', label: 'Chip Focus Ring', type: 'token-ref', path: 'theme.components.suggestion.chip.focusRing', defaultValue: 'semantic.colors.interactive.focus', tokenRef: { tokenType: 'color' } },
|
|
492
|
+
{ id: 'suggestion-card-bg', label: 'Card Background', type: 'token-ref', path: 'theme.components.suggestion.card.background', defaultValue: 'semantic.colors.surface', tokenRef: { tokenType: 'color' } },
|
|
493
|
+
{ id: 'suggestion-card-fg', label: 'Card Text', type: 'token-ref', path: 'theme.components.suggestion.card.foreground', defaultValue: 'semantic.colors.text', tokenRef: { tokenType: 'color' } },
|
|
494
|
+
{ id: 'suggestion-card-border', label: 'Card Border', type: 'token-ref', path: 'theme.components.suggestion.card.border', defaultValue: 'semantic.colors.border', tokenRef: { tokenType: 'color' } },
|
|
495
|
+
{ id: 'suggestion-card-hover', label: 'Card Hover', type: 'token-ref', path: 'theme.components.suggestion.card.hoverBackground', defaultValue: 'palette.colors.gray.100', tokenRef: { tokenType: 'color' } },
|
|
496
|
+
{ id: 'suggestion-card-shadow', label: 'Card Shadow', type: 'select', path: 'theme.components.suggestion.card.shadow', defaultValue: 'palette.shadows.sm', options: [
|
|
497
|
+
{ value: 'palette.shadows.none', label: 'None' },
|
|
498
|
+
{ value: 'palette.shadows.sm', label: 'Small' },
|
|
499
|
+
{ value: 'palette.shadows.md', label: 'Medium' },
|
|
500
|
+
{ value: 'palette.shadows.lg', label: 'Large' },
|
|
501
|
+
] },
|
|
502
|
+
{ id: 'suggestion-list-bg', label: 'List Background', type: 'color', path: 'theme.components.suggestion.list.background', defaultValue: 'transparent' },
|
|
503
|
+
{ id: 'suggestion-list-border', label: 'List Border', type: 'token-ref', path: 'theme.components.suggestion.list.border', defaultValue: 'semantic.colors.border', tokenRef: { tokenType: 'color' } },
|
|
504
|
+
],
|
|
505
|
+
};
|
|
506
|
+
|
|
469
507
|
// Reusable shadow-scale options matching the launcher / panel / scroll-to-bottom selects.
|
|
470
508
|
const SHADOW_SCALE_OPTIONS: { value: string; label: string }[] = [
|
|
471
509
|
{ value: 'palette.shadows.none', label: 'None' },
|
|
@@ -502,6 +540,7 @@ export const COMPONENT_SHAPE_SECTIONS: SectionDef[] = [
|
|
|
502
540
|
launcherLayoutSectionDef,
|
|
503
541
|
messageShapeSectionDef,
|
|
504
542
|
inputShapeSectionDef,
|
|
543
|
+
composerSpacingSectionDef,
|
|
505
544
|
buttonShapeSectionDef,
|
|
506
545
|
componentShadowsSectionDef,
|
|
507
546
|
];
|
|
@@ -513,6 +552,7 @@ export const COMPONENT_COLOR_SECTIONS: SectionDef[] = [
|
|
|
513
552
|
inputColorsSectionDef,
|
|
514
553
|
buttonColorsSectionDef,
|
|
515
554
|
scrollToBottomSectionDef,
|
|
555
|
+
suggestionStyleSectionDef,
|
|
516
556
|
];
|
|
517
557
|
|
|
518
558
|
export const COMPONENTS_SECTIONS: SectionDef[] = [
|
|
@@ -526,9 +566,9 @@ export const COMPONENTS_SECTIONS: SectionDef[] = [
|
|
|
526
566
|
|
|
527
567
|
const MB = 1024 * 1024;
|
|
528
568
|
const DEFAULT_SUGGESTION_CHIPS = [
|
|
529
|
-
'
|
|
530
|
-
'
|
|
531
|
-
'
|
|
569
|
+
'Show me what you can help with',
|
|
570
|
+
'Walk me through getting started',
|
|
571
|
+
'Answer a question about this page',
|
|
532
572
|
];
|
|
533
573
|
|
|
534
574
|
const ATTACHMENT_TYPE_PRESETS: Record<string, string[]> = {
|
|
@@ -576,9 +616,17 @@ const copySectionDef: SectionDef = {
|
|
|
576
616
|
};
|
|
577
617
|
|
|
578
618
|
const suggestionsSectionDef: SectionDef = {
|
|
579
|
-
id: 'suggestions', title: '
|
|
619
|
+
id: 'suggestions', title: 'Suggestions', description: 'Configure starter prompts and agent follow-ups.', collapsed: true,
|
|
580
620
|
fields: [
|
|
581
|
-
{ id: 'suggestions-list', label: '
|
|
621
|
+
{ id: 'suggestions-list', label: 'Starter Prompts', description: 'Add, edit, and remove starter prompts directly.', type: 'chip-list', path: 'suggestionChips', defaultValue: DEFAULT_SUGGESTION_CHIPS },
|
|
622
|
+
{ id: 'suggestions-starter-variant', label: 'Starter Style', type: 'select', path: 'suggestions.starters.variant', defaultValue: 'card', options: [{ value: 'card', label: 'Cards' }, { value: 'chip', label: 'Chips' }, { value: 'list', label: 'List' }] },
|
|
623
|
+
{ id: 'suggestions-starter-placement', label: 'Starter Placement', type: 'select', path: 'suggestions.starters.placement', defaultValue: 'auto', options: [{ value: 'auto', label: 'Automatic' }, { value: 'welcome', label: 'Welcome Surface' }, { value: 'composer', label: 'Above Composer' }] },
|
|
624
|
+
{ id: 'suggestions-starter-behavior', label: 'Starter Click', type: 'select', path: 'suggestions.starters.behavior', defaultValue: 'send', options: [{ value: 'send', label: 'Send Immediately' }, { value: 'fill', label: 'Fill Composer' }] },
|
|
625
|
+
{ id: 'suggestions-starter-overflow', label: 'Starter Overflow', type: 'select', path: 'suggestions.starters.overflow', defaultValue: 'wrap', options: [{ value: 'wrap', label: 'Wrap Rows' }, { value: 'scroll', label: 'Horizontal Scroll' }] },
|
|
626
|
+
{ id: 'suggestions-followup-variant', label: 'Follow-up Style', type: 'select', path: 'suggestions.followUps.variant', defaultValue: 'chip', options: [{ value: 'chip', label: 'Chips' }, { value: 'card', label: 'Cards' }, { value: 'list', label: 'List' }] },
|
|
627
|
+
{ id: 'suggestions-followup-placement', label: 'Follow-up Placement', type: 'select', path: 'suggestions.followUps.placement', defaultValue: 'auto', options: [{ value: 'auto', label: 'Automatic' }, { value: 'after-message', label: 'After Messages' }, { value: 'composer', label: 'Above Composer' }] },
|
|
628
|
+
{ id: 'suggestions-followup-behavior', label: 'Follow-up Click', type: 'select', path: 'suggestions.followUps.behavior', defaultValue: 'send', options: [{ value: 'send', label: 'Send Immediately' }, { value: 'fill', label: 'Fill Composer' }] },
|
|
629
|
+
{ id: 'suggestions-followup-overflow', label: 'Follow-up Overflow', type: 'select', path: 'suggestions.followUps.overflow', defaultValue: 'scroll', options: [{ value: 'scroll', label: 'Horizontal Scroll' }, { value: 'wrap', label: 'Wrap Rows' }] },
|
|
582
630
|
],
|
|
583
631
|
};
|
|
584
632
|
|
package/src/theme-reference.ts
CHANGED
|
@@ -142,7 +142,8 @@ export const THEME_TOKEN_DOCS = {
|
|
|
142
142
|
'Floating scroll-to-bottom affordance shared by transcript and event stream: background, foreground, border, size, borderRadius, shadow, padding, gap, fontSize, iconSize.',
|
|
143
143
|
toolBubble: 'shadow: tool call row box-shadow.',
|
|
144
144
|
reasoningBubble: 'shadow: reasoning/thinking row box-shadow.',
|
|
145
|
-
composer:
|
|
145
|
+
composer:
|
|
146
|
+
'Message input form: shadow (box-shadow), padding (form inset), gap (textarea row to actions row), fontSize and lineHeight (textarea type).',
|
|
146
147
|
artifact:
|
|
147
148
|
'toolbar (icon styling, copy menu), tab (background, active states), pane (background, toolbarBackground).',
|
|
148
149
|
},
|
|
@@ -245,8 +246,14 @@ export const THEME_TOKEN_DOCS = {
|
|
|
245
246
|
properties:
|
|
246
247
|
'enabled, provider ("browser" | "runtype"), browserFallback, voice, rate, pitch.',
|
|
247
248
|
},
|
|
249
|
+
suggestions: {
|
|
250
|
+
description:
|
|
251
|
+
'Rich starter and follow-up suggestion surfaces. Strings remain valid item shorthand.',
|
|
252
|
+
properties:
|
|
253
|
+
'starters.items, starters.variant ("card" | "chip" | "list"), starters.placement ("auto" | "welcome" | "composer"), starters.behavior ("send" | "fill"), starters.overflow ("scroll" | "wrap"), starters.maxItems, followUps.enabled, followUps.expose, followUps.variant, followUps.placement ("auto" | "after-message" | "composer"), followUps.behavior, followUps.overflow ("scroll" | "wrap"), followUps.maxItems.',
|
|
254
|
+
},
|
|
248
255
|
suggestionChips:
|
|
249
|
-
'string[]
|
|
256
|
+
'Deprecated string[] shorthand for starter prompts. Prefer suggestions.starters.items.',
|
|
250
257
|
messageActions: {
|
|
251
258
|
description: 'Message action buttons (copy, upvote, downvote).',
|
|
252
259
|
properties:
|
package/src/types/theme.ts
CHANGED
|
@@ -365,6 +365,14 @@ export interface ReasoningBubbleTokens {
|
|
|
365
365
|
export interface ComposerChromeTokens {
|
|
366
366
|
/** Box-shadow on the composer form (raw CSS, e.g. `none`). */
|
|
367
367
|
shadow: string;
|
|
368
|
+
/** Inner padding of the composer form (raw CSS shorthand). @default "0.75rem 1rem" */
|
|
369
|
+
padding?: string;
|
|
370
|
+
/** Gap between the textarea row and the actions row. @default "0.5rem" */
|
|
371
|
+
gap?: string;
|
|
372
|
+
/** Composer textarea font-size. @default "0.875rem" */
|
|
373
|
+
fontSize?: string;
|
|
374
|
+
/** Composer textarea line-height. @default "1.25rem" */
|
|
375
|
+
lineHeight?: string;
|
|
368
376
|
}
|
|
369
377
|
|
|
370
378
|
/** Artifact toolbar chrome. */
|
|
@@ -524,6 +532,28 @@ export interface ScrollToBottomTokens extends ComponentTokenSet {
|
|
|
524
532
|
iconSize?: string;
|
|
525
533
|
}
|
|
526
534
|
|
|
535
|
+
/** Visual tokens shared by one suggestion presentation variant. */
|
|
536
|
+
export interface SuggestionVariantTokens extends ComponentTokenSet {
|
|
537
|
+
gap?: string;
|
|
538
|
+
minHeight?: string;
|
|
539
|
+
fontSize?: string;
|
|
540
|
+
lineHeight?: string;
|
|
541
|
+
iconSize?: string;
|
|
542
|
+
hoverBackground?: TokenReference<'color'>;
|
|
543
|
+
hoverForeground?: TokenReference<'color'>;
|
|
544
|
+
hoverBorder?: TokenReference<'color'>;
|
|
545
|
+
pressedBackground?: TokenReference<'color'>;
|
|
546
|
+
focusRing?: TokenReference<'color'>;
|
|
547
|
+
disabledOpacity?: string;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** Starter and follow-up suggestion chrome. */
|
|
551
|
+
export interface SuggestionTokens {
|
|
552
|
+
chip: SuggestionVariantTokens;
|
|
553
|
+
card: SuggestionVariantTokens;
|
|
554
|
+
list: SuggestionVariantTokens;
|
|
555
|
+
}
|
|
556
|
+
|
|
527
557
|
/** Toggle group chrome (used by createToggleGroup). */
|
|
528
558
|
export interface ToggleGroupTokens {
|
|
529
559
|
/** Gap between toggle buttons. Default: 0 (connected). */
|
|
@@ -555,6 +585,8 @@ export interface ComponentTokens {
|
|
|
555
585
|
labelButton?: LabelButtonTokens;
|
|
556
586
|
/** Scroll-to-bottom indicator styling tokens. */
|
|
557
587
|
scrollToBottom?: ScrollToBottomTokens;
|
|
588
|
+
/** Starter prompt and follow-up suggestion styling tokens. */
|
|
589
|
+
suggestion?: SuggestionTokens;
|
|
558
590
|
/** Toggle group styling tokens. */
|
|
559
591
|
toggleGroup?: ToggleGroupTokens;
|
|
560
592
|
/** Artifact toolbar, tab strip, and pane chrome. */
|