@runtypelabs/persona 3.8.3 → 3.9.1
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 +1 -1
- package/dist/index.cjs +42 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.global.js +56 -56
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +42 -42
- package/dist/index.js.map +1 -1
- package/dist/theme-editor.cjs +590 -517
- package/dist/theme-editor.js +590 -517
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.js +1 -1
- package/package.json +1 -1
- package/src/client.test.ts +400 -0
- package/src/client.ts +86 -3
- package/src/components/panel.ts +2 -1
- package/src/defaults.ts +11 -1
- package/src/index.ts +2 -0
- package/src/presets.ts +2 -1
- package/src/theme-editor/sections.ts +7 -3
- package/src/theme-reference.ts +1 -1
- package/src/ui.ts +4 -4
- package/src/utils/tokens.ts +6 -2
package/src/client.ts
CHANGED
|
@@ -1018,6 +1018,7 @@ export class AgentWidgetClient {
|
|
|
1018
1018
|
const assistantMessageRef = { current: null as AgentWidgetMessage | null };
|
|
1019
1019
|
// Track current partId for message segmentation at tool boundaries
|
|
1020
1020
|
const partIdState = { current: null as string | null };
|
|
1021
|
+
let didSplitByPartId = false;
|
|
1021
1022
|
const reasoningMessages = new Map<string, AgentWidgetMessage>();
|
|
1022
1023
|
const toolMessages = new Map<string, AgentWidgetMessage>();
|
|
1023
1024
|
const reasoningContext = {
|
|
@@ -1060,11 +1061,22 @@ export class AgentWidgetClient {
|
|
|
1060
1061
|
payload.step_id
|
|
1061
1062
|
);
|
|
1062
1063
|
|
|
1064
|
+
const baseAssistantId = assistantMessageId;
|
|
1065
|
+
let assistantIdConsumed = false;
|
|
1066
|
+
|
|
1063
1067
|
const ensureAssistantMessage = () => {
|
|
1064
1068
|
if (assistantMessage) return assistantMessage;
|
|
1069
|
+
let id: string;
|
|
1070
|
+
if (!assistantIdConsumed && baseAssistantId) {
|
|
1071
|
+
id = baseAssistantId;
|
|
1072
|
+
assistantIdConsumed = true;
|
|
1073
|
+
} else if (baseAssistantId && partIdState.current) {
|
|
1074
|
+
id = `${baseAssistantId}_${partIdState.current}`;
|
|
1075
|
+
} else {
|
|
1076
|
+
id = `assistant-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
1077
|
+
}
|
|
1065
1078
|
assistantMessage = {
|
|
1066
|
-
|
|
1067
|
-
id: assistantMessageId ?? `assistant-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
1079
|
+
id,
|
|
1068
1080
|
role: "assistant",
|
|
1069
1081
|
content: "",
|
|
1070
1082
|
createdAt: new Date().toISOString(),
|
|
@@ -1507,6 +1519,31 @@ export class AgentWidgetClient {
|
|
|
1507
1519
|
if (callKey) {
|
|
1508
1520
|
toolContext.byCall.delete(callKey);
|
|
1509
1521
|
}
|
|
1522
|
+
} else if (payloadType === "text_start") {
|
|
1523
|
+
// Lifecycle event: a new text segment is beginning (emitted at tool boundaries)
|
|
1524
|
+
const incomingPartId = payload.partId;
|
|
1525
|
+
if (incomingPartId !== undefined && partIdState.current !== null && incomingPartId !== partIdState.current) {
|
|
1526
|
+
const prev = assistantMessage as AgentWidgetMessage | null;
|
|
1527
|
+
if (prev) {
|
|
1528
|
+
prev.streaming = false;
|
|
1529
|
+
emitMessage(prev);
|
|
1530
|
+
assistantMessage = null;
|
|
1531
|
+
didSplitByPartId = true;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
if (incomingPartId !== undefined) {
|
|
1535
|
+
partIdState.current = incomingPartId;
|
|
1536
|
+
}
|
|
1537
|
+
} else if (payloadType === "text_end") {
|
|
1538
|
+
// Lifecycle event: current text segment ended (tool call about to start)
|
|
1539
|
+
// Seal the current assistant message so the next segment gets a new one
|
|
1540
|
+
const prev = assistantMessage as AgentWidgetMessage | null;
|
|
1541
|
+
if (prev) {
|
|
1542
|
+
prev.streaming = false;
|
|
1543
|
+
emitMessage(prev);
|
|
1544
|
+
assistantMessage = null;
|
|
1545
|
+
didSplitByPartId = true;
|
|
1546
|
+
}
|
|
1510
1547
|
} else if (payloadType === "step_chunk" || payloadType === "step_delta") {
|
|
1511
1548
|
// Only process chunks for prompt steps, not tool/context steps
|
|
1512
1549
|
const stepType = (payload as any).stepType;
|
|
@@ -1515,7 +1552,27 @@ export class AgentWidgetClient {
|
|
|
1515
1552
|
// Skip tool-related chunks - they're handled by tool_start/tool_complete
|
|
1516
1553
|
continue;
|
|
1517
1554
|
}
|
|
1555
|
+
|
|
1556
|
+
// partId-based segmentation: when partId changes, seal current message
|
|
1557
|
+
// and start a new one so text and tools render in chronological order
|
|
1558
|
+
const incomingPartId = payload.partId;
|
|
1559
|
+
if (incomingPartId !== undefined && partIdState.current !== null && incomingPartId !== partIdState.current) {
|
|
1560
|
+
const prev = assistantMessage as AgentWidgetMessage | null;
|
|
1561
|
+
if (prev) {
|
|
1562
|
+
prev.streaming = false;
|
|
1563
|
+
emitMessage(prev);
|
|
1564
|
+
assistantMessage = null;
|
|
1565
|
+
didSplitByPartId = true;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
if (incomingPartId !== undefined) {
|
|
1569
|
+
partIdState.current = incomingPartId;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1518
1572
|
const assistant = ensureAssistantMessage();
|
|
1573
|
+
if (incomingPartId !== undefined && !assistant.partId) {
|
|
1574
|
+
assistant.partId = incomingPartId;
|
|
1575
|
+
}
|
|
1519
1576
|
// Support various field names: text, delta, content, chunk (Runtype uses 'chunk')
|
|
1520
1577
|
const chunk = payload.text ?? payload.delta ?? payload.content ?? payload.chunk ?? "";
|
|
1521
1578
|
if (chunk) {
|
|
@@ -1700,6 +1757,20 @@ export class AgentWidgetClient {
|
|
|
1700
1757
|
// Skip tool-related completions - they're handled by tool_complete
|
|
1701
1758
|
continue;
|
|
1702
1759
|
}
|
|
1760
|
+
if (didSplitByPartId) {
|
|
1761
|
+
// text_end already sealed the assistant message(s) — don't recreate
|
|
1762
|
+
// one from step_complete's full response (would cause duplication)
|
|
1763
|
+
if (assistantMessage !== null) {
|
|
1764
|
+
const msg: AgentWidgetMessage = assistantMessage;
|
|
1765
|
+
streamParsers.delete(msg.id);
|
|
1766
|
+
rawContentBuffers.delete(msg.id);
|
|
1767
|
+
if (msg.streaming !== false) {
|
|
1768
|
+
msg.streaming = false;
|
|
1769
|
+
emitMessage(msg);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1703
1774
|
const finalContent = payload.result?.response;
|
|
1704
1775
|
const assistant = ensureAssistantMessage();
|
|
1705
1776
|
if (finalContent !== undefined && finalContent !== null) {
|
|
@@ -1822,7 +1893,19 @@ export class AgentWidgetClient {
|
|
|
1822
1893
|
}
|
|
1823
1894
|
} else if (payloadType === "flow_complete") {
|
|
1824
1895
|
const finalContent = payload.result?.response;
|
|
1825
|
-
if (
|
|
1896
|
+
if (didSplitByPartId) {
|
|
1897
|
+
// Content was split into multiple assistant messages — the full response
|
|
1898
|
+
// in flow_complete would overwrite the last segment. Just finalize streaming.
|
|
1899
|
+
if (assistantMessage !== null) {
|
|
1900
|
+
const msg: AgentWidgetMessage = assistantMessage;
|
|
1901
|
+
streamParsers.delete(msg.id);
|
|
1902
|
+
rawContentBuffers.delete(msg.id);
|
|
1903
|
+
if (msg.streaming !== false) {
|
|
1904
|
+
msg.streaming = false;
|
|
1905
|
+
emitMessage(msg);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
} else if (finalContent !== undefined && finalContent !== null) {
|
|
1826
1909
|
const assistant = ensureAssistantMessage();
|
|
1827
1910
|
// Check if we have raw content buffer that needs final processing
|
|
1828
1911
|
const rawBuffer = rawContentBuffers.get(assistant.id);
|
package/src/components/panel.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createElement } from "../utils/dom";
|
|
2
|
+
import { DEFAULT_FLOATING_LAUNCHER_WIDTH } from "../defaults";
|
|
2
3
|
import { AgentWidgetConfig } from "../types";
|
|
3
4
|
import { positionMap } from "../utils/positioning";
|
|
4
5
|
import { isDockedMountMode } from "../utils/dock";
|
|
@@ -68,7 +69,7 @@ export const createWrapper = (config?: AgentWidgetConfig): PanelWrapper => {
|
|
|
68
69
|
"persona-widget-panel persona-relative persona-min-h-[320px]"
|
|
69
70
|
);
|
|
70
71
|
const launcherWidth = config?.launcher?.width ?? config?.launcherWidth;
|
|
71
|
-
const width = launcherWidth ??
|
|
72
|
+
const width = launcherWidth ?? DEFAULT_FLOATING_LAUNCHER_WIDTH;
|
|
72
73
|
panel.style.width = width;
|
|
73
74
|
panel.style.maxWidth = width;
|
|
74
75
|
|
package/src/defaults.ts
CHANGED
|
@@ -2,6 +2,16 @@ import type { AgentWidgetConfig } from "./types";
|
|
|
2
2
|
import type { DeepPartial, PersonaTheme } from "./types/theme";
|
|
3
3
|
import { deepMerge } from "./utils/deep-merge";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Default width for the floating launcher panel (when not overridden).
|
|
7
|
+
* Benchmarks: many chat products use ~300–400px; 400px is a frequent “standard” default.
|
|
8
|
+
* We use 440px to better fit code/JSON and structured replies while staying responsive via `min(..., 100vw)`.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_FLOATING_LAUNCHER_WIDTH = "min(440px, calc(100vw - 24px))";
|
|
11
|
+
|
|
12
|
+
/** Max width cap paired with {@link DEFAULT_FLOATING_LAUNCHER_WIDTH} for theme defaults. */
|
|
13
|
+
export const DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH = "440px";
|
|
14
|
+
|
|
5
15
|
/**
|
|
6
16
|
* Default widget configuration
|
|
7
17
|
* Single source of truth for all default values
|
|
@@ -26,7 +36,7 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
|
|
|
26
36
|
agentIconName: "bot",
|
|
27
37
|
headerIconName: "bot",
|
|
28
38
|
position: "bottom-right",
|
|
29
|
-
width:
|
|
39
|
+
width: DEFAULT_FLOATING_LAUNCHER_WIDTH,
|
|
30
40
|
heightOffset: 0,
|
|
31
41
|
autoExpand: false,
|
|
32
42
|
callToActionIconHidden: false,
|
package/src/index.ts
CHANGED
package/src/presets.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentWidgetConfig } from "./types";
|
|
2
2
|
import type { DeepPartial, PersonaTheme } from "./types/theme";
|
|
3
|
+
import { DEFAULT_FLOATING_LAUNCHER_WIDTH } from "./defaults";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* A named preset containing partial widget configuration.
|
|
@@ -65,7 +66,7 @@ export const PRESET_SHOP: WidgetPreset = {
|
|
|
65
66
|
subtitle: "Here to help you find what you need",
|
|
66
67
|
agentIconText: "🛍️",
|
|
67
68
|
position: "bottom-right",
|
|
68
|
-
width:
|
|
69
|
+
width: DEFAULT_FLOATING_LAUNCHER_WIDTH,
|
|
69
70
|
},
|
|
70
71
|
copy: {
|
|
71
72
|
welcomeTitle: "Welcome to our shop!",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/** Declarative section/field definitions for the theme editor (pure data — no DOM, no render logic) */
|
|
2
2
|
|
|
3
3
|
import type { SectionDef, TabDef, SubGroupDef, FieldDef } from './types';
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH,
|
|
6
|
+
DEFAULT_FLOATING_LAUNCHER_WIDTH,
|
|
7
|
+
} from '../defaults';
|
|
4
8
|
import { COLOR_FAMILIES } from './color-utils';
|
|
5
9
|
import {
|
|
6
10
|
ROLE_SURFACES,
|
|
@@ -273,8 +277,8 @@ const panelLayoutSectionDef: SectionDef = {
|
|
|
273
277
|
title: 'Panel',
|
|
274
278
|
collapsed: false,
|
|
275
279
|
fields: [
|
|
276
|
-
{ id: 'panel-width', label: 'Width', type: 'text', path: 'theme.components.panel.width', defaultValue:
|
|
277
|
-
{ id: 'panel-max-width', label: 'Max Width', type: 'text', path: 'theme.components.panel.maxWidth', defaultValue:
|
|
280
|
+
{ id: 'panel-width', label: 'Width', type: 'text', path: 'theme.components.panel.width', defaultValue: DEFAULT_FLOATING_LAUNCHER_WIDTH },
|
|
281
|
+
{ id: 'panel-max-width', label: 'Max Width', type: 'text', path: 'theme.components.panel.maxWidth', defaultValue: DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH },
|
|
278
282
|
{ id: 'panel-height', label: 'Height', type: 'text', path: 'theme.components.panel.height', defaultValue: '600px' },
|
|
279
283
|
{ id: 'panel-max-height', label: 'Max Height', type: 'text', path: 'theme.components.panel.maxHeight', defaultValue: 'calc(100vh - 80px)' },
|
|
280
284
|
{ id: 'panel-border-radius', label: 'Border Radius', type: 'select', path: 'theme.components.panel.borderRadius', defaultValue: 'palette.radius.xl', options: [
|
|
@@ -590,7 +594,7 @@ const launcherBasicsSectionDef: SectionDef = {
|
|
|
590
594
|
{ id: 'launch-enabled', label: 'Enabled', type: 'toggle', path: 'launcher.enabled', defaultValue: true },
|
|
591
595
|
{ id: 'launch-mount-mode', label: 'Mount Mode', type: 'select', path: 'launcher.mountMode', defaultValue: 'floating', options: [{ value: 'floating', label: 'Floating' }, { value: 'docked', label: 'Docked' }] },
|
|
592
596
|
{ id: 'launch-position', label: 'Position', type: 'select', path: 'launcher.position', defaultValue: 'bottom-right', options: [{ value: 'bottom-right', label: 'Bottom Right' }, { value: 'bottom-left', label: 'Bottom Left' }, { value: 'top-right', label: 'Top Right' }, { value: 'top-left', label: 'Top Left' }] },
|
|
593
|
-
{ id: 'launch-width', label: 'Width', type: 'text', path: 'launcher.width', defaultValue:
|
|
597
|
+
{ id: 'launch-width', label: 'Width', type: 'text', path: 'launcher.width', defaultValue: DEFAULT_FLOATING_LAUNCHER_WIDTH },
|
|
594
598
|
{ id: 'launch-auto-expand', label: 'Auto Expand', type: 'toggle', path: 'launcher.autoExpand', defaultValue: false },
|
|
595
599
|
{ id: 'launch-title', label: 'Title', type: 'text', path: 'launcher.title', defaultValue: 'Chat Assistant' },
|
|
596
600
|
{ id: 'launch-subtitle', label: 'Subtitle', type: 'text', path: 'launcher.subtitle', defaultValue: 'Here to help you get answers fast' },
|
package/src/theme-reference.ts
CHANGED
|
@@ -115,7 +115,7 @@ export const THEME_TOKEN_DOCS = {
|
|
|
115
115
|
panel: {
|
|
116
116
|
description: 'Chat panel container.',
|
|
117
117
|
properties:
|
|
118
|
-
'width, maxWidth (
|
|
118
|
+
'width, maxWidth (440px), height (600px), maxHeight, borderRadius, shadow.',
|
|
119
119
|
},
|
|
120
120
|
header: {
|
|
121
121
|
description: 'Chat panel header.',
|
package/src/ui.ts
CHANGED
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
import { readFlexGapPx, resolveArtifactPaneWidthPx } from "./utils/artifact-resize";
|
|
70
70
|
import { enhanceWithForms } from "./components/forms";
|
|
71
71
|
import { pluginRegistry } from "./plugins/registry";
|
|
72
|
-
import { mergeWithDefaults } from "./defaults";
|
|
72
|
+
import { mergeWithDefaults, DEFAULT_FLOATING_LAUNCHER_WIDTH } from "./defaults";
|
|
73
73
|
import { createEventBus } from "./utils/events";
|
|
74
74
|
import {
|
|
75
75
|
createActionManager,
|
|
@@ -1508,7 +1508,7 @@ export const createAgentExperience = (
|
|
|
1508
1508
|
if (mobileFullscreen && ownerWindow.innerWidth <= mobileBreakpoint) return;
|
|
1509
1509
|
if (!shouldExpandLauncherForArtifacts(config, launcherEnabled)) return;
|
|
1510
1510
|
|
|
1511
|
-
const base = config.launcher?.width ?? config.launcherWidth ??
|
|
1511
|
+
const base = config.launcher?.width ?? config.launcherWidth ?? DEFAULT_FLOATING_LAUNCHER_WIDTH;
|
|
1512
1512
|
const expanded =
|
|
1513
1513
|
config.features?.artifacts?.layout?.expandedPanelWidth ??
|
|
1514
1514
|
"min(720px, calc(100vw - 24px))";
|
|
@@ -1659,7 +1659,7 @@ export const createAgentExperience = (
|
|
|
1659
1659
|
|
|
1660
1660
|
// Re-apply panel width/maxWidth from initial setup
|
|
1661
1661
|
const launcherWidth = config?.launcher?.width ?? config?.launcherWidth;
|
|
1662
|
-
const width = launcherWidth ??
|
|
1662
|
+
const width = launcherWidth ?? DEFAULT_FLOATING_LAUNCHER_WIDTH;
|
|
1663
1663
|
if (!sidebarMode && !dockedMode) {
|
|
1664
1664
|
if (isInlineEmbed && fullHeight) {
|
|
1665
1665
|
panel.style.width = "100%";
|
|
@@ -3609,7 +3609,7 @@ export const createAgentExperience = (
|
|
|
3609
3609
|
// In sidebar/fullHeight mode, don't override the width - it's handled by applyFullHeightStyles
|
|
3610
3610
|
if (!sidebarMode && !dockedMode) {
|
|
3611
3611
|
const launcherWidth = config?.launcher?.width ?? config?.launcherWidth;
|
|
3612
|
-
const width = launcherWidth ??
|
|
3612
|
+
const width = launcherWidth ?? DEFAULT_FLOATING_LAUNCHER_WIDTH;
|
|
3613
3613
|
panel.style.width = width;
|
|
3614
3614
|
panel.style.maxWidth = width;
|
|
3615
3615
|
}
|
package/src/utils/tokens.ts
CHANGED
|
@@ -8,6 +8,10 @@ import type {
|
|
|
8
8
|
ComponentTokens,
|
|
9
9
|
SemanticTokens,
|
|
10
10
|
} from '../types/theme';
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH,
|
|
13
|
+
DEFAULT_FLOATING_LAUNCHER_WIDTH,
|
|
14
|
+
} from '../defaults';
|
|
11
15
|
|
|
12
16
|
export const DEFAULT_PALETTE = {
|
|
13
17
|
colors: {
|
|
@@ -277,8 +281,8 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
|
|
|
277
281
|
shadow: 'palette.shadows.lg',
|
|
278
282
|
},
|
|
279
283
|
panel: {
|
|
280
|
-
width:
|
|
281
|
-
maxWidth:
|
|
284
|
+
width: DEFAULT_FLOATING_LAUNCHER_WIDTH,
|
|
285
|
+
maxWidth: DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH,
|
|
282
286
|
height: '600px',
|
|
283
287
|
maxHeight: 'calc(100vh - 80px)',
|
|
284
288
|
borderRadius: 'palette.radius.xl',
|