@runtypelabs/persona 4.7.0 → 4.8.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.cjs +2 -2
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/glyph-cycle.js +2 -2
- package/dist/animations/{types-CSmiKRVa.d.cts → types-BsZtXPKK.d.cts} +43 -3
- package/dist/animations/{types-CSmiKRVa.d.ts → types-BsZtXPKK.d.ts} +43 -3
- package/dist/animations/wipe.cjs +2 -2
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/chunk-5EIIHQLQ.js +1 -0
- package/dist/codegen.cjs +12 -12
- package/dist/codegen.js +14 -14
- package/dist/index.cjs +91 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +877 -172
- package/dist/index.d.ts +877 -172
- package/dist/index.global.js +81 -57
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +91 -68
- 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 +3 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/markdown-parsers.js +24 -24
- package/dist/plugin-kit.cjs +1 -1
- package/dist/plugin-kit.js +1 -1
- package/dist/runtype-tts-entry-UJAEF7NZ.js +1 -0
- package/dist/runtype-tts.js +1 -1
- package/dist/session-reconnect-JKIJBHS5.js +1 -0
- package/dist/smart-dom-reader.cjs +17 -17
- package/dist/smart-dom-reader.d.cts +753 -15
- package/dist/smart-dom-reader.d.ts +753 -15
- package/dist/smart-dom-reader.js +17 -17
- package/dist/testing.cjs +3 -3
- package/dist/testing.js +3 -3
- package/dist/theme-editor-preview.cjs +81 -58
- package/dist/theme-editor-preview.d.cts +761 -15
- package/dist/theme-editor-preview.d.ts +761 -15
- package/dist/theme-editor-preview.js +81 -58
- package/dist/theme-editor.cjs +6 -6
- package/dist/theme-editor.d.cts +753 -15
- package/dist/theme-editor.d.ts +753 -15
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +74 -0
- package/dist/theme-reference.d.ts +74 -0
- package/dist/theme-reference.js +1 -1
- package/dist/voice-worklet-player.cjs +2 -2
- package/dist/voice-worklet-player.js +2 -2
- package/dist/webmcp-polyfill.js +2 -2
- package/dist/widget.css +1 -1
- package/package.json +2 -3
- package/src/artifacts-session.test.ts +178 -0
- package/src/client.test.ts +186 -1
- package/src/client.ts +92 -9
- package/src/components/artifact-card.test.ts +333 -0
- package/src/components/artifact-card.ts +75 -28
- package/src/components/artifact-inline.test.ts +1328 -0
- package/src/components/artifact-inline.ts +920 -0
- package/src/components/artifact-pane.test.ts +1042 -0
- package/src/components/artifact-pane.ts +440 -131
- package/src/components/artifact-preview.test.ts +1155 -0
- package/src/components/artifact-preview.ts +994 -0
- package/src/components/pill-composer-builder.test.ts +6 -2
- package/src/components/pill-composer-builder.ts +6 -6
- package/src/components/reasoning-bubble.ts +1 -13
- package/src/components/registry.ts +38 -3
- package/src/components/tool-bubble.ts +1 -13
- package/src/defaults.ts +1 -0
- package/src/generated/runtype-openapi-contract.ts +55 -3
- package/src/index-core.ts +20 -1
- package/src/index.ts +8 -0
- package/src/markdown-parsers-loader.test.ts +158 -0
- package/src/markdown-parsers-loader.ts +74 -9
- package/src/runtime/host-layout.test.ts +163 -0
- package/src/runtime/host-layout.ts +110 -7
- package/src/runtime/init.ts +18 -61
- package/src/session.ts +76 -22
- package/src/styles/widget.css +773 -26
- package/src/theme-editor/preview.ts +2 -0
- package/src/theme-editor/sections.test.ts +26 -1
- package/src/theme-editor/sections.ts +10 -2
- package/src/theme-reference.ts +2 -2
- package/src/tool-call-display-defaults.test.ts +1 -0
- package/src/types/theme.ts +77 -0
- package/src/types.ts +516 -17
- package/src/ui.artifact-pane-gating.test.ts +636 -0
- package/src/ui.component-directive.test.ts +104 -0
- package/src/ui.composer-bar.test.ts +60 -2
- package/src/ui.detached-panel.test.ts +1049 -0
- package/src/ui.tool-display.test.ts +51 -0
- package/src/ui.ts +698 -109
- package/src/utils/artifact-custom-actions.ts +128 -0
- package/src/utils/artifact-display.test.ts +42 -0
- package/src/utils/artifact-display.ts +84 -0
- package/src/utils/artifact-file.test.ts +116 -0
- package/src/utils/artifact-file.ts +117 -0
- package/src/utils/artifact-gate.test.ts +112 -5
- package/src/utils/artifact-gate.ts +39 -14
- package/src/utils/artifact-loading-status.ts +55 -0
- package/src/utils/artifact-status-label.ts +190 -0
- package/src/utils/buttons.ts +7 -1
- package/src/utils/code-highlight.test.ts +186 -0
- package/src/utils/code-highlight.ts +400 -0
- package/src/utils/icons.ts +2 -0
- package/src/utils/roving-tablist.test.ts +152 -0
- package/src/utils/roving-tablist.ts +111 -0
- package/src/utils/spinner.ts +45 -0
- package/src/utils/theme.test.ts +48 -0
- package/src/utils/theme.ts +7 -0
- package/src/utils/tokens.ts +91 -0
- package/src/utils/tool-loading-animation.test.ts +32 -0
- package/src/utils/tool-loading-animation.ts +24 -0
- package/dist/chunk-DFBSCFYN.js +0 -1
- package/dist/runtype-tts-entry-HFUV2UF7.js +0 -1
- package/dist/session-reconnect-U77QFUR7.js +0 -1
|
@@ -527,4 +527,167 @@ describe("createWidgetHostLayout docked", () => {
|
|
|
527
527
|
layout.destroy();
|
|
528
528
|
});
|
|
529
529
|
});
|
|
530
|
+
|
|
531
|
+
describe("detached docked", () => {
|
|
532
|
+
it("leaves the dock slot flush when detachedPanel is unset", () => {
|
|
533
|
+
const parent = document.createElement("div");
|
|
534
|
+
document.body.appendChild(parent);
|
|
535
|
+
const target = document.createElement("div");
|
|
536
|
+
parent.appendChild(target);
|
|
537
|
+
|
|
538
|
+
const layout = createWidgetHostLayout(target, {
|
|
539
|
+
launcher: {
|
|
540
|
+
mountMode: "docked",
|
|
541
|
+
autoExpand: true,
|
|
542
|
+
dock: { width: "320px" },
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
547
|
+
expect(dockSlot?.style.padding).toBe("");
|
|
548
|
+
expect(dockSlot?.style.background).toBe("");
|
|
549
|
+
|
|
550
|
+
layout.destroy();
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it("pads the dock slot with the default inset and canvas when detached", () => {
|
|
554
|
+
const parent = document.createElement("div");
|
|
555
|
+
document.body.appendChild(parent);
|
|
556
|
+
const target = document.createElement("div");
|
|
557
|
+
parent.appendChild(target);
|
|
558
|
+
|
|
559
|
+
const layout = createWidgetHostLayout(target, {
|
|
560
|
+
launcher: {
|
|
561
|
+
mountMode: "docked",
|
|
562
|
+
autoExpand: true,
|
|
563
|
+
detachedPanel: true,
|
|
564
|
+
dock: { width: "320px" },
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
569
|
+
expect(dockSlot?.style.padding).toBe("16px");
|
|
570
|
+
expect(dockSlot?.style.background).toBe("transparent");
|
|
571
|
+
expect(dockSlot?.style.boxSizing).toBe("border-box");
|
|
572
|
+
|
|
573
|
+
layout.destroy();
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it("resolves detached inset and canvas from panel theme tokens", () => {
|
|
577
|
+
const parent = document.createElement("div");
|
|
578
|
+
document.body.appendChild(parent);
|
|
579
|
+
const target = document.createElement("div");
|
|
580
|
+
parent.appendChild(target);
|
|
581
|
+
|
|
582
|
+
const layout = createWidgetHostLayout(target, {
|
|
583
|
+
launcher: {
|
|
584
|
+
mountMode: "docked",
|
|
585
|
+
autoExpand: true,
|
|
586
|
+
detachedPanel: true,
|
|
587
|
+
dock: { width: "320px" },
|
|
588
|
+
},
|
|
589
|
+
theme: {
|
|
590
|
+
components: { panel: { inset: "24px", canvasBackground: "#eee" } },
|
|
591
|
+
},
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
595
|
+
expect(dockSlot?.style.padding).toBe("24px");
|
|
596
|
+
expect(dockSlot?.style.background).toBe("rgb(238, 238, 238)");
|
|
597
|
+
|
|
598
|
+
layout.destroy();
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it("shrinks the emerge host by the inset so it fits the padded slot", () => {
|
|
602
|
+
const parent = document.createElement("div");
|
|
603
|
+
document.body.appendChild(parent);
|
|
604
|
+
const target = document.createElement("div");
|
|
605
|
+
parent.appendChild(target);
|
|
606
|
+
|
|
607
|
+
const layout = createWidgetHostLayout(target, {
|
|
608
|
+
launcher: {
|
|
609
|
+
mountMode: "docked",
|
|
610
|
+
autoExpand: true,
|
|
611
|
+
detachedPanel: true,
|
|
612
|
+
dock: { width: "320px", reveal: "emerge" },
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
const host = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="host"]');
|
|
617
|
+
// jsdom simplifies the calc expression (320px - 2 * 16px).
|
|
618
|
+
expect(host?.style.width).toBe("calc(288px)");
|
|
619
|
+
|
|
620
|
+
layout.destroy();
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it("resolves a percentage emerge width to px before subtracting the inset", () => {
|
|
624
|
+
const parent = document.createElement("div");
|
|
625
|
+
document.body.appendChild(parent);
|
|
626
|
+
const target = document.createElement("div");
|
|
627
|
+
parent.appendChild(target);
|
|
628
|
+
|
|
629
|
+
const layout = createWidgetHostLayout(target, {
|
|
630
|
+
launcher: {
|
|
631
|
+
mountMode: "docked",
|
|
632
|
+
autoExpand: true,
|
|
633
|
+
detachedPanel: true,
|
|
634
|
+
dock: { width: "50%", reveal: "emerge" },
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
const shell = layout.shell!;
|
|
638
|
+
Object.defineProperty(shell, "clientWidth", { get: () => 800, configurable: true });
|
|
639
|
+
layout.updateConfig({
|
|
640
|
+
launcher: {
|
|
641
|
+
mountMode: "docked",
|
|
642
|
+
autoExpand: true,
|
|
643
|
+
detachedPanel: true,
|
|
644
|
+
dock: { width: "50%", reveal: "emerge" },
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
const host = shell.querySelector<HTMLElement>('[data-persona-dock-role="host"]');
|
|
649
|
+
// 50% of 800px resolved to px, then jsdom simplifies (400px - 2 * 16px).
|
|
650
|
+
expect(host?.style.width).toBe("calc(368px)");
|
|
651
|
+
|
|
652
|
+
layout.destroy();
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it("clears detached chrome and pads nothing while the panel is collapsed", () => {
|
|
656
|
+
for (const reveal of ["resize", "emerge"] as const) {
|
|
657
|
+
const parent = document.createElement("div");
|
|
658
|
+
document.body.appendChild(parent);
|
|
659
|
+
const target = document.createElement("div");
|
|
660
|
+
parent.appendChild(target);
|
|
661
|
+
|
|
662
|
+
const layout = createWidgetHostLayout(target, {
|
|
663
|
+
launcher: {
|
|
664
|
+
mountMode: "docked",
|
|
665
|
+
autoExpand: false,
|
|
666
|
+
detachedPanel: true,
|
|
667
|
+
dock: { width: "320px", reveal },
|
|
668
|
+
},
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
672
|
+
// Collapsed: truly 0-width slot with no padding/background/box-sizing residue.
|
|
673
|
+
expect(dockSlot?.style.width, reveal).toBe("0px");
|
|
674
|
+
expect(dockSlot?.style.padding, reveal).toBe("");
|
|
675
|
+
expect(dockSlot?.style.background, reveal).toBe("");
|
|
676
|
+
expect(dockSlot?.style.boxSizing, reveal).toBe("");
|
|
677
|
+
// Padding rides the width transition so the canvas gutter shrinks with the column.
|
|
678
|
+
expect(dockSlot?.style.transition, reveal).toContain("padding");
|
|
679
|
+
|
|
680
|
+
// Opening restores the chrome; closing clears it again.
|
|
681
|
+
layout.syncWidgetState({ open: true, launcherEnabled: true });
|
|
682
|
+
expect(dockSlot?.style.padding, reveal).toBe("16px");
|
|
683
|
+
expect(dockSlot?.style.boxSizing, reveal).toBe("border-box");
|
|
684
|
+
layout.syncWidgetState({ open: false, launcherEnabled: true });
|
|
685
|
+
expect(dockSlot?.style.padding, reveal).toBe("");
|
|
686
|
+
expect(dockSlot?.style.boxSizing, reveal).toBe("");
|
|
687
|
+
|
|
688
|
+
layout.destroy();
|
|
689
|
+
document.body.innerHTML = "";
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
});
|
|
530
693
|
});
|
|
@@ -5,6 +5,12 @@ import type {
|
|
|
5
5
|
} from "../types";
|
|
6
6
|
import { isDockedMountMode, resolveDockConfig } from "../utils/dock";
|
|
7
7
|
import { DEFAULT_OVERLAY_Z_INDEX } from "../utils/constants";
|
|
8
|
+
import { createThemeObserver, getActiveTheme } from "../utils/theme";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_PANEL_CANVAS_BACKGROUND,
|
|
11
|
+
DEFAULT_PANEL_INSET,
|
|
12
|
+
resolveTokenValue,
|
|
13
|
+
} from "../utils/tokens";
|
|
8
14
|
|
|
9
15
|
export type WidgetHostLayoutMode = "direct" | "docked";
|
|
10
16
|
|
|
@@ -208,6 +214,51 @@ const orderDockChildren = (
|
|
|
208
214
|
}
|
|
209
215
|
};
|
|
210
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Detached inset + canvas literals for the dockSlot. The panel CSS vars are set
|
|
219
|
+
* inline on the widget root inside the slot, so they cannot cascade up to the
|
|
220
|
+
* slot; resolve the token values here and apply literals.
|
|
221
|
+
*/
|
|
222
|
+
const resolveDetachedSlotStyles = (
|
|
223
|
+
config: AgentWidgetConfig | undefined
|
|
224
|
+
): { inset: string; canvasBackground: string } => {
|
|
225
|
+
const theme = getActiveTheme(config);
|
|
226
|
+
const panel = theme.components?.panel;
|
|
227
|
+
const resolve = (raw: string | undefined, fallback: string): string => {
|
|
228
|
+
if (raw == null || raw === "") return fallback;
|
|
229
|
+
return resolveTokenValue(theme, raw) ?? raw;
|
|
230
|
+
};
|
|
231
|
+
return {
|
|
232
|
+
inset: resolve(panel?.inset, DEFAULT_PANEL_INSET),
|
|
233
|
+
canvasBackground: resolve(panel?.canvasBackground, DEFAULT_PANEL_CANVAS_BACKGROUND),
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
type DetachedSlotStyles = { inset: string; canvasBackground: string };
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Detached docked mode pads the slot so the canvas shows around the panel card.
|
|
241
|
+
* Padding, never margin, keeps parseDockWidthToPx push math truthful; box-sizing
|
|
242
|
+
* border-box keeps the slot outer width (and max-height) inclusive of the
|
|
243
|
+
* padding so the panel shrinks inside rather than overflowing.
|
|
244
|
+
*/
|
|
245
|
+
const applyDetachedDockSlotChrome = (
|
|
246
|
+
dockSlot: HTMLElement,
|
|
247
|
+
detached: boolean,
|
|
248
|
+
inset: string,
|
|
249
|
+
canvasBackground: string
|
|
250
|
+
): void => {
|
|
251
|
+
if (!detached) {
|
|
252
|
+
dockSlot.style.padding = "";
|
|
253
|
+
dockSlot.style.background = "";
|
|
254
|
+
dockSlot.style.boxSizing = "";
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
dockSlot.style.boxSizing = "border-box";
|
|
258
|
+
dockSlot.style.padding = inset;
|
|
259
|
+
dockSlot.style.background = canvasBackground;
|
|
260
|
+
};
|
|
261
|
+
|
|
211
262
|
const applyDockStyles = (
|
|
212
263
|
shell: HTMLElement,
|
|
213
264
|
pushTrack: HTMLElement,
|
|
@@ -215,10 +266,16 @@ const applyDockStyles = (
|
|
|
215
266
|
dockSlot: HTMLElement,
|
|
216
267
|
host: HTMLElement,
|
|
217
268
|
config: AgentWidgetConfig | undefined,
|
|
218
|
-
expanded: boolean
|
|
269
|
+
expanded: boolean,
|
|
270
|
+
// Cached slot styles; null when not detached. Resolved once per config/scheme
|
|
271
|
+
// change so getActiveTheme never runs on the resize/ResizeObserver path.
|
|
272
|
+
detachedSlotStyles: DetachedSlotStyles | null
|
|
219
273
|
): void => {
|
|
220
274
|
const dock = resolveDockConfig(config);
|
|
221
275
|
const usePush = dock.reveal === "push";
|
|
276
|
+
const detached = detachedSlotStyles != null;
|
|
277
|
+
const detachedInset = detachedSlotStyles?.inset ?? "";
|
|
278
|
+
const detachedCanvas = detachedSlotStyles?.canvasBackground ?? "";
|
|
222
279
|
|
|
223
280
|
migrateDockChildren(shell, pushTrack, contentSlot, dockSlot, usePush);
|
|
224
281
|
orderDockChildren(shell, pushTrack, contentSlot, dockSlot, dock.side, usePush);
|
|
@@ -260,6 +317,8 @@ const applyDockStyles = (
|
|
|
260
317
|
clearMobileFullscreenDockSlotStyles(dockSlot);
|
|
261
318
|
resetContentSlotFlexSizing(contentSlot);
|
|
262
319
|
clearEmergeDockStyles(host, dockSlot);
|
|
320
|
+
// Mobile fullscreen is flush regardless of detachedPanel.
|
|
321
|
+
applyDetachedDockSlotChrome(dockSlot, false, "", "");
|
|
263
322
|
|
|
264
323
|
shell.style.display = "flex";
|
|
265
324
|
shell.style.flexDirection = "column";
|
|
@@ -314,6 +373,9 @@ const applyDockStyles = (
|
|
|
314
373
|
shell.removeAttribute("data-persona-dock-mobile-fullscreen");
|
|
315
374
|
clearMobileFullscreenDockSlotStyles(dockSlot);
|
|
316
375
|
applyDockSlotMaxHeight(dockSlot, dock.maxHeight);
|
|
376
|
+
// Chrome only when open: a collapsed detached slot with padding + border-box
|
|
377
|
+
// floors at 2*inset, painting a permanent canvas strip past the 0px width.
|
|
378
|
+
applyDetachedDockSlotChrome(dockSlot, detached && expanded, detachedInset, detachedCanvas);
|
|
317
379
|
|
|
318
380
|
if (dock.reveal === "overlay") {
|
|
319
381
|
shell.style.display = "flex";
|
|
@@ -435,8 +497,10 @@ const applyDockStyles = (
|
|
|
435
497
|
}
|
|
436
498
|
|
|
437
499
|
const width = expanded ? dock.width : "0px";
|
|
500
|
+
// Detached collapse snaps padding off as width animates to 0; animate padding
|
|
501
|
+
// alongside so the canvas gutter shrinks with the column.
|
|
438
502
|
const dockTransition = dock.animate
|
|
439
|
-
?
|
|
503
|
+
? `width 180ms ease, min-width 180ms ease, max-width 180ms ease, flex-basis 180ms ease${detached ? ", padding 180ms ease" : ""}`
|
|
440
504
|
: "none";
|
|
441
505
|
const collapsedClosed = !expanded;
|
|
442
506
|
|
|
@@ -454,9 +518,17 @@ const applyDockStyles = (
|
|
|
454
518
|
|
|
455
519
|
if (isEmerge) {
|
|
456
520
|
dockSlot.style.alignItems = dock.side === "right" ? "flex-start" : "flex-end";
|
|
457
|
-
host
|
|
458
|
-
|
|
459
|
-
|
|
521
|
+
// Emerge pins the host to a fixed width and reveals it as the slot grows.
|
|
522
|
+
// When detached, shrink that fixed width by the padding so the host fits
|
|
523
|
+
// the padded slot content-box instead of overflowing one side. Resolve a
|
|
524
|
+
// percentage dock.width to px first: a raw % re-resolves against the
|
|
525
|
+
// padding-reduced content box and un-pins as the slot animates open.
|
|
526
|
+
const emergeHostWidth = detached
|
|
527
|
+
? `calc(${parseDockWidthToPx(dock.width, shell.clientWidth)}px - (2 * ${detachedInset}))`
|
|
528
|
+
: dock.width;
|
|
529
|
+
host.style.width = emergeHostWidth;
|
|
530
|
+
host.style.minWidth = emergeHostWidth;
|
|
531
|
+
host.style.maxWidth = emergeHostWidth;
|
|
460
532
|
host.style.boxSizing = "border-box";
|
|
461
533
|
}
|
|
462
534
|
}
|
|
@@ -518,12 +590,38 @@ const createDockedLayout = (target: HTMLElement, config?: AgentWidgetConfig): Wi
|
|
|
518
590
|
resizeObserver = null;
|
|
519
591
|
};
|
|
520
592
|
|
|
593
|
+
// Resolved once per config/scheme change; the resize paths reuse this so
|
|
594
|
+
// getActiveTheme never runs per frame.
|
|
595
|
+
let detachedSlotStyles: DetachedSlotStyles | null = null;
|
|
596
|
+
const refreshDetachedSlotStyles = (): void => {
|
|
597
|
+
detachedSlotStyles =
|
|
598
|
+
config?.launcher?.detachedPanel === true ? resolveDetachedSlotStyles(config) : null;
|
|
599
|
+
};
|
|
600
|
+
refreshDetachedSlotStyles();
|
|
601
|
+
|
|
602
|
+
const reapplyDockStyles = (): void => {
|
|
603
|
+
applyDockStyles(shell, pushTrack, contentSlot, dockSlot, host, config, expanded, detachedSlotStyles);
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
// Scheme-dependent canvas tokens are baked to literals at layout time, so an
|
|
607
|
+
// OS dark-mode flip leaves a stale canvas until we recompute and re-apply.
|
|
608
|
+
let cleanupSchemeObserver: (() => void) | null = null;
|
|
609
|
+
const syncSchemeObserver = (): void => {
|
|
610
|
+
cleanupSchemeObserver?.();
|
|
611
|
+
cleanupSchemeObserver = null;
|
|
612
|
+
if (config?.launcher?.detachedPanel !== true || config?.colorScheme !== "auto") return;
|
|
613
|
+
cleanupSchemeObserver = createThemeObserver(() => {
|
|
614
|
+
refreshDetachedSlotStyles();
|
|
615
|
+
reapplyDockStyles();
|
|
616
|
+
});
|
|
617
|
+
};
|
|
618
|
+
|
|
521
619
|
const syncPushResizeObserver = (): void => {
|
|
522
620
|
disconnectResizeObserver();
|
|
523
621
|
if (resolveDockConfig(config).reveal !== "push") return;
|
|
524
622
|
if (typeof ResizeObserver === "undefined") return;
|
|
525
623
|
resizeObserver = new ResizeObserver(() => {
|
|
526
|
-
|
|
624
|
+
reapplyDockStyles();
|
|
527
625
|
});
|
|
528
626
|
resizeObserver.observe(shell);
|
|
529
627
|
};
|
|
@@ -531,7 +629,7 @@ const createDockedLayout = (target: HTMLElement, config?: AgentWidgetConfig): Wi
|
|
|
531
629
|
let heightChainChecked = false;
|
|
532
630
|
|
|
533
631
|
const layout = (): void => {
|
|
534
|
-
|
|
632
|
+
reapplyDockStyles();
|
|
535
633
|
syncPushResizeObserver();
|
|
536
634
|
// Check the height chain once, the first time the panel is actually shown
|
|
537
635
|
// in a layout that depends on it (mobile fullscreen is fixed-position and
|
|
@@ -562,6 +660,7 @@ const createDockedLayout = (target: HTMLElement, config?: AgentWidgetConfig): Wi
|
|
|
562
660
|
}
|
|
563
661
|
|
|
564
662
|
layout();
|
|
663
|
+
syncSchemeObserver();
|
|
565
664
|
|
|
566
665
|
return {
|
|
567
666
|
mode: "docked",
|
|
@@ -578,10 +677,14 @@ const createDockedLayout = (target: HTMLElement, config?: AgentWidgetConfig): Wi
|
|
|
578
677
|
if ((config?.launcher?.enabled ?? true) === false) {
|
|
579
678
|
expanded = true;
|
|
580
679
|
}
|
|
680
|
+
refreshDetachedSlotStyles();
|
|
581
681
|
layout();
|
|
682
|
+
syncSchemeObserver();
|
|
582
683
|
},
|
|
583
684
|
destroy() {
|
|
584
685
|
ownerWindow?.removeEventListener("resize", onViewportResize);
|
|
686
|
+
cleanupSchemeObserver?.();
|
|
687
|
+
cleanupSchemeObserver = null;
|
|
585
688
|
disconnectResizeObserver();
|
|
586
689
|
if (originalParent.isConnected) {
|
|
587
690
|
if (originalNextSibling && originalNextSibling.parentNode === originalParent) {
|
package/src/runtime/init.ts
CHANGED
|
@@ -19,71 +19,28 @@ const ensureTarget = (target: string | HTMLElement): HTMLElement => {
|
|
|
19
19
|
return target;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return new URL("../widget.css", import.meta.url).href;
|
|
27
|
-
}
|
|
28
|
-
} catch {
|
|
29
|
-
// Fallback for IIFE builds where CSS should be loaded separately
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
};
|
|
33
|
-
|
|
22
|
+
// CSS arrives via the installer's link[data-persona] or a consumer import of
|
|
23
|
+
// @runtypelabs/persona/widget.css; the widget never self-locates its stylesheet.
|
|
24
|
+
// A new URL(..., import.meta.url) here breaks consumer bundlers (webpack
|
|
25
|
+
// resolves the literal at build time), so shadow roots only clone the head link.
|
|
34
26
|
const mountStyles = (root: ShadowRoot | HTMLElement, ownerDocument: Document) => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!(root instanceof ShadowRoot)) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (root.querySelector('link[data-persona]')) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const globalLink = ownerDocument.head.querySelector<HTMLLinkElement>(
|
|
47
|
-
'link[data-persona]'
|
|
48
|
-
);
|
|
49
|
-
if (!globalLink) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
27
|
+
if (!(root instanceof ShadowRoot)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
52
30
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
31
|
+
if (root.querySelector('link[data-persona]')) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
56
34
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
link.href = href;
|
|
63
|
-
link.setAttribute("data-persona", "true");
|
|
64
|
-
root.insertBefore(link, root.firstChild);
|
|
65
|
-
} else {
|
|
66
|
-
adoptExistingStylesheet();
|
|
67
|
-
}
|
|
68
|
-
// If href is null (IIFE build), CSS should already be loaded globally
|
|
69
|
-
} else {
|
|
70
|
-
// For non-shadow DOM, check if CSS is already loaded
|
|
71
|
-
const existing = ownerDocument.head.querySelector<HTMLLinkElement>(
|
|
72
|
-
"link[data-persona]"
|
|
73
|
-
);
|
|
74
|
-
if (!existing) {
|
|
75
|
-
if (href) {
|
|
76
|
-
// ESM build - load CSS dynamically
|
|
77
|
-
const link = ownerDocument.createElement("link");
|
|
78
|
-
link.rel = "stylesheet";
|
|
79
|
-
link.href = href;
|
|
80
|
-
link.setAttribute("data-persona", "true");
|
|
81
|
-
ownerDocument.head.appendChild(link);
|
|
82
|
-
}
|
|
83
|
-
// IIFE build - CSS should be loaded via <link> tag before script
|
|
84
|
-
// If not found, we'll assume it's loaded globally or warn in dev
|
|
85
|
-
}
|
|
35
|
+
const globalLink = ownerDocument.head.querySelector<HTMLLinkElement>(
|
|
36
|
+
'link[data-persona]'
|
|
37
|
+
);
|
|
38
|
+
if (!globalLink) {
|
|
39
|
+
return;
|
|
86
40
|
}
|
|
41
|
+
|
|
42
|
+
const clonedLink = globalLink.cloneNode(true) as HTMLLinkElement;
|
|
43
|
+
root.insertBefore(clonedLink, root.firstChild);
|
|
87
44
|
};
|
|
88
45
|
|
|
89
46
|
export type AgentWidgetInitHandle = AgentWidgetController & { host: HTMLElement };
|
package/src/session.ts
CHANGED
|
@@ -28,6 +28,10 @@ import {
|
|
|
28
28
|
generateAssistantMessageId
|
|
29
29
|
} from "./utils/message-id";
|
|
30
30
|
import { IMAGE_ONLY_MESSAGE_FALLBACK_TEXT } from "./utils/content";
|
|
31
|
+
import {
|
|
32
|
+
buildArtifactRefRawContent,
|
|
33
|
+
resolveArtifactDisplayMode
|
|
34
|
+
} from "./utils/artifact-display";
|
|
31
35
|
import type {
|
|
32
36
|
VoiceProvider,
|
|
33
37
|
VoiceStatus,
|
|
@@ -2607,33 +2611,82 @@ export class AgentWidgetSession {
|
|
|
2607
2611
|
const id =
|
|
2608
2612
|
manual.id ||
|
|
2609
2613
|
`art_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
component: manual.component,
|
|
2629
|
-
props: manual.props ?? {}
|
|
2630
|
-
};
|
|
2614
|
+
const rec: PersonaArtifactRecord =
|
|
2615
|
+
manual.artifactType === "markdown"
|
|
2616
|
+
? {
|
|
2617
|
+
id,
|
|
2618
|
+
artifactType: "markdown",
|
|
2619
|
+
title: manual.title,
|
|
2620
|
+
status: "complete",
|
|
2621
|
+
markdown: manual.content,
|
|
2622
|
+
...(manual.file ? { file: manual.file } : {})
|
|
2623
|
+
}
|
|
2624
|
+
: {
|
|
2625
|
+
id,
|
|
2626
|
+
artifactType: "component",
|
|
2627
|
+
title: manual.title,
|
|
2628
|
+
status: "complete",
|
|
2629
|
+
component: manual.component,
|
|
2630
|
+
props: manual.props ?? {}
|
|
2631
|
+
};
|
|
2631
2632
|
this.artifacts.set(id, rec);
|
|
2632
2633
|
this.selectedArtifactId = id;
|
|
2633
2634
|
this.emitArtifactsState();
|
|
2635
|
+
if (manual.transcript !== false) {
|
|
2636
|
+
this.injectArtifactRefBlock(rec);
|
|
2637
|
+
}
|
|
2634
2638
|
return rec;
|
|
2635
2639
|
}
|
|
2636
2640
|
|
|
2641
|
+
/**
|
|
2642
|
+
* Injects (or refreshes) the in-thread artifact block for a programmatically
|
|
2643
|
+
* upserted artifact, matching the streamed UX: the resolved display mode
|
|
2644
|
+
* picks the component ("card"/"panel" → reference card, "inline" → inline
|
|
2645
|
+
* preview).
|
|
2646
|
+
*
|
|
2647
|
+
* Unlike the streamed path (which embeds content on `artifact_complete`),
|
|
2648
|
+
* the programmatic record is complete up front, so the final markdown /
|
|
2649
|
+
* file meta / component name + props are embedded immediately and the block
|
|
2650
|
+
* hydrates after a refresh without the original registry state.
|
|
2651
|
+
*
|
|
2652
|
+
* A re-upsert of the same id rebuilds the existing block's persisted
|
|
2653
|
+
* rawContent in place (the registry is not persisted, so hydration reads
|
|
2654
|
+
* these props); without this the block would keep the first version's
|
|
2655
|
+
* content after a refresh.
|
|
2656
|
+
*/
|
|
2657
|
+
private injectArtifactRefBlock(rec: PersonaArtifactRecord): void {
|
|
2658
|
+
const refId = `artifact-ref-${rec.id}`;
|
|
2659
|
+
const displayMode = resolveArtifactDisplayMode(
|
|
2660
|
+
this.config.features?.artifacts,
|
|
2661
|
+
rec.artifactType
|
|
2662
|
+
);
|
|
2663
|
+
const rawContent = buildArtifactRefRawContent(displayMode, {
|
|
2664
|
+
artifactId: rec.id,
|
|
2665
|
+
title: rec.title,
|
|
2666
|
+
artifactType: rec.artifactType,
|
|
2667
|
+
status: "complete",
|
|
2668
|
+
...(rec.file ? { file: rec.file } : {}),
|
|
2669
|
+
...(rec.component ? { component: rec.component } : {}),
|
|
2670
|
+
...(rec.props ? { componentProps: rec.props } : {}),
|
|
2671
|
+
...(rec.markdown !== undefined ? { markdown: rec.markdown } : {})
|
|
2672
|
+
});
|
|
2673
|
+
// Re-upsert: mutate the STORED message (inject stores a copy via
|
|
2674
|
+
// ensureSequence, so a local copy would be lost) and notify so the
|
|
2675
|
+
// transcript re-renders and persistence picks up the new version.
|
|
2676
|
+
const existing = this.messages.find((m) => m.id === refId);
|
|
2677
|
+
if (existing) {
|
|
2678
|
+
existing.rawContent = rawContent;
|
|
2679
|
+
existing.streaming = false;
|
|
2680
|
+
this.callbacks.onMessagesChanged([...this.messages]);
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
this.injectAssistantMessage({
|
|
2684
|
+
id: refId,
|
|
2685
|
+
content: "",
|
|
2686
|
+
rawContent
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2637
2690
|
private clearArtifactState(): void {
|
|
2638
2691
|
if (this.artifacts.size === 0 && this.selectedArtifactId === null) return;
|
|
2639
2692
|
this.artifacts.clear();
|
|
@@ -2657,7 +2710,8 @@ export class AgentWidgetSession {
|
|
|
2657
2710
|
artifactType: "markdown",
|
|
2658
2711
|
title: ev.title,
|
|
2659
2712
|
status: "streaming",
|
|
2660
|
-
markdown: ""
|
|
2713
|
+
markdown: "",
|
|
2714
|
+
...(ev.file ? { file: ev.file } : {})
|
|
2661
2715
|
});
|
|
2662
2716
|
} else {
|
|
2663
2717
|
this.artifacts.set(ev.id, {
|