@kitn.ai/ui 0.18.2 → 0.20.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 (258) hide show
  1. package/LICENSE +6 -0
  2. package/NOTICE +54 -0
  3. package/dist/components/audio-visualizer/audio-visualizer.voice-fixture.d.ts +59 -0
  4. package/dist/components/audio-visualizer/aurora.glsl.d.ts +114 -0
  5. package/dist/components/audio-visualizer/index.d.ts +155 -0
  6. package/dist/components/audio-visualizer/shader-canvas.d.ts +130 -0
  7. package/dist/components/audio-visualizer/sizes.d.ts +25 -0
  8. package/dist/components/audio-visualizer/variant-aurora.d.ts +89 -0
  9. package/dist/components/audio-visualizer/variant-bar.d.ts +46 -0
  10. package/dist/components/audio-visualizer/variant-custom.d.ts +73 -0
  11. package/dist/components/audio-visualizer/variant-grid.d.ts +22 -0
  12. package/dist/components/audio-visualizer/variant-radial.d.ts +13 -0
  13. package/dist/components/audio-visualizer/variant-wave.d.ts +11 -0
  14. package/dist/components/audio-visualizer/wave.glsl.d.ts +16 -0
  15. package/dist/components/thread.d.ts +54 -0
  16. package/dist/create-tween-hvpmSuwW.js +7 -0
  17. package/dist/custom-elements.json +477 -0
  18. package/dist/elements/agent-card.js +1 -1
  19. package/dist/elements/artifact.js +1 -1
  20. package/dist/elements/attachments.js +1 -1
  21. package/dist/elements/audio-visualizer.d.ts +1 -0
  22. package/dist/elements/audio-visualizer.js +1 -0
  23. package/dist/elements/autoloader.js +1 -1
  24. package/dist/elements/avatar.js +1 -1
  25. package/dist/elements/badge.js +1 -1
  26. package/dist/elements/button.js +1 -1
  27. package/dist/elements/card.js +1 -1
  28. package/dist/elements/cards.js +1 -1
  29. package/dist/elements/chain-of-thought.js +1 -1
  30. package/dist/elements/chat-scope-picker.js +1 -1
  31. package/dist/elements/chat-workspace.js +1 -1
  32. package/dist/elements/chat.js +1 -1
  33. package/dist/elements/checkpoint.js +1 -1
  34. package/dist/elements/choice.js +1 -1
  35. package/dist/elements/chunks/{Icon-C2LTALdM.js → Icon-Tj58ES_d.js} +1 -1
  36. package/dist/elements/chunks/action-icons-ns1Fnedj.js +1 -0
  37. package/dist/elements/chunks/arrow-left-DQocUFvy.js +1 -0
  38. package/dist/elements/chunks/{attachments-DLJcWCDV.js → attachments-mCB5F1Fq.js} +1 -1
  39. package/dist/elements/chunks/audio-visualizer-BqNy7dfm.js +1 -0
  40. package/dist/elements/chunks/{badge-BTNZTODL.js → badge-B4Zuajl2.js} +1 -1
  41. package/dist/elements/chunks/{button-DBDZebGr.js → button-ZWNif3--.js} +1 -1
  42. package/dist/elements/chunks/{card-YfCyI4fH.js → card-BkHbp4BA.js} +1 -1
  43. package/dist/elements/chunks/chat-thread-CkRN6pZ7.js +1 -0
  44. package/dist/elements/chunks/check-B9-gn6SS.js +1 -0
  45. package/dist/elements/chunks/chevron-down-BNsxc2hk.js +1 -0
  46. package/dist/elements/chunks/chevron-right-Hv00StBO.js +1 -0
  47. package/dist/elements/chunks/circle-0e3KJTDr.js +1 -0
  48. package/dist/elements/chunks/circle-check-BNAS0JEy.js +1 -0
  49. package/dist/elements/chunks/{circle-x-pDtzlghQ.js → circle-x-B7faJFlY.js} +1 -1
  50. package/dist/elements/chunks/{code-block-DZ6mz6vs.js → code-block-BFUHn6Va.js} +1 -1
  51. package/dist/elements/chunks/collapsible-DM1Ckd4e.js +1 -0
  52. package/dist/elements/chunks/{composer-BZyPU6gD.js → composer-BguS0QL_.js} +7 -3
  53. package/dist/elements/chunks/{context-Cx6eQA_G.js → context-Cb7k4F-v.js} +1 -1
  54. package/dist/elements/chunks/{conversation-list-C4xTQSpT.js → conversation-list-CvoAkWhN.js} +1 -1
  55. package/dist/elements/chunks/create-tween-oVvAIG8r.js +7 -0
  56. package/dist/elements/chunks/{default-input-BZcwyKcP.js → default-input-Cqqilg9t.js} +1 -1
  57. package/dist/elements/chunks/define-D1din7Ld.js +1 -0
  58. package/dist/elements/chunks/{disclosure-BcBs--Jf.js → disclosure-CIZ5c8nO.js} +1 -1
  59. package/dist/elements/chunks/{download-BDQwcFav.js → download-Dsbg3ste.js} +1 -1
  60. package/dist/elements/chunks/{dropdown-wgs8xCJZ.js → dropdown-BDd1-Zgi.js} +1 -1
  61. package/dist/elements/chunks/{ellipsis-CUiEJ6R1.js → ellipsis-Dw6qrss-.js} +1 -1
  62. package/dist/elements/chunks/{external-link-DJUmC7g2.js → external-link-Vt74zAoL.js} +1 -1
  63. package/dist/elements/chunks/{file-text-DOYfYvrh.js → file-text-CZrZF6_v.js} +1 -1
  64. package/dist/elements/chunks/{file-tree-DQzNQrbr.js → file-tree-DF4okKyo.js} +1 -1
  65. package/dist/elements/chunks/{folder-DBDUBANz.js → folder-glP5rha4.js} +1 -1
  66. package/dist/elements/chunks/hover-card-Cmm6Q53m.js +1 -0
  67. package/dist/elements/chunks/icon-BLb8beoi.js +1 -0
  68. package/dist/elements/chunks/{info-DBQl-m6e.js → info-CIsv92JK.js} +1 -1
  69. package/dist/elements/chunks/{input-DZD67EvW.js → input-Dds1kFyt.js} +1 -1
  70. package/dist/elements/chunks/{kbd-DbyVrlnU.js → kbd-De_xbgi9.js} +1 -1
  71. package/dist/elements/chunks/{link-vko7AttA.js → link-i4r_k7z4.js} +1 -1
  72. package/dist/elements/chunks/{loader-Cg6rO8tQ.js → loader-BmQH4s3V.js} +1 -1
  73. package/dist/elements/chunks/{markdown-n3Xbj3PR.js → markdown-Zi21WhUz.js} +7 -7
  74. package/dist/elements/chunks/{message-CgKytiSB.js → message-BqyXDN3-.js} +1 -1
  75. package/dist/elements/chunks/{message-feedback-Ds3-US0e.js → message-feedback-Djr3z6Yb.js} +1 -1
  76. package/dist/elements/chunks/message-square-vFAgbDaX.js +1 -0
  77. package/dist/elements/chunks/{minimize-2-3kCdE0q3.js → minimize-2--ifeCGrl.js} +1 -1
  78. package/dist/elements/chunks/{model-switcher-BMtXGwLU.js → model-switcher-B94YAKsM.js} +1 -1
  79. package/dist/elements/chunks/{overlay-B1-MBI6h.js → overlay-kD8dwRv_.js} +1 -1
  80. package/dist/elements/chunks/{paperclip-DDEfmkHc.js → paperclip-q21dYOSK.js} +1 -1
  81. package/dist/elements/chunks/{progress-bar-Dwbiyv3s.js → progress-bar-BrAIIaaJ.js} +1 -1
  82. package/dist/elements/chunks/{prompt-suggestion-9lvgFcEa.js → prompt-suggestion-Bjn2K-fR.js} +1 -1
  83. package/dist/elements/chunks/{reasoning-DzHWsdAJ.js → reasoning-DTpCGtnY.js} +1 -1
  84. package/dist/elements/chunks/resizable-Dp4JExql.js +1 -0
  85. package/dist/elements/chunks/{rotate-cw-jpisOdOV.js → rotate-cw-CFWWkpj2.js} +1 -1
  86. package/dist/elements/chunks/{scroll-area-g5Ze3K6f.js → scroll-area-Bb0kHH1j.js} +1 -1
  87. package/dist/elements/chunks/scroll-button-B0cH5fXf.js +1 -0
  88. package/dist/elements/chunks/{separator-Bmjc5XyX.js → separator-9kRgr1X9.js} +1 -1
  89. package/dist/elements/chunks/{settings-Cz1bMJP9.js → settings-B8lmec4D.js} +1 -1
  90. package/dist/elements/chunks/{settings-group-9imY3kRA.js → settings-group-I0hEuXiN.js} +1 -1
  91. package/dist/elements/chunks/{share-BdenF0wQ.js → share-C3lTiYGt.js} +1 -1
  92. package/dist/elements/chunks/skeleton-DSXW5Yya.js +1 -0
  93. package/dist/elements/chunks/{slots-DkZ-Cmha.js → slots--V19HwUb.js} +1 -1
  94. package/dist/elements/chunks/{star-DY6z1UUH.js → star-CSJtGxgv.js} +1 -1
  95. package/dist/elements/chunks/{store-th4VyJXM.js → store-CHyW95TP.js} +1 -1
  96. package/dist/elements/chunks/{text-shimmer-CEi-tlXr.js → text-shimmer-Cn9TmNpI.js} +1 -1
  97. package/dist/elements/chunks/textarea-CUwqAX0G.js +1 -0
  98. package/dist/elements/chunks/{thumbs-up-BR33KLfA.js → thumbs-up-PNVVThEF.js} +1 -1
  99. package/dist/elements/chunks/{toast-store-BEMda0fv.js → toast-store-DKbQn3Wn.js} +1 -1
  100. package/dist/elements/chunks/{tool-TMued3WO.js → tool-DjTRpIpl.js} +1 -1
  101. package/dist/elements/chunks/tooltip-CgBZCVKp.js +1 -0
  102. package/dist/elements/chunks/{triangle-alert-B07fX3Q8.js → triangle-alert-DsHTHCPm.js} +1 -1
  103. package/dist/elements/chunks/use-card-resolution-BSu_XHjH.js +1 -0
  104. package/dist/elements/chunks/variant-aurora-Dc4nQUia.js +218 -0
  105. package/dist/elements/chunks/variant-custom-y_3kVQhS.js +1 -0
  106. package/dist/elements/chunks/variant-wave-D5OR1-9Z.js +116 -0
  107. package/dist/elements/chunks/{video-MKp3LmNT.js → video-B7vmenoC.js} +1 -1
  108. package/dist/elements/chunks/x-GRUeeQFT.js +1 -0
  109. package/dist/elements/coachmark.js +1 -1
  110. package/dist/elements/code-block.js +1 -1
  111. package/dist/elements/command.js +1 -1
  112. package/dist/elements/compare.js +1 -1
  113. package/dist/elements/composer.js +1 -1
  114. package/dist/elements/confirm-card.js +1 -1
  115. package/dist/elements/context-meter.js +1 -1
  116. package/dist/elements/conversation-list.js +1 -1
  117. package/dist/elements/define.d.ts +11 -0
  118. package/dist/elements/dialog.js +1 -1
  119. package/dist/elements/editable-label.js +1 -1
  120. package/dist/elements/embed.js +1 -1
  121. package/dist/elements/empty.js +1 -1
  122. package/dist/elements/feedback-bar.js +1 -1
  123. package/dist/elements/file-tree.js +1 -1
  124. package/dist/elements/file-upload.js +1 -1
  125. package/dist/elements/form.js +1 -1
  126. package/dist/elements/hover-card.js +1 -1
  127. package/dist/elements/icon.js +1 -1
  128. package/dist/elements/image.js +1 -1
  129. package/dist/elements/input.js +1 -1
  130. package/dist/elements/kbd.js +1 -1
  131. package/dist/elements/link-preview.js +1 -1
  132. package/dist/elements/loader.js +1 -1
  133. package/dist/elements/markdown.js +1 -1
  134. package/dist/elements/menu.js +1 -1
  135. package/dist/elements/message-skills.js +1 -1
  136. package/dist/elements/message.js +1 -1
  137. package/dist/elements/model-switcher.js +1 -1
  138. package/dist/elements/nav.js +1 -1
  139. package/dist/elements/notice.js +1 -1
  140. package/dist/elements/pane-group.js +1 -1
  141. package/dist/elements/pane.js +1 -1
  142. package/dist/elements/popover.js +1 -1
  143. package/dist/elements/progress-bar.js +1 -1
  144. package/dist/elements/prompt-dock.js +1 -1
  145. package/dist/elements/prompt-input.js +1 -1
  146. package/dist/elements/prompt-suggestions.js +1 -1
  147. package/dist/elements/reasoning.js +1 -1
  148. package/dist/elements/remote.js +1 -1
  149. package/dist/elements/resizable.js +1 -1
  150. package/dist/elements/response-stream.js +1 -1
  151. package/dist/elements/screen.js +1 -1
  152. package/dist/elements/scroll-area.js +1 -1
  153. package/dist/elements/scroll-button.js +1 -1
  154. package/dist/elements/search.js +1 -1
  155. package/dist/elements/segmented.js +1 -1
  156. package/dist/elements/separator.js +1 -1
  157. package/dist/elements/setting-item.js +1 -1
  158. package/dist/elements/settings-group.js +1 -1
  159. package/dist/elements/skeleton.js +1 -1
  160. package/dist/elements/slots.d.ts +15 -0
  161. package/dist/elements/source.js +1 -1
  162. package/dist/elements/status.js +1 -1
  163. package/dist/elements/switch.js +1 -1
  164. package/dist/elements/tabs.js +1 -1
  165. package/dist/elements/tasks.js +1 -1
  166. package/dist/elements/text-shimmer.js +1 -1
  167. package/dist/elements/thinking-bar.js +1 -1
  168. package/dist/elements/thread.d.ts +1 -0
  169. package/dist/elements/thread.js +1 -0
  170. package/dist/elements/toast.js +1 -1
  171. package/dist/elements/tool.js +1 -1
  172. package/dist/elements/tooltip.js +1 -1
  173. package/dist/elements/voice-input.js +1 -1
  174. package/dist/elements/voice-output.js +1 -1
  175. package/dist/elements.d.ts +62 -0
  176. package/dist/index.js +3411 -3328
  177. package/dist/kai.es.js +1 -1
  178. package/dist/llms/llms-full.txt +70 -4
  179. package/dist/llms/llms.txt +3 -3
  180. package/dist/primitives/audio-bands.d.ts +106 -0
  181. package/dist/primitives/create-tween.d.ts +33 -0
  182. package/dist/primitives/use-audio-analysis.d.ts +149 -0
  183. package/dist/primitives/use-sequencer.d.ts +13 -0
  184. package/dist/primitives/use-voice-recorder.d.ts +1 -0
  185. package/dist/primitives/visualizer-sequences.d.ts +39 -0
  186. package/dist/react/index.d.ts +105 -0
  187. package/dist/react/use-voice-input.d.ts +15 -0
  188. package/dist/react.js +269 -240
  189. package/dist/register-impl-DQyejdfb.js +145 -0
  190. package/dist/test-utils/fake-clock.d.ts +45 -0
  191. package/dist/ui/resizable.d.ts +9 -3
  192. package/dist/variant-aurora-DVtGqVH6.js +218 -0
  193. package/dist/variant-custom-Dif-IV4u.js +1 -0
  194. package/dist/variant-wave-DbfNdvSi.js +116 -0
  195. package/frameworks/react/index.tsx +74 -1
  196. package/frameworks/react/use-voice-input.tsx +59 -0
  197. package/llms-full.txt +70 -4
  198. package/llms.txt +3 -3
  199. package/package.json +32 -7
  200. package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +257 -0
  201. package/src/components/audio-visualizer/aurora.glsl.ts +330 -0
  202. package/src/components/audio-visualizer/index.tsx +442 -0
  203. package/src/components/audio-visualizer/shader-canvas.tsx +846 -0
  204. package/src/components/audio-visualizer/sizes.ts +54 -0
  205. package/src/components/audio-visualizer/variant-aurora.tsx +299 -0
  206. package/src/components/audio-visualizer/variant-bar.tsx +143 -0
  207. package/src/components/audio-visualizer/variant-custom.tsx +338 -0
  208. package/src/components/audio-visualizer/variant-grid.tsx +170 -0
  209. package/src/components/audio-visualizer/variant-radial.tsx +158 -0
  210. package/src/components/audio-visualizer/variant-wave.tsx +125 -0
  211. package/src/components/audio-visualizer/wave.glsl.ts +130 -0
  212. package/src/components/component-meta.json +34 -32
  213. package/src/components/composer.tsx +5 -0
  214. package/src/components/thread.tsx +177 -0
  215. package/src/elements/audio-visualizer.tsx +183 -0
  216. package/src/elements/chat-workspace.tsx +1 -1
  217. package/src/elements/compiled.css +1 -1
  218. package/src/elements/define.tsx +12 -1
  219. package/src/elements/element-manifest.json +8 -0
  220. package/src/elements/element-meta.json +287 -0
  221. package/src/elements/element-types.d.ts +62 -0
  222. package/src/elements/icon-names.json +2 -0
  223. package/src/elements/menu.tsx +7 -4
  224. package/src/elements/register-impl.ts +3 -0
  225. package/src/elements/resizable.tsx +78 -2
  226. package/src/elements/slots.ts +42 -0
  227. package/src/elements/thread.tsx +105 -0
  228. package/src/primitives/audio-bands.ts +174 -0
  229. package/src/primitives/create-tween.ts +214 -0
  230. package/src/primitives/use-audio-analysis.ts +321 -0
  231. package/src/primitives/use-sequencer.ts +53 -0
  232. package/src/primitives/use-voice-recorder.ts +16 -4
  233. package/src/primitives/visualizer-sequences.ts +350 -0
  234. package/src/test-utils/fake-clock.ts +88 -0
  235. package/src/ui/action-icons.ts +2 -1
  236. package/src/ui/icon.tsx +4 -0
  237. package/src/ui/overlay.tsx +8 -1
  238. package/src/ui/resizable.tsx +47 -9
  239. package/dist/elements/chunks/action-icons-Dtl8Ox5D.js +0 -1
  240. package/dist/elements/chunks/arrow-left-GbBceGBE.js +0 -1
  241. package/dist/elements/chunks/chat-thread-Dz2qbzvI.js +0 -1
  242. package/dist/elements/chunks/check-fhmRksq_.js +0 -1
  243. package/dist/elements/chunks/chevron-down-DcRg1aRU.js +0 -1
  244. package/dist/elements/chunks/chevron-right-ChzBcNJ-.js +0 -1
  245. package/dist/elements/chunks/circle-B4fTe9v6.js +0 -1
  246. package/dist/elements/chunks/circle-check-CJV-3Rem.js +0 -1
  247. package/dist/elements/chunks/collapsible-CWUAShMC.js +0 -1
  248. package/dist/elements/chunks/define-CNSKFZCd.js +0 -1
  249. package/dist/elements/chunks/hover-card-BQGdGAQ3.js +0 -1
  250. package/dist/elements/chunks/icon-CAyRqVrg.js +0 -1
  251. package/dist/elements/chunks/message-square-MmRkvc-M.js +0 -1
  252. package/dist/elements/chunks/resizable-Bq_eHxUm.js +0 -1
  253. package/dist/elements/chunks/skeleton-Pjc5jiaY.js +0 -1
  254. package/dist/elements/chunks/textarea-CboigarR.js +0 -1
  255. package/dist/elements/chunks/tooltip-Cft_Rxiw.js +0 -1
  256. package/dist/elements/chunks/use-card-resolution-M36zVciB.js +0 -1
  257. package/dist/elements/chunks/x-Ckwicf6g.js +0 -1
  258. package/dist/register-impl-DcjZDELj.js +0 -141
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Pure `state -> highlight set` mappers for the audio visualizer variants.
3
+ *
4
+ * Ported from livekit/components-js `packages/shadcn/hooks/agents-ui/`
5
+ * (Apache License 2.0). Upstream runs four separate animator hooks, each with
6
+ * its own requestAnimationFrame loop. We keep the sequences (verbatim) but move
7
+ * the timing into one shared driver, `use-sequencer.ts`.
8
+ *
9
+ * Every generator returns a NON-EMPTY array so callers can index with
10
+ * `sequence[tick % sequence.length]` without a guard.
11
+ */
12
+
13
+ export type VisualizerState =
14
+ | 'idle'
15
+ | 'connecting'
16
+ | 'listening'
17
+ | 'thinking'
18
+ | 'speaking'
19
+ | 'disconnected';
20
+
21
+ export interface Coordinate {
22
+ x: number;
23
+ y: number;
24
+ }
25
+
26
+ const KNOWN: readonly VisualizerState[] = [
27
+ 'idle',
28
+ 'connecting',
29
+ 'listening',
30
+ 'thinking',
31
+ 'speaking',
32
+ // First-class since 2026-08-10 (Rob): the dead-connection look (flat wave,
33
+ // nothing lit) upstream renders for its 'disconnected' state. It was an
34
+ // alias for 'idle' until idle adopted upstream's gentle wave, which left
35
+ // the flat line unreachable.
36
+ 'disconnected',
37
+ ];
38
+
39
+ /**
40
+ * LiveKit's AgentState carries room-lifecycle values that mean nothing here.
41
+ * Accept them so markup ported from LiveKit works unchanged.
42
+ */
43
+ const ALIASES: Record<string, VisualizerState> = {
44
+ // Both are dead-connection looks upstream, so 'failed' rides with
45
+ // 'disconnected' rather than 'idle'.
46
+ failed: 'disconnected',
47
+ initializing: 'connecting',
48
+ 'pre-connect-buffering': 'connecting',
49
+ };
50
+
51
+ export function normalizeState(input: string | undefined): VisualizerState {
52
+ if (!input) return 'idle';
53
+ if ((KNOWN as readonly string[]).includes(input)) return input as VisualizerState;
54
+ return ALIASES[input] ?? 'idle';
55
+ }
56
+
57
+ // ---------------------------------------------------------------- bar
58
+
59
+ export function barSequence(state: VisualizerState, barCount: number): number[][] {
60
+ switch (state) {
61
+ case 'connecting': {
62
+ // A mirrored pair sweeping outward: [0, n-1], [1, n-2], ...
63
+ const seq: number[][] = [];
64
+ for (let x = 0; x < barCount; x++) seq.push([x, barCount - 1 - x]);
65
+ return seq.length ? seq : [[]];
66
+ }
67
+ case 'listening':
68
+ case 'thinking': {
69
+ // Blink the center bar. -1 is a deliberate no-index frame (the "off" beat).
70
+ return [[Math.floor(barCount / 2)], [-1]];
71
+ }
72
+ case 'speaking':
73
+ return [Array.from({ length: barCount }, (_, i) => i)];
74
+ // Dead-connection look: mirrors idle (nothing lit) for now. A dedicated
75
+ // arm so the pending LiveKit disconnected-state measurement can adjust
76
+ // it in one line.
77
+ case 'disconnected':
78
+ return [[]];
79
+ case 'idle':
80
+ default:
81
+ return [[]];
82
+ }
83
+ }
84
+
85
+ export function barInterval(state: VisualizerState, barCount: number): number {
86
+ switch (state) {
87
+ // The whole sweep should take 2s regardless of how many bars it crosses.
88
+ case 'connecting':
89
+ return 2000 / Math.max(1, barCount);
90
+ case 'listening':
91
+ return 500;
92
+ case 'thinking':
93
+ return 150;
94
+ default:
95
+ return 1000;
96
+ }
97
+ }
98
+
99
+ // ---------------------------------------------------------------- grid
100
+
101
+ /**
102
+ * Walk the perimeter of a ring `spread` cells out from center, clockwise.
103
+ *
104
+ * NOTE: This diverges from upstream use-agent-audio-visualizer-grid.ts in two ways:
105
+ * (1) We use separate centerX and centerY (upstream uses row-center for both axes).
106
+ * Upstream never ships non-square grids, so the bug never manifested. Our public
107
+ * API is square-only too now (the grid's single `count` prop, 2026-08-09), but
108
+ * this function keeps taking rows/columns as plain params, so the correct
109
+ * arbitrary-shape handling stays -- it costs nothing and guards any future caller.
110
+ * (2) We distinguish spread === 0 from spread === undefined (upstream treats 0 as falsy).
111
+ * Again, upstream never ships spread=0 explicitly; we do for API completeness.
112
+ */
113
+ function gridRing(rows: number, columns: number, spread: number): Coordinate[] {
114
+ const seq: Coordinate[] = [];
115
+ const centerX = Math.floor(columns / 2);
116
+ const centerY = Math.floor(rows / 2);
117
+ const topLeft = { x: Math.max(0, centerX - spread), y: Math.max(0, centerY - spread) };
118
+ const bottomRight = {
119
+ x: Math.min(columns - 1, centerX + spread),
120
+ y: Math.min(rows - 1, centerY + spread),
121
+ };
122
+
123
+ for (let x = topLeft.x; x <= bottomRight.x; x++) seq.push({ x, y: topLeft.y });
124
+ for (let y = topLeft.y + 1; y <= bottomRight.y; y++) seq.push({ x: bottomRight.x, y });
125
+ for (let x = bottomRight.x - 1; x >= topLeft.x; x--) seq.push({ x, y: bottomRight.y });
126
+ for (let y = bottomRight.y - 1; y > topLeft.y; y--) seq.push({ x: topLeft.x, y });
127
+
128
+ return seq;
129
+ }
130
+
131
+ export function gridSequence(
132
+ state: VisualizerState,
133
+ rows: number,
134
+ columns: number,
135
+ spread?: number,
136
+ ): Coordinate[] {
137
+ const center = { x: Math.floor(columns / 2), y: Math.floor(rows / 2) };
138
+
139
+ switch (state) {
140
+ case 'connecting': {
141
+ const maxSpread = Math.floor(Math.max(rows, columns) / 2);
142
+ const clamped = spread !== undefined ? Math.min(spread, maxSpread) : maxSpread;
143
+ const ring = gridRing(rows, columns, clamped);
144
+ return ring.length ? ring : [center];
145
+ }
146
+ case 'listening': {
147
+ // One lit frame followed by eight dark ones: a slow heartbeat.
148
+ const off = { x: -1, y: -1 };
149
+ return [center, off, off, off, off, off, off, off, off];
150
+ }
151
+ case 'thinking': {
152
+ // Sweep the middle row left to right, then back.
153
+ const y = Math.floor(rows / 2);
154
+ const seq: Coordinate[] = [];
155
+ for (let x = 0; x < columns; x++) seq.push({ x, y });
156
+ for (let x = columns - 1; x >= 0; x--) seq.push({ x, y });
157
+ return seq.length ? seq : [center];
158
+ }
159
+ // Dead-connection look: the same centre resting frame as idle
160
+ // (variant-grid suppresses the highlight for both, so nothing lights).
161
+ // A dedicated arm so the pending LiveKit measurement can adjust it in
162
+ // one line.
163
+ case 'disconnected':
164
+ return [center];
165
+ default:
166
+ return [center];
167
+ }
168
+ }
169
+
170
+ // ---------------------------------------------------------------- radial
171
+
172
+ /** Largest divisor of `n` that is at most `max`. Always at least 1. */
173
+ function largestDivisorAtMost(n: number, max: number): number {
174
+ const gcd = (a: number, b: number): number => {
175
+ while (b !== 0) {
176
+ const t = b;
177
+ b = a % b;
178
+ a = t;
179
+ }
180
+ return a;
181
+ };
182
+ for (let i = max; i >= 1; i--) if (gcd(n, i) === i) return i;
183
+ return 1;
184
+ }
185
+
186
+ /**
187
+ * Partition the ring into interleaved groups. Lighting one group per tick reads
188
+ * as a rotating pattern rather than a single travelling dot. Every index appears
189
+ * in exactly one group.
190
+ */
191
+ function radialGroups(barCount: number): number[][] {
192
+ const divisor =
193
+ barCount > 8
194
+ ? barCount / largestDivisorAtMost(barCount, 4)
195
+ : largestDivisorAtMost(barCount, 2);
196
+ const safe = Math.max(1, Math.floor(divisor));
197
+ const perGroup = Math.floor(barCount / safe);
198
+ return Array.from({ length: safe }, (_, i) =>
199
+ Array.from({ length: perGroup }, (_, j) => j * safe + i),
200
+ );
201
+ }
202
+
203
+ export function radialSequence(state: VisualizerState, barCount: number): number[][] {
204
+ switch (state) {
205
+ case 'connecting': {
206
+ // Pair each bar with the one opposite it, sweeping around the circle.
207
+ const center = Math.floor(barCount / 2);
208
+ const seq: number[][] = [];
209
+ for (let x = 0; x < barCount; x++) seq.push([x, (x + center) % barCount]);
210
+ return seq.length ? seq : [[]];
211
+ }
212
+ case 'listening':
213
+ case 'thinking': {
214
+ const groups = radialGroups(barCount);
215
+ return groups.length ? groups : [[]];
216
+ }
217
+ case 'speaking':
218
+ return [Array.from({ length: barCount }, (_, i) => i)];
219
+ // Dead-connection look: mirrors idle (nothing lit) for now, one-line
220
+ // adjustable once the LiveKit measurement lands.
221
+ case 'disconnected':
222
+ return [[]];
223
+ case 'idle':
224
+ default:
225
+ return [[]];
226
+ }
227
+ }
228
+
229
+ export function radialInterval(state: VisualizerState): number {
230
+ switch (state) {
231
+ case 'connecting':
232
+ case 'listening':
233
+ return 500;
234
+ // Infinity parks the sequencer: `thinking` spins the whole container in CSS
235
+ // with every bar lit, so ticking would fight the animation.
236
+ case 'thinking':
237
+ return Infinity;
238
+ default:
239
+ return 1000;
240
+ }
241
+ }
242
+
243
+ // ---------------------------------------------------------------- shader
244
+
245
+ /**
246
+ * Uniform targets for the aura and custom shaders. An array intensity means
247
+ * "ping-pong between these two" (see create-tween). `speaking` intensity is
248
+ * omitted from the pulse because live volume drives it instantly instead.
249
+ */
250
+ export function shaderTargets(
251
+ state: VisualizerState,
252
+ ): { intensity: number | [number, number]; speed: number } {
253
+ switch (state) {
254
+ case 'listening':
255
+ return { intensity: [0.5, 0.8], speed: 2.5 };
256
+ case 'thinking':
257
+ return { intensity: [0.25, 0.5], speed: 4.0 };
258
+ // Same pulse as thinking, its own speed: uSpeed is the only
259
+ // state-correlated scalar besides intensity that reaches a custom
260
+ // shader's GLSL (see customUniforms in variant-custom.tsx), so the two
261
+ // transitional states sharing 4.0 made them indistinguishable to any
262
+ // consumer shader keying off the kit's uniforms -- the Custom story's
263
+ // per-state looks are the concrete consumer. Every state now maps to a
264
+ // distinct speed: idle 1, listening/speaking 2.5 (told apart by band
265
+ // energy), thinking 4, connecting 6.
266
+ case 'connecting':
267
+ return { intensity: [0.25, 0.5], speed: 6.0 };
268
+ case 'speaking':
269
+ return { intensity: 0.3, speed: 2.5 };
270
+ // Dead-connection look: idle's dim intensity but its OWN speed, so a
271
+ // shader keying off uSpeed can draw a dead line distinct from idle's
272
+ // calm ridge (see the connecting arm's comment: uSpeed is the only
273
+ // other state-correlated scalar reaching consumer GLSL). Still
274
+ // one-line adjustable once the LiveKit measurement lands.
275
+ case 'disconnected':
276
+ return { intensity: 0.3, speed: 0.5 };
277
+ case 'idle':
278
+ default:
279
+ return { intensity: 0.3, speed: 1 };
280
+ }
281
+ }
282
+
283
+ const WAVE_SPEED = 5;
284
+ const WAVE_AMPLITUDE = 0.025;
285
+ const WAVE_FREQUENCY = 10;
286
+
287
+ /** Uniform targets for the wave shader. Same shape, different curve. */
288
+ export function waveTargets(state: VisualizerState): {
289
+ speed: number;
290
+ amplitude: number;
291
+ frequency: number;
292
+ opacity: number | [number, number];
293
+ pulseDuration: number;
294
+ } {
295
+ switch (state) {
296
+ case 'listening':
297
+ return {
298
+ speed: WAVE_SPEED,
299
+ amplitude: WAVE_AMPLITUDE,
300
+ frequency: WAVE_FREQUENCY,
301
+ opacity: [1.0, 0.3],
302
+ pulseDuration: 0.75,
303
+ };
304
+ case 'thinking':
305
+ case 'connecting':
306
+ return {
307
+ speed: WAVE_SPEED * 4,
308
+ amplitude: WAVE_AMPLITUDE / 4,
309
+ frequency: WAVE_FREQUENCY * 4,
310
+ opacity: [1.0, 0.3],
311
+ pulseDuration: 0.4,
312
+ };
313
+ case 'speaking':
314
+ // Upstream's wave switch has NO 'idle' arm: idle falls through to this
315
+ // same speaking/default arm (speed 10, amplitude 0.025, frequency 10,
316
+ // opacity 1), a gentle undulation (~1.05px centreline waviness, ~1.5px/s
317
+ // phase drift, measured on the md tile). Upstream's explicitly FLAT
318
+ // state is 'disconnected' -- the dedicated arm below. We had flattened
319
+ // idle instead; Rob wants upstream's look (2026-08-09).
320
+ case 'idle':
321
+ return {
322
+ speed: WAVE_SPEED * 2,
323
+ amplitude: WAVE_AMPLITUDE,
324
+ frequency: WAVE_FREQUENCY,
325
+ opacity: 1.0,
326
+ pulseDuration: 0,
327
+ };
328
+ // Upstream's EXPLICIT flat state, measured: 'disconnected' is the one
329
+ // arm of their wave switch that zeroes the line. First-class here since
330
+ // 2026-08-10 (Rob: 'the disconnected wave needs to be that kind of flat
331
+ // line'). `default` keeps the same flat targets for out-of-union
332
+ // strings, which normalizeState prevents real callers from reaching.
333
+ case 'disconnected':
334
+ return {
335
+ speed: WAVE_SPEED,
336
+ amplitude: 0,
337
+ frequency: 0,
338
+ opacity: 1.0,
339
+ pulseDuration: 0,
340
+ };
341
+ default:
342
+ return {
343
+ speed: WAVE_SPEED,
344
+ amplitude: 0,
345
+ frequency: 0,
346
+ opacity: 1.0,
347
+ pulseDuration: 0,
348
+ };
349
+ }
350
+ }
@@ -0,0 +1,88 @@
1
+ import { vi, beforeEach, afterEach } from 'vitest';
2
+
3
+ /**
4
+ * Shared fake `requestAnimationFrame` + `performance.now()` clock for tests
5
+ * that need to drive time deterministically instead of racing real timers.
6
+ *
7
+ * requestAnimationFrame's timestamp and performance.now() share a time origin
8
+ * per spec, so production code can read either. Stubbing them off the same
9
+ * fake `now` keeps the two from silently disagreeing.
10
+ *
11
+ * The stub holds a REAL per-registration queue, matching a real browser: a
12
+ * single frame fires every callback that was pending BEFORE that frame, all
13
+ * with the SAME advanced `now`, in registration order -- so two
14
+ * independently-animating things under test (e.g. a `createTween`'s own step
15
+ * loop and `ShaderCanvas`'s separate `draw` loop, both requestAnimationFrame
16
+ * consumers, both mounted at once) tick together on one `advance()` call
17
+ * instead of the second silently stealing the first's slot.
18
+ *
19
+ * This used to be a single PENDING-callback slot, not a queue: a second
20
+ * consumer scheduling a new frame before `advance()` ran overwrote the
21
+ * first rather than queueing behind it. That was fine for anything that only
22
+ * ever has ONE thing pending at a time (the common case, e.g. bar/grid/radial's
23
+ * single sequencer tick, or a lone `createTween`), which is why switching to a
24
+ * queue does not change any of those. It was NOT fine for a mounted shader
25
+ * variant, where a tween and ShaderCanvas's `draw` are both independently
26
+ * pending at once: `draw` always registers last and re-arms itself every
27
+ * `advance()`, so it permanently "won" the single slot and the tween's step
28
+ * never fired -- silently defeating any test trying to prove something holds
29
+ * while UNIFORM VALUES genuinely change every frame (see
30
+ * `variant-custom.test.tsx`'s recompile-guard test for the concrete case this
31
+ * was fixed for).
32
+ *
33
+ * `cancelAnimationFrame` cancels by id now (each `requestAnimationFrame` call
34
+ * gets its own), not by clearing whatever happens to be in the single slot --
35
+ * required once more than one callback can be pending at a time, so a
36
+ * `cancelAnimationFrame(idA)` cannot accidentally drop a DIFFERENT pending
37
+ * callback `idB`.
38
+ *
39
+ * Call once per `describe` block (or at file scope for the whole file); it
40
+ * registers `beforeEach`/`afterEach` hooks scoped to wherever it is called,
41
+ * exactly like inlining the hooks would, and returns the driver bound to
42
+ * that scope's fake clock.
43
+ */
44
+ export function installFakeClock() {
45
+ let now = 0;
46
+ let nextId = 1;
47
+ let pending = new Map<number, (t: number) => void>();
48
+
49
+ beforeEach(() => {
50
+ now = 0;
51
+ nextId = 1;
52
+ pending = new Map();
53
+ vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
54
+ const id = nextId++;
55
+ pending.set(id, cb as (t: number) => void);
56
+ return id;
57
+ });
58
+ vi.stubGlobal('cancelAnimationFrame', (id: number) => {
59
+ pending.delete(id);
60
+ });
61
+ vi.stubGlobal('performance', { now: () => now });
62
+ });
63
+
64
+ afterEach(() => vi.unstubAllGlobals());
65
+
66
+ /**
67
+ * Advance the fake clock and run every callback pending BEFORE this call,
68
+ * all at the new `now` -- one simulated frame. A callback that reschedules
69
+ * itself (the normal RAF-loop pattern) lands in a FRESH pending set for the
70
+ * NEXT `advance()`, not this one: snapshotting the callback list before
71
+ * invoking any of them is what keeps a self-rescheduling loop from looping
72
+ * forever inside a single `advance()` call, matching how a real browser
73
+ * frame never re-enters itself either.
74
+ */
75
+ function advance(ms: number) {
76
+ now += ms;
77
+ const callbacks = Array.from(pending.values());
78
+ pending = new Map();
79
+ for (const cb of callbacks) cb(now);
80
+ }
81
+
82
+ /** Whether at least one requestAnimationFrame callback is currently pending. */
83
+ function isFramePending(): boolean {
84
+ return pending.size > 0;
85
+ }
86
+
87
+ return { advance, isFramePending };
88
+ }
@@ -1,7 +1,7 @@
1
1
  import type { Component } from 'solid-js';
2
2
  import {
3
3
  Copy, ThumbsUp, ThumbsDown, RefreshCw, Pencil,
4
- Share, Bookmark, Download, Link, Trash2, Check, X, Star, Flag, Reply, MoreHorizontal,
4
+ Share, Bookmark, Download, Link, Trash2, Check, X, Star, Flag, Reply, MoreHorizontal, Volume2,
5
5
  } from 'lucide-solid';
6
6
  import type { ChatMessageAction } from '../elements/chat-types';
7
7
 
@@ -33,6 +33,7 @@ const ICONS: Record<string, IconComponent> = {
33
33
  flag: Flag,
34
34
  reply: Reply,
35
35
  more: MoreHorizontal,
36
+ 'volume-2': Volume2,
36
37
  };
37
38
 
38
39
  /** Resolve a curated icon name to its component, or `undefined` when unknown
package/src/ui/icon.tsx CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  House, Clock, Lock, Box, Briefcase, PanelLeft, Circle, SlidersHorizontal, Workflow, SquarePen,
9
9
  Archive, ArrowUp, Bookmark, Copy, Ellipsis, ExternalLink, Flag,
10
10
  GitBranch, GitPullRequest, ListFilter, RotateCw,
11
+ Moon, Sun,
11
12
  } from 'lucide-solid';
12
13
 
13
14
  type IconComponent = Component<{ class?: string }>;
@@ -64,6 +65,9 @@ const NAMED_ICONS: Record<string, IconComponent> = {
64
65
  'git-pull-request': GitPullRequest,
65
66
  'list-filter': ListFilter,
66
67
  'rotate-cw': RotateCw,
68
+ // Theme / appearance glyphs.
69
+ moon: Moon,
70
+ sun: Sun,
67
71
  };
68
72
 
69
73
  /** Render an item icon.
@@ -206,7 +206,14 @@ export function usePosition(
206
206
  // on unmount via onCleanup.
207
207
  const cleanup = autoUpdate(ref, float, update);
208
208
  let ro: ResizeObserver | undefined;
209
- if (typeof ResizeObserver === 'function') {
209
+ // Only a REAL element can be observed. The composer's trigger menu anchors to a
210
+ // VIRTUAL reference (a caret-rect object exposing just getBoundingClientRect, no
211
+ // DOM node); ResizeObserver.observe() throws "parameter 1 is not of type 'Element'"
212
+ // on it, and that uncaught throw aborts this effect before onCleanup registers,
213
+ // which corrupted the menu's reactive scope so it never re-opened after the first
214
+ // time. autoUpdate already tracks the virtual anchor; this ResizeObserver is a
215
+ // real-element-only add-on for ancestor/element resizes.
216
+ if (typeof ResizeObserver === 'function' && ref instanceof Element) {
210
217
  ro = new ResizeObserver(update);
211
218
  ro.observe(ref);
212
219
  for (const ancestor of getOverflowAncestors(ref)) {
@@ -237,7 +237,13 @@ function ResizablePanel(props: ResizablePanelProps) {
237
237
  // --- ResizableHandle ---
238
238
 
239
239
  export interface ResizableHandleProps extends JSX.HTMLAttributes<HTMLDivElement> {
240
- withHandle?: boolean;
240
+ /**
241
+ * Divider affordance drawn inside the 8px grab zone:
242
+ * - `line` (default) — a 1px hairline, transparent at rest, tinting on hover/drag.
243
+ * - `grip` — a dotted grip handle.
244
+ * - `none` — no visible divider (invisible hit-area only).
245
+ */
246
+ handle?: 'line' | 'grip' | 'none';
241
247
  onPanelResize?: (delta: number) => void;
242
248
  /** Keyboard nudge step in pixels (default 16). Home/End jump to min/max. */
243
249
  keyboardStep?: number;
@@ -265,12 +271,15 @@ function readBound(el: HTMLElement, kind: 'min' | 'max', containerPx: number, fa
265
271
 
266
272
  function ResizableHandle(props: ResizableHandleProps) {
267
273
  const [local, rest] = splitProps(props, [
268
- 'withHandle', 'onPanelResize', 'class', 'keyboardStep', 'static', 'orientation',
274
+ 'handle', 'onPanelResize', 'class', 'keyboardStep', 'static', 'orientation',
269
275
  ]);
270
276
  const ctx = useContext(ResizableContext);
271
277
  const orientation = () => local.orientation ?? ctx?.orientation ?? 'horizontal';
272
278
  const [isDragging, setIsDragging] = createSignal(false);
279
+ const [hovering, setHovering] = createSignal(false);
273
280
  const isStatic = () => !!local.static;
281
+ // Divider affordance; `line` (hairline) is the default.
282
+ const mode = () => local.handle ?? 'line';
274
283
 
275
284
  let startPos = 0;
276
285
  let prevEl: HTMLElement | null = null;
@@ -481,18 +490,33 @@ function ResizableHandle(props: ResizableHandleProps) {
481
490
  nextEl = null;
482
491
  };
483
492
 
493
+ // The full-strip drag highlight is only for `grip`/`none`; `line` shows its tint
494
+ // on the hairline alone (below), so the whole 8px zone must stay transparent.
495
+ const showStripHighlight = () => isDragging() && mode() !== 'line';
496
+ // Hairline tint (priority: dragging > hovering > rest). Inline CSS-var values
497
+ // with literal-hex fallbacks — this renders in a shadow root AND in light-DOM
498
+ // stories, where Tailwind arbitrary-value classes aren't reliably present.
499
+ const lineColor = () =>
500
+ isDragging()
501
+ ? 'var(--color-muted, #f4f4f5)'
502
+ : hovering()
503
+ ? 'var(--color-border, #e4e4e7)'
504
+ : 'transparent';
505
+
484
506
  return (
485
507
  <div
486
508
  class={cn('relative flex items-center justify-center', local.class)}
487
509
  style={{
488
510
  cursor: isStatic() ? 'default' : isHoriz() ? 'col-resize' : 'row-resize',
489
511
  [isHoriz() ? 'width' : 'height']: '8px',
490
- 'background': isDragging() ? 'var(--color-muted-foreground, #98989f)' : 'transparent',
491
- 'opacity': isDragging() ? '0.3' : '1',
512
+ 'background': showStripHighlight() ? 'var(--color-muted-foreground, #98989f)' : 'transparent',
513
+ 'opacity': showStripHighlight() ? '0.3' : '1',
492
514
  }}
493
515
  onPointerDown={handlePointerDown}
494
516
  onPointerMove={handlePointerMove}
495
517
  onPointerUp={handlePointerUp}
518
+ onPointerEnter={() => setHovering(true)}
519
+ onPointerLeave={() => setHovering(false)}
496
520
  onDblClick={handleDblClick}
497
521
  onKeyDown={handleKeyDown}
498
522
  role="separator"
@@ -505,7 +529,7 @@ function ResizableHandle(props: ResizableHandleProps) {
505
529
  aria-valuenow={valueNow()}
506
530
  {...rest}
507
531
  >
508
- <Show when={local.withHandle}>
532
+ <Show when={mode() === 'grip'}>
509
533
  <div
510
534
  class={cn(
511
535
  'z-10 flex items-center justify-center',
@@ -529,6 +553,20 @@ function ResizableHandle(props: ResizableHandleProps) {
529
553
  </svg>
530
554
  </div>
531
555
  </Show>
556
+ <Show when={mode() === 'line'}>
557
+ {/* 1px hairline centered in the 8px grab zone; never eats the drag. */}
558
+ <div
559
+ class="pointer-events-none"
560
+ data-handle-line
561
+ style={{
562
+ position: 'absolute',
563
+ background: lineColor(),
564
+ ...(isHoriz()
565
+ ? { top: '0', bottom: '0', left: '50%', width: '1px', transform: 'translateX(-0.5px)' }
566
+ : { left: '0', right: '0', top: '50%', height: '1px', transform: 'translateY(-0.5px)' }),
567
+ }}
568
+ />
569
+ </Show>
532
570
  </div>
533
571
  );
534
572
  }
@@ -539,8 +577,8 @@ export interface ResizableProps {
539
577
  orientation?: Orientation;
540
578
  /** Fired on drag-end / keyboard resize / visibility change with the current panel sizes (percent). */
541
579
  onChange?: (sizes: number[]) => void;
542
- /** Show a visible grip on each interactive handle. */
543
- withHandle?: boolean;
580
+ /** Divider affordance on each interactive handle: `line` (default), `grip`, or `none`. */
581
+ handle?: 'line' | 'grip' | 'none';
544
582
  class?: string;
545
583
  /** Which panel index is maximized (null = none). Hides the others. */
546
584
  maximizedIndex?: number | null;
@@ -559,7 +597,7 @@ export interface ResizableProps {
559
597
  */
560
598
  function Resizable(props: ResizableProps) {
561
599
  const [local] = splitProps(props, [
562
- 'orientation', 'onChange', 'withHandle', 'class', 'children', 'maximizedIndex', 'onMaximizeChange',
600
+ 'orientation', 'onChange', 'handle', 'class', 'children', 'maximizedIndex', 'onMaximizeChange',
563
601
  ]);
564
602
  const orientation = () => local.orientation ?? 'horizontal';
565
603
 
@@ -627,7 +665,7 @@ function Resizable(props: ResizableProps) {
627
665
  <>
628
666
  <Show when={i() > 0}>
629
667
  <ResizableHandle
630
- withHandle={local.withHandle}
668
+ handle={local.handle ?? 'line'}
631
669
  static={panel.locked || renderPanels()[i() - 1]?.locked}
632
670
  onPanelResize={() => emitChange()}
633
671
  />
@@ -1 +0,0 @@
1
- import{e as t}from"./ellipsis-CUiEJ6R1.js";import{f as i,b as s,s as p,p as m,c as k}from"./share-BdenF0wQ.js";import{c as d}from"./check-fhmRksq_.js";import{d as y}from"./download-BDQwcFav.js";import{l as c}from"./link-vko7AttA.js";import{I as a}from"./Icon-C2LTALdM.js";import{c as o,m as r}from"./define-CNSKFZCd.js";import{s as n}from"./star-DY6z1UUH.js";import{a as f,t as h}from"./thumbs-up-BR33KLfA.js";import{x as l}from"./x-Ckwicf6g.js";var v=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],x=e=>o(a,r(e,{name:"RefreshCw",iconNode:v})),M=x,g=[["polyline",{points:"9 17 4 12 9 7",key:"hvgpf2"}],["path",{d:"M20 18v-2a4 4 0 0 0-4-4H4",key:"5vmcpk"}]],u=e=>o(a,r(e,{name:"Reply",iconNode:g})),w=u,N=[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]],C=e=>o(a,r(e,{name:"Trash2",iconNode:N})),H=C;const L={copy:k,like:h,dislike:f,regenerate:M,edit:m,share:p,bookmark:s,download:y,link:c,trash:H,check:d,x:l,star:n,flag:i,reply:w,more:t};function R(e){if(e)return L[e]}const b={copy:"Copy",like:"Like",dislike:"Dislike",regenerate:"Regenerate",edit:"Edit"};export{b as B,R as a};
@@ -1 +0,0 @@
1
- import{I as o}from"./Icon-C2LTALdM.js";import{c as m,m as r}from"./define-CNSKFZCd.js";var e=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],t=a=>m(o,r(a,{name:"ArrowLeft",iconNode:e})),p=t;export{p as a};
@@ -1 +0,0 @@
1
- import{g as A,p as Q,s as B,c as r,u as z,k as M,m as _,i,t as c,v as U,w as G,x as J,h as K,o as N,S as h,q as b,n as W,F as X,a as E,b as q,C as Y}from"./define-CNSKFZCd.js";import{s as Z,n as ee}from"./composer-BZyPU6gD.js";import{c as $}from"./cn-CU7UAWzk.js";import{M as te,a as I,b as re}from"./message-CgKytiSB.js";import{c as ne}from"./message-feedback-Ds3-US0e.js";import{M as le}from"./model-switcher-BMtXGwLU.js";import{B as se}from"./button-DBDZebGr.js";import{c as oe}from"./chevron-down-DcRg1aRU.js";import{C as ae,a as ie,b as ce,c as ue,d as de,e as ge,f as he,g as me}from"./context-Cx6eQA_G.js";import{D as ve}from"./default-input-BZcwyKcP.js";const fe=50;function xe(){const[e,u]=A(!0);let o,s=!0;function x(){if(!o)return;const{scrollTop:g,scrollHeight:w,clientHeight:S}=o,y=w-g-S<fe;u(y),s=y}function k(g="smooth"){o&&(o.scrollTo({top:o.scrollHeight,behavior:g}),s=!0,u(!0))}function m(){s&&requestAnimationFrame(()=>k("instant"))}function T(g){o=g,g.addEventListener("scroll",x,{passive:!0});const w=new MutationObserver(m);w.observe(g,{childList:!0,subtree:!0,characterData:!0}),Q(()=>{g.removeEventListener("scroll",x),w.disconnect()})}return{ref:T,isAtBottom:e,scrollToBottom:k}}var pe=c("<div role=log tabindex=0>"),be=c("<div>"),we=c("<div aria-hidden=true>");const L=U();function Ce(){const e=G(L);if(!e)throw new Error("useChatContainer must be used within ChatContainer");return e}function ke(e){const[u,o]=B(e,["children","class"]),{ref:s,isAtBottom:x,scrollToBottom:k}=xe();return r(L.Provider,{value:{isAtBottom:x,scrollToBottom:k},get children(){var m=pe();return z(s,m),M(m,_({get class(){return $("flex flex-col overflow-y-auto",u.class)}},o),!1,!0),i(m,()=>u.children),m}})}function Te(e){const[u,o]=B(e,["children","class"]);return(()=>{var s=be();return M(s,_({get class(){return $("flex w-full flex-col",u.class)}},o),!1,!0),i(s,()=>u.children),s})()}function ye(e){const[u,o]=B(e,["class"]);return(()=>{var s=we();return M(s,_({get class(){return $("h-px w-full shrink-0 scroll-mt-4",u.class)}},o),!1,!1),s})()}function Se(e){const{isAtBottom:u,scrollToBottom:o}=Ce();return r(se,{get variant(){return e.variant??"outline"},get size(){return e.size??"sm"},"aria-label":"Scroll to bottom",get class(){return $("h-10 w-10 rounded-full transition-all duration-150 ease-out",u()?"pointer-events-none translate-y-4 scale-95 opacity-0":"translate-y-0 scale-100 opacity-100",e.class)},onClick:()=>o(),get children(){return r(oe,{class:"h-5 w-5"})}})}var $e=c('<aside part=sidebar class="flex w-64 shrink-0 flex-col overflow-hidden border-r border-border"><slot name=sidebar>'),Ae=c("<header part=header class=shrink-0><slot name=header>"),Be=c("<slot name=empty>"),Me=c('<div class="absolute bottom-4 left-1/2 flex w-full max-w-3xl -translate-x-1/2 justify-center px-5">'),_e=c('<div class="shrink-0 px-4"><div class="mx-auto flex max-w-3xl items-center gap-2 pb-2"><slot name=composer-actions>'),He=c("<slot name=composer>"),Fe=c('<div part=footer class="shrink-0 px-4 pb-3"><div class="mx-auto max-w-3xl text-center text-xs text-muted-foreground"><slot name=footer>'),Ve=c('<div><div class="flex h-full min-w-0 flex-1 flex-col"><div class="relative flex-1 overflow-hidden"></div><div class="shrink-0 px-4 pb-4"><div class="mx-auto max-w-3xl">'),ze=c('<div class="text-sm font-semibold text-foreground">'),Ee=c("<div class=space-y-1.5>"),qe=c('<header part=header-bar class="flex h-14 shrink-0 items-center justify-between border-b border-border px-5"><div class="flex items-center gap-2"><slot name=header-start></slot></div><div class="flex items-center gap-2"><slot name=header-end>'),Ie=c("<div>");function Le(e){const u=J(),o=()=>e.actionsReveal==="hover"?"hover":"always";let s;const x=ne({emit:n=>{var l;return(l=e.onMessageAction)==null?void 0:l.call(e,n)},target:()=>s}),[k,m]=A(e.value??""),[T,g]=A([]),w=()=>typeof e.value=="string"?e.value:k();K(()=>{const n=e.value;n!=null&&typeof n!="string"&&m(n)});const S=n=>{var l;m(n),(l=e.onValueChange)==null||l.call(e,n)},y=()=>{g([]),e.value===void 0&&m("")},H=()=>{var n;(n=e.onSubmit)==null||n.call(e,{value:Z(ee(w())),attachments:T()}),y()},R=n=>{var l,d;(e.suggestionMode??"submit")==="fill"?(S(n),(l=e.onSuggestionClick)==null||l.call(e,n)):((d=e.onSubmit)==null||d.call(e,{value:n,attachments:T()}),y())},j=()=>!!(e.chatTitle||e.models||e.context||e.headerStart||e.headerEnd),D=()=>e.persistSuggestions||e.messages.length===0?e.suggestions:void 0,O=()=>e.scrollButton!==!1;return N(()=>{var n;(n=e.controllerRef)==null||n.call(e,{focus:l=>{var d;return(d=s?.querySelector('[contenteditable]:not([contenteditable="false"]), textarea'))==null?void 0:d.focus(l)},clear:()=>{var l;m(""),g([]),(l=e.onValueChange)==null||l.call(e,"")},send:()=>H(),scrollToBottom:l=>{const d=s?.querySelector(".overflow-y-auto");d?.scrollTo({top:d.scrollHeight,behavior:l??"smooth"})}})}),r(Y,{get proseSize(){return e.proseSize},get codeTheme(){return e.codeTheme},get codeHighlight(){return e.codeHighlight!==!1},get portalMount(){return u.portalMount()},get children(){var n=Ve(),l=n.firstChild,d=l.firstChild,F=d.nextSibling,P=F.firstChild;return z(t=>s=t,n),i(n,r(h,{get when(){return e.sidebar},get children(){var t=$e(),a=t.firstChild;return a._$owner=b(),t}}),l),i(l,r(h,{get when(){return e.headerFull},get fallback(){return r(h,{get when(){return j()},get children(){var t=qe(),a=t.firstChild,p=a.firstChild,f=a.nextSibling,C=f.firstChild;return p._$owner=b(),i(a,r(h,{get when(){return e.chatTitle},get children(){var v=ze();return i(v,()=>e.chatTitle),v}}),null),i(f,r(h,{get when(){return e.models},get children(){return r(le,{get models(){return e.models},get currentModelId(){var v;return e.currentModel??((v=e.models[0])==null?void 0:v.id)??""},onModelChange:v=>{var V;return(V=e.onModelChange)==null?void 0:V.call(e,v)}})}}),C),i(f,r(h,{get when(){return e.context},get children(){return r(ae,{get usedTokens(){return e.context.usedTokens},get maxTokens(){return e.context.maxTokens},get inputTokens(){return e.context.inputTokens},get outputTokens(){return e.context.outputTokens},get estimatedCost(){return e.context.estimatedCost},get children(){return[r(ie,{}),r(ce,{get children(){return[r(ue,{}),r(de,{get children(){var v=Ee();return i(v,r(ge,{}),null),i(v,r(he,{}),null),v}}),r(me,{})]}})]}})}}),C),C._$owner=b(),t}})},get children(){var t=Ae(),a=t.firstChild;return a._$owner=b(),t}}),d),i(d,r(ke,{class:"h-full px-4 py-3",get children(){return[r(Te,{class:"mx-auto w-full max-w-3xl space-y-4",get children(){return[r(h,{get when(){return W(()=>!!e.empty)()&&e.messages.length===0},get children(){var t=Be();return t._$owner=b(),t}}),r(X,{get each(){return e.messages},children:t=>{const a=r(te,{get content(){return t.content},get reasoning(){return t.reasoning},get tools(){return t.tools},get attachments(){return t.attachments},get isUser(){return t.role==="user"},get markdown(){return t.role==="assistant"},get actions(){return t.actions},get actionsReveal(){return o()},get activeFeedback(){return x.resolveFeedback(t)},get copied(){return x.isCopied(t.id)},onAction:f=>x.handleAction(t,f)}),p=o()==="hover"?"group ":"";return r(h,{get when(){return t.avatar},get fallback(){return r(I,{get class(){return`${p}${t.role==="user"?"flex-col items-end":"flex-col items-start"}`},children:a})},children:f=>r(I,{class:p,get children(){return[r(re,{get src(){return f().src??""},get alt(){return f().alt??""},get fallback(){return f().fallback}}),(()=>{var C=Ie();return i(C,a),E(()=>q(C,`flex min-w-0 flex-1 flex-col ${t.role==="user"?"items-end":"items-start"}`)),C})()]}})})}}),r(ye,{})]}}),r(h,{get when(){return O()},get children(){var t=Me();return i(t,r(Se,{class:"shadow-sm"})),t}})]}})),i(l,r(h,{get when(){return e.composerActions},get children(){var t=_e(),a=t.firstChild,p=a.firstChild;return p._$owner=b(),t}}),F),i(P,r(h,{get when(){return e.composer},get fallback(){return r(ve,{get value(){return w()},get placeholder(){return e.placeholder},get loading(){return e.loading===!0},get suggestions(){return D()},get attachments(){return T()},get search(){return e.search===!0},get voice(){return e.voice===!0},get triggers(){return e.triggers},get kindIcons(){return e.kindIcons},onValueChange:S,onSubmit:H,onSuggestionClick:R,onAttachmentsChange:t=>{var a;g(t),(a=e.onAttachmentsChange)==null||a.call(e,t)},onSearch:()=>{var t;return(t=e.onSearch)==null?void 0:t.call(e)},onVoice:()=>{var t;return(t=e.onVoice)==null?void 0:t.call(e)}})},get children(){var t=He();return t._$owner=b(),t}})),i(l,r(h,{get when(){return e.footer},get children(){var t=Fe(),a=t.firstChild,p=a.firstChild;return p._$owner=b(),t}}),null),E(()=>q(n,`flex h-full bg-background ${e.class??""}`)),n}})}export{Le as C};
@@ -1 +0,0 @@
1
- import{I as m}from"./Icon-C2LTALdM.js";import{c as o,m as r}from"./define-CNSKFZCd.js";var c=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],e=a=>o(m,r(a,{name:"Check",iconNode:c})),f=e;export{f as c};
@@ -1 +0,0 @@
1
- import{I as r}from"./Icon-C2LTALdM.js";import{c as a,m}from"./define-CNSKFZCd.js";var e=[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]],n=o=>a(r,m(o,{name:"ChevronDown",iconNode:e})),p=n;export{p as c};
@@ -1 +0,0 @@
1
- import{I as o}from"./Icon-C2LTALdM.js";import{c as a,m as r}from"./define-CNSKFZCd.js";var t=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],e=m=>a(o,r(m,{name:"ChevronRight",iconNode:t})),h=e;export{h as c};
@@ -1 +0,0 @@
1
- import{I as c}from"./Icon-C2LTALdM.js";import{c as m,m as o}from"./define-CNSKFZCd.js";var a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],e=r=>m(c,o(r,{name:"Circle",iconNode:a})),i=e;export{i as c};
@@ -1 +0,0 @@
1
- import{I as c}from"./Icon-C2LTALdM.js";import{c as r,m as a}from"./define-CNSKFZCd.js";var e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],o=m=>r(c,a(m,{name:"CircleCheck",iconNode:e})),i=o;export{i as c};