@runtypelabs/persona 4.15.0 → 4.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/context-mentions.d.cts +283 -9
- package/dist/context-mentions.d.ts +283 -9
- package/dist/index.cjs +68 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +299 -10
- package/dist/index.d.ts +299 -10
- package/dist/index.global.js +57 -57
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +69 -69
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/smart-dom-reader.d.cts +283 -9
- package/dist/smart-dom-reader.d.ts +283 -9
- package/dist/testing.cjs +3 -3
- package/dist/testing.d.cts +7 -0
- package/dist/testing.d.ts +7 -0
- package/dist/testing.js +3 -3
- package/dist/theme-editor-preview.cjs +64 -64
- package/dist/theme-editor-preview.d.cts +283 -9
- package/dist/theme-editor-preview.d.ts +283 -9
- package/dist/theme-editor-preview.js +56 -56
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +283 -10
- package/dist/theme-editor.d.ts +283 -10
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +66 -3
- package/dist/theme-reference.d.ts +66 -3
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +1 -1
- package/src/components/artifact-pane.test.ts +27 -0
- package/src/components/artifact-pane.ts +7 -3
- package/src/components/header-builder.ts +27 -2
- package/src/components/header-layouts.test.ts +56 -0
- package/src/components/header-layouts.ts +29 -9
- package/src/components/launcher-surface.test.ts +403 -0
- package/src/components/launcher.ts +297 -2
- package/src/components/panel.ts +69 -74
- package/src/components/suggestions.test.ts +40 -0
- package/src/components/suggestions.ts +213 -153
- package/src/components/welcome.test.ts +121 -0
- package/src/components/welcome.ts +305 -0
- package/src/components/widget-view.test.ts +5 -3
- package/src/components/widget-view.ts +14 -7
- package/src/defaults.ts +2 -2
- package/src/index-core.ts +12 -1
- package/src/launcher-global.ts +10 -7
- package/src/plugins/types.ts +66 -1
- package/src/styles/widget-styles.test.ts +220 -0
- package/src/styles/widget.css +353 -9
- package/src/testing/mock-stream.test.ts +19 -0
- package/src/testing/mock-stream.ts +20 -0
- package/src/theme-editor/sections.ts +30 -1
- package/src/theme-reference.ts +26 -4
- package/src/types/theme.ts +40 -3
- package/src/types.ts +164 -6
- package/src/ui.body-top-slot.test.ts +95 -0
- package/src/ui.content-width.test.ts +91 -0
- package/src/ui.docked.test.ts +7 -4
- package/src/ui.launcher-teaser.test.ts +162 -0
- package/src/ui.rebuild-composer.test.ts +491 -0
- package/src/ui.render-welcome.test.ts +505 -0
- package/src/ui.scroll-additive.test.ts +91 -21
- package/src/ui.scroll.test.ts +302 -14
- package/src/ui.suggest-replies.test.ts +46 -0
- package/src/ui.ts +747 -136
- package/src/ui.welcome.test.ts +504 -0
- package/src/utils/artifact-gate.test.ts +27 -0
- package/src/utils/artifact-gate.ts +9 -0
- package/src/utils/attachment-manager.test.ts +37 -0
- package/src/utils/attachment-manager.ts +15 -0
- package/src/utils/auto-follow.test.ts +23 -0
- package/src/utils/auto-follow.ts +11 -1
- package/src/utils/config-merge.ts +2 -0
- package/src/utils/content-width.test.ts +37 -0
- package/src/utils/content-width.ts +17 -0
- package/src/utils/plugin-storage.test.ts +94 -0
- package/src/utils/plugin-storage.ts +98 -0
- package/src/utils/theme.test.ts +137 -0
- package/src/utils/tokens.ts +49 -3
- package/src/welcome.test.ts +213 -0
- package/src/welcome.ts +130 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_WIDGET_CONFIG, mergeWithDefaults } from "./defaults";
|
|
4
|
+
import { mergeConfigUpdate } from "./utils/config-merge";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_WELCOME_SUBTITLE,
|
|
7
|
+
DEFAULT_WELCOME_TITLE,
|
|
8
|
+
isWelcomeVisible,
|
|
9
|
+
resolveWelcomeConfig,
|
|
10
|
+
} from "./welcome";
|
|
11
|
+
import type { AgentWidgetConfig, AgentWidgetMessage } from "./types";
|
|
12
|
+
|
|
13
|
+
const config = (partial: Partial<AgentWidgetConfig>): AgentWidgetConfig =>
|
|
14
|
+
partial as AgentWidgetConfig;
|
|
15
|
+
|
|
16
|
+
const userMessage: AgentWidgetMessage = {
|
|
17
|
+
id: "u1",
|
|
18
|
+
role: "user",
|
|
19
|
+
content: "hi",
|
|
20
|
+
createdAt: "2026-08-01T00:00:00.000Z",
|
|
21
|
+
streaming: false,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const assistantMessage: AgentWidgetMessage = {
|
|
25
|
+
...userMessage,
|
|
26
|
+
id: "a1",
|
|
27
|
+
role: "assistant",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe("resolveWelcomeConfig", () => {
|
|
31
|
+
it("falls back to resolver-owned defaults", () => {
|
|
32
|
+
const resolved = resolveWelcomeConfig(config({}));
|
|
33
|
+
expect(resolved.title).toBe(DEFAULT_WELCOME_TITLE);
|
|
34
|
+
expect(resolved.subtitle).toBe(DEFAULT_WELCOME_SUBTITLE);
|
|
35
|
+
expect(resolved.variant).toBe("card");
|
|
36
|
+
expect(resolved.dismiss).toBe("never");
|
|
37
|
+
expect(resolved.message).toBeUndefined();
|
|
38
|
+
expect(resolved.icon).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("prefers welcome.* over the legacy copy aliases, per field", () => {
|
|
42
|
+
const resolved = resolveWelcomeConfig(
|
|
43
|
+
config({
|
|
44
|
+
welcome: { title: "New title" },
|
|
45
|
+
copy: { welcomeTitle: "Legacy title", welcomeSubtitle: "Legacy subtitle" },
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
expect(resolved.title).toBe("New title");
|
|
49
|
+
expect(resolved.subtitle).toBe("Legacy subtitle");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("uses the legacy aliases when welcome does not set the field", () => {
|
|
53
|
+
const resolved = resolveWelcomeConfig(
|
|
54
|
+
config({ copy: { welcomeTitle: "Legacy", welcomeSubtitle: "Scope" } })
|
|
55
|
+
);
|
|
56
|
+
expect(resolved.title).toBe("Legacy");
|
|
57
|
+
expect(resolved.subtitle).toBe("Scope");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("honors an explicitly empty title or subtitle", () => {
|
|
61
|
+
const resolved = resolveWelcomeConfig(
|
|
62
|
+
config({ welcome: { title: "", subtitle: "" } })
|
|
63
|
+
);
|
|
64
|
+
expect(resolved.title).toBe("");
|
|
65
|
+
expect(resolved.subtitle).toBe("");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("maps showWelcomeCard: false onto variant none", () => {
|
|
69
|
+
expect(
|
|
70
|
+
resolveWelcomeConfig(config({ copy: { showWelcomeCard: false } })).variant
|
|
71
|
+
).toBe("none");
|
|
72
|
+
expect(
|
|
73
|
+
resolveWelcomeConfig(config({ copy: { showWelcomeCard: true } })).variant
|
|
74
|
+
).toBe("card");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("lets welcome.variant win over showWelcomeCard", () => {
|
|
78
|
+
expect(
|
|
79
|
+
resolveWelcomeConfig(
|
|
80
|
+
config({ welcome: { variant: "hero" }, copy: { showWelcomeCard: false } })
|
|
81
|
+
).variant
|
|
82
|
+
).toBe("hero");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("forces on-first-message dismissal for hero and suppresses the greeting", () => {
|
|
86
|
+
const resolved = resolveWelcomeConfig(
|
|
87
|
+
config({
|
|
88
|
+
welcome: { variant: "hero", dismiss: "never", message: "Hi there" },
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
expect(resolved.dismiss).toBe("on-first-message");
|
|
92
|
+
expect(resolved.message).toBeUndefined();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("keeps the greeting for card and none variants", () => {
|
|
96
|
+
expect(
|
|
97
|
+
resolveWelcomeConfig(config({ welcome: { message: "Hi there" } })).message
|
|
98
|
+
).toBe("Hi there");
|
|
99
|
+
expect(
|
|
100
|
+
resolveWelcomeConfig(
|
|
101
|
+
config({ welcome: { variant: "none", message: "Hi there" } })
|
|
102
|
+
).message
|
|
103
|
+
).toBe("Hi there");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("warns once in debug mode when a field is set through both namespaces", () => {
|
|
107
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
108
|
+
const withBoth = config({
|
|
109
|
+
debug: true,
|
|
110
|
+
welcome: { title: "New" },
|
|
111
|
+
copy: { welcomeTitle: "Legacy" },
|
|
112
|
+
});
|
|
113
|
+
resolveWelcomeConfig(withBoth);
|
|
114
|
+
resolveWelcomeConfig(withBoth);
|
|
115
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
116
|
+
expect(warn.mock.calls[0][0]).toContain("copy.welcomeTitle");
|
|
117
|
+
warn.mockRestore();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("warns in debug mode when message is set with variant hero", () => {
|
|
121
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
122
|
+
resolveWelcomeConfig(
|
|
123
|
+
config({ debug: true, welcome: { variant: "hero", message: "Hi" } })
|
|
124
|
+
);
|
|
125
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
126
|
+
expect(warn.mock.calls[0][0]).toContain("welcome.message is ignored");
|
|
127
|
+
warn.mockRestore();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("stays silent outside debug mode", () => {
|
|
131
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
132
|
+
resolveWelcomeConfig(
|
|
133
|
+
config({ welcome: { title: "New" }, copy: { welcomeTitle: "Legacy" } })
|
|
134
|
+
);
|
|
135
|
+
expect(warn).not.toHaveBeenCalled();
|
|
136
|
+
warn.mockRestore();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("welcome defaults are never materialized into config", () => {
|
|
141
|
+
it("keeps welcome copy out of DEFAULT_WIDGET_CONFIG", () => {
|
|
142
|
+
expect(DEFAULT_WIDGET_CONFIG.copy).not.toHaveProperty("welcomeTitle");
|
|
143
|
+
expect(DEFAULT_WIDGET_CONFIG.copy).not.toHaveProperty("welcomeSubtitle");
|
|
144
|
+
expect(DEFAULT_WIDGET_CONFIG).not.toHaveProperty("welcome");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("keeps welcome copy out of a defaults-merged config", () => {
|
|
148
|
+
const merged = mergeWithDefaults({ apiUrl: "https://example.com" });
|
|
149
|
+
expect(merged.copy).not.toHaveProperty("welcomeTitle");
|
|
150
|
+
expect(merged.copy).not.toHaveProperty("welcomeSubtitle");
|
|
151
|
+
expect(merged.welcome).toBeUndefined();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("resets to the resolver default on an explicit-undefined patch", () => {
|
|
155
|
+
const initial = mergeWithDefaults({
|
|
156
|
+
apiUrl: "https://example.com",
|
|
157
|
+
welcome: { title: "Set by host" },
|
|
158
|
+
}) as AgentWidgetConfig;
|
|
159
|
+
expect(resolveWelcomeConfig(initial).title).toBe("Set by host");
|
|
160
|
+
|
|
161
|
+
const cleared = mergeConfigUpdate(initial, {
|
|
162
|
+
welcome: { title: undefined },
|
|
163
|
+
});
|
|
164
|
+
expect(cleared.welcome).not.toHaveProperty("title");
|
|
165
|
+
expect(resolveWelcomeConfig(cleared).title).toBe(DEFAULT_WELCOME_TITLE);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("falls back to the legacy alias after clearing the welcome field", () => {
|
|
169
|
+
const initial = mergeWithDefaults({
|
|
170
|
+
apiUrl: "https://example.com",
|
|
171
|
+
welcome: { title: "New" },
|
|
172
|
+
copy: { welcomeTitle: "Legacy" },
|
|
173
|
+
}) as AgentWidgetConfig;
|
|
174
|
+
const cleared = mergeConfigUpdate(initial, { welcome: { title: undefined } });
|
|
175
|
+
expect(resolveWelcomeConfig(cleared).title).toBe("Legacy");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("replaces the icon wholesale instead of splicing union variants", () => {
|
|
179
|
+
const initial = mergeWithDefaults({
|
|
180
|
+
apiUrl: "https://example.com",
|
|
181
|
+
welcome: { icon: { type: "image", url: "/logo.png", alt: "Logo" } },
|
|
182
|
+
}) as AgentWidgetConfig;
|
|
183
|
+
const updated = mergeConfigUpdate(initial, {
|
|
184
|
+
welcome: { icon: { type: "lucide", name: "sparkles" } },
|
|
185
|
+
});
|
|
186
|
+
expect(updated.welcome?.icon).toEqual({ type: "lucide", name: "sparkles" });
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("isWelcomeVisible", () => {
|
|
191
|
+
const card = resolveWelcomeConfig(config({}));
|
|
192
|
+
const hero = resolveWelcomeConfig(config({ welcome: { variant: "hero" } }));
|
|
193
|
+
const none = resolveWelcomeConfig(config({ welcome: { variant: "none" } }));
|
|
194
|
+
|
|
195
|
+
it("shows a never-dismissing card regardless of transcript content", () => {
|
|
196
|
+
expect(isWelcomeVisible(card, [])).toBe(true);
|
|
197
|
+
expect(isWelcomeVisible(card, [userMessage])).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("hides an on-first-message surface once a user message exists", () => {
|
|
201
|
+
expect(isWelcomeVisible(hero, [])).toBe(true);
|
|
202
|
+
expect(isWelcomeVisible(hero, [assistantMessage])).toBe(true);
|
|
203
|
+
expect(isWelcomeVisible(hero, [userMessage, assistantMessage])).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("never shows variant none", () => {
|
|
207
|
+
expect(isWelcomeVisible(none, [])).toBe(false);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("treats a missing message list as empty", () => {
|
|
211
|
+
expect(isWelcomeVisible(hero, undefined)).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
});
|
package/src/welcome.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentWidgetConfig,
|
|
3
|
+
AgentWidgetMessage,
|
|
4
|
+
AgentWidgetWelcomeDismiss,
|
|
5
|
+
AgentWidgetWelcomeIcon,
|
|
6
|
+
AgentWidgetWelcomeVariant,
|
|
7
|
+
} from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Single source of truth for the welcome surface. Renderers consume the
|
|
11
|
+
* resolved shape only and never read `copy.welcome*` directly.
|
|
12
|
+
*
|
|
13
|
+
* The defaults live here, not in `defaults.ts`: `mergeConfigUpdate`
|
|
14
|
+
* re-applies `mergeWithDefaults` after every patch, so anything defaulted
|
|
15
|
+
* there would be materialized into the stored config and presence-based
|
|
16
|
+
* precedence could no longer tell a host-set value from a default.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Default welcome title, applied when neither `welcome` nor `copy` sets one. */
|
|
20
|
+
export const DEFAULT_WELCOME_TITLE = "Hello 👋";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Scope statement in the assistant's voice, deliberately generic so the
|
|
24
|
+
* placeholder shape reads as something to replace with domain copy.
|
|
25
|
+
*/
|
|
26
|
+
export const DEFAULT_WELCOME_SUBTITLE =
|
|
27
|
+
"I can answer questions and help you get things done here.";
|
|
28
|
+
|
|
29
|
+
export interface ResolvedWelcomeConfig {
|
|
30
|
+
title: string;
|
|
31
|
+
subtitle: string;
|
|
32
|
+
icon?: AgentWidgetWelcomeIcon;
|
|
33
|
+
variant: AgentWidgetWelcomeVariant;
|
|
34
|
+
dismiss: AgentWidgetWelcomeDismiss;
|
|
35
|
+
/** Undefined under `variant: "hero"`, which suppresses the greeting. */
|
|
36
|
+
message?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Keyed on the config object: the resolver runs on every render, so warnings
|
|
40
|
+
// fire once per config identity instead of once per frame.
|
|
41
|
+
const warnedConfigs = new WeakSet<AgentWidgetConfig>();
|
|
42
|
+
|
|
43
|
+
const isSet = <T, K extends keyof T>(source: T | undefined, key: K): boolean =>
|
|
44
|
+
!!source && source[key] !== undefined;
|
|
45
|
+
|
|
46
|
+
const warnOnce = (config: AgentWidgetConfig | undefined, messages: string[]) => {
|
|
47
|
+
if (!config || config.debug !== true || !messages.length) return;
|
|
48
|
+
if (warnedConfigs.has(config)) return;
|
|
49
|
+
warnedConfigs.add(config);
|
|
50
|
+
messages.forEach((message) => console.warn(`[persona] ${message}`));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Per-field, presence-based precedence: `welcome.*` wins, else the legacy
|
|
55
|
+
* `copy` alias, else the default above.
|
|
56
|
+
*/
|
|
57
|
+
export const resolveWelcomeConfig = (
|
|
58
|
+
config?: AgentWidgetConfig
|
|
59
|
+
): ResolvedWelcomeConfig => {
|
|
60
|
+
const welcome = config?.welcome;
|
|
61
|
+
const copy = config?.copy;
|
|
62
|
+
|
|
63
|
+
const variant: AgentWidgetWelcomeVariant = isSet(welcome, "variant")
|
|
64
|
+
? welcome!.variant!
|
|
65
|
+
: copy?.showWelcomeCard === false
|
|
66
|
+
? "none"
|
|
67
|
+
: "card";
|
|
68
|
+
|
|
69
|
+
// Hero IS the greeting: `dismiss` is forced and `message` is suppressed.
|
|
70
|
+
const dismiss: AgentWidgetWelcomeDismiss =
|
|
71
|
+
variant === "hero"
|
|
72
|
+
? "on-first-message"
|
|
73
|
+
: isSet(welcome, "dismiss")
|
|
74
|
+
? welcome!.dismiss!
|
|
75
|
+
: "never";
|
|
76
|
+
|
|
77
|
+
const conflicts: string[] = [];
|
|
78
|
+
if (isSet(welcome, "title") && isSet(copy, "welcomeTitle")) {
|
|
79
|
+
conflicts.push(
|
|
80
|
+
"welcome.title and copy.welcomeTitle are both set; welcome.title wins. copy.welcomeTitle is deprecated."
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (isSet(welcome, "subtitle") && isSet(copy, "welcomeSubtitle")) {
|
|
84
|
+
conflicts.push(
|
|
85
|
+
"welcome.subtitle and copy.welcomeSubtitle are both set; welcome.subtitle wins. copy.welcomeSubtitle is deprecated."
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (isSet(welcome, "variant") && isSet(copy, "showWelcomeCard")) {
|
|
89
|
+
conflicts.push(
|
|
90
|
+
"welcome.variant and copy.showWelcomeCard are both set; welcome.variant wins. copy.showWelcomeCard is deprecated."
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (variant === "hero" && isSet(welcome, "message")) {
|
|
94
|
+
conflicts.push(
|
|
95
|
+
'welcome.message is ignored when welcome.variant is "hero": the hero is the greeting.'
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (variant === "hero" && isSet(welcome, "dismiss") && welcome!.dismiss !== "on-first-message") {
|
|
99
|
+
conflicts.push(
|
|
100
|
+
'welcome.dismiss is pinned to "on-first-message" when welcome.variant is "hero".'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
warnOnce(config, conflicts);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
title: isSet(welcome, "title")
|
|
107
|
+
? welcome!.title!
|
|
108
|
+
: copy?.welcomeTitle ?? DEFAULT_WELCOME_TITLE,
|
|
109
|
+
subtitle: isSet(welcome, "subtitle")
|
|
110
|
+
? welcome!.subtitle!
|
|
111
|
+
: copy?.welcomeSubtitle ?? DEFAULT_WELCOME_SUBTITLE,
|
|
112
|
+
icon: welcome?.icon,
|
|
113
|
+
variant,
|
|
114
|
+
dismiss,
|
|
115
|
+
message: variant === "hero" ? undefined : welcome?.message,
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Derived visibility, never stored. "User activity" is the same predicate the
|
|
121
|
+
* starters use: the session contains at least one `role: "user"` message.
|
|
122
|
+
*/
|
|
123
|
+
export const isWelcomeVisible = (
|
|
124
|
+
resolved: ResolvedWelcomeConfig,
|
|
125
|
+
messages: readonly AgentWidgetMessage[] | undefined
|
|
126
|
+
): boolean => {
|
|
127
|
+
if (resolved.variant === "none") return false;
|
|
128
|
+
if (resolved.dismiss === "never") return true;
|
|
129
|
+
return !(messages ?? []).some((message) => message.role === "user");
|
|
130
|
+
};
|