@runtypelabs/persona 4.6.1 → 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.
Files changed (120) hide show
  1. package/dist/animations/glyph-cycle.cjs +2 -2
  2. package/dist/animations/glyph-cycle.d.cts +1 -1
  3. package/dist/animations/glyph-cycle.d.ts +1 -1
  4. package/dist/animations/glyph-cycle.js +2 -2
  5. package/dist/animations/{types-CSmiKRVa.d.cts → types-BsZtXPKK.d.cts} +43 -3
  6. package/dist/animations/{types-CSmiKRVa.d.ts → types-BsZtXPKK.d.ts} +43 -3
  7. package/dist/animations/wipe.cjs +2 -2
  8. package/dist/animations/wipe.d.cts +1 -1
  9. package/dist/animations/wipe.d.ts +1 -1
  10. package/dist/chunk-5EIIHQLQ.js +1 -0
  11. package/dist/codegen.cjs +12 -12
  12. package/dist/codegen.js +14 -14
  13. package/dist/index.cjs +91 -68
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +918 -174
  16. package/dist/index.d.ts +918 -174
  17. package/dist/index.global.js +81 -57
  18. package/dist/index.global.js.map +1 -1
  19. package/dist/index.js +89 -66
  20. package/dist/index.js.map +1 -1
  21. package/dist/install.global.js +1 -1
  22. package/dist/install.global.js.map +1 -1
  23. package/dist/launcher.global.js +3 -2
  24. package/dist/launcher.global.js.map +1 -1
  25. package/dist/markdown-parsers.js +24 -24
  26. package/dist/plugin-kit.cjs +1 -1
  27. package/dist/plugin-kit.js +1 -1
  28. package/dist/runtype-tts-entry-UJAEF7NZ.js +1 -0
  29. package/dist/runtype-tts.js +1 -1
  30. package/dist/session-reconnect-JKIJBHS5.js +1 -0
  31. package/dist/smart-dom-reader.cjs +17 -17
  32. package/dist/smart-dom-reader.d.cts +753 -15
  33. package/dist/smart-dom-reader.d.ts +753 -15
  34. package/dist/smart-dom-reader.js +17 -17
  35. package/dist/testing.cjs +3 -3
  36. package/dist/testing.js +3 -3
  37. package/dist/theme-editor-preview.cjs +81 -58
  38. package/dist/theme-editor-preview.d.cts +761 -15
  39. package/dist/theme-editor-preview.d.ts +761 -15
  40. package/dist/theme-editor-preview.js +81 -58
  41. package/dist/theme-editor.cjs +6 -6
  42. package/dist/theme-editor.d.cts +753 -15
  43. package/dist/theme-editor.d.ts +753 -15
  44. package/dist/theme-editor.js +10 -10
  45. package/dist/theme-reference.cjs +1 -1
  46. package/dist/theme-reference.d.cts +74 -0
  47. package/dist/theme-reference.d.ts +74 -0
  48. package/dist/theme-reference.js +1 -1
  49. package/dist/voice-worklet-player.cjs +2 -2
  50. package/dist/voice-worklet-player.js +2 -2
  51. package/dist/webmcp-polyfill.js +2 -2
  52. package/dist/widget.css +1 -1
  53. package/package.json +2 -3
  54. package/src/artifacts-session.test.ts +178 -0
  55. package/src/client.test.ts +500 -1
  56. package/src/client.ts +285 -93
  57. package/src/components/artifact-card.test.ts +333 -0
  58. package/src/components/artifact-card.ts +75 -28
  59. package/src/components/artifact-inline.test.ts +1328 -0
  60. package/src/components/artifact-inline.ts +920 -0
  61. package/src/components/artifact-pane.test.ts +1042 -0
  62. package/src/components/artifact-pane.ts +440 -131
  63. package/src/components/artifact-preview.test.ts +1155 -0
  64. package/src/components/artifact-preview.ts +994 -0
  65. package/src/components/pill-composer-builder.test.ts +6 -2
  66. package/src/components/pill-composer-builder.ts +6 -6
  67. package/src/components/reasoning-bubble.ts +1 -13
  68. package/src/components/registry.ts +38 -3
  69. package/src/components/tool-bubble.ts +1 -13
  70. package/src/defaults.ts +1 -0
  71. package/src/generated/runtype-openapi-contract.ts +55 -3
  72. package/src/index-core.ts +20 -1
  73. package/src/index.ts +8 -0
  74. package/src/markdown-parsers-loader.test.ts +158 -0
  75. package/src/markdown-parsers-loader.ts +74 -9
  76. package/src/runtime/host-layout.test.ts +163 -0
  77. package/src/runtime/host-layout.ts +110 -7
  78. package/src/runtime/init.ts +18 -61
  79. package/src/runtime/persist-state.test.ts +118 -0
  80. package/src/session.ts +76 -22
  81. package/src/styles/widget.css +773 -26
  82. package/src/theme-editor/preview.ts +2 -0
  83. package/src/theme-editor/sections.test.ts +26 -1
  84. package/src/theme-editor/sections.ts +10 -2
  85. package/src/theme-reference.ts +2 -2
  86. package/src/tool-call-display-defaults.test.ts +1 -0
  87. package/src/types/theme.ts +77 -0
  88. package/src/types.ts +516 -17
  89. package/src/ui.artifact-pane-gating.test.ts +636 -0
  90. package/src/ui.component-directive.test.ts +104 -0
  91. package/src/ui.composer-bar.test.ts +60 -2
  92. package/src/ui.detached-panel.test.ts +1049 -0
  93. package/src/ui.scroll-additive.test.ts +1 -1
  94. package/src/ui.streaming-coalescing.test.ts +312 -0
  95. package/src/ui.tool-display.test.ts +51 -0
  96. package/src/ui.ts +933 -214
  97. package/src/utils/artifact-custom-actions.ts +128 -0
  98. package/src/utils/artifact-display.test.ts +42 -0
  99. package/src/utils/artifact-display.ts +84 -0
  100. package/src/utils/artifact-file.test.ts +116 -0
  101. package/src/utils/artifact-file.ts +117 -0
  102. package/src/utils/artifact-gate.test.ts +112 -5
  103. package/src/utils/artifact-gate.ts +39 -14
  104. package/src/utils/artifact-loading-status.ts +55 -0
  105. package/src/utils/artifact-status-label.ts +190 -0
  106. package/src/utils/buttons.ts +7 -1
  107. package/src/utils/code-highlight.test.ts +186 -0
  108. package/src/utils/code-highlight.ts +400 -0
  109. package/src/utils/icons.ts +2 -0
  110. package/src/utils/roving-tablist.test.ts +152 -0
  111. package/src/utils/roving-tablist.ts +111 -0
  112. package/src/utils/spinner.ts +45 -0
  113. package/src/utils/theme.test.ts +48 -0
  114. package/src/utils/theme.ts +7 -0
  115. package/src/utils/tokens.ts +91 -0
  116. package/src/utils/tool-loading-animation.test.ts +32 -0
  117. package/src/utils/tool-loading-animation.ts +24 -0
  118. package/dist/chunk-DFBSCFYN.js +0 -1
  119. package/dist/runtype-tts-entry-HFUV2UF7.js +0 -1
  120. package/dist/session-reconnect-U77QFUR7.js +0 -1
@@ -0,0 +1,45 @@
1
+ import { cx } from "./dom";
2
+
3
+ const SVG_NS = "http://www.w3.org/2000/svg";
4
+
5
+ /**
6
+ * Reusable icon spinner: a small SVG ring with a faint full track plus a
7
+ * rotating arc.
8
+ *
9
+ * Icon-first loading is the norm for preview surfaces (Sandpack, YouTube/Figma/
10
+ * CodePen embeds, ChatGPT/Claude/v0) and every major design system (Apple HIG,
11
+ * Material, Carbon, Polaris, Geist); text-only "Loading…" is used by none, and
12
+ * HIG explicitly warns against the vague word "loading". So the default preview
13
+ * indicator is this spinner, with any work-naming label added only later as an
14
+ * escalation. The arc spins via a GPU-friendly `transform: rotate` keyframe
15
+ * (see `.persona-spinner` in widget.css); under `prefers-reduced-motion` the
16
+ * rotation stops and the static arc still reads as a progress ring.
17
+ *
18
+ * Structure and animation live in CSS (class `persona-spinner`); geometry here
19
+ * is viewBox-relative so the rendered size follows `--persona-artifact-spinner-size`.
20
+ */
21
+ export function createSpinner(className?: string): SVGSVGElement {
22
+ const svg = document.createElementNS(SVG_NS, "svg");
23
+ svg.setAttribute("class", cx("persona-spinner", className));
24
+ svg.setAttribute("viewBox", "0 0 24 24");
25
+ // Decorative: the loading state is announced by surrounding copy/labels, so
26
+ // the spinner itself must not add redundant noise to the accessibility tree.
27
+ svg.setAttribute("aria-hidden", "true");
28
+ svg.setAttribute("focusable", "false");
29
+
30
+ const track = document.createElementNS(SVG_NS, "circle");
31
+ track.setAttribute("class", "persona-spinner-track");
32
+ track.setAttribute("cx", "12");
33
+ track.setAttribute("cy", "12");
34
+ track.setAttribute("r", "9");
35
+
36
+ const arc = document.createElementNS(SVG_NS, "circle");
37
+ arc.setAttribute("class", "persona-spinner-arc");
38
+ arc.setAttribute("cx", "12");
39
+ arc.setAttribute("cy", "12");
40
+ arc.setAttribute("r", "9");
41
+
42
+ svg.appendChild(track);
43
+ svg.appendChild(arc);
44
+ return svg;
45
+ }
@@ -192,6 +192,38 @@ describe('theme utils', () => {
192
192
  expect(surfaceVars['--persona-artifact-toolbar-bg']).toBe('#f9fafb');
193
193
  });
194
194
 
195
+ it('maps artifact inline chrome tokens to dedicated CSS variables (with semantic refs)', () => {
196
+ const theme = createTheme({
197
+ components: {
198
+ artifact: {
199
+ inline: {
200
+ background: 'semantic.colors.surface',
201
+ border: '1px solid #e5e7eb',
202
+ borderRadius: '12px',
203
+ chromeBackground: 'semantic.colors.container',
204
+ chromeBorder: '#e5e7eb',
205
+ titleColor: 'palette.colors.gray.900',
206
+ mutedColor: 'palette.colors.gray.500',
207
+ frameHeight: '400px',
208
+ },
209
+ },
210
+ },
211
+ } as any);
212
+
213
+ const cssVars = themeToCssVariables(theme);
214
+
215
+ // semantic.* refs resolve to concrete colors; plain values pass through.
216
+ // surface and container both default to gray.50.
217
+ expect(cssVars['--persona-artifact-inline-bg']).toBe('#f9fafb');
218
+ expect(cssVars['--persona-artifact-inline-border']).toBe('1px solid #e5e7eb');
219
+ expect(cssVars['--persona-artifact-inline-radius']).toBe('12px');
220
+ expect(cssVars['--persona-artifact-inline-chrome-bg']).toBe('#f9fafb');
221
+ expect(cssVars['--persona-artifact-inline-chrome-border']).toBe('#e5e7eb');
222
+ expect(cssVars['--persona-artifact-inline-title-color']).toBe('#111827');
223
+ expect(cssVars['--persona-artifact-inline-muted-color']).toBe('#6b7280');
224
+ expect(cssVars['--persona-artifact-inline-frame-height']).toBe('400px');
225
+ });
226
+
195
227
  it('maps component bubble shadow tokens to consumer CSS variables', () => {
196
228
  const cfg = {
197
229
  colorScheme: 'light' as const,
@@ -305,4 +337,20 @@ describe('theme utils', () => {
305
337
  '0 1px 2px rgba(255,0,0,0.5)'
306
338
  );
307
339
  });
340
+
341
+ it('stamps the resolved color scheme on the root so scheme-scoped CSS (syntax palette) follows the widget, not the OS', () => {
342
+ const el = document.createElement('div');
343
+ applyThemeVariables(el, { colorScheme: 'light' });
344
+ expect(el.getAttribute('data-persona-color-scheme')).toBe('light');
345
+ applyThemeVariables(el, { colorScheme: 'dark' });
346
+ expect(el.getAttribute('data-persona-color-scheme')).toBe('dark');
347
+ });
348
+
349
+ it('maps components.code.background to --persona-code-bg', () => {
350
+ const theme = createTheme({
351
+ components: { code: { background: '#fafafa' } },
352
+ });
353
+ const cssVars = themeToCssVariables(theme);
354
+ expect(cssVars['--persona-code-bg']).toBe('#fafafa');
355
+ });
308
356
  });
@@ -194,6 +194,13 @@ export const applyThemeVariables = (
194
194
  for (const [name, value] of Object.entries(cssVars)) {
195
195
  element.style.setProperty(name, value);
196
196
  }
197
+
198
+ // Stamp the resolved scheme so stylesheet rules can key scheme-specific
199
+ // defaults (e.g. the syntax-highlight palette) off the widget's own color
200
+ // scheme instead of the OS prefers-color-scheme, which diverges whenever a
201
+ // host pins colorScheme. Re-applied on every theme application, so live
202
+ // config updates and theme-observer callbacks keep it current.
203
+ element.setAttribute("data-persona-color-scheme", getColorScheme(config));
197
204
  };
198
205
 
199
206
  export const createThemeObserver = (
@@ -13,6 +13,11 @@ import {
13
13
  DEFAULT_FLOATING_LAUNCHER_WIDTH,
14
14
  } from '../defaults';
15
15
 
16
+ // Detached/docked panel defaults, shared by the panel token defaults, the alias
17
+ // fallbacks below, host-layout, the theme editor, and the artifact gate.
18
+ export const DEFAULT_PANEL_INSET = '16px';
19
+ export const DEFAULT_PANEL_CANVAS_BACKGROUND = 'transparent';
20
+
16
21
  export const DEFAULT_PALETTE = {
17
22
  colors: {
18
23
  primary: {
@@ -287,6 +292,8 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
287
292
  maxHeight: 'calc(100vh - 80px)',
288
293
  borderRadius: 'palette.radius.xl',
289
294
  shadow: 'palette.shadows.xl',
295
+ inset: DEFAULT_PANEL_INSET,
296
+ canvasBackground: DEFAULT_PANEL_CANVAS_BACKGROUND,
290
297
  },
291
298
  header: {
292
299
  // Header role: solid primary
@@ -742,6 +749,10 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
742
749
  cssVars['--persona-components-panel-shadow'] ??
743
750
  cssVars['--persona-palette-shadows-xl'] ??
744
751
  '0 25px 50px -12px rgba(0, 0, 0, 0.25)';
752
+ cssVars['--persona-panel-inset'] =
753
+ cssVars['--persona-components-panel-inset'] ?? DEFAULT_PANEL_INSET;
754
+ cssVars['--persona-panel-canvas-bg'] =
755
+ cssVars['--persona-components-panel-canvasBackground'] ?? DEFAULT_PANEL_CANVAS_BACKGROUND;
745
756
  cssVars['--persona-launcher-shadow'] =
746
757
  cssVars['--persona-components-launcher-shadow'] ??
747
758
  '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)';
@@ -967,6 +978,10 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
967
978
  if (t.iconBorder) cssVars['--persona-artifact-toolbar-icon-border'] = t.iconBorder;
968
979
  if (t.toggleGroupGap) cssVars['--persona-artifact-toolbar-toggle-group-gap'] = t.toggleGroupGap;
969
980
  if (t.toggleBorderRadius) cssVars['--persona-artifact-toolbar-toggle-radius'] = t.toggleBorderRadius;
981
+ if (t.toggleGroupPadding) cssVars['--persona-artifact-toolbar-toggle-group-padding'] = t.toggleGroupPadding;
982
+ if (t.toggleGroupBorder) cssVars['--persona-artifact-toolbar-toggle-group-border'] = t.toggleGroupBorder;
983
+ if (t.toggleGroupBorderRadius) cssVars['--persona-artifact-toolbar-toggle-group-radius'] = t.toggleGroupBorderRadius;
984
+ if (t.toggleGroupBackground) cssVars['--persona-artifact-toolbar-toggle-group-bg'] = resolveTokenValue(theme, t.toggleGroupBackground) ?? t.toggleGroupBackground;
970
985
  if (t.copyBackground) cssVars['--persona-artifact-toolbar-copy-bg'] = t.copyBackground;
971
986
  if (t.copyBorder) cssVars['--persona-artifact-toolbar-copy-border'] = t.copyBorder;
972
987
  if (t.copyColor) cssVars['--persona-artifact-toolbar-copy-color'] = t.copyColor;
@@ -1015,6 +1030,80 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
1015
1030
  cssVars['--persona-artifact-toolbar-bg'] = toolbarBg;
1016
1031
  }
1017
1032
  }
1033
+ if (artifact?.card) {
1034
+ const t = artifact.card;
1035
+ if (t.background) cssVars['--persona-artifact-card-bg'] = t.background;
1036
+ if (t.border) cssVars['--persona-artifact-card-border'] = t.border;
1037
+ if (t.borderRadius) cssVars['--persona-artifact-card-radius'] = t.borderRadius;
1038
+ if (t.hoverBackground) cssVars['--persona-artifact-card-hover-bg'] = t.hoverBackground;
1039
+ if (t.hoverBorderColor) cssVars['--persona-artifact-card-hover-border'] = t.hoverBorderColor;
1040
+ }
1041
+ if (artifact?.inline) {
1042
+ const t = artifact.inline;
1043
+ if (t.background) {
1044
+ cssVars['--persona-artifact-inline-bg'] =
1045
+ resolveTokenValue(theme, t.background) ?? t.background;
1046
+ }
1047
+ if (t.border) cssVars['--persona-artifact-inline-border'] = t.border;
1048
+ if (t.borderRadius) cssVars['--persona-artifact-inline-radius'] = t.borderRadius;
1049
+ if (t.chromeBackground) {
1050
+ cssVars['--persona-artifact-inline-chrome-bg'] =
1051
+ resolveTokenValue(theme, t.chromeBackground) ?? t.chromeBackground;
1052
+ }
1053
+ if (t.chromeBorder) {
1054
+ cssVars['--persona-artifact-inline-chrome-border'] =
1055
+ resolveTokenValue(theme, t.chromeBorder) ?? t.chromeBorder;
1056
+ }
1057
+ if (t.titleColor) {
1058
+ cssVars['--persona-artifact-inline-title-color'] =
1059
+ resolveTokenValue(theme, t.titleColor) ?? t.titleColor;
1060
+ }
1061
+ if (t.mutedColor) {
1062
+ cssVars['--persona-artifact-inline-muted-color'] =
1063
+ resolveTokenValue(theme, t.mutedColor) ?? t.mutedColor;
1064
+ }
1065
+ if (t.frameHeight) cssVars['--persona-artifact-inline-frame-height'] = t.frameHeight;
1066
+ }
1067
+
1068
+ // Code (syntax-highlighted artifact source view) tokens.
1069
+ const code = components?.code;
1070
+ if (code) {
1071
+ if (code.keywordColor) cssVars['--persona-code-keyword-color'] = code.keywordColor;
1072
+ if (code.stringColor) cssVars['--persona-code-string-color'] = code.stringColor;
1073
+ if (code.commentColor) cssVars['--persona-code-comment-color'] = code.commentColor;
1074
+ if (code.numberColor) cssVars['--persona-code-number-color'] = code.numberColor;
1075
+ if (code.tagColor) cssVars['--persona-code-tag-color'] = code.tagColor;
1076
+ if (code.attrColor) cssVars['--persona-code-attr-color'] = code.attrColor;
1077
+ if (code.propertyColor) cssVars['--persona-code-property-color'] = code.propertyColor;
1078
+ if (code.lineNumberColor) cssVars['--persona-code-line-number-color'] = code.lineNumberColor;
1079
+ if (code.gutterBorderColor) cssVars['--persona-code-gutter-border-color'] = code.gutterBorderColor;
1080
+ if (code.background)
1081
+ cssVars['--persona-code-bg'] = resolveTokenValue(theme, code.background) ?? code.background;
1082
+ }
1083
+
1084
+ // Interactive-state defaults. The default preset resolves container === surface,
1085
+ // which turns every hover/active rule that falls back to --persona-container
1086
+ // into a visual no-op; anchor those states one gray step down in that case.
1087
+ // Component config emitted above must keep winning, so only fill vars not set.
1088
+ const stateSurface = cssVars['--persona-surface'];
1089
+ const stateContainer = cssVars['--persona-container'];
1090
+ const gray100 = cssVars['--persona-palette-colors-gray-100'] ?? '#f3f4f6';
1091
+ const gray200 = cssVars['--persona-palette-colors-gray-200'] ?? '#e5e7eb';
1092
+ const gray300 = cssVars['--persona-palette-colors-gray-300'] ?? '#d1d5db';
1093
+ const flatTheme = !stateContainer || stateContainer === stateSurface;
1094
+ const hoverBgDefault = flatTheme ? gray100 : stateContainer;
1095
+ const activeBgDefault = flatTheme ? gray200 : stateContainer;
1096
+ cssVars['--persona-icon-btn-hover-bg'] = cssVars['--persona-icon-btn-hover-bg'] ?? hoverBgDefault;
1097
+ cssVars['--persona-icon-btn-active-bg'] = cssVars['--persona-icon-btn-active-bg'] ?? activeBgDefault;
1098
+ if (flatTheme) {
1099
+ cssVars['--persona-icon-btn-active-border'] =
1100
+ cssVars['--persona-icon-btn-active-border'] ?? gray300;
1101
+ }
1102
+ cssVars['--persona-label-btn-hover-bg'] = cssVars['--persona-label-btn-hover-bg'] ?? hoverBgDefault;
1103
+ cssVars['--persona-artifact-tab-hover-bg'] =
1104
+ cssVars['--persona-artifact-tab-hover-bg'] ?? hoverBgDefault;
1105
+ cssVars['--persona-artifact-card-hover-bg'] =
1106
+ cssVars['--persona-artifact-card-hover-bg'] ?? hoverBgDefault;
1018
1107
 
1019
1108
  return cssVars;
1020
1109
  }
@@ -1041,6 +1130,8 @@ export const THEME_ZONES = {
1041
1130
  container: 'Main widget container',
1042
1131
  'artifact-pane': 'Artifact sidebar',
1043
1132
  'artifact-toolbar': 'Artifact toolbar',
1133
+ 'artifact-inline': 'Inline artifact block',
1134
+ 'artifact-inline-chrome': 'Inline artifact title bar',
1044
1135
  } as const;
1045
1136
 
1046
1137
  export type ThemeZone = keyof typeof THEME_ZONES;
@@ -0,0 +1,32 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, it } from "vitest";
4
+ import { appendCharSpans } from "./tool-loading-animation";
5
+
6
+ const NBSP = String.fromCharCode(0xa0);
7
+
8
+ describe("appendCharSpans", () => {
9
+ it("wraps each character in a persona-tool-char span with a staggered index", () => {
10
+ const container = document.createElement("div");
11
+ const next = appendCharSpans(container, "ab", 0);
12
+
13
+ const spans = container.querySelectorAll(".persona-tool-char");
14
+ expect(spans.length).toBe(2);
15
+ expect((spans[0] as HTMLElement).style.getPropertyValue("--char-index")).toBe("0");
16
+ expect((spans[1] as HTMLElement).style.getPropertyValue("--char-index")).toBe("1");
17
+ expect(spans[0].textContent).toBe("a");
18
+ expect(spans[1].textContent).toBe("b");
19
+ expect(next).toBe(2);
20
+ });
21
+
22
+ it("renders spaces as non-breaking spaces and honors the start index", () => {
23
+ const container = document.createElement("div");
24
+ const next = appendCharSpans(container, "a b", 5);
25
+
26
+ const spans = container.querySelectorAll(".persona-tool-char");
27
+ expect(spans.length).toBe(3);
28
+ expect((spans[0] as HTMLElement).style.getPropertyValue("--char-index")).toBe("5");
29
+ expect(spans[1].textContent).toBe(NBSP);
30
+ expect(next).toBe(8);
31
+ });
32
+ });
@@ -0,0 +1,24 @@
1
+ import { createElement } from "./dom";
2
+
3
+ /**
4
+ * Append text as individual animated character spans.
5
+ * Each character becomes a `persona-tool-char` span carrying a `--char-index`
6
+ * custom property so the shimmer/rainbow keyframes can stagger their delay.
7
+ * Spaces are rendered as non-breaking spaces so they animate like any other
8
+ * character. Returns the next available character index.
9
+ */
10
+ export const appendCharSpans = (
11
+ container: HTMLElement,
12
+ text: string,
13
+ startIndex: number
14
+ ): number => {
15
+ let idx = startIndex;
16
+ for (const char of text) {
17
+ const span = createElement("span", "persona-tool-char");
18
+ span.style.setProperty("--char-index", String(idx));
19
+ span.textContent = char === " " ? "\u00A0" : char;
20
+ container.appendChild(span);
21
+ idx++;
22
+ }
23
+ return idx;
24
+ };
@@ -1 +0,0 @@
1
- var a=class{constructor(e=24e3,t={}){this.ctx=null;this.nextStartTime=0;this.activeSources=[];this.finishedCallbacks=[];this.startedCallbacks=[];this.playing=!1;this.streamEnded=!1;this.pendingCount=0;this.started=!1;this.userPaused=!1;this.pendingBuffers=[];this.pendingSamples=0;this.remainder=null;var s;this.sampleRate=e;let i=Math.max(0,(s=t.prebufferMs)!=null?s:0);this.waterlineSamples=Math.round(e*i/1e3),this.buffering=this.waterlineSamples>0}ensureContext(){if(!this.ctx){let t=typeof window!="undefined"?window:void 0;if(!t)throw new Error("AudioPlaybackManager requires a browser environment");let i=t.AudioContext||t.webkitAudioContext;this.ctx=new i({sampleRate:this.sampleRate})}let e=this.ctx;return e.state==="suspended"&&!this.userPaused&&e.resume(),e}enqueue(e){if(e.length===0)return;let t=e;if(this.remainder){let s=new Uint8Array(this.remainder.length+e.length);s.set(this.remainder),s.set(e,this.remainder.length),t=s,this.remainder=null}if(t.length%2!==0&&(this.remainder=new Uint8Array([t[t.length-1]]),t=t.subarray(0,t.length-1)),t.length===0)return;let i=this.pcmToFloat32(t);i.length!==0&&(this.buffering?(this.pendingBuffers.push(i),this.pendingSamples+=i.length,this.pendingSamples>=this.waterlineSamples&&this.releaseBuffer()):this.scheduleSamples(i))}markStreamEnd(){this.pendingBuffers.length>0&&this.releaseBuffer(),this.streamEnded=!0,this.checkFinished()}flush(){for(let e of this.activeSources)try{e.stop(),e.disconnect()}catch{}this.activeSources=[],this.pendingCount=0,this.nextStartTime=0,this.playing=!1,this.streamEnded=!1,this.finishedCallbacks=[],this.startedCallbacks=[],this.remainder=null,this.pendingBuffers=[],this.pendingSamples=0,this.buffering=this.waterlineSamples>0,this.started=!1}isPlaying(){return this.playing}onFinished(e){this.finishedCallbacks.push(e)}onStarted(e){this.startedCallbacks.push(e)}pause(){this.userPaused=!0,this.ctx&&this.ctx.state==="running"&&this.ctx.suspend()}resume(){this.userPaused=!1,this.ctx&&this.ctx.state==="suspended"&&this.ctx.resume()}async destroy(){this.flush(),this.ctx&&(await this.ctx.close(),this.ctx=null)}releaseBuffer(){this.buffering=!1;let e=this.pendingBuffers;this.pendingBuffers=[],this.pendingSamples=0;for(let t of e)this.scheduleSamples(t)}scheduleSamples(e){if(e.length===0)return;let t=this.ensureContext(),i=t.createBuffer(1,e.length,this.sampleRate);i.getChannelData(0).set(e);let s=t.createBufferSource();s.buffer=i,s.connect(t.destination);let n=t.currentTime;if(this.nextStartTime===0?this.nextStartTime=n:this.nextStartTime<n&&(this.nextStartTime=n,this.waterlineSamples>0&&(this.buffering=!0)),s.start(this.nextStartTime),this.nextStartTime+=i.duration,this.activeSources.push(s),this.pendingCount++,this.playing=!0,!this.started){this.started=!0;let r=this.startedCallbacks.slice();this.startedCallbacks=[];for(let h of r)h()}s.onended=()=>{let r=this.activeSources.indexOf(s);r!==-1&&this.activeSources.splice(r,1),this.pendingCount--,this.checkFinished()}}checkFinished(){if(this.streamEnded&&this.pendingCount<=0&&this.pendingBuffers.length===0){this.playing=!1,this.streamEnded=!1;let e=this.finishedCallbacks.slice();this.finishedCallbacks=[];for(let t of e)t()}}pcmToFloat32(e){let t=Math.floor(e.length/2),i=new Float32Array(t),s=new DataView(e.buffer,e.byteOffset,e.byteLength);for(let n=0;n<t;n++){let r=s.getInt16(n*2,!0);i[n]=r/32768}return i}};export{a};
@@ -1 +0,0 @@
1
- import{a as c}from"./chunk-DFBSCFYN.js";function g(o){return o.replace(/\/+$/,"")}var m=class{constructor(e){this.opts=e;this.id="runtype-tts";this.supportsPause=!0;this.player=null;this.playerPromise=null;this.generation=0}ensurePlayer(){var e,t;return(t=this.playerPromise)!=null?t:this.playerPromise=Promise.resolve(this.opts.createPlaybackEngine?this.opts.createPlaybackEngine():new c(24e3,{prebufferMs:(e=this.opts.prebufferMs)!=null?e:200})).then(r=>this.player=r)}speak(e,t){let r=++this.generation;this.run(r,e,t)}async run(e,t,r){var i,a,p,h;try{let n=await this.ensurePlayer();if(e!==this.generation)return;n.flush(),n.resume(),n.onStarted(()=>{var s;e===this.generation&&((s=r.onStart)==null||s.call(r))}),n.onFinished(()=>{var s;e===this.generation&&((s=r.onEnd)==null||s.call(r))});let u=`${g(this.opts.host)}/v1/agents/${encodeURIComponent(this.opts.agentId)}/speak`,y=await fetch(u,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.opts.clientToken}`},body:JSON.stringify({text:t.text,voice:(i=t.voice)!=null?i:this.opts.voice,format:"pcm"})});if(e!==this.generation)return;if(!y.ok||!y.body)throw new Error(await S(y));let v=y.body.getReader();for(;;){let{done:s,value:d}=await v.read();if(e!==this.generation){await v.cancel().catch(()=>{});return}if(s)break;d&&d.byteLength>0&&n.enqueue(d)}n.markStreamEnd()}catch(n){if(e!==this.generation)return;let u=n instanceof Error?n:new Error(String(n));(p=(a=this.opts).onError)==null||p.call(a,u),(h=r.onError)==null||h.call(r,u)}}pause(){var e;(e=this.player)==null||e.pause()}resume(){var e;(e=this.player)==null||e.resume()}stop(){var e;this.generation++,(e=this.player)==null||e.flush()}destroy(){var e;this.generation++,(e=this.player)==null||e.destroy(),this.player=null,this.playerPromise=null}};async function S(o){var e,t;try{let r=await o.json();return r.detail?`${(e=r.error)!=null?e:`Runtype TTS ${o.status}`}: ${r.detail}`:(t=r.error)!=null?t:`Runtype TTS request failed (${o.status})`}catch{return`Runtype TTS request failed (${o.status})`}}var f=class{constructor(e,t,r={}){this.primary=e;this.fallback=t;this.options=r;this.id="fallback";this.active=e}get supportsPause(){return this.active.supportsPause}speak(e,t){this.active=this.primary;let r=!1;this.primary.speak(e,{onStart:()=>{var i;r=!0,(i=t.onStart)==null||i.call(t)},onEnd:()=>{var i;return(i=t.onEnd)==null?void 0:i.call(t)},onError:i=>{var a,p,h;if(r){(a=t.onError)==null||a.call(t,i);return}(h=(p=this.options).onFallback)==null||h.call(p,i),this.active=this.fallback,this.fallback.speak(e,t)}})}pause(){this.active.pause()}resume(){this.active.resume()}stop(){this.active.stop()}destroy(){var e,t,r,i;(t=(e=this.primary).destroy)==null||t.call(e),(i=(r=this.fallback).destroy)==null||i.call(r)}};export{f as FallbackSpeechEngine,m as RuntypeSpeechEngine};
@@ -1 +0,0 @@
1
- var k=[1e3,2e3,4e3,8e3,8e3];function T(e){let t=0,r=null,i=null,g=!1,b=()=>{if(i&&(clearTimeout(i),i=null),r){let n=r;r=null,n()}},v=()=>{let n=e.getStatus();n!=="resuming"&&n!=="paused"||b()},p=()=>{typeof document!="undefined"&&document.visibilityState==="hidden"||v()},R=()=>{v()},C=()=>{g||(typeof document!="undefined"&&document.addEventListener("visibilitychange",p),typeof window!="undefined"&&window.addEventListener("online",R),g=!0)},m=()=>{g&&(typeof document!="undefined"&&document.removeEventListener("visibilitychange",p),typeof window!="undefined"&&window.removeEventListener("online",R),g=!1)},M=n=>new Promise(s=>{r=s,i=setTimeout(()=>{i=null,r=null,s()},n)}),E=()=>{var d;let n=e.getResumable();e.clearResumable(),e.setReconnecting(!1),t=0,m(),e.setAbortController(null);let s=!1;for(let c of e.getMessages())c.streaming&&(c.streaming=!1,s=!0);let l=e.buildErrorContent("Connection lost and the response could not be resumed.");l?e.appendMessage({id:`reconnect-failed-${(d=n==null?void 0:n.executionId)!=null?d:e.nextSequence()}`,role:"assistant",content:l,createdAt:new Date().toISOString(),streaming:!1,sequence:e.nextSequence()}):s&&e.notifyMessagesChanged(),e.setStreaming(!1),e.setStatus("idle"),e.onError(new Error("Durable session reconnect failed."))},x=async()=>{var d,c,y,S,w,A;let n=(c=(d=e.config.reconnect)==null?void 0:d.backoffMs)!=null?c:k,s=(S=(y=e.config.reconnect)==null?void 0:y.maxAttempts)!=null?S:n.length,l=e.config.reconnectStream;if(!l){e.setReconnecting(!1);return}for(;e.getResumable()&&t<s;){t+=1,e.setStatus("resuming");let o=e.getResumable(),I=o.lastEventId,u=new AbortController;e.setAbortController(u),e.emitReconnect({phase:"resuming",handle:o,attempt:t});let f=null;try{let a=await l({executionId:o.executionId,after:o.lastEventId,signal:u.signal});a&&a.ok&&a.body&&(f=a.body)}catch{f=null}if(u.signal.aborted)return;if(f){let a=(w=e.getMessages().find(W=>W.id===o.assistantMessageId))==null?void 0:w.content,L=typeof a=="string"?a:"";try{await e.resumeConnect(f,o.assistantMessageId,L)}catch{}if(u.signal.aborted)return;if(!e.getResumable()){e.setReconnecting(!1),t=0,m(),e.emitReconnect({phase:"resumed",handle:o});return}e.getResumable().lastEventId!==I&&(t=0)}if(e.getResumable()&&t<s&&(e.setStatus("paused"),await M((A=n[Math.min(t-1,n.length-1)])!=null?A:1e3),u.signal.aborted))return}e.getResumable()&&E()};return{begin(){t=0,C(),x()},teardown(){i&&(clearTimeout(i),i=null),r=null,t=0,m()},wake:b}}export{T as createReconnectController};