@runtypelabs/persona 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -3
- package/dist/index.cjs +38 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -2
- package/dist/index.d.ts +41 -2
- package/dist/index.global.js +51 -51
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +38 -38
- package/dist/index.js.map +1 -1
- package/dist/widget.css +12 -0
- package/package.json +1 -1
- package/src/components/launcher.test.ts +58 -0
- package/src/components/launcher.ts +10 -6
- package/src/styles/widget.css +12 -0
- package/src/types.ts +39 -2
- package/src/ui.ts +18 -2
package/dist/widget.css
CHANGED
|
@@ -349,6 +349,11 @@
|
|
|
349
349
|
word-break: break-all;
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
.persona-break-words {
|
|
353
|
+
overflow-wrap: break-word;
|
|
354
|
+
word-break: break-word;
|
|
355
|
+
}
|
|
356
|
+
|
|
352
357
|
.persona-px-4 {
|
|
353
358
|
padding-left: 1rem;
|
|
354
359
|
padding-right: 1rem;
|
|
@@ -1299,6 +1304,7 @@
|
|
|
1299
1304
|
/* Ensure user message paragraphs and lists have proper styling too */
|
|
1300
1305
|
.vanilla-message-user-bubble p {
|
|
1301
1306
|
margin: 0;
|
|
1307
|
+
color: inherit;
|
|
1302
1308
|
}
|
|
1303
1309
|
|
|
1304
1310
|
.vanilla-message-user-bubble p + p {
|
|
@@ -1322,6 +1328,12 @@
|
|
|
1322
1328
|
.vanilla-message-user-bubble li {
|
|
1323
1329
|
margin: 0.25rem 0;
|
|
1324
1330
|
padding-left: 0.25rem;
|
|
1331
|
+
color: inherit;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
.vanilla-message-assistant-bubble p,
|
|
1335
|
+
.vanilla-message-assistant-bubble li {
|
|
1336
|
+
color: inherit;
|
|
1325
1337
|
}
|
|
1326
1338
|
|
|
1327
1339
|
/* ============================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/persona",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { createLauncherButton } from "./launcher";
|
|
5
|
+
import { DEFAULT_WIDGET_CONFIG } from "../defaults";
|
|
6
|
+
|
|
7
|
+
describe("createLauncherButton", () => {
|
|
8
|
+
it("applies collapsedMaxWidth when set", () => {
|
|
9
|
+
const { element, update } = createLauncherButton(undefined, () => {});
|
|
10
|
+
update({
|
|
11
|
+
...DEFAULT_WIDGET_CONFIG,
|
|
12
|
+
launcher: {
|
|
13
|
+
...DEFAULT_WIDGET_CONFIG.launcher,
|
|
14
|
+
collapsedMaxWidth: "min(380px, calc(100vw - 48px))",
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
expect(element.style.maxWidth).toBe("min(380px, calc(100vw - 48px))");
|
|
18
|
+
element.remove();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("sets title tooltip on launcher title and subtitle for truncated text", () => {
|
|
22
|
+
const { element, update } = createLauncherButton(undefined, () => {});
|
|
23
|
+
update({
|
|
24
|
+
...DEFAULT_WIDGET_CONFIG,
|
|
25
|
+
launcher: {
|
|
26
|
+
...DEFAULT_WIDGET_CONFIG.launcher,
|
|
27
|
+
title: "Hello",
|
|
28
|
+
subtitle: "Long subtitle for tooltip",
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
const titleEl = element.querySelector("[data-role='launcher-title']");
|
|
32
|
+
const subtitleEl = element.querySelector("[data-role='launcher-subtitle']");
|
|
33
|
+
expect(titleEl?.getAttribute("title")).toBe("Hello");
|
|
34
|
+
expect(subtitleEl?.getAttribute("title")).toBe("Long subtitle for tooltip");
|
|
35
|
+
element.remove();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("clears maxWidth when collapsedMaxWidth is unset", () => {
|
|
39
|
+
const { element, update } = createLauncherButton(undefined, () => {});
|
|
40
|
+
update({
|
|
41
|
+
...DEFAULT_WIDGET_CONFIG,
|
|
42
|
+
launcher: {
|
|
43
|
+
...DEFAULT_WIDGET_CONFIG.launcher,
|
|
44
|
+
collapsedMaxWidth: "320px",
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
expect(element.style.maxWidth).toBe("320px");
|
|
48
|
+
update({
|
|
49
|
+
...DEFAULT_WIDGET_CONFIG,
|
|
50
|
+
launcher: {
|
|
51
|
+
...DEFAULT_WIDGET_CONFIG.launcher,
|
|
52
|
+
collapsedMaxWidth: undefined,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
expect(element.style.maxWidth).toBe("");
|
|
56
|
+
element.remove();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -19,9 +19,9 @@ export const createLauncherButton = (
|
|
|
19
19
|
button.innerHTML = `
|
|
20
20
|
<span class="persona-inline-flex persona-items-center persona-justify-center persona-rounded-full persona-bg-persona-primary persona-text-white" data-role="launcher-icon">💬</span>
|
|
21
21
|
<img data-role="launcher-image" class="persona-rounded-full persona-object-cover" alt="" style="display:none" />
|
|
22
|
-
<span class="persona-flex persona-flex-col persona-items-start persona-text-left">
|
|
23
|
-
<span class="persona-text-sm persona-font-semibold persona-text-persona-primary" data-role="launcher-title"></span>
|
|
24
|
-
<span class="persona-text-xs persona-text-persona-muted" data-role="launcher-subtitle"></span>
|
|
22
|
+
<span class="persona-flex persona-min-w-0 persona-flex-1 persona-flex-col persona-items-start persona-text-left">
|
|
23
|
+
<span class="persona-block persona-w-full persona-truncate persona-text-sm persona-font-semibold persona-text-persona-primary" data-role="launcher-title"></span>
|
|
24
|
+
<span class="persona-block persona-w-full persona-truncate persona-text-xs persona-text-persona-muted" data-role="launcher-subtitle"></span>
|
|
25
25
|
</span>
|
|
26
26
|
<span class="persona-ml-2 persona-grid persona-place-items-center persona-rounded-full persona-bg-persona-primary persona-text-persona-call-to-action" data-role="launcher-call-to-action-icon">↗</span>
|
|
27
27
|
`;
|
|
@@ -33,12 +33,16 @@ export const createLauncherButton = (
|
|
|
33
33
|
|
|
34
34
|
const titleEl = button.querySelector("[data-role='launcher-title']");
|
|
35
35
|
if (titleEl) {
|
|
36
|
-
|
|
36
|
+
const t = launcher.title ?? "Chat Assistant";
|
|
37
|
+
titleEl.textContent = t;
|
|
38
|
+
titleEl.setAttribute("title", t);
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
const subtitleEl = button.querySelector("[data-role='launcher-subtitle']");
|
|
40
42
|
if (subtitleEl) {
|
|
41
|
-
|
|
43
|
+
const s = launcher.subtitle ?? "Get answers fast";
|
|
44
|
+
subtitleEl.textContent = s;
|
|
45
|
+
subtitleEl.setAttribute("title", s);
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
// Hide/show text container
|
|
@@ -186,7 +190,7 @@ export const createLauncherButton = (
|
|
|
186
190
|
} else {
|
|
187
191
|
button.style.width = "";
|
|
188
192
|
button.style.minWidth = "";
|
|
189
|
-
button.style.maxWidth = "";
|
|
193
|
+
button.style.maxWidth = launcher.collapsedMaxWidth ?? "";
|
|
190
194
|
button.style.justifyContent = "";
|
|
191
195
|
button.style.padding = "";
|
|
192
196
|
button.style.overflow = "";
|
package/src/styles/widget.css
CHANGED
|
@@ -349,6 +349,11 @@
|
|
|
349
349
|
word-break: break-all;
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
.persona-break-words {
|
|
353
|
+
overflow-wrap: break-word;
|
|
354
|
+
word-break: break-word;
|
|
355
|
+
}
|
|
356
|
+
|
|
352
357
|
.persona-px-4 {
|
|
353
358
|
padding-left: 1rem;
|
|
354
359
|
padding-right: 1rem;
|
|
@@ -1299,6 +1304,7 @@
|
|
|
1299
1304
|
/* Ensure user message paragraphs and lists have proper styling too */
|
|
1300
1305
|
.vanilla-message-user-bubble p {
|
|
1301
1306
|
margin: 0;
|
|
1307
|
+
color: inherit;
|
|
1302
1308
|
}
|
|
1303
1309
|
|
|
1304
1310
|
.vanilla-message-user-bubble p + p {
|
|
@@ -1322,6 +1328,12 @@
|
|
|
1322
1328
|
.vanilla-message-user-bubble li {
|
|
1323
1329
|
margin: 0.25rem 0;
|
|
1324
1330
|
padding-left: 0.25rem;
|
|
1331
|
+
color: inherit;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
.vanilla-message-assistant-bubble p,
|
|
1335
|
+
.vanilla-message-assistant-bubble li {
|
|
1336
|
+
color: inherit;
|
|
1325
1337
|
}
|
|
1326
1338
|
|
|
1327
1339
|
/* ============================================
|
package/src/types.ts
CHANGED
|
@@ -827,6 +827,13 @@ export type AgentWidgetLauncherConfig = {
|
|
|
827
827
|
* @default "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)"
|
|
828
828
|
*/
|
|
829
829
|
shadow?: string;
|
|
830
|
+
/**
|
|
831
|
+
* CSS `max-width` for the floating launcher button when the panel is closed.
|
|
832
|
+
* Title and subtitle each truncate with an ellipsis when space is tight; full strings are available via the native `title` tooltip. Does not affect the open chat panel (`width` / `launcherWidth`).
|
|
833
|
+
*
|
|
834
|
+
* @example "min(380px, calc(100vw - 48px))"
|
|
835
|
+
*/
|
|
836
|
+
collapsedMaxWidth?: string;
|
|
830
837
|
};
|
|
831
838
|
|
|
832
839
|
export type AgentWidgetSendButtonConfig = {
|
|
@@ -2346,11 +2353,15 @@ export type AgentWidgetConfig = {
|
|
|
2346
2353
|
*
|
|
2347
2354
|
* This hook runs synchronously and must return the (potentially modified) state.
|
|
2348
2355
|
*
|
|
2356
|
+
* Returning `{ state, open: true }` also signals that the widget panel should
|
|
2357
|
+
* open after initialization — useful when injecting a post-navigation message
|
|
2358
|
+
* that the user should immediately see.
|
|
2359
|
+
*
|
|
2349
2360
|
* @example
|
|
2350
2361
|
* ```typescript
|
|
2362
|
+
* // Plain state transform (existing form, still supported)
|
|
2351
2363
|
* config: {
|
|
2352
2364
|
* onStateLoaded: (state) => {
|
|
2353
|
-
* // Check for pending navigation message
|
|
2354
2365
|
* const navMessage = consumeNavigationFlag();
|
|
2355
2366
|
* if (navMessage) {
|
|
2356
2367
|
* return {
|
|
@@ -2367,8 +2378,34 @@ export type AgentWidgetConfig = {
|
|
|
2367
2378
|
* }
|
|
2368
2379
|
* }
|
|
2369
2380
|
* ```
|
|
2381
|
+
*
|
|
2382
|
+
* @example
|
|
2383
|
+
* ```typescript
|
|
2384
|
+
* // Return { state, open: true } to also open the panel
|
|
2385
|
+
* config: {
|
|
2386
|
+
* onStateLoaded: (state) => {
|
|
2387
|
+
* const navMessage = consumeNavigationFlag();
|
|
2388
|
+
* if (navMessage) {
|
|
2389
|
+
* return {
|
|
2390
|
+
* state: {
|
|
2391
|
+
* ...state,
|
|
2392
|
+
* messages: [...(state.messages || []), {
|
|
2393
|
+
* id: `nav-${Date.now()}`,
|
|
2394
|
+
* role: 'assistant',
|
|
2395
|
+
* content: navMessage,
|
|
2396
|
+
* createdAt: new Date().toISOString()
|
|
2397
|
+
* }]
|
|
2398
|
+
* },
|
|
2399
|
+
* open: true
|
|
2400
|
+
* };
|
|
2401
|
+
* }
|
|
2402
|
+
* return state;
|
|
2403
|
+
* }
|
|
2404
|
+
* }
|
|
2405
|
+
* ```
|
|
2370
2406
|
*/
|
|
2371
|
-
onStateLoaded?: (state: AgentWidgetStoredState) =>
|
|
2407
|
+
onStateLoaded?: (state: AgentWidgetStoredState) =>
|
|
2408
|
+
AgentWidgetStoredState | { state: AgentWidgetStoredState; open?: boolean };
|
|
2372
2409
|
/**
|
|
2373
2410
|
* Registry of custom components that can be rendered from JSON directives.
|
|
2374
2411
|
* Components are registered by name and can be invoked via JSON responses
|
package/src/ui.ts
CHANGED
|
@@ -412,11 +412,20 @@ export const createAgentExperience = (
|
|
|
412
412
|
let persistentMetadata: Record<string, unknown> = {};
|
|
413
413
|
let pendingStoredState: Promise<AgentWidgetStoredState | null> | null = null;
|
|
414
414
|
|
|
415
|
-
|
|
415
|
+
let shouldOpenAfterStateLoaded = false;
|
|
416
|
+
|
|
417
|
+
// Helper to apply onStateLoaded hook and extract state.
|
|
418
|
+
// Supports both the legacy plain-state return and the new { state, open? } return.
|
|
416
419
|
const applyStateLoadedHook = (state: AgentWidgetStoredState): AgentWidgetStoredState => {
|
|
417
420
|
if (config.onStateLoaded) {
|
|
418
421
|
try {
|
|
419
|
-
|
|
422
|
+
const result = config.onStateLoaded(state);
|
|
423
|
+
if (result && typeof result === 'object' && 'state' in result) {
|
|
424
|
+
const { state: processedState, open } = result as { state: AgentWidgetStoredState; open?: boolean };
|
|
425
|
+
if (open) shouldOpenAfterStateLoaded = true;
|
|
426
|
+
return processedState;
|
|
427
|
+
}
|
|
428
|
+
return result as AgentWidgetStoredState;
|
|
420
429
|
} catch (error) {
|
|
421
430
|
if (typeof console !== "undefined") {
|
|
422
431
|
// eslint-disable-next-line no-console
|
|
@@ -5379,6 +5388,13 @@ export const createAgentExperience = (
|
|
|
5379
5388
|
}
|
|
5380
5389
|
}
|
|
5381
5390
|
|
|
5391
|
+
// If onStateLoaded signalled open: true, open the panel after init.
|
|
5392
|
+
// Mirrors the same setTimeout(0) pattern used by persistState restore so both
|
|
5393
|
+
// can fire independently without interfering with each other.
|
|
5394
|
+
if (shouldOpenAfterStateLoaded && launcherEnabled) {
|
|
5395
|
+
setTimeout(() => { controller.open(); }, 0);
|
|
5396
|
+
}
|
|
5397
|
+
|
|
5382
5398
|
return controller;
|
|
5383
5399
|
};
|
|
5384
5400
|
|