@runtypelabs/persona 4.23.1 → 4.24.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/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-9Vxm-PPz.d.cts → types-Db61aI9J.d.cts} +25 -2
- package/dist/animations/{types-9Vxm-PPz.d.ts → types-Db61aI9J.d.ts} +25 -2
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/animations-extra.d.cts +25 -2
- package/dist/animations-extra.d.ts +25 -2
- package/dist/approval-ui-3NMYNXBD.js +1 -0
- package/dist/approval-ui.cjs +2 -2
- package/dist/approval-ui.d.cts +261 -40
- package/dist/approval-ui.d.ts +261 -40
- package/dist/approval-ui.js +2 -2
- package/dist/{artifacts-ui-F4N6VMKF.js → artifacts-ui-IGB5YKLO.js} +4 -4
- package/dist/artifacts-ui.d.cts +256 -28
- package/dist/artifacts-ui.d.ts +256 -28
- package/dist/{chunk-NIKYKSNZ.js → chunk-7CO4T5KB.js} +1 -1
- package/dist/codegen.cjs +11 -11
- package/dist/codegen.js +13 -13
- package/dist/context-mentions.d.cts +256 -28
- package/dist/context-mentions.d.ts +256 -28
- package/dist/event-stream-view.d.cts +256 -28
- package/dist/event-stream-view.d.ts +256 -28
- package/dist/forms-ui.d.cts +291 -30
- package/dist/forms-ui.d.ts +291 -30
- package/dist/history-view.d.cts +25 -2
- package/dist/history-view.d.ts +25 -2
- package/dist/index.cjs +70 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +337 -51
- package/dist/index.d.ts +337 -51
- package/dist/index.global.js +57 -54
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +70 -69
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/session-reconnect.d.cts +256 -28
- package/dist/session-reconnect.d.ts +256 -28
- package/dist/smart-dom-reader.d.cts +256 -28
- package/dist/smart-dom-reader.d.ts +256 -28
- package/dist/theme-editor-preview.cjs +74 -73
- package/dist/theme-editor-preview.d.cts +263 -28
- package/dist/theme-editor-preview.d.ts +263 -28
- package/dist/theme-editor-preview.js +51 -50
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +276 -30
- package/dist/theme-editor.d.ts +276 -30
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +1522 -11
- package/dist/theme-reference.d.ts +1522 -11
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +3 -2
- package/src/__fixtures__/defaults-parity.v4.json +776 -0
- package/src/approval-ui-entry.ts +0 -1
- package/src/approval-ui-loader.ts +5 -4
- package/src/approval-ui.ts +0 -1
- package/src/client-identity-proof.test.ts +193 -0
- package/src/client-session-warm.test.ts +216 -0
- package/src/client-visitor-history.test.ts +6 -0
- package/src/client.tool-pair-replay.test.ts +325 -0
- package/src/client.ts +154 -61
- package/src/codegen.test.ts +24 -0
- package/src/components/activity-row.test.ts +100 -0
- package/src/components/activity-row.ts +136 -0
- package/src/components/approval-actions.test.ts +8 -5
- package/src/components/approval-actions.ts +19 -6
- package/src/components/approval-bubble.test.ts +56 -9
- package/src/components/approval-bubble.ts +36 -41
- package/src/components/composer-builder.ts +5 -2
- package/src/components/composer-parts.ts +10 -2
- package/src/components/header-builder.test.ts +32 -0
- package/src/components/header-builder.ts +7 -4
- package/src/components/header-layouts.test.ts +12 -0
- package/src/components/header-layouts.ts +3 -2
- package/src/components/launcher.ts +11 -2
- package/src/components/message-bubble.test.ts +22 -0
- package/src/components/message-bubble.ts +7 -3
- package/src/components/panel.test.ts +52 -1
- package/src/components/panel.ts +47 -8
- package/src/components/pill-composer-builder.test.ts +2 -0
- package/src/components/pill-composer-builder.ts +0 -1
- package/src/components/reasoning-bubble.ts +29 -17
- package/src/components/suggestions.test.ts +1 -0
- package/src/components/suggestions.ts +0 -16
- package/src/components/tool-bubble.ts +131 -114
- package/src/components/tool-details.test.ts +87 -0
- package/src/components/tool-details.ts +102 -0
- package/src/components/welcome.test.ts +8 -0
- package/src/components/welcome.ts +4 -2
- package/src/defaults-parity.test.ts +158 -0
- package/src/defaults.test.ts +69 -0
- package/src/defaults.ts +128 -49
- package/src/generated/runtype-openapi-contract.ts +28 -13
- package/src/index-core.ts +2 -0
- package/src/install.test.ts +24 -0
- package/src/install.ts +3 -1
- package/src/launcher-global.ts +2 -1
- package/src/launcher-variants.test.ts +52 -0
- package/src/runtime/init-update-reset.test.ts +21 -0
- package/src/session.ts +61 -9
- package/src/styles/widget-styles.test.ts +19 -0
- package/src/styles/widget.css +295 -23
- package/src/theme-editor/index.ts +2 -0
- package/src/theme-editor/preview-utils.test.ts +8 -0
- package/src/theme-editor/preview-utils.ts +7 -3
- package/src/theme-editor/sections.test.ts +31 -0
- package/src/theme-editor/sections.ts +55 -1
- package/src/theme-editor/state-preferences.test.ts +49 -0
- package/src/theme-editor/state.ts +73 -15
- package/src/theme-reference.test.ts +20 -0
- package/src/theme-reference.ts +11 -5
- package/src/tool-call-display-defaults.test.ts +33 -1
- package/src/types/theme.ts +80 -3
- package/src/types.ts +179 -23
- package/src/ui.activity-row.test.ts +91 -0
- package/src/ui.approval-plugin.test.ts +63 -0
- package/src/ui.composer-compact.test.ts +24 -2
- package/src/ui.composer-placement.test.ts +23 -0
- package/src/ui.detached-panel.test.ts +33 -6
- package/src/ui.docked.test.ts +19 -0
- package/src/ui.header-update-stability.test.ts +25 -0
- package/src/ui.identity-proof.test.ts +58 -0
- package/src/ui.launcher-teaser.test.ts +25 -0
- package/src/ui.message-width.test.ts +17 -2
- package/src/ui.reasoning-display.test.ts +92 -0
- package/src/ui.rebuild-composer.test.ts +20 -3
- package/src/ui.scroll.test.ts +32 -0
- package/src/ui.session-init.test.ts +312 -0
- package/src/ui.status-indicator.test.ts +82 -0
- package/src/ui.suggest-replies.test.ts +11 -0
- package/src/ui.ts +363 -74
- package/src/ui.v5-defaults.test.ts +144 -0
- package/src/ui.welcome.test.ts +92 -3
- package/src/utils/__snapshots__/tokens-parity.test.ts.snap +811 -140
- package/src/utils/code-generators.ts +14 -0
- package/src/utils/composer-compact.test.ts +0 -2
- package/src/utils/composer-compact.ts +2 -4
- package/src/utils/composer-placement.ts +2 -3
- package/src/utils/config-merge.test.ts +81 -5
- package/src/utils/config-merge.ts +13 -3
- package/src/utils/defaults-provenance.ts +67 -0
- package/src/utils/dock.ts +7 -1
- package/src/utils/launcher-variant.ts +16 -0
- package/src/utils/panel-config.ts +97 -0
- package/src/utils/status-indicator.ts +39 -0
- package/src/utils/theme.test.ts +182 -0
- package/src/utils/theme.ts +131 -18
- package/src/utils/tokens.test.ts +122 -1
- package/src/utils/tokens.ts +177 -17
- package/src/utils/tool-pair-replay.ts +100 -0
- package/src/welcome.test.ts +23 -0
- package/src/welcome.ts +16 -9
- package/dist/approval-ui-K7R5Q5OC.js +0 -1
package/src/ui.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { activityVariant, activityDuration, createActivityLifecycle, createActivityGroup } from "./components/activity-row";
|
|
2
|
+
import { applyStatusIndicatorState, placeStatusIndicator } from "./utils/status-indicator";
|
|
1
3
|
import { usesSessionVoice } from "./utils/voice-support";
|
|
2
4
|
import { escapeHtml, createMarkdownProcessorFromConfig } from "./postprocessors";
|
|
3
5
|
import { resolveSanitizer } from "./utils/sanitize";
|
|
@@ -13,6 +15,7 @@ import {
|
|
|
13
15
|
import {
|
|
14
16
|
AgentWidgetConfig,
|
|
15
17
|
AgentWidgetConfigPatch,
|
|
18
|
+
AgentWidgetSessionInitHook,
|
|
16
19
|
AgentWidgetApprovalDecisionOptions,
|
|
17
20
|
AgentWidgetMessage,
|
|
18
21
|
AgentWidgetEvent,
|
|
@@ -147,8 +150,9 @@ import {
|
|
|
147
150
|
} from "./utils/context-mention-orchestrator";
|
|
148
151
|
import type { MentionSubmitBundle } from "./utils/context-mention-manager";
|
|
149
152
|
import { createTextPart, ALL_SUPPORTED_MIME_TYPES } from "./utils/content";
|
|
150
|
-
import { applyThemeVariables, createThemeObserver, getActiveTheme } from "./utils/theme";
|
|
153
|
+
import { applyThemeVariables, createThemeObserver, getActiveTheme, getColorScheme } from "./utils/theme";
|
|
151
154
|
import { resolveTokenValue } from "./utils/tokens";
|
|
155
|
+
import { deepMerge } from "./utils/deep-merge";
|
|
152
156
|
import { Activity, Check, Copy } from "lucide";
|
|
153
157
|
import { renderLucideIcon, onExtraIconsReady } from "./utils/icons";
|
|
154
158
|
import { renderIconNode } from "./utils/icon-node";
|
|
@@ -222,6 +226,7 @@ import {
|
|
|
222
226
|
attachHeaderToContainer,
|
|
223
227
|
COMPOSER_BAR_CLEAR_CHAT_ICON_SIZE,
|
|
224
228
|
COMPOSER_BAR_CLOSE_ICON_SIZE,
|
|
229
|
+
resolvePanelGeometry,
|
|
225
230
|
} from "./components/panel";
|
|
226
231
|
import { buildPillComposer } from "./components/pill-composer-builder";
|
|
227
232
|
import {
|
|
@@ -263,8 +268,9 @@ import {
|
|
|
263
268
|
getBubbleClasses,
|
|
264
269
|
CUSTOM_MESSAGE_ACTION_PREFIX,
|
|
265
270
|
} from "./components/message-bubble";
|
|
266
|
-
import { createReasoningBubble,
|
|
267
|
-
import {
|
|
271
|
+
import { createReasoningBubble, updateReasoningBubbleUI } from "./components/reasoning-bubble";
|
|
272
|
+
import { copyToolDetail } from "./components/tool-details";
|
|
273
|
+
import { createToolBubble, updateToolBubbleUI } from "./components/tool-bubble";
|
|
268
274
|
import {
|
|
269
275
|
buildStructuredAnswers,
|
|
270
276
|
ensureAskUserQuestionSheet,
|
|
@@ -324,8 +330,9 @@ import {
|
|
|
324
330
|
} from "./utils/artifact-resize";
|
|
325
331
|
import { loadFormsUi, getFormsUiSync } from "./forms-ui-loader";
|
|
326
332
|
import { pluginRegistry } from "./plugins/registry";
|
|
327
|
-
import {
|
|
333
|
+
import { DEFAULT_HEADER_ICON_SIZE, mergeWithDefaults } from "./defaults";
|
|
328
334
|
import { mergeConfigUpdate } from "./utils/config-merge";
|
|
335
|
+
import { getPanelAliasProvenance } from "./utils/panel-config";
|
|
329
336
|
import { createEventBus } from "./utils/events";
|
|
330
337
|
import {
|
|
331
338
|
createActionManager,
|
|
@@ -701,6 +708,13 @@ type Controller = {
|
|
|
701
708
|
) => Promise<void>;
|
|
702
709
|
/** Clean local state plus a new owned server conversation. `focus` as on `openConversation`. */
|
|
703
710
|
startNewConversation: (opts?: { focus?: boolean }) => Promise<void>;
|
|
711
|
+
/**
|
|
712
|
+
* Client token mode: start `/v1/client/init` now, ahead of the first send
|
|
713
|
+
* (e.g. when the visitor hovers your own "Chat with us" button).
|
|
714
|
+
* Fire-and-forget, at most once per session lifetime, errors swallowed; the
|
|
715
|
+
* send reuses the in-flight init. No-op in other modes. See `sessionInit`.
|
|
716
|
+
*/
|
|
717
|
+
warmSession: () => void;
|
|
704
718
|
/** Permanently delete one server record; the active one is replaced. */
|
|
705
719
|
deleteConversation: (conversationId: string) => Promise<void>;
|
|
706
720
|
/**
|
|
@@ -930,6 +944,18 @@ export const createAgentExperience = (
|
|
|
930
944
|
}
|
|
931
945
|
|
|
932
946
|
let config = mergeWithDefaults(initialConfig) as AgentWidgetConfig;
|
|
947
|
+
// Message ids are only unique within a widget session. Keep disclosure state
|
|
948
|
+
// beside the widget that renders it, not in component module scope.
|
|
949
|
+
const toolExpansionState = new Set<string>();
|
|
950
|
+
const reasoningExpansionState = new Set<string>();
|
|
951
|
+
const activityLifecycle = createActivityLifecycle((id, kind) => {
|
|
952
|
+
messageCache.delete(id);
|
|
953
|
+
const bubble = Array.from(messagesWrapper.querySelectorAll<HTMLElement>("[data-message-id]")).find(node => node.dataset.messageId === id);
|
|
954
|
+
if (!bubble) return;
|
|
955
|
+
if (kind === "tool") updateToolBubbleUI(id, bubble, toolExpansionState, config);
|
|
956
|
+
else updateReasoningBubbleUI(id, bubble, reasoningExpansionState);
|
|
957
|
+
});
|
|
958
|
+
const approvalDetailsExpansionState = new Map<string, boolean>();
|
|
933
959
|
const applyTooltipTiming = (): void => {
|
|
934
960
|
configureTooltipTiming({
|
|
935
961
|
delayMs: config.tooltip?.delayMs ?? DEFAULT_TOOLTIP_DELAY_MS,
|
|
@@ -983,7 +1009,7 @@ export const createAgentExperience = (
|
|
|
983
1009
|
mod.initApprovalUi({
|
|
984
1010
|
webMcpToolTitle: getWebMcpToolDisplayTitle,
|
|
985
1011
|
});
|
|
986
|
-
const built = mod.createBuiltInApprovalPlugin();
|
|
1012
|
+
const built = mod.createBuiltInApprovalPlugin(approvalDetailsExpansionState);
|
|
987
1013
|
builtInApprovalPlugin = built.plugin;
|
|
988
1014
|
teardownBuiltInApprovals = built.teardown;
|
|
989
1015
|
};
|
|
@@ -1361,7 +1387,7 @@ export const createAgentExperience = (
|
|
|
1361
1387
|
|
|
1362
1388
|
// Get status indicator config
|
|
1363
1389
|
const statusConfig = config.statusIndicator ?? {};
|
|
1364
|
-
const _getStatusText = (status: AgentWidgetSessionStatus): string => {
|
|
1390
|
+
const _getStatusText = (status: AgentWidgetSessionStatus, statusConfig = config.statusIndicator ?? {}): string => {
|
|
1365
1391
|
if (status === "idle") return statusConfig.idleText ?? statusCopy.idle;
|
|
1366
1392
|
if (status === "connecting") return statusConfig.connectingText ?? statusCopy.connecting;
|
|
1367
1393
|
if (status === "connected") return statusConfig.connectedText ?? statusCopy.connected;
|
|
@@ -1373,6 +1399,10 @@ export const createAgentExperience = (
|
|
|
1373
1399
|
|
|
1374
1400
|
/** Update statusText element, rendering a link for idle status when idleLink is configured. */
|
|
1375
1401
|
function applyStatusToElement(el: HTMLElement, text: string, statusCfg: typeof statusConfig, status: string): void {
|
|
1402
|
+
if (!isComposerBar() && (statusCfg.mode === "transient" || el.classList.contains("persona-mb-2"))) {
|
|
1403
|
+
placeStatusIndicator(el, composerForm, statusCfg);
|
|
1404
|
+
applyStatusIndicatorState(el, statusCfg, status);
|
|
1405
|
+
}
|
|
1376
1406
|
// A composer lock reason owns the status region until the lock clears.
|
|
1377
1407
|
if (el.hasAttribute(COMPOSER_REASON_ATTR)) return;
|
|
1378
1408
|
if (status === "idle" && statusCfg.idleLink) {
|
|
@@ -1544,6 +1574,16 @@ export const createAgentExperience = (
|
|
|
1544
1574
|
}, 400);
|
|
1545
1575
|
};
|
|
1546
1576
|
|
|
1577
|
+
const isWelcomeFullscreen = () => {
|
|
1578
|
+
const win = mount.ownerDocument.defaultView ?? window;
|
|
1579
|
+
const mobile = (config.launcher?.mobileFullscreen ?? true) &&
|
|
1580
|
+
win.innerWidth <= (config.launcher?.mobileBreakpoint ?? 640) &&
|
|
1581
|
+
(launcherEnabled || isDockedMountMode(config));
|
|
1582
|
+
return mobile || (config.launcher?.fullHeight === true &&
|
|
1583
|
+
!config.launcher.sidebarMode && !isDockedMountMode(config));
|
|
1584
|
+
};
|
|
1585
|
+
const getWelcomeConfig = () => resolveWelcomeConfig(config, isWelcomeFullscreen());
|
|
1586
|
+
|
|
1547
1587
|
// Derived from the session's user messages on every welcome render; the
|
|
1548
1588
|
// public `data-persona-conversation-state` contract mirrors it.
|
|
1549
1589
|
let conversationState: "empty" | "active" = "empty";
|
|
@@ -1578,7 +1618,11 @@ export const createAgentExperience = (
|
|
|
1578
1618
|
// updateScrollToBottomButtonOffset reads them, never writes.
|
|
1579
1619
|
const syncComposerOverlayMetrics = () => {
|
|
1580
1620
|
const placement = resolveComposerPlacement(config, isComposerBar());
|
|
1581
|
-
const resolved =
|
|
1621
|
+
const resolved = getWelcomeConfig();
|
|
1622
|
+
mount.setAttribute("data-persona-welcome-fullscreen", String(isWelcomeFullscreen()));
|
|
1623
|
+
mount.setAttribute("data-persona-welcome-layout", resolved.layout ?? "top");
|
|
1624
|
+
mount.setAttribute("data-persona-welcome-layout-explicit", config.welcome?.layout ?? "");
|
|
1625
|
+
mount.setAttribute("data-persona-welcome-anchor", resolved.anchor ?? "bottom");
|
|
1582
1626
|
const footerHidden = footer.style.display === "none";
|
|
1583
1627
|
const footerHeight = footerHidden ? 0 : footer.offsetHeight;
|
|
1584
1628
|
|
|
@@ -1587,10 +1631,25 @@ export const createAgentExperience = (
|
|
|
1587
1631
|
? parseAnchorFraction(resolved.anchorComposerTop) ??
|
|
1588
1632
|
parseAnchorFraction(DEFAULT_ANCHOR_COMPOSER_TOP)!
|
|
1589
1633
|
: null;
|
|
1634
|
+
// The implicit fullscreen layout centers the complete greeting/composer
|
|
1635
|
+
// group. Explicit anchoring retains its percentage-based geometry.
|
|
1636
|
+
const centerGroup = fraction !== null && resolved.layout === "centered" &&
|
|
1637
|
+
config.welcome?.anchor === undefined &&
|
|
1638
|
+
config.welcome?.anchorComposerTop === undefined;
|
|
1639
|
+
const measuredBodyStyle = centerGroup ? getComputedStyle(body) : null;
|
|
1640
|
+
const previousOverlayHeight = parseFloat(mount.style.getPropertyValue("--persona-composer-overlay-height")) || 0;
|
|
1641
|
+
const previousOverlayLift = parseFloat(mount.style.getPropertyValue("--persona-composer-lift")) || 0;
|
|
1642
|
+
const groupGap = Math.max(0, (parseFloat(measuredBodyStyle?.paddingBottom ?? "") || 0) -
|
|
1643
|
+
(placement === "overlay" ? previousOverlayHeight + previousOverlayLift : 0));
|
|
1644
|
+
const columnTop = centerGroup ? body.getBoundingClientRect().top - container.getBoundingClientRect().top : 0;
|
|
1645
|
+
const greetingHeight = !centerGroup || welcomeHost.hidden ? 0 :
|
|
1646
|
+
(welcomeHost.querySelector<HTMLElement>("[data-persona-welcome-plugin]") ?? welcomeHost).offsetHeight;
|
|
1590
1647
|
const lift =
|
|
1591
1648
|
fraction === null || isComposerBar()
|
|
1592
1649
|
? 0
|
|
1593
|
-
:
|
|
1650
|
+
: centerGroup
|
|
1651
|
+
? Math.max(0, Math.round((container.clientHeight - columnTop - greetingHeight - groupGap - footerHeight) / 2))
|
|
1652
|
+
: computeComposerLift({
|
|
1594
1653
|
columnHeight: container.clientHeight,
|
|
1595
1654
|
footerHeight,
|
|
1596
1655
|
fraction,
|
|
@@ -2506,9 +2565,6 @@ export const createAgentExperience = (
|
|
|
2506
2565
|
pruneComposerModes(state.activeModeIds, composerChipModes()).length > 0
|
|
2507
2566
|
);
|
|
2508
2567
|
};
|
|
2509
|
-
// Bound once `voiceState` exists (further down this closure); the compact
|
|
2510
|
-
// sweep can run before then only via a microtask, which is later still.
|
|
2511
|
-
let isDictationActive: () => boolean = () => false;
|
|
2512
2568
|
function syncComposerCompact(): void {
|
|
2513
2569
|
const state = composerStore.getState();
|
|
2514
2570
|
const wrapped = composerCompactLatch.observe(
|
|
@@ -2524,7 +2580,6 @@ export const createAgentExperience = (
|
|
|
2524
2580
|
hasChips: hasComposerChips(),
|
|
2525
2581
|
hasQuote: state.quote !== undefined,
|
|
2526
2582
|
hasPendingSubmission: state.pendingSubmission !== undefined,
|
|
2527
|
-
dictationActive: isDictationActive(),
|
|
2528
2583
|
})
|
|
2529
2584
|
);
|
|
2530
2585
|
}
|
|
@@ -2667,6 +2722,16 @@ export const createAgentExperience = (
|
|
|
2667
2722
|
// Render custom slots
|
|
2668
2723
|
renderSlots();
|
|
2669
2724
|
|
|
2725
|
+
messagesWrapper.addEventListener("click", (event) => {
|
|
2726
|
+
const target = event.target;
|
|
2727
|
+
if (!(target instanceof Element)) return;
|
|
2728
|
+
const button = target.closest<HTMLButtonElement>("button[data-persona-copy-tool-detail]");
|
|
2729
|
+
if (!button) return;
|
|
2730
|
+
event.preventDefault();
|
|
2731
|
+
event.stopPropagation();
|
|
2732
|
+
void copyToolDetail(button);
|
|
2733
|
+
});
|
|
2734
|
+
|
|
2670
2735
|
// Add event delegation for reasoning and tool bubble expansion
|
|
2671
2736
|
// This handles clicks even after idiomorph morphs the DOM
|
|
2672
2737
|
const handleBubbleExpansion = (event: Event) => {
|
|
@@ -2684,7 +2749,17 @@ export const createAgentExperience = (
|
|
|
2684
2749
|
const messageId = bubble.getAttribute('data-message-id');
|
|
2685
2750
|
if (!messageId) return;
|
|
2686
2751
|
|
|
2752
|
+
// Disclosure is reading intent, not new streamed content. Pause before the
|
|
2753
|
+
// DOM grows so the resize observer cannot pull the opened details away.
|
|
2754
|
+
markReaderEngaged();
|
|
2755
|
+
pauseAutoScroll();
|
|
2687
2756
|
const bubbleType = headerButton.getAttribute('data-bubble-type');
|
|
2757
|
+
if (bubble.classList.contains("persona-activity-row")) {
|
|
2758
|
+
activityLifecycle.manual(messageId);
|
|
2759
|
+
const group = bubble.parentElement?.closest<HTMLElement>("[data-persona-tool-group]");
|
|
2760
|
+
if (group?.dataset.messageId) activityLifecycle.manual(group.dataset.messageId);
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2688
2763
|
|
|
2689
2764
|
// Toggle expansion state
|
|
2690
2765
|
if (bubbleType === 'reasoning') {
|
|
@@ -2693,22 +2768,22 @@ export const createAgentExperience = (
|
|
|
2693
2768
|
} else {
|
|
2694
2769
|
reasoningExpansionState.add(messageId);
|
|
2695
2770
|
}
|
|
2696
|
-
updateReasoningBubbleUI(messageId, bubble);
|
|
2771
|
+
updateReasoningBubbleUI(messageId, bubble, reasoningExpansionState);
|
|
2697
2772
|
} else if (bubbleType === 'tool') {
|
|
2698
2773
|
if (toolExpansionState.has(messageId)) {
|
|
2699
2774
|
toolExpansionState.delete(messageId);
|
|
2700
2775
|
} else {
|
|
2701
2776
|
toolExpansionState.add(messageId);
|
|
2702
2777
|
}
|
|
2703
|
-
updateToolBubbleUI(messageId, bubble, config);
|
|
2778
|
+
updateToolBubbleUI(messageId, bubble, toolExpansionState, config);
|
|
2704
2779
|
} else if (bubbleType === 'approval' && approvalUi) {
|
|
2705
2780
|
// approvalUi is always set here: approval bubbles only exist after the
|
|
2706
2781
|
// chunk was adopted. The guard keeps a stray click on a stub harmless.
|
|
2707
2782
|
const approvalConfig = config.approval !== false ? config.approval : undefined;
|
|
2708
2783
|
const defaultExpanded = (approvalConfig?.detailsDisplay ?? 'collapsed') === 'expanded';
|
|
2709
|
-
const expanded =
|
|
2710
|
-
|
|
2711
|
-
approvalUi.updateApprovalDetailsUI(messageId, bubble, config);
|
|
2784
|
+
const expanded = approvalDetailsExpansionState.get(messageId) ?? defaultExpanded;
|
|
2785
|
+
approvalDetailsExpansionState.set(messageId, !expanded);
|
|
2786
|
+
approvalUi.updateApprovalDetailsUI(messageId, bubble, config, approvalDetailsExpansionState);
|
|
2712
2787
|
}
|
|
2713
2788
|
// Invalidate cached wrapper so next render rebuilds with current expansion state
|
|
2714
2789
|
messageCache.delete(messageId);
|
|
@@ -2723,6 +2798,11 @@ export const createAgentExperience = (
|
|
|
2723
2798
|
}
|
|
2724
2799
|
});
|
|
2725
2800
|
|
|
2801
|
+
messagesWrapper.addEventListener('click', (event) => {
|
|
2802
|
+
// Assistive technologies activate buttons with a click rather than a pointer.
|
|
2803
|
+
if (event.detail === 0 && (event.target as HTMLElement).closest('.persona-activity-row')) handleBubbleExpansion(event);
|
|
2804
|
+
});
|
|
2805
|
+
|
|
2726
2806
|
messagesWrapper.addEventListener('keydown', (event) => {
|
|
2727
2807
|
const target = event.target as HTMLElement;
|
|
2728
2808
|
if ((event.key === 'Enter' || event.key === ' ') && target.closest('button[data-expand-header="true"]')) {
|
|
@@ -3984,7 +4064,7 @@ export const createAgentExperience = (
|
|
|
3984
4064
|
if (mobileFullscreen && ownerWindow.innerWidth <= mobileBreakpoint) return;
|
|
3985
4065
|
if (!shouldExpandLauncherForArtifacts(config, launcherEnabled)) return;
|
|
3986
4066
|
|
|
3987
|
-
const base = config.
|
|
4067
|
+
const base = resolvePanelGeometry(config).width;
|
|
3988
4068
|
const expanded =
|
|
3989
4069
|
config.features?.artifacts?.layout?.expandedPanelWidth ??
|
|
3990
4070
|
"min(720px, calc(100vw - 24px))";
|
|
@@ -4035,6 +4115,13 @@ export const createAgentExperience = (
|
|
|
4035
4115
|
// Apply full-height and sidebar styles if enabled
|
|
4036
4116
|
// This ensures the widget fills its container height with proper flex layout
|
|
4037
4117
|
const applyFullHeightStyles = () => {
|
|
4118
|
+
const fullScreenTranscript = isMobileFullscreenActive() ||
|
|
4119
|
+
(isComposerBar() && config.launcher?.composerBar?.expandedSize === "fullscreen") ||
|
|
4120
|
+
(config.launcher?.fullHeight === true && !config.launcher?.sidebarMode && !isDockedMountMode(config));
|
|
4121
|
+
messagesWrapper.style.gap = fullScreenTranscript
|
|
4122
|
+
? "var(--persona-components-message-fullscreenGap, var(--persona-components-message-gap, 12px))"
|
|
4123
|
+
: "var(--persona-components-message-gap, 12px)";
|
|
4124
|
+
messagesWrapper.style.setProperty("--persona-transcript-gap", messagesWrapper.style.gap);
|
|
4038
4125
|
// Composer-bar mode owns its own sizing/chrome. Geometry comes from
|
|
4039
4126
|
// `applyComposerBarGeometry()` (per-state inline on the wrapper), the
|
|
4040
4127
|
// pill carries its own chrome via `.persona-pill-composer`, and the
|
|
@@ -4083,7 +4170,14 @@ export const createAgentExperience = (
|
|
|
4083
4170
|
const isInlineEmbed = config.launcher?.enabled === false;
|
|
4084
4171
|
/** Detached appearance: inset card over a canvas instead of flush chrome. */
|
|
4085
4172
|
const isDetached = config.launcher?.detachedPanel === true;
|
|
4086
|
-
const
|
|
4173
|
+
const lightPanelPartial = config.theme?.components?.panel;
|
|
4174
|
+
const darkPanelPartial = config.darkTheme?.components?.panel;
|
|
4175
|
+
const panelPartial: typeof lightPanelPartial = getColorScheme(config) === "dark"
|
|
4176
|
+
? deepMerge(
|
|
4177
|
+
(lightPanelPartial ?? {}) as Record<string, unknown>,
|
|
4178
|
+
(darkPanelPartial ?? {}) as Record<string, unknown>
|
|
4179
|
+
) as typeof lightPanelPartial
|
|
4180
|
+
: lightPanelPartial;
|
|
4087
4181
|
const activeTheme = getActiveTheme(config);
|
|
4088
4182
|
const resolvePanelChrome = (raw: string | undefined, fallback: string): string => {
|
|
4089
4183
|
if (raw == null || raw === "") return fallback;
|
|
@@ -4110,11 +4204,38 @@ export const createAgentExperience = (
|
|
|
4110
4204
|
// Card chrome defaults (floating look): reused to restore detached chrome.
|
|
4111
4205
|
// Defaults chain through the aliases themeToCssVariables emits so explicit
|
|
4112
4206
|
// theme.components.panel overrides and these defaults never diverge.
|
|
4113
|
-
const
|
|
4114
|
-
|
|
4115
|
-
|
|
4207
|
+
const modeChrome = (
|
|
4208
|
+
mode: "floating" | "inline" | "sidebar" | "docked" | "mobile",
|
|
4209
|
+
field: "border" | "shadow" | "borderRadius",
|
|
4210
|
+
fallback: string
|
|
4211
|
+
): string => `var(--persona-components-panel-modes-${mode}-${field}, ${fallback})`;
|
|
4212
|
+
const configuredFloatingPanel = panelPartial?.modes?.floating;
|
|
4213
|
+
const cardBorder = resolvePanelChrome(
|
|
4214
|
+
configuredFloatingPanel?.border ?? panelPartial?.border,
|
|
4215
|
+
modeChrome("floating", "border", 'var(--persona-panel-border, 1px solid var(--persona-border))')
|
|
4216
|
+
);
|
|
4217
|
+
const cardShadow = resolvePanelChrome(
|
|
4218
|
+
configuredFloatingPanel?.shadow ?? panelPartial?.shadow,
|
|
4219
|
+
modeChrome("floating", "shadow", 'var(--persona-panel-shadow, var(--persona-palette-shadows-xl, 0 25px 50px -12px rgba(0, 0, 0, 0.25)))')
|
|
4220
|
+
);
|
|
4221
|
+
const cardRadius = resolvePanelChrome(
|
|
4222
|
+
configuredFloatingPanel?.borderRadius ?? panelPartial?.borderRadius,
|
|
4223
|
+
modeChrome("floating", "borderRadius", 'var(--persona-panel-radius, var(--persona-radius-xl, 0.75rem))')
|
|
4224
|
+
);
|
|
4116
4225
|
/** Detached restores card chrome except when a host layout goes fullscreen. */
|
|
4117
4226
|
const detachedCard = isDetached && !shouldGoFullscreen && !dockedHostFullscreen;
|
|
4227
|
+
const panelMode = shouldGoFullscreen || dockedHostFullscreen
|
|
4228
|
+
? "mobile"
|
|
4229
|
+
: detachedCard
|
|
4230
|
+
? "floating"
|
|
4231
|
+
: dockedMode
|
|
4232
|
+
? "docked"
|
|
4233
|
+
: sidebarMode
|
|
4234
|
+
? "sidebar"
|
|
4235
|
+
: isInlineEmbed
|
|
4236
|
+
? "inline"
|
|
4237
|
+
: "floating";
|
|
4238
|
+
const configuredModePanel = panelPartial?.modes?.[panelMode];
|
|
4118
4239
|
// Stamp reflects rendered chrome: cleared when a fullscreen host layout
|
|
4119
4240
|
// suppresses the card, so the attribute never lies to the detached CSS.
|
|
4120
4241
|
if (detachedCard) {
|
|
@@ -4132,7 +4253,7 @@ export const createAgentExperience = (
|
|
|
4132
4253
|
: shouldGoFullscreen
|
|
4133
4254
|
? 'none'
|
|
4134
4255
|
: sidebarMode
|
|
4135
|
-
? (isLeftSidebar ? '
|
|
4256
|
+
? (isLeftSidebar ? '2px 0 12px rgba(0, 0, 0, 0.08)' : '-2px 0 12px rgba(0, 0, 0, 0.08)')
|
|
4136
4257
|
// Flush inline embeds fill their container: no elevation by default
|
|
4137
4258
|
// (detachedPanel or components.panel.shadow opts back in).
|
|
4138
4259
|
: isInlineEmbed ? 'none' : cardShadow;
|
|
@@ -4146,9 +4267,18 @@ export const createAgentExperience = (
|
|
|
4146
4267
|
: (sidebarMode || shouldGoFullscreen) ? '0' : cardRadius;
|
|
4147
4268
|
|
|
4148
4269
|
// Apply theme overrides or defaults (components.panel.*)
|
|
4149
|
-
const panelBorder = resolvePanelChrome(
|
|
4150
|
-
|
|
4151
|
-
|
|
4270
|
+
const panelBorder = resolvePanelChrome(
|
|
4271
|
+
configuredModePanel?.border ?? panelPartial?.border,
|
|
4272
|
+
modeChrome(panelMode, "border", defaultPanelBorder)
|
|
4273
|
+
);
|
|
4274
|
+
const panelShadow = resolvePanelChrome(
|
|
4275
|
+
configuredModePanel?.shadow ?? panelPartial?.shadow,
|
|
4276
|
+
modeChrome(panelMode, "shadow", defaultPanelShadow)
|
|
4277
|
+
);
|
|
4278
|
+
const panelBorderRadius = resolvePanelChrome(
|
|
4279
|
+
configuredModePanel?.borderRadius ?? panelPartial?.borderRadius,
|
|
4280
|
+
modeChrome(panelMode, "borderRadius", defaultPanelBorderRadius)
|
|
4281
|
+
);
|
|
4152
4282
|
|
|
4153
4283
|
// Split chrome: 'welded' folds the card border onto the outer panel so it
|
|
4154
4284
|
// wraps both columns as one card (shadow/radius already on the panel);
|
|
@@ -4192,7 +4322,8 @@ export const createAgentExperience = (
|
|
|
4192
4322
|
// Flush fills the container flush, so the outer panel squares off by default;
|
|
4193
4323
|
// the pane keeps its own rounded radius. An explicit panel.borderRadius wins.
|
|
4194
4324
|
const panelRadiusExplicit =
|
|
4195
|
-
|
|
4325
|
+
(configuredModePanel?.borderRadius != null && configuredModePanel.borderRadius !== '') ||
|
|
4326
|
+
(panelPartial?.borderRadius != null && panelPartial.borderRadius !== '');
|
|
4196
4327
|
const appliedPanelRadius =
|
|
4197
4328
|
chatFlush && !panelRadiusExplicit ? '0' : panelBorderRadius;
|
|
4198
4329
|
|
|
@@ -4317,15 +4448,14 @@ export const createAgentExperience = (
|
|
|
4317
4448
|
}
|
|
4318
4449
|
|
|
4319
4450
|
// Re-apply panel width/maxWidth from initial setup
|
|
4320
|
-
const
|
|
4321
|
-
const width = launcherWidth ?? DEFAULT_FLOATING_LAUNCHER_WIDTH;
|
|
4451
|
+
const geometry = resolvePanelGeometry(config);
|
|
4322
4452
|
if (!sidebarMode && !dockedMode) {
|
|
4323
4453
|
if (isInlineEmbed && fullHeight) {
|
|
4324
4454
|
panel.style.width = "100%";
|
|
4325
4455
|
panel.style.maxWidth = "100%";
|
|
4326
4456
|
} else {
|
|
4327
|
-
panel.style.width = width;
|
|
4328
|
-
panel.style.maxWidth =
|
|
4457
|
+
panel.style.width = geometry.width;
|
|
4458
|
+
panel.style.maxWidth = geometry.maxWidth;
|
|
4329
4459
|
}
|
|
4330
4460
|
} else if (dockedMode) {
|
|
4331
4461
|
const dockReveal = resolveDockConfig(config).reveal;
|
|
@@ -4375,7 +4505,9 @@ export const createAgentExperience = (
|
|
|
4375
4505
|
weldedOuterRadius = null;
|
|
4376
4506
|
}
|
|
4377
4507
|
|
|
4378
|
-
|
|
4508
|
+
const panelBorderExplicit =
|
|
4509
|
+
configuredModePanel?.border != null || panelPartial?.border != null;
|
|
4510
|
+
if (dockedMode && !shouldGoFullscreen && !detachedCard && !detachedSplitActive && !weldedSplitActive && !panelBorderExplicit) {
|
|
4379
4511
|
container.style.border = 'none';
|
|
4380
4512
|
const dockSide = resolveDockConfig(config).side;
|
|
4381
4513
|
if (dockSide === 'right') {
|
|
@@ -4389,7 +4521,7 @@ export const createAgentExperience = (
|
|
|
4389
4521
|
// mode resolves its border to none, so add the dock-facing hairline there so
|
|
4390
4522
|
// the split still separates from the host page. Mirrors the flush block's
|
|
4391
4523
|
// side choice (right dock => left edge faces the page).
|
|
4392
|
-
if (dockedMode && !shouldGoFullscreen && weldedSplitActive &&
|
|
4524
|
+
if (dockedMode && !shouldGoFullscreen && weldedSplitActive && !panelBorderExplicit) {
|
|
4393
4525
|
const dockSide = resolveDockConfig(config).side;
|
|
4394
4526
|
if (dockSide === 'right') {
|
|
4395
4527
|
panel.style.borderLeft = '1px solid var(--persona-border)';
|
|
@@ -4478,7 +4610,9 @@ export const createAgentExperience = (
|
|
|
4478
4610
|
|
|
4479
4611
|
// Apply sidebar-specific styles
|
|
4480
4612
|
if (sidebarMode) {
|
|
4481
|
-
const sidebarWidth = config.
|
|
4613
|
+
const sidebarWidth = getPanelAliasProvenance(config).sidebarWidth
|
|
4614
|
+
? config.launcher?.sidebarWidth ?? "420px"
|
|
4615
|
+
: resolveTokenValue(getActiveTheme(config), "components.panel.width") ?? "420px";
|
|
4482
4616
|
|
|
4483
4617
|
// Wrapper - fixed position. Detached insets the card off the edges and
|
|
4484
4618
|
// shrinks its height by the inset on both ends; flush hugs the edges.
|
|
@@ -4592,7 +4726,12 @@ export const createAgentExperience = (
|
|
|
4592
4726
|
let warnedComposerBarPlacement = false;
|
|
4593
4727
|
// mount.style.cssText is wiped by applyFullHeightStyles, so every
|
|
4594
4728
|
// mount-level var re-stamps here, next to applyContentMaxWidthVar.
|
|
4729
|
+
function syncTranscriptTopFade() {
|
|
4730
|
+
if (config.layout?.topFade && body.scrollTop > 1) body.setAttribute("data-persona-top-fade", "true");
|
|
4731
|
+
else body.removeAttribute("data-persona-top-fade");
|
|
4732
|
+
}
|
|
4595
4733
|
const applyComposerPlacement = () => {
|
|
4734
|
+
syncTranscriptTopFade();
|
|
4596
4735
|
const placement = resolveComposerPlacement(config, isComposerBar());
|
|
4597
4736
|
if (
|
|
4598
4737
|
isComposerBar() &&
|
|
@@ -4605,7 +4744,7 @@ export const createAgentExperience = (
|
|
|
4605
4744
|
"[persona] composer.placement is ignored in composer-bar mount mode."
|
|
4606
4745
|
);
|
|
4607
4746
|
}
|
|
4608
|
-
const welcome =
|
|
4747
|
+
const welcome = getWelcomeConfig();
|
|
4609
4748
|
mount.setAttribute("data-persona-composer-placement", placement);
|
|
4610
4749
|
mount.setAttribute("data-persona-conversation-state", conversationState);
|
|
4611
4750
|
// Root-level mirror of the welcome host's own anchor attribute, so the
|
|
@@ -4632,12 +4771,9 @@ export const createAgentExperience = (
|
|
|
4632
4771
|
const mobileFullscreen = config.launcher?.mobileFullscreen ?? true;
|
|
4633
4772
|
const mobileBreakpoint = config.launcher?.mobileBreakpoint ?? 640;
|
|
4634
4773
|
if (mobileFullscreen && ownerWindow.innerWidth <= mobileBreakpoint) return;
|
|
4635
|
-
const
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
const available = Math.max(200, viewportHeight - verticalMargin);
|
|
4639
|
-
const clamped = Math.min(640, available);
|
|
4640
|
-
panel.style.height = `${Math.max(200, clamped - heightOffset)}px`;
|
|
4774
|
+
const geometry = resolvePanelGeometry(config);
|
|
4775
|
+
panel.style.height = geometry.height;
|
|
4776
|
+
panel.style.maxHeight = geometry.maxHeight;
|
|
4641
4777
|
};
|
|
4642
4778
|
applyFullHeightStyles();
|
|
4643
4779
|
// Apply theme variables after applyFullHeightStyles since it resets mount.style.cssText
|
|
@@ -4981,12 +5117,14 @@ export const createAgentExperience = (
|
|
|
4981
5117
|
return;
|
|
4982
5118
|
}
|
|
4983
5119
|
|
|
4984
|
-
const
|
|
5120
|
+
const v5 = config.future?.v5Defaults === true;
|
|
5121
|
+
const fullscreen = isWelcomeFullscreen();
|
|
5122
|
+
const starters = config.suggestions?.starters ?? (v5 ? {} : undefined);
|
|
4985
5123
|
if (starters) {
|
|
4986
5124
|
// The starter host lives inside the welcome surface, so a pinned
|
|
4987
5125
|
// "welcome" placement has nowhere to render when that surface is hidden.
|
|
4988
5126
|
const welcomeCardVisible = isWelcomeVisible(
|
|
4989
|
-
|
|
5127
|
+
getWelcomeConfig(),
|
|
4990
5128
|
current
|
|
4991
5129
|
);
|
|
4992
5130
|
const requestedPlacement = starters.placement ?? "auto";
|
|
@@ -5014,10 +5152,10 @@ export const createAgentExperience = (
|
|
|
5014
5152
|
config.suggestionChipsConfig,
|
|
5015
5153
|
{
|
|
5016
5154
|
surface: "starter",
|
|
5017
|
-
variant: starters.variant ?? "card",
|
|
5155
|
+
variant: starters.variant ?? (v5 && !fullscreen ? "chip" : "card"),
|
|
5018
5156
|
behavior: starters.behavior ?? "send",
|
|
5019
5157
|
overflow: starters.overflow ?? "wrap",
|
|
5020
|
-
maxItems: starters.maxItems ?? 4,
|
|
5158
|
+
maxItems: starters.maxItems ?? (v5 && !fullscreen ? 3 : 4),
|
|
5021
5159
|
config,
|
|
5022
5160
|
plugins,
|
|
5023
5161
|
submitPrompt: submitSuggestionPrompt,
|
|
@@ -5038,6 +5176,7 @@ export const createAgentExperience = (
|
|
|
5038
5176
|
variant: "chip",
|
|
5039
5177
|
behavior: "send",
|
|
5040
5178
|
overflow: "wrap",
|
|
5179
|
+
maxItems: 4,
|
|
5041
5180
|
config,
|
|
5042
5181
|
plugins,
|
|
5043
5182
|
submitPrompt: submitSuggestionPrompt,
|
|
@@ -5220,7 +5359,6 @@ export const createAgentExperience = (
|
|
|
5220
5359
|
lastUserMessageWasVoice: false,
|
|
5221
5360
|
lastUserMessageId: null as string | null
|
|
5222
5361
|
};
|
|
5223
|
-
isDictationActive = () => voiceState.active;
|
|
5224
5362
|
// First stamp: an untouched composer emits no store change, so the attribute
|
|
5225
5363
|
// would otherwise not appear until the first keystroke.
|
|
5226
5364
|
syncComposerCompact();
|
|
@@ -6163,7 +6301,9 @@ export const createAgentExperience = (
|
|
|
6163
6301
|
// Only an explicit config value is stamped inline; the 85%/100% defaults
|
|
6164
6302
|
// live in widget.css so `components.message.<role>.maxWidth` can win.
|
|
6165
6303
|
const maxWidth =
|
|
6166
|
-
roleLayout?.maxWidth ?? (width === "full"
|
|
6304
|
+
roleLayout?.maxWidth ?? (width === "full"
|
|
6305
|
+
? `var(--persona-message-${sizingRole}-max-width, 100%)`
|
|
6306
|
+
: undefined);
|
|
6167
6307
|
|
|
6168
6308
|
wrapper.classList.add("persona-message-row");
|
|
6169
6309
|
wrapper.classList.remove(
|
|
@@ -6201,6 +6341,15 @@ export const createAgentExperience = (
|
|
|
6201
6341
|
messages: AgentWidgetMessage[],
|
|
6202
6342
|
transform: MessageTransform
|
|
6203
6343
|
) => {
|
|
6344
|
+
const activityIds = new Set<string>();
|
|
6345
|
+
for (const message of messages) {
|
|
6346
|
+
const kind = message.variant === "tool" ? "tool" : message.variant === "reasoning" ? "reasoning" : null;
|
|
6347
|
+
if (!kind || activityVariant(config, kind) !== "row") continue;
|
|
6348
|
+
const display = kind === "tool" ? config.features?.toolCallDisplay : config.features?.reasoningDisplay;
|
|
6349
|
+
if (display?.expandable === false) continue;
|
|
6350
|
+
activityIds.add(message.id);
|
|
6351
|
+
activityLifecycle.observe(message, kind, kind === "tool" ? toolExpansionState : reasoningExpansionState, display);
|
|
6352
|
+
}
|
|
6204
6353
|
// Build new content in a temporary container for morphing
|
|
6205
6354
|
const tempContainer = document.createElement("div");
|
|
6206
6355
|
|
|
@@ -6613,7 +6762,7 @@ export const createAgentExperience = (
|
|
|
6613
6762
|
};
|
|
6614
6763
|
liveBubble = approvalPlugin.renderApproval({
|
|
6615
6764
|
message,
|
|
6616
|
-
defaultRenderer: () => approvalMod.createApprovalBubble(message, config),
|
|
6765
|
+
defaultRenderer: () => approvalMod.createApprovalBubble(message, config, approvalDetailsExpansionState),
|
|
6617
6766
|
config,
|
|
6618
6767
|
approve: (options) => resolveDecision("approved", options),
|
|
6619
6768
|
deny: (options) => resolveDecision("denied", options)
|
|
@@ -6629,7 +6778,7 @@ export const createAgentExperience = (
|
|
|
6629
6778
|
const existing = container.querySelector<HTMLElement>(`#wrapper-${message.id}`);
|
|
6630
6779
|
existing?.removeAttribute("data-preserve-runtime");
|
|
6631
6780
|
lastApprovalBubbleFingerprint.delete(message.id);
|
|
6632
|
-
bubble = approvalMod.createApprovalBubble(message, config);
|
|
6781
|
+
bubble = approvalMod.createApprovalBubble(message, config, approvalDetailsExpansionState);
|
|
6633
6782
|
} else {
|
|
6634
6783
|
// A fresh live bubble to hydrate (needsRebuild), or fingerprint
|
|
6635
6784
|
// unchanged so we reuse the preserved live wrapper (`bubble: null`).
|
|
@@ -6649,14 +6798,14 @@ export const createAgentExperience = (
|
|
|
6649
6798
|
if (!showReasoning) return;
|
|
6650
6799
|
bubble = matchingPlugin.renderReasoning({
|
|
6651
6800
|
message,
|
|
6652
|
-
defaultRenderer: () => createReasoningBubble(message, config),
|
|
6801
|
+
defaultRenderer: () => createReasoningBubble(message, config, reasoningExpansionState),
|
|
6653
6802
|
config
|
|
6654
6803
|
});
|
|
6655
6804
|
} else if (message.variant === "tool" && message.toolCall && matchingPlugin.renderToolCall) {
|
|
6656
6805
|
if (!showToolCalls) return;
|
|
6657
6806
|
bubble = matchingPlugin.renderToolCall({
|
|
6658
6807
|
message,
|
|
6659
|
-
defaultRenderer: () => createToolBubble(message, config),
|
|
6808
|
+
defaultRenderer: () => createToolBubble(message, config, toolExpansionState),
|
|
6660
6809
|
config
|
|
6661
6810
|
});
|
|
6662
6811
|
} else if (matchingPlugin.renderMessage) {
|
|
@@ -6824,15 +6973,15 @@ export const createAgentExperience = (
|
|
|
6824
6973
|
if (!bubble) {
|
|
6825
6974
|
if (message.variant === "reasoning" && message.reasoning) {
|
|
6826
6975
|
if (!showReasoning) return;
|
|
6827
|
-
bubble = createReasoningBubble(message, config);
|
|
6976
|
+
bubble = createReasoningBubble(message, config, reasoningExpansionState);
|
|
6828
6977
|
} else if (message.variant === "tool" && message.toolCall) {
|
|
6829
6978
|
if (!showToolCalls) return;
|
|
6830
|
-
bubble = createToolBubble(message, config);
|
|
6979
|
+
bubble = createToolBubble(message, config, toolExpansionState);
|
|
6831
6980
|
} else if (message.variant === "approval" && message.approval) {
|
|
6832
6981
|
if (config.approval === false) return;
|
|
6833
6982
|
const approvalMod = ensureApprovalUi();
|
|
6834
6983
|
if (!approvalMod) return;
|
|
6835
|
-
bubble = approvalMod.createApprovalBubble(message, config);
|
|
6984
|
+
bubble = approvalMod.createApprovalBubble(message, config, approvalDetailsExpansionState);
|
|
6836
6985
|
} else {
|
|
6837
6986
|
// Check for custom message renderers in layout config
|
|
6838
6987
|
const messageLayoutConfig = config.layout?.messages;
|
|
@@ -6950,6 +7099,32 @@ export const createAgentExperience = (
|
|
|
6950
7099
|
);
|
|
6951
7100
|
groupWrapper.setAttribute("data-persona-tool-group-row", "true");
|
|
6952
7101
|
|
|
7102
|
+
if (config.features?.toolCallDisplay?.groupedMode === "collapsible") {
|
|
7103
|
+
const id = `tool-group-${group[0].id}`;
|
|
7104
|
+
const active = group.some(item => item.toolCall?.status !== "complete");
|
|
7105
|
+
const synthetic: AgentWidgetMessage = { ...group[0], id, toolCall: {
|
|
7106
|
+
id, name: `${group.length} tools`, status: active ? "running" : "complete",
|
|
7107
|
+
chunks: group.flatMap(item => item.toolCall?.chunks ?? []),
|
|
7108
|
+
success: !group.some(item => item.toolCall?.success === false),
|
|
7109
|
+
} };
|
|
7110
|
+
activityIds.add(id);
|
|
7111
|
+
activityLifecycle.observe(synthetic, "tool", toolExpansionState, config.features.toolCallDisplay);
|
|
7112
|
+
const label = `${active ? "Using" : "Used"} ${group.length} tools`;
|
|
7113
|
+
const custom = config.toolCall?.renderGroupedSummary?.({ messages: group, toolCalls: group.map(item => item.toolCall!), defaultSummary: label, config });
|
|
7114
|
+
const row = createActivityGroup(synthetic, config, toolExpansionState.has(id), custom ?? label);
|
|
7115
|
+
row.bubble.classList.add("persona-tool-group");
|
|
7116
|
+
row.body.dataset.personaToolGroupStack = "true";
|
|
7117
|
+
wrappers[0].before(groupWrapper);
|
|
7118
|
+
groupWrapper.appendChild(row.bubble);
|
|
7119
|
+
wrappers.forEach((wrapper, index) => {
|
|
7120
|
+
wrapper.style.setProperty("--persona-message-row-max-width", "100%");
|
|
7121
|
+
wrapper.style.setProperty("--persona-activity-stagger", `${index * 40}ms`);
|
|
7122
|
+
wrapper.classList.add("persona-activity-group-child");
|
|
7123
|
+
row.body.appendChild(wrapper);
|
|
7124
|
+
});
|
|
7125
|
+
return;
|
|
7126
|
+
}
|
|
7127
|
+
|
|
6953
7128
|
const groupContainer = document.createElement("div");
|
|
6954
7129
|
groupContainer.className =
|
|
6955
7130
|
"persona-tool-group persona-flex persona-w-full persona-flex-col persona-gap-2";
|
|
@@ -7006,6 +7181,8 @@ export const createAgentExperience = (
|
|
|
7006
7181
|
});
|
|
7007
7182
|
}
|
|
7008
7183
|
|
|
7184
|
+
activityLifecycle.prune(activityIds);
|
|
7185
|
+
|
|
7009
7186
|
// Remove cache entries for messages that no longer exist
|
|
7010
7187
|
pruneCache(messageCache, activeMessageIds);
|
|
7011
7188
|
|
|
@@ -8018,6 +8195,7 @@ export const createAgentExperience = (
|
|
|
8018
8195
|
statusText.setAttribute("aria-live", "polite");
|
|
8019
8196
|
statusText.setAttribute(COMPOSER_REASON_ATTR, "");
|
|
8020
8197
|
statusText.textContent = reason;
|
|
8198
|
+
applyStatusIndicatorState(statusText, config.statusIndicator ?? {}, "idle");
|
|
8021
8199
|
return;
|
|
8022
8200
|
}
|
|
8023
8201
|
if (!statusText.hasAttribute(COMPOSER_REASON_ATTR)) return;
|
|
@@ -8176,7 +8354,8 @@ export const createAgentExperience = (
|
|
|
8176
8354
|
composerLiftAnimation = animateComposerLiftChange(
|
|
8177
8355
|
footer,
|
|
8178
8356
|
next === "active" ? previousLift : readLiftPx(),
|
|
8179
|
-
next === "active" ? "drop" : "rise"
|
|
8357
|
+
next === "active" ? "drop" : "rise",
|
|
8358
|
+
getWelcomeConfig().layout === "centered" ? 320 : 260
|
|
8180
8359
|
);
|
|
8181
8360
|
}
|
|
8182
8361
|
// The reservation shrinks by the lift in one frame, above the anchor.
|
|
@@ -8194,8 +8373,8 @@ export const createAgentExperience = (
|
|
|
8194
8373
|
// so the pending stand-in the overlay body class would hide stays visible.
|
|
8195
8374
|
let welcomePluginSuppressed = false;
|
|
8196
8375
|
const updateWelcome = (messages?: AgentWidgetMessage[]) => {
|
|
8197
|
-
const resolved =
|
|
8198
|
-
const welcomeKey = `${resolved.variant}|${resolved.dismiss}|${resolved.anchor ?? "bottom"}|${resolved.align ?? ""}|${resolved.iconPlacement}|${resolved.kicker ?? ""}|${resolved.title}|${resolved.subtitle}`;
|
|
8376
|
+
const resolved = getWelcomeConfig();
|
|
8377
|
+
const welcomeKey = `${resolved.variant}|${resolved.layout}|${resolved.dismiss}|${resolved.anchor ?? "bottom"}|${resolved.align ?? ""}|${resolved.iconPlacement}|${resolved.kicker ?? ""}|${resolved.title}|${resolved.subtitle}`;
|
|
8199
8378
|
if (welcomeKey !== lastWelcomeKey || resolved.icon !== lastWelcomeIcon) {
|
|
8200
8379
|
lastWelcomeKey = welcomeKey;
|
|
8201
8380
|
lastWelcomeIcon = resolved.icon;
|
|
@@ -8348,8 +8527,8 @@ export const createAgentExperience = (
|
|
|
8348
8527
|
// A plugin calling requestRender() from inside its own render would recurse.
|
|
8349
8528
|
if (welcomeArbitrating) return;
|
|
8350
8529
|
welcomeArbitrating = true;
|
|
8351
|
-
const resolved =
|
|
8352
|
-
lastWelcomeArbitrationKey = `${resolved.variant}|${resolved.dismiss}|${resolved.title}|${resolved.subtitle}|${resolved.message ?? ""}`;
|
|
8530
|
+
const resolved = getWelcomeConfig();
|
|
8531
|
+
lastWelcomeArbitrationKey = `${resolved.variant}|${resolved.layout}|${resolved.dismiss}|${resolved.title}|${resolved.subtitle}|${resolved.message ?? ""}`;
|
|
8353
8532
|
lastWelcomeArbitrationIcon = resolved.icon;
|
|
8354
8533
|
try {
|
|
8355
8534
|
runWelcomeCleanups();
|
|
@@ -8398,8 +8577,8 @@ export const createAgentExperience = (
|
|
|
8398
8577
|
|
|
8399
8578
|
/** Re-arbitrate only when the resolved welcome config actually changed. */
|
|
8400
8579
|
const refreshWelcomePlugins = () => {
|
|
8401
|
-
const resolved =
|
|
8402
|
-
const key = `${resolved.variant}|${resolved.dismiss}|${resolved.title}|${resolved.subtitle}|${resolved.message ?? ""}`;
|
|
8580
|
+
const resolved = getWelcomeConfig();
|
|
8581
|
+
const key = `${resolved.variant}|${resolved.layout}|${resolved.dismiss}|${resolved.title}|${resolved.subtitle}|${resolved.message ?? ""}`;
|
|
8403
8582
|
if (
|
|
8404
8583
|
key === lastWelcomeArbitrationKey &&
|
|
8405
8584
|
resolved.icon === lastWelcomeArbitrationIcon
|
|
@@ -8639,7 +8818,7 @@ export const createAgentExperience = (
|
|
|
8639
8818
|
spans.forEach((span) => {
|
|
8640
8819
|
const startedAt = Number(span.getAttribute("data-tool-elapsed"));
|
|
8641
8820
|
if (!startedAt) return;
|
|
8642
|
-
span.textContent = formatElapsedMs(now - startedAt);
|
|
8821
|
+
span.textContent = span.closest(".persona-activity-row") ? activityDuration(now - startedAt) : formatElapsedMs(now - startedAt);
|
|
8643
8822
|
});
|
|
8644
8823
|
}, 100);
|
|
8645
8824
|
};
|
|
@@ -11413,9 +11592,15 @@ export const createAgentExperience = (
|
|
|
11413
11592
|
historySurface = null;
|
|
11414
11593
|
});
|
|
11415
11594
|
|
|
11416
|
-
//
|
|
11417
|
-
//
|
|
11418
|
-
|
|
11595
|
+
// Client-token init on mount is reserved for widgets that need a session at
|
|
11596
|
+
// boot: visitor history (boot reconciliation) and `sessionInit: 'mount'`.
|
|
11597
|
+
// Everything else inits on intent (the `sessionInit` triggers below) or at
|
|
11598
|
+
// send, since init is no longer free server-side (it pre-allocates the
|
|
11599
|
+
// conversation's session owner).
|
|
11600
|
+
if (
|
|
11601
|
+
config.clientToken &&
|
|
11602
|
+
(config.features?.history?.enabled === true || config.sessionInit === "mount")
|
|
11603
|
+
) {
|
|
11419
11604
|
session
|
|
11420
11605
|
.initClientSession()
|
|
11421
11606
|
.then(() => {
|
|
@@ -11435,6 +11620,73 @@ export const createAgentExperience = (
|
|
|
11435
11620
|
});
|
|
11436
11621
|
}
|
|
11437
11622
|
|
|
11623
|
+
// Early session init on visitor intent (`sessionInit`). Delegated on the
|
|
11624
|
+
// mount so composer rebuilds stay covered, and read `config` at event time so
|
|
11625
|
+
// `update()` applies without re-wiring.
|
|
11626
|
+
const warmSession = () => session.warmClientSession();
|
|
11627
|
+
// One delegated listener. `input` counts only real typing, paste, drop or
|
|
11628
|
+
// IME (an InputEvent with an inputType; the widget's programmatic edits
|
|
11629
|
+
// dispatch a plain Event) that leaves text. In 'focus' mode a pointer-down in
|
|
11630
|
+
// the composer or a Tab into it also counts; `.focus()` (autofocus on
|
|
11631
|
+
// open/mount) produces neither.
|
|
11632
|
+
const handleSessionInitIntent = (event: Event) => {
|
|
11633
|
+
const mode = config.sessionInit ?? "input";
|
|
11634
|
+
const input = composerBindings?.input ?? textarea;
|
|
11635
|
+
const target = event.target as Node;
|
|
11636
|
+
if (
|
|
11637
|
+
input &&
|
|
11638
|
+
(event.type === "input"
|
|
11639
|
+
? (mode === "input" || mode === "focus") &&
|
|
11640
|
+
target === input &&
|
|
11641
|
+
!!(event as InputEvent).inputType &&
|
|
11642
|
+
!!(input as HTMLTextAreaElement).value.trim()
|
|
11643
|
+
: mode === "focus" &&
|
|
11644
|
+
(event.type === "keyup"
|
|
11645
|
+
? target === input && (event as KeyboardEvent).key === "Tab"
|
|
11646
|
+
: !!(composerBindings?.form ?? composerForm)?.contains(target)))
|
|
11647
|
+
) {
|
|
11648
|
+
warmSession();
|
|
11649
|
+
}
|
|
11650
|
+
};
|
|
11651
|
+
const sessionInitEvents = ["input", "pointerdown", "keyup"];
|
|
11652
|
+
sessionInitEvents.forEach((type) => mount.addEventListener(type, handleSessionInitIntent));
|
|
11653
|
+
// The custom trigger (a `sessionInit` function) runs on mount and again
|
|
11654
|
+
// whenever `update()` swaps in a different function, cleaning up the old one.
|
|
11655
|
+
let sessionInitHook: AgentWidgetSessionInitHook | undefined;
|
|
11656
|
+
let sessionInitHookCleanup: ReturnType<AgentWidgetSessionInitHook>;
|
|
11657
|
+
// Host hook code may throw; neither its setup nor its cleanup may leave
|
|
11658
|
+
// mount, update() or destroy() half done. The send still inits either way.
|
|
11659
|
+
const releaseSessionInitHook = () => {
|
|
11660
|
+
const cleanup = sessionInitHookCleanup;
|
|
11661
|
+
sessionInitHookCleanup = undefined;
|
|
11662
|
+
try {
|
|
11663
|
+
if (typeof cleanup === "function") cleanup();
|
|
11664
|
+
} catch {
|
|
11665
|
+
// ignored, see above
|
|
11666
|
+
}
|
|
11667
|
+
};
|
|
11668
|
+
const syncSessionInitHook = () => {
|
|
11669
|
+
const next =
|
|
11670
|
+
config.clientToken && typeof config.sessionInit === "function"
|
|
11671
|
+
? config.sessionInit
|
|
11672
|
+
: undefined;
|
|
11673
|
+
if (next === sessionInitHook) return;
|
|
11674
|
+
releaseSessionInitHook();
|
|
11675
|
+
sessionInitHook = next;
|
|
11676
|
+
try {
|
|
11677
|
+
sessionInitHookCleanup = next?.({ warm: warmSession, mount });
|
|
11678
|
+
} catch {
|
|
11679
|
+
// ignored, see above
|
|
11680
|
+
}
|
|
11681
|
+
};
|
|
11682
|
+
syncSessionInitHook();
|
|
11683
|
+
destroyCallbacks.push(() => {
|
|
11684
|
+
sessionInitEvents.forEach((type) =>
|
|
11685
|
+
mount.removeEventListener(type, handleSessionInitIntent)
|
|
11686
|
+
);
|
|
11687
|
+
releaseSessionInitHook();
|
|
11688
|
+
});
|
|
11689
|
+
|
|
11438
11690
|
// Wire up optional SSE tap (host) + event stream buffer to capture SSE
|
|
11439
11691
|
// events. `feedEventStream` stages events while the lazy chunk is in flight.
|
|
11440
11692
|
if (showEventStreamToggle || config.onSSEEvent) {
|
|
@@ -11607,6 +11859,7 @@ export const createAgentExperience = (
|
|
|
11607
11859
|
statusText.setAttribute("role", "status");
|
|
11608
11860
|
statusText.setAttribute("aria-live", "polite");
|
|
11609
11861
|
statusText.textContent = text;
|
|
11862
|
+
applyStatusIndicatorState(statusText, config.statusIndicator ?? {}, "idle");
|
|
11610
11863
|
if (composerNoticeTimer) clearTimeout(composerNoticeTimer);
|
|
11611
11864
|
composerNoticeTimer = setTimeout(() => {
|
|
11612
11865
|
composerNoticeTimer = null;
|
|
@@ -12429,7 +12682,7 @@ export const createAgentExperience = (
|
|
|
12429
12682
|
}
|
|
12430
12683
|
emitVoiceState(source);
|
|
12431
12684
|
persistVoiceMetadata();
|
|
12432
|
-
//
|
|
12685
|
+
// Reconcile content geometry without treating recording as extra content.
|
|
12433
12686
|
syncComposerCompact();
|
|
12434
12687
|
if (micButton) {
|
|
12435
12688
|
// Store original styles (including icon info for restoration)
|
|
@@ -12846,8 +13099,10 @@ export const createAgentExperience = (
|
|
|
12846
13099
|
footerResizeObserver.disconnect();
|
|
12847
13100
|
footerResizeObserver.observe(footer);
|
|
12848
13101
|
footerResizeObserver.observe(container);
|
|
13102
|
+
footerResizeObserver.observe(welcomeHost);
|
|
12849
13103
|
};
|
|
12850
13104
|
|
|
13105
|
+
let lastWelcomeFullscreen = isWelcomeFullscreen();
|
|
12851
13106
|
const recalcPanelHeight = () => {
|
|
12852
13107
|
// Composer-bar mode lets CSS own all sizing: collapsed pill is auto-sized
|
|
12853
13108
|
// by the footer; expanded fullscreen/modal are driven by CSS attribute
|
|
@@ -12909,10 +13164,9 @@ export const createAgentExperience = (
|
|
|
12909
13164
|
|
|
12910
13165
|
// In sidebar/fullHeight mode, don't override the width - it's handled by applyFullHeightStyles
|
|
12911
13166
|
if (!sidebarMode && !dockedMode) {
|
|
12912
|
-
const
|
|
12913
|
-
|
|
12914
|
-
panel.style.
|
|
12915
|
-
panel.style.maxWidth = width;
|
|
13167
|
+
const geometry = resolvePanelGeometry(config);
|
|
13168
|
+
panel.style.width = geometry.width;
|
|
13169
|
+
panel.style.maxWidth = geometry.maxWidth;
|
|
12916
13170
|
}
|
|
12917
13171
|
applyLauncherArtifactPanelWidth();
|
|
12918
13172
|
|
|
@@ -12923,6 +13177,12 @@ export const createAgentExperience = (
|
|
|
12923
13177
|
// overwrites updateOpenState()'s display:none when docked+closed. Re-sync after every recalc.
|
|
12924
13178
|
updateScrollToBottomButtonOffset();
|
|
12925
13179
|
syncComposerOverlayMetrics();
|
|
13180
|
+
const welcomeFullscreen = isWelcomeFullscreen();
|
|
13181
|
+
if (welcomeFullscreen !== lastWelcomeFullscreen) {
|
|
13182
|
+
lastWelcomeFullscreen = welcomeFullscreen;
|
|
13183
|
+
updateWelcome();
|
|
13184
|
+
renderSuggestions();
|
|
13185
|
+
}
|
|
12926
13186
|
updateOpenState();
|
|
12927
13187
|
|
|
12928
13188
|
// Sync scroll lock and host stacking when viewport mode changes (e.g. orientation change)
|
|
@@ -12985,6 +13245,7 @@ export const createAgentExperience = (
|
|
|
12985
13245
|
let lastBottomOffset = getScrollBottomOffset(body);
|
|
12986
13246
|
|
|
12987
13247
|
const handleScroll = () => {
|
|
13248
|
+
syncTranscriptTopFade();
|
|
12988
13249
|
const scrollTop = body.scrollTop;
|
|
12989
13250
|
// When content mutates (e.g. stream-animation plugins re-rendering text)
|
|
12990
13251
|
// or the viewport grows (composer shrinking back), the maximum scroll
|
|
@@ -13203,6 +13464,8 @@ export const createAgentExperience = (
|
|
|
13203
13464
|
}
|
|
13204
13465
|
// Clear messages in session (this will trigger onMessagesChanged which re-renders)
|
|
13205
13466
|
session.clearMessages();
|
|
13467
|
+
if (config.layout?.topFade) body.scrollTop = 0;
|
|
13468
|
+
body.removeAttribute("data-persona-top-fade");
|
|
13206
13469
|
messageCache.clear();
|
|
13207
13470
|
resumeAutoScroll();
|
|
13208
13471
|
|
|
@@ -13418,6 +13681,10 @@ export const createAgentExperience = (
|
|
|
13418
13681
|
updateCopy();
|
|
13419
13682
|
renderSuggestions();
|
|
13420
13683
|
setComposerDisabled(session.isStreaming());
|
|
13684
|
+
if (config.statusIndicator?.mode === "transient" && !isComposerBar()) {
|
|
13685
|
+
const status = session.getStatus();
|
|
13686
|
+
applyStatusToElement(statusText, _getStatusText(status), config.statusIndicator, status);
|
|
13687
|
+
}
|
|
13421
13688
|
// The rebuilt mic is stamped idle by its builder. A rebuild mid-recording
|
|
13422
13689
|
// has to restore the live state, which also re-arms the level loop
|
|
13423
13690
|
// against the new footer and wrapper.
|
|
@@ -13589,6 +13856,7 @@ export const createAgentExperience = (
|
|
|
13589
13856
|
|
|
13590
13857
|
const controller: Controller = {
|
|
13591
13858
|
update(nextConfig: AgentWidgetConfigPatch) {
|
|
13859
|
+
const previousDefaultsVersion = config.future?.v5Defaults === true;
|
|
13592
13860
|
const previousToolCallConfig = config.toolCall;
|
|
13593
13861
|
const previousMessageActions = config.messageActions;
|
|
13594
13862
|
const previousLayoutMessages = config.layout?.messages;
|
|
@@ -13862,7 +14130,8 @@ export const createAgentExperience = (
|
|
|
13862
14130
|
|| (config.features?.showToolCalls ?? true) !== (previousShowToolCalls ?? true)
|
|
13863
14131
|
|| JSON.stringify(config.features?.toolCallDisplay) !== JSON.stringify(previousToolCallDisplay)
|
|
13864
14132
|
|| JSON.stringify(config.features?.reasoningDisplay) !== JSON.stringify(previousReasoningDisplay);
|
|
13865
|
-
const messagesConfigChanged =
|
|
14133
|
+
const messagesConfigChanged = (config.future?.v5Defaults === true) !== previousDefaultsVersion
|
|
14134
|
+
|| toolCallConfigChanged || messageActionsChanged || layoutMessagesChanged
|
|
13866
14135
|
|| loadingIndicatorChanged || iterationDisplayChanged || featuresChanged;
|
|
13867
14136
|
if (messagesConfigChanged && session) {
|
|
13868
14137
|
configVersion++;
|
|
@@ -13899,7 +14168,7 @@ export const createAgentExperience = (
|
|
|
13899
14168
|
// Hide icon if either headerIconHidden is true OR layout.header.showIcon is false
|
|
13900
14169
|
const shouldHideIcon = headerIconHidden || layoutShowIcon === false;
|
|
13901
14170
|
const headerIconName = launcher.headerIconName;
|
|
13902
|
-
const headerIconSize = launcher.headerIconSize ??
|
|
14171
|
+
const headerIconSize = launcher.headerIconSize ?? DEFAULT_HEADER_ICON_SIZE;
|
|
13903
14172
|
|
|
13904
14173
|
if (iconHolder) {
|
|
13905
14174
|
const headerEl = header;
|
|
@@ -13940,6 +14209,8 @@ export const createAgentExperience = (
|
|
|
13940
14209
|
const iconSize = parseFloat(headerIconSize) || 24;
|
|
13941
14210
|
const iconSvg = renderLucideIcon(headerIconName, iconSize * 0.6, "currentColor", 1);
|
|
13942
14211
|
if (iconSvg) {
|
|
14212
|
+
iconSvg.style.width = `calc(${iconSize}px * var(--persona-components-header-iconScale, 0.6))`;
|
|
14213
|
+
iconSvg.style.height = `calc(${iconSize}px * var(--persona-components-header-iconScale, 0.6))`;
|
|
13943
14214
|
iconHolder.replaceChildren(iconSvg);
|
|
13944
14215
|
} else {
|
|
13945
14216
|
// Fallback to agentIconText if Lucide icon fails
|
|
@@ -14306,6 +14577,7 @@ export const createAgentExperience = (
|
|
|
14306
14577
|
// Re-key the visitor store before any client rebuild sees the new config.
|
|
14307
14578
|
syncVisitorStore();
|
|
14308
14579
|
session.updateConfig(config);
|
|
14580
|
+
syncSessionInitHook();
|
|
14309
14581
|
if (
|
|
14310
14582
|
(config.features?.history?.enabled === true) !== previousHistoryEnabled ||
|
|
14311
14583
|
config.features?.history?.provider !== previousHistoryProvider ||
|
|
@@ -14816,6 +15088,8 @@ export const createAgentExperience = (
|
|
|
14816
15088
|
}
|
|
14817
15089
|
|
|
14818
15090
|
syncComposerLayout();
|
|
15091
|
+
// Placement may change through a live option or defaults-version switch.
|
|
15092
|
+
applyComposerPlacement();
|
|
14819
15093
|
|
|
14820
15094
|
// Modes can be added or removed live: drop selections whose mode is gone
|
|
14821
15095
|
// before anything reads `activeModeIds`, and mount/unmount the mode chips.
|
|
@@ -14900,8 +15174,8 @@ export const createAgentExperience = (
|
|
|
14900
15174
|
|
|
14901
15175
|
// Update status indicator visibility and text
|
|
14902
15176
|
const statusIndicatorConfig = config.statusIndicator ?? {};
|
|
14903
|
-
|
|
14904
|
-
statusText
|
|
15177
|
+
placeStatusIndicator(statusText, composerForm, statusIndicatorConfig);
|
|
15178
|
+
applyStatusIndicatorState(statusText, statusIndicatorConfig, session?.getStatus() ?? "idle");
|
|
14905
15179
|
|
|
14906
15180
|
// Update status text if status is currently set
|
|
14907
15181
|
if (session) {
|
|
@@ -14911,6 +15185,8 @@ export const createAgentExperience = (
|
|
|
14911
15185
|
if (s === "connecting") return statusIndicatorConfig.connectingText ?? statusCopy.connecting;
|
|
14912
15186
|
if (s === "connected") return statusIndicatorConfig.connectedText ?? statusCopy.connected;
|
|
14913
15187
|
if (s === "error") return statusIndicatorConfig.errorText ?? statusCopy.error;
|
|
15188
|
+
if (s === "paused") return statusIndicatorConfig.pausedText ?? statusCopy.paused;
|
|
15189
|
+
if (s === "resuming") return statusIndicatorConfig.resumingText ?? statusCopy.resuming;
|
|
14914
15190
|
return statusCopy[s];
|
|
14915
15191
|
};
|
|
14916
15192
|
applyStatusToElement(statusText, getCurrentStatusText(currentStatus), statusIndicatorConfig, currentStatus);
|
|
@@ -14951,7 +15227,13 @@ export const createAgentExperience = (
|
|
|
14951
15227
|
clearChat() {
|
|
14952
15228
|
// Clear messages in session (this will trigger onMessagesChanged which re-renders)
|
|
14953
15229
|
artifactsPaneUserHidden = false;
|
|
15230
|
+
activityLifecycle.clear();
|
|
15231
|
+
toolExpansionState.clear();
|
|
15232
|
+
reasoningExpansionState.clear();
|
|
15233
|
+
approvalDetailsExpansionState.clear();
|
|
14954
15234
|
session.clearMessages();
|
|
15235
|
+
if (config.layout?.topFade) body.scrollTop = 0;
|
|
15236
|
+
body.removeAttribute("data-persona-top-fade");
|
|
14955
15237
|
messageCache.clear();
|
|
14956
15238
|
resumeAutoScroll();
|
|
14957
15239
|
|
|
@@ -15207,6 +15489,9 @@ export const createAgentExperience = (
|
|
|
15207
15489
|
startNewConversation(opts?: { focus?: boolean }): Promise<void> {
|
|
15208
15490
|
return startNewConversation({ focusComposer: opts?.focus === true });
|
|
15209
15491
|
},
|
|
15492
|
+
warmSession(): void {
|
|
15493
|
+
warmSession();
|
|
15494
|
+
},
|
|
15210
15495
|
deleteConversation(conversationId: string): Promise<void> {
|
|
15211
15496
|
return deleteHistoryConversation(conversationId);
|
|
15212
15497
|
},
|
|
@@ -15475,6 +15760,10 @@ export const createAgentExperience = (
|
|
|
15475
15760
|
clearInterval(toolElapsedTimerId);
|
|
15476
15761
|
toolElapsedTimerId = null;
|
|
15477
15762
|
}
|
|
15763
|
+
activityLifecycle.clear();
|
|
15764
|
+
toolExpansionState.clear();
|
|
15765
|
+
reasoningExpansionState.clear();
|
|
15766
|
+
approvalDetailsExpansionState.clear();
|
|
15478
15767
|
destroyCallbacks.forEach((cb) => cb());
|
|
15479
15768
|
wrapper.remove();
|
|
15480
15769
|
pillRoot?.remove();
|