@astryx-vue/core 0.2.0 → 0.2.2

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 (225) hide show
  1. package/dist/AppShell/index.js +1 -1
  2. package/dist/{AppShell-EiGyPFbA.js → AppShell-Cb41gIAj.js} +2 -2
  3. package/dist/Avatar/index.d.ts +1 -1
  4. package/dist/Avatar/index.js +1 -1
  5. package/dist/Avatar-jmHInHwj.js +244 -0
  6. package/dist/AvatarGroup/index.d.ts +1 -1
  7. package/dist/AvatarGroup/index.js +1 -1
  8. package/dist/{AvatarGroup-CZcgAMjM.js → AvatarGroup-QyzSrpYA.js} +8 -7
  9. package/dist/Badge/index.d.ts +2 -1
  10. package/dist/Badge/index.js +1 -1
  11. package/dist/Badge-CjDQPF5g.js +30 -0
  12. package/dist/Banner/index.d.ts +1 -1
  13. package/dist/Breadcrumbs/index.d.ts +2 -2
  14. package/dist/Breadcrumbs/index.js +1 -1
  15. package/dist/{Breadcrumbs-C3YZFQ9w.js → Breadcrumbs-CTQ7fjDn.js} +45 -11
  16. package/dist/ButtonGroup/index.d.ts +1 -1
  17. package/dist/Calendar/index.d.ts +2 -2
  18. package/dist/Calendar/index.js +1 -1
  19. package/dist/Calendar-BFaI5AMv.js +352 -0
  20. package/dist/Card/index.d.ts +1 -1
  21. package/dist/Carousel/index.js +1 -1
  22. package/dist/{Carousel-BTtHkKea.js → Carousel-X0cuQkvg.js} +2 -2
  23. package/dist/Chat/index.d.ts +2 -2
  24. package/dist/Chat/index.js +2 -2
  25. package/dist/Chat-M0tiGrNT.js +3148 -0
  26. package/dist/CheckboxInput/index.d.ts +1 -1
  27. package/dist/CheckboxList/index.d.ts +1 -1
  28. package/dist/CheckboxList/index.js +1 -1
  29. package/dist/Citation/index.js +1 -1
  30. package/dist/{Citation-DijdbUUu.js → Citation-C1veUk5p.js} +2 -2
  31. package/dist/ClickableCard/index.d.ts +1 -1
  32. package/dist/Code/index.js +1 -1
  33. package/dist/{CodeBlock-CxTb6j9_.js → CodeBlock-DesTJAkm.js} +1 -1
  34. package/dist/Collapsible/index.js +1 -1
  35. package/dist/{Collapsible-9MC08X39.js → Collapsible-BestK35V.js} +1 -1
  36. package/dist/CommandPalette/index.js +1 -1
  37. package/dist/{CommandPalette-BD2iODF5.js → CommandPalette-BArt97V8.js} +1 -1
  38. package/dist/ComplexSelector/index.d.ts +1 -1
  39. package/dist/ComplexSelector/index.js +1 -1
  40. package/dist/{ComplexSelector-D9iXd5cH.js → ComplexSelector-CAoig1RK.js} +2 -2
  41. package/dist/ContextMenu/index.d.ts +2 -2
  42. package/dist/ContextMenu/index.js +1 -1
  43. package/dist/{ContextMenu-HS_WqYIg.js → ContextMenu-9Im6i43E.js} +101 -63
  44. package/dist/DateInput/index.d.ts +2 -2
  45. package/dist/DateInput/index.js +1 -1
  46. package/dist/DateInput-BLocxqC3.js +376 -0
  47. package/dist/DateRangeInput/index.d.ts +1 -1
  48. package/dist/DateRangeInput/index.js +1 -1
  49. package/dist/{DateRangeInput-1bhPGG2s.js → DateRangeInput-DwDy3eRo.js} +2 -2
  50. package/dist/DateTimeInput/index.d.ts +1 -1
  51. package/dist/DateTimeInput/index.js +1 -1
  52. package/dist/{DateTimeInput-SnPdgk6P.js → DateTimeInput-BYiaC9bn.js} +5 -3
  53. package/dist/DropdownMenu/index.d.ts +3 -2
  54. package/dist/DropdownMenu/index.js +3 -2
  55. package/dist/DropdownMenu-Ddfr-aEC.js +164 -0
  56. package/dist/{DropdownMenu-UJGych6v.js → DropdownMenu-Dh-WAHKb.js} +63 -16
  57. package/dist/FileInput/index.d.ts +1 -1
  58. package/dist/FileInput/index.js +1 -1
  59. package/dist/{FileInput-BQiwE-GB.js → FileInput-DLO6V12n.js} +2 -2
  60. package/dist/Icon/index.d.ts +4 -3
  61. package/dist/Icon/index.js +3 -2
  62. package/dist/Icon/plugin.d.ts +45 -0
  63. package/dist/Icon/plugin.js +177 -0
  64. package/dist/Icon-CJKjdJy-.js +198 -0
  65. package/dist/Lightbox/index.js +1 -1
  66. package/dist/{Lightbox-CshKoB0r.js → Lightbox-CdFCS_EA.js} +2 -2
  67. package/dist/Link/index.d.ts +1 -1
  68. package/dist/Link/index.js +1 -1
  69. package/dist/Link-DqdQZ37C.js +141 -0
  70. package/dist/Markdown/index.js +1 -1
  71. package/dist/{Markdown-D-Ua055V.js → Markdown-BqKloI3w.js} +3 -3
  72. package/dist/MetadataList/index.js +1 -1
  73. package/dist/{MetadataList-sYph_d8Q.js → MetadataList-CvZkmisf.js} +1 -1
  74. package/dist/MobileNav/index.js +1 -1
  75. package/dist/{MobileNavToggle-DD9D01aj.js → MobileNavToggle-Bwi0qXXM.js} +1 -1
  76. package/dist/MoreMenu/index.d.ts +1 -1
  77. package/dist/MoreMenu/index.js +1 -1
  78. package/dist/{MoreMenu-BVrK5kkk.js → MoreMenu-BwJaJrkB.js} +2 -2
  79. package/dist/MultiSelector/index.js +1 -1
  80. package/dist/{MultiSelector-DiYov9sF.js → MultiSelector-C-ETfFy_.js} +2 -2
  81. package/dist/NumberInput/index.d.ts +2 -2
  82. package/dist/NumberInput/index.js +1 -1
  83. package/dist/NumberInput-DjetVB8O.js +210 -0
  84. package/dist/Outline/index.js +1 -1
  85. package/dist/{Outline-DQfEBxlc.js → Outline-Bz9c-u5y.js} +1 -1
  86. package/dist/OverflowList/index.d.ts +1 -1
  87. package/dist/OverflowList/index.js +1 -1
  88. package/dist/{OverflowList-BU5VCntl.js → OverflowList-ED-KN9Y5.js} +1 -1
  89. package/dist/Overlay/index.d.ts +1 -1
  90. package/dist/Pagination/index.d.ts +2 -2
  91. package/dist/Pagination/index.js +1 -1
  92. package/dist/{Pagination-HdhtykeF.js → Pagination-Cb66HMEf.js} +76 -22
  93. package/dist/PowerSearch/index.d.ts +2 -0
  94. package/dist/PowerSearch/index.js +2 -0
  95. package/dist/PowerSearch-CjU8y4mi.js +2386 -0
  96. package/dist/ProgressBar/index.d.ts +1 -1
  97. package/dist/ProgressBar/index.js +1 -1
  98. package/dist/{ProgressBar-ChDp-7oe.js → ProgressBar-DO9Chic6.js} +31 -11
  99. package/dist/Radio/index.d.ts +1 -1
  100. package/dist/RadioList/index.d.ts +1 -1
  101. package/dist/Resizable/index.d.ts +1 -1
  102. package/dist/Resizable/index.js +1 -1
  103. package/dist/{Resizable-BhSMfO5B.js → Resizable-D-TBCg0Y.js} +1 -1
  104. package/dist/ScrollableArea/index.d.ts +1 -1
  105. package/dist/SelectableCard/index.d.ts +1 -1
  106. package/dist/Selector/index.d.ts +2 -2
  107. package/dist/Selector/index.js +1 -1
  108. package/dist/Selector-DE2IEFML.js +468 -0
  109. package/dist/SideNav/index.d.ts +1 -1
  110. package/dist/SideNav/index.js +1 -1
  111. package/dist/{SideNav-B-Om6Bz3.js → SideNav-DonEfiAx.js} +17 -17
  112. package/dist/Stepper/index.js +1 -1
  113. package/dist/{Stepper-BZ_8SEZt.js → Stepper-D2ZIcUNx.js} +1 -1
  114. package/dist/Switch/index.d.ts +1 -1
  115. package/dist/TabList/index.d.ts +1 -1
  116. package/dist/TabList/index.js +1 -1
  117. package/dist/{TabList-Cnc2Hs1H.js → TabList-D3F-stq-.js} +10 -3
  118. package/dist/Table/index.d.ts +3 -3
  119. package/dist/Table/index.js +2 -2
  120. package/dist/Table-CrP2Uffq.js +1982 -0
  121. package/dist/TextArea/index.d.ts +1 -1
  122. package/dist/TextArea/index.js +1 -1
  123. package/dist/{TextArea-Cv9UVbzS.js → TextArea-CPwSytdO.js} +12 -6
  124. package/dist/TextInput/index.d.ts +1 -1
  125. package/dist/Thumbnail/index.js +1 -1
  126. package/dist/{Thumbnail-Dc1UpykG.js → Thumbnail-BC5JGPGZ.js} +11 -11
  127. package/dist/{TimeField-C7t9Knq9.js → TimeField-DpHFrNv0.js} +2 -2
  128. package/dist/TimeInput/index.d.ts +1 -1
  129. package/dist/TimeInput/index.js +1 -1
  130. package/dist/{TimeInput-BXxCOY98.js → TimeInput-DJLV0_4z.js} +1 -1
  131. package/dist/Timestamp/index.d.ts +1 -1
  132. package/dist/Timestamp/index.js +2 -1
  133. package/dist/{Timestamp-DlZSmgCJ.js → Timestamp-GKNjjt5g.js} +4 -121
  134. package/dist/Toast/index.d.ts +2 -2
  135. package/dist/Toast/index.js +1 -1
  136. package/dist/{Toast-BKXPvEMp.js → Toast-C2ML-bmi.js} +37 -7
  137. package/dist/ToggleButton/index.d.ts +1 -1
  138. package/dist/Token/index.js +1 -1
  139. package/dist/{Token-wt4e80IM.js → Token-Chu_eCVs.js} +1 -1
  140. package/dist/Tokenizer/index.js +1 -1
  141. package/dist/{Tokenizer-nmTqdS9n.js → Tokenizer-DRDpvQ_l.js} +3 -3
  142. package/dist/Toolbar/index.d.ts +1 -1
  143. package/dist/TopNav/index.d.ts +1 -1
  144. package/dist/TopNav/index.js +1 -1
  145. package/dist/{TopNav-notp-mJx.js → TopNav-CEyWddtA.js} +3 -3
  146. package/dist/TreeList/index.js +1 -1
  147. package/dist/{TreeList-s1xBQ0qq.js → TreeList-COS8US8e.js} +3 -3
  148. package/dist/Typeahead/index.js +1 -1
  149. package/dist/{Typeahead-VAuiCurV.js → Typeahead-P7cCqtaQ.js} +3 -3
  150. package/dist/{useTypeaheadMenu-Ba7XpcZl.js → TypeaheadMenu-CGvo0Tud.js} +107 -107
  151. package/dist/composables/index.d.ts +3 -3
  152. package/dist/{context-Dbo22sck.js → context-CiKncNSH.js} +184 -6
  153. package/dist/formatInstant-BDWUIxS7.js +119 -0
  154. package/dist/{index-SMh42NI8.d.ts → index-B1TG2GPS.d.ts} +2 -2
  155. package/dist/{index-DRmb-oUa.d.ts → index-B9WzWqzk.d.ts} +3 -29
  156. package/dist/{index-BuubzuE8.d.ts → index-BDZCJ9ri.d.ts} +2 -2
  157. package/dist/{index-z4p2k9VR.d.ts → index-Bgv4ZmBl.d.ts} +20 -2
  158. package/dist/{index-BoRIw-bP.d.ts → index-Bt7ewoz2.d.ts} +32 -2
  159. package/dist/{index-uQrLBZfF.d.ts → index-Bu3oXIIS.d.ts} +24 -0
  160. package/dist/index-CJlBAXwj.d.ts +100 -0
  161. package/dist/{index-DT9S92sh.d.ts → index-CNjbOgty.d.ts} +15 -0
  162. package/dist/{index-DXSs3rwa.d.ts → index-CngORuiq.d.ts} +137 -2
  163. package/dist/index-Cs5pcwY3.d.ts +416 -0
  164. package/dist/{index-NzLDrOkk.d.ts → index-D4I_obqR.d.ts} +14 -1
  165. package/dist/{index-BtPZKO20.d.ts → index-DGXviOlp.d.ts} +49 -7
  166. package/dist/{index-CTolyeii.d.ts → index-DN7MXPHM.d.ts} +15 -1
  167. package/dist/{index-C6iJwTL0.d.ts → index-DNRBbwp_.d.ts} +165 -55
  168. package/dist/{index-C59PdX9L.d.ts → index-DWH_HzXi.d.ts} +1 -1
  169. package/dist/{index-BfOtLyNH.d.ts → index-D_nYxnNv.d.ts} +1 -1
  170. package/dist/{index-Bn_odHQ9.d.ts → index-Dg3ZmwEg.d.ts} +12 -1
  171. package/dist/{index-BiMGXtD-.d.ts → index-DimuSES0.d.ts} +14 -1
  172. package/dist/{index-DEeJowZf.d.ts → index-cGagYM1g.d.ts} +15 -2
  173. package/dist/{index-Ck8qy6Rf.d.ts → index-c_TaW8mM.d.ts} +24 -2
  174. package/dist/index-kVhSuA9t.d.ts +902 -0
  175. package/dist/{index-1uRMrk63.d.ts → index-nCEmW6wa.d.ts} +1 -1
  176. package/dist/{index-DoFCm2Q2.d.ts → index-qiZtHnGK.d.ts} +26 -0
  177. package/dist/{index-Bainr_3n.d.ts → index-u5ozu0G7.d.ts} +2 -2
  178. package/dist/index.d.ts +79 -76
  179. package/dist/index.js +111 -100
  180. package/dist/locale/index.d.ts +1 -1
  181. package/dist/locale/index.js +1 -1
  182. package/dist/power-search-DrDfcoL1.d.ts +68 -0
  183. package/dist/registry-B1ObuP--.d.ts +45 -0
  184. package/dist/registry-C7OZb0PJ.js +135 -0
  185. package/dist/{types-JktRxujg.d.ts → types-Cp4liug-.d.ts} +66 -3
  186. package/dist/{index-B8MbfMwV.d.ts → types-DDAAT1Xd.d.ts} +36 -82
  187. package/dist/types-DIYwbkpe.d.ts +33 -0
  188. package/dist/{types-DZoEm_DG.d.ts → types-pnljWjEE.d.ts} +23 -6
  189. package/dist/{useResizable-CeESZS1v.d.ts → useResizable-CUoNPmUw.d.ts} +1 -1
  190. package/package.json +10 -1
  191. package/styles/avatar.css +14 -0
  192. package/styles/badge.css +8 -0
  193. package/styles/breadcrumbs.css +6 -1
  194. package/styles/button.css +13 -0
  195. package/styles/calendar.css +16 -0
  196. package/styles/card.css +2 -0
  197. package/styles/chat.css +474 -3
  198. package/styles/dropdown-menu.css +11 -0
  199. package/styles/icon.css +58 -8
  200. package/styles/link.css +5 -0
  201. package/styles/number-input.css +22 -0
  202. package/styles/pagination.css +45 -1
  203. package/styles/power-search.css +303 -0
  204. package/styles/progress-bar.css +4 -0
  205. package/styles/selector.css +58 -0
  206. package/styles/tab-list.css +15 -0
  207. package/styles/table.css +304 -1
  208. package/styles/text-area.css +17 -0
  209. package/styles/text-input.css +5 -0
  210. package/styles/toast.css +7 -0
  211. package/styles/toolbar.css +11 -0
  212. package/styles.css +1 -0
  213. package/dist/Avatar-5s3HSrtN.js +0 -136
  214. package/dist/Badge-Ba0EwsTj.js +0 -19
  215. package/dist/Calendar-BG7wEsro.js +0 -277
  216. package/dist/Chat-DjNxw5gv.js +0 -1051
  217. package/dist/DateInput-DE2TN1Rq.js +0 -250
  218. package/dist/Icon-Dfr4k6-S.js +0 -86
  219. package/dist/Link-CprjOuTI.js +0 -92
  220. package/dist/NumberInput-CTYiQxHq.js +0 -147
  221. package/dist/Selector-Da-2BO3q.js +0 -325
  222. package/dist/Table-D-LhAy_J.js +0 -967
  223. package/dist/index-DJo-BmXZ.d.ts +0 -431
  224. package/dist/index-DtjAdYEc.d.ts +0 -68
  225. package/dist/{CheckboxList-EsIEXjK_.js → CheckboxList-BbCPiend.js} +1 -1
@@ -0,0 +1,3148 @@
1
+ import { t as _sfc_main$24 } from "./VisuallyHidden-C7NN3VEP.js";
2
+ import { t as _sfc_main$25 } from "./Button-DrqeomlE.js";
3
+ import { t as useAnnounce } from "./announce-Cnio10Wk.js";
4
+ import { r as useChatNewMessages, t as useChatStreamScroll } from "./useChatStreamScroll-oDfybEos.js";
5
+ import { t as useDismiss } from "./useDismiss-CAiHU6QD.js";
6
+ import { a as useAstryxMessages, i as useAstryxLocale } from "./context-CiKncNSH.js";
7
+ import { t as usePositionedLayer } from "./usePositionedLayer-C1NBOsVT.js";
8
+ import { t as _sfc_main$26 } from "./Badge-CjDQPF5g.js";
9
+ import { t as _sfc_main$27 } from "./Icon-CJKjdJy-.js";
10
+ import { t as _sfc_main$28 } from "./Divider-BtwnL18I.js";
11
+ import { t as _sfc_main$29 } from "./HoverCard-EhbWaGOW.js";
12
+ import { t as _sfc_main$30 } from "./Spinner-9cYUCnDy.js";
13
+ import { Fragment, Teleport, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, getCurrentInstance, inject, mergeModels, mergeProps, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, onScopeDispose, openBlock, provide, reactive, ref, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, unref, useId, useModel, useSlots, useTemplateRef, vShow, watch, withCtx, withDirectives } from "vue";
14
+ import "../styles/popover.css";
15
+ import "../styles/chat.css";
16
+ //#region src/Chat/useChatPasteAsToken.ts
17
+ const PASTE_TOKEN_THRESHOLD = 200;
18
+ function pastedTextLabel(text) {
19
+ const lines = text.split("\n").length;
20
+ const chars = text.length;
21
+ return lines > 1 ? `${lines} lines, ${chars} chars` : `${chars} chars`;
22
+ }
23
+ function defaultToToken(text) {
24
+ return {
25
+ value: text,
26
+ label: pastedTextLabel(text),
27
+ variant: "neutral"
28
+ };
29
+ }
30
+ function useChatPasteAsToken(options) {
31
+ const threshold = options.threshold ?? 200;
32
+ const toToken = options.toToken ?? defaultToToken;
33
+ function onPaste(text) {
34
+ if (text.length <= threshold) return false;
35
+ options.input.value?.insertToken(toToken(text));
36
+ return true;
37
+ }
38
+ return { onPaste };
39
+ }
40
+ //#endregion
41
+ //#region src/Chat/ChatPastedTextToken.vue
42
+ const _hoisted_1$19 = { class: "astryx-chat-paste" };
43
+ const _hoisted_2$14 = { class: "astryx-chat-paste__text" };
44
+ const _hoisted_3$12 = { class: "astryx-chat-paste__footer" };
45
+ const _hoisted_4$11 = { class: "astryx-chat-paste__meta" };
46
+ const _sfc_main$23 = /*@__PURE__*/ defineComponent({
47
+ __name: "ChatPastedTextToken",
48
+ props: { text: {} },
49
+ emits: ["expand"],
50
+ setup(__props, { emit: __emit }) {
51
+ const props = __props;
52
+ const emit = __emit;
53
+ const messages = useAstryxMessages();
54
+ const label = computed(() => pastedTextLabel(props.text));
55
+ return (_ctx, _cache) => {
56
+ return openBlock(), createBlock(_sfc_main$29, {
57
+ placement: "above",
58
+ alignment: "start",
59
+ "has-hover-indication": false
60
+ }, {
61
+ content: withCtx(() => [createElementVNode("div", _hoisted_1$19, [createElementVNode("div", _hoisted_2$14, toDisplayString(__props.text), 1), createElementVNode("div", _hoisted_3$12, [createElementVNode("span", _hoisted_4$11, toDisplayString(label.value), 1), createVNode(_sfc_main$25, {
62
+ label: unref(messages).chat.expandPastedText,
63
+ variant: "ghost",
64
+ size: "sm",
65
+ onClick: _cache[0] || (_cache[0] = ($event) => emit("expand"))
66
+ }, null, 8, ["label"])])])]),
67
+ default: withCtx(() => [createVNode(_sfc_main$26, {
68
+ label: label.value,
69
+ variant: "neutral"
70
+ }, null, 8, ["label"])]),
71
+ _: 1
72
+ });
73
+ };
74
+ }
75
+ });
76
+ //#endregion
77
+ //#region src/Chat/tokens.ts
78
+ function isCustomToken(token) {
79
+ return "render" in token && typeof token.render === "function";
80
+ }
81
+ //#endregion
82
+ //#region src/Chat/ChatTokenContent.vue
83
+ const _sfc_main$22 = /*@__PURE__*/ defineComponent({
84
+ __name: "ChatTokenContent",
85
+ props: { token: {} },
86
+ setup(__props) {
87
+ const props = __props;
88
+ const custom = computed(() => isCustomToken(props.token) ? props.token : void 0);
89
+ const badge = computed(() => isCustomToken(props.token) ? void 0 : props.token);
90
+ const view = computed(() => {
91
+ const render = custom.value?.render;
92
+ if (!render) return void 0;
93
+ return { render: () => render() };
94
+ });
95
+ const iconName = computed(() => {
96
+ const icon = badge.value?.icon;
97
+ return icon?.includes(":") ? icon : void 0;
98
+ });
99
+ const glyph = computed(() => {
100
+ const icon = badge.value?.icon;
101
+ return icon && !icon.includes(":") ? icon : void 0;
102
+ });
103
+ return (_ctx, _cache) => {
104
+ return view.value ? (openBlock(), createBlock(resolveDynamicComponent(view.value), { key: 0 })) : badge.value ? (openBlock(), createBlock(_sfc_main$26, {
105
+ key: 1,
106
+ label: badge.value.label ?? badge.value.value,
107
+ variant: badge.value.variant
108
+ }, createSlots({ _: 2 }, [iconName.value || glyph.value ? {
109
+ name: "icon",
110
+ fn: withCtx(() => [iconName.value ? (openBlock(), createBlock(_sfc_main$27, {
111
+ key: 0,
112
+ name: iconName.value
113
+ }, null, 8, ["name"])) : (openBlock(), createBlock(_sfc_main$27, {
114
+ key: 1,
115
+ icon: glyph.value
116
+ }, null, 8, ["icon"]))]),
117
+ key: "0"
118
+ } : void 0]), 1032, ["label", "variant"])) : createCommentVNode("v-if", true);
119
+ };
120
+ }
121
+ });
122
+ //#endregion
123
+ //#region src/Chat/groupSearchItems.ts
124
+ function itemGroup(item) {
125
+ const aux = item.auxiliaryData;
126
+ if (aux == null || typeof aux !== "object") return void 0;
127
+ const group = aux.group;
128
+ return typeof group === "string" ? group : void 0;
129
+ }
130
+ /** Keep the order groups first appear in. Ungrouped items come last. */
131
+ function groupSearchItems(items) {
132
+ if (!items.some((item) => itemGroup(item) != null)) return [{
133
+ heading: null,
134
+ items: [...items]
135
+ }];
136
+ const order = [];
137
+ const groups = /* @__PURE__ */ new Map();
138
+ const ungrouped = [];
139
+ for (const item of items) {
140
+ const group = itemGroup(item);
141
+ if (group == null) {
142
+ ungrouped.push(item);
143
+ continue;
144
+ }
145
+ const list = groups.get(group);
146
+ if (list) list.push(item);
147
+ else {
148
+ order.push(group);
149
+ groups.set(group, [item]);
150
+ }
151
+ }
152
+ const named = order.map((heading) => ({
153
+ heading,
154
+ items: groups.get(heading) ?? []
155
+ }));
156
+ return ungrouped.length > 0 ? [...named, {
157
+ heading: null,
158
+ items: ungrouped
159
+ }] : named;
160
+ }
161
+ //#endregion
162
+ //#region src/Chat/ChatTriggerMenu.vue
163
+ const _hoisted_1$18 = ["id", "aria-label"];
164
+ const _hoisted_2$13 = {
165
+ key: 0,
166
+ class: "astryx-chat-trigger__status",
167
+ role: "status"
168
+ };
169
+ const _hoisted_3$11 = {
170
+ key: 1,
171
+ class: "astryx-chat-trigger__status"
172
+ };
173
+ const _hoisted_4$10 = ["aria-label"];
174
+ const _hoisted_5$8 = {
175
+ class: "astryx-chat-trigger__heading",
176
+ "aria-hidden": "true"
177
+ };
178
+ const _hoisted_6$7 = [
179
+ "id",
180
+ "aria-selected",
181
+ "data-highlighted",
182
+ "onMousedown",
183
+ "onMouseenter",
184
+ "onFocus"
185
+ ];
186
+ const _hoisted_7$6 = {
187
+ key: 1,
188
+ class: "astryx-chat-trigger__label"
189
+ };
190
+ const _hoisted_8$4 = [
191
+ "id",
192
+ "aria-selected",
193
+ "data-highlighted",
194
+ "onMousedown",
195
+ "onMouseenter",
196
+ "onFocus"
197
+ ];
198
+ const _hoisted_9$4 = {
199
+ key: 1,
200
+ class: "astryx-chat-trigger__label"
201
+ };
202
+ const _sfc_main$21 = /*@__PURE__*/ defineComponent({
203
+ __name: "ChatTriggerMenu",
204
+ props: {
205
+ open: { type: Boolean },
206
+ anchor: {},
207
+ listboxId: {},
208
+ label: {},
209
+ items: {},
210
+ highlightedIndex: {},
211
+ loading: { type: Boolean },
212
+ loadingText: {},
213
+ empty: {},
214
+ keyboardMove: { type: Boolean },
215
+ renderItem: { type: Function }
216
+ },
217
+ emits: [
218
+ "select",
219
+ "hover",
220
+ "dismiss"
221
+ ],
222
+ setup(__props, { emit: __emit }) {
223
+ const props = __props;
224
+ const emit = __emit;
225
+ const surfaceRef = ref(null);
226
+ const open = computed(() => props.open);
227
+ const anchorWritable = ref(null);
228
+ watch(() => props.anchor, (value) => {
229
+ anchorWritable.value = value;
230
+ }, { immediate: true });
231
+ const { position } = usePositionedLayer({
232
+ anchor: anchorWritable,
233
+ layer: surfaceRef,
234
+ isOpen: open,
235
+ placement: computed(() => "above"),
236
+ alignment: computed(() => "start"),
237
+ gap: 4
238
+ });
239
+ useDismiss({
240
+ container: surfaceRef,
241
+ trigger: anchorWritable,
242
+ isEnabled: open,
243
+ enableEscape: computed(() => false),
244
+ onDismiss: () => emit("dismiss")
245
+ });
246
+ const groups = computed(() => groupSearchItems(props.items));
247
+ const style = computed(() => ({
248
+ top: `${position.value.top}px`,
249
+ left: `${position.value.left}px`
250
+ }));
251
+ function optionId(index) {
252
+ return `${props.listboxId}-option-${index}`;
253
+ }
254
+ function flatIndex(group, indexInGroup) {
255
+ let offset = 0;
256
+ for (const entry of groups.value) {
257
+ if (entry === group) return offset + indexInGroup;
258
+ offset += entry.items.length;
259
+ }
260
+ return indexInGroup;
261
+ }
262
+ const emptyView = computed(() => {
263
+ const value = props.empty;
264
+ if (value == null || typeof value === "string") return void 0;
265
+ return { render: () => value };
266
+ });
267
+ const itemView = computed(() => {
268
+ const render = props.renderItem;
269
+ if (!render) return void 0;
270
+ return {
271
+ props: ["item"],
272
+ setup(viewProps) {
273
+ return () => render(viewProps.item);
274
+ }
275
+ };
276
+ });
277
+ watch(() => props.highlightedIndex, (index) => {
278
+ if (!props.open || !props.keyboardMove || index < 0) return;
279
+ document.getElementById(optionId(index))?.scrollIntoView({ block: "nearest" });
280
+ });
281
+ function onSelect(item, event) {
282
+ event.preventDefault();
283
+ emit("select", item);
284
+ }
285
+ return (_ctx, _cache) => {
286
+ return openBlock(), createBlock(Teleport, { to: "body" }, [withDirectives(createElementVNode("div", {
287
+ ref_key: "surfaceRef",
288
+ ref: surfaceRef,
289
+ class: "astryx-popover astryx-chat-trigger",
290
+ "data-placement": "above",
291
+ style: normalizeStyle(style.value)
292
+ }, [createElementVNode("div", {
293
+ id: __props.listboxId,
294
+ class: "astryx-chat-trigger__list",
295
+ role: "listbox",
296
+ "aria-label": __props.label
297
+ }, [__props.loading ? (openBlock(), createElementBlock("div", _hoisted_2$13, toDisplayString(__props.loadingText), 1)) : __props.items.length === 0 && __props.empty !== "" ? (openBlock(), createElementBlock("div", _hoisted_3$11, [typeof __props.empty === "string" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(toDisplayString(__props.empty), 1)], 64)) : emptyView.value ? (openBlock(), createBlock(resolveDynamicComponent(emptyView.value), { key: 1 })) : createCommentVNode("v-if", true)])) : (openBlock(true), createElementBlock(Fragment, { key: 2 }, renderList(groups.value, (group) => {
298
+ return openBlock(), createElementBlock(Fragment, { key: group.heading ?? "ungrouped" }, [group.heading ? (openBlock(), createElementBlock("div", {
299
+ key: 0,
300
+ role: "group",
301
+ "aria-label": group.heading
302
+ }, [createElementVNode("div", _hoisted_5$8, toDisplayString(group.heading), 1), (openBlock(true), createElementBlock(Fragment, null, renderList(group.items, (item, index) => {
303
+ return openBlock(), createElementBlock("div", {
304
+ id: optionId(flatIndex(group, index)),
305
+ key: item.id,
306
+ class: "astryx-chat-trigger__item",
307
+ role: "option",
308
+ "aria-selected": flatIndex(group, index) === __props.highlightedIndex,
309
+ "data-highlighted": flatIndex(group, index) === __props.highlightedIndex ? "" : void 0,
310
+ tabindex: "-1",
311
+ onMousedown: ($event) => onSelect(item, $event),
312
+ onMouseenter: ($event) => emit("hover", flatIndex(group, index)),
313
+ onFocus: ($event) => emit("hover", flatIndex(group, index))
314
+ }, [itemView.value ? (openBlock(), createBlock(resolveDynamicComponent(itemView.value), {
315
+ key: 0,
316
+ item
317
+ }, null, 8, ["item"])) : (openBlock(), createElementBlock("span", _hoisted_7$6, toDisplayString(item.label), 1))], 40, _hoisted_6$7);
318
+ }), 128))], 8, _hoisted_4$10)) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(group.items, (item, index) => {
319
+ return openBlock(), createElementBlock("div", {
320
+ id: optionId(flatIndex(group, index)),
321
+ key: item.id,
322
+ class: "astryx-chat-trigger__item",
323
+ role: "option",
324
+ "aria-selected": flatIndex(group, index) === __props.highlightedIndex,
325
+ "data-highlighted": flatIndex(group, index) === __props.highlightedIndex ? "" : void 0,
326
+ tabindex: "-1",
327
+ onMousedown: ($event) => onSelect(item, $event),
328
+ onMouseenter: ($event) => emit("hover", flatIndex(group, index)),
329
+ onFocus: ($event) => emit("hover", flatIndex(group, index))
330
+ }, [itemView.value ? (openBlock(), createBlock(resolveDynamicComponent(itemView.value), {
331
+ key: 0,
332
+ item
333
+ }, null, 8, ["item"])) : (openBlock(), createElementBlock("span", _hoisted_9$4, toDisplayString(item.label), 1))], 40, _hoisted_8$4);
334
+ }), 128))], 64);
335
+ }), 128))], 8, _hoisted_1$18)], 4), [[vShow, open.value]])]);
336
+ };
337
+ }
338
+ });
339
+ //#endregion
340
+ //#region src/Chat/context.ts
341
+ const chatMessageContextKey = Symbol("astryx-chat-message");
342
+ function useChatMessageContext() {
343
+ return inject(chatMessageContextKey, void 0);
344
+ }
345
+ const chatListContextKey = Symbol("astryx-chat-list");
346
+ function useChatListContext() {
347
+ return inject(chatListContextKey, void 0);
348
+ }
349
+ const chatLayoutContextKey = Symbol("astryx-chat-layout");
350
+ function useChatLayoutContext() {
351
+ return inject(chatLayoutContextKey, void 0);
352
+ }
353
+ const chatComposerContextKey = Symbol("astryx-chat-composer");
354
+ function useChatComposerContext() {
355
+ return inject(chatComposerContextKey, void 0);
356
+ }
357
+ //#endregion
358
+ //#region src/Chat/events.ts
359
+ /** Whether the vnode was given a listener for this emit, such as `onSubmit`. */
360
+ function hasEmitListener(name) {
361
+ const props = getCurrentInstance()?.vnode.props;
362
+ if (props == null) return false;
363
+ return props[`on${name.charAt(0).toUpperCase()}${name.slice(1)}`] != null;
364
+ }
365
+ /**
366
+ * Whether the parent passed this prop.
367
+ *
368
+ * Vue casts an omitted boolean to false, so `props.value ?? fallback` cannot
369
+ * tell an omitted flag from an explicit false.
370
+ */
371
+ function wasPropPassed(name) {
372
+ const props = getCurrentInstance()?.vnode.props;
373
+ if (props == null) return false;
374
+ if (Object.hasOwn(props, name)) return true;
375
+ const kebab = name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
376
+ return Object.hasOwn(props, kebab);
377
+ }
378
+ //#endregion
379
+ //#region src/Chat/ime.ts
380
+ const IME_PROCESSING_KEY_CODE = 229;
381
+ /**
382
+ * Enter and Escape during an IME composition commit the candidate. They are not
383
+ * composer commands. `keyCode` 229 covers engines that have not set `isComposing` yet.
384
+ */
385
+ function isImeKeyEvent(event) {
386
+ return event.isComposing === true || event.keyCode === IME_PROCESSING_KEY_CODE;
387
+ }
388
+ //#endregion
389
+ //#region src/Chat/selection.ts
390
+ /**
391
+ * Caret helpers for the contenteditable composer.
392
+ *
393
+ * A programmatic focus is not a click on the padding: Chromium drops the caret
394
+ * at the start of the draft, which is exactly where ArrowUp means "recall the
395
+ * previous message". Callers that care where the caret lands place it themselves.
396
+ */
397
+ function getSelectionRangeInside(editable) {
398
+ const selection = window.getSelection();
399
+ if (!selection || selection.rangeCount === 0) return null;
400
+ const range = selection.getRangeAt(0);
401
+ if (!editable.contains(range.startContainer) || !editable.contains(range.endContainer)) return null;
402
+ return range.cloneRange();
403
+ }
404
+ function restoreSelectionRange(range) {
405
+ const selection = window.getSelection();
406
+ if (!selection) return;
407
+ selection.removeAllRanges();
408
+ selection.addRange(range);
409
+ }
410
+ function placeCaretAtEnd(editable) {
411
+ const selection = window.getSelection();
412
+ if (!selection) return false;
413
+ const range = document.createRange();
414
+ range.selectNodeContents(editable);
415
+ range.collapse(false);
416
+ selection.removeAllRanges();
417
+ selection.addRange(range);
418
+ return true;
419
+ }
420
+ function selectAllContents(editable) {
421
+ const selection = window.getSelection();
422
+ if (!selection) return;
423
+ const range = document.createRange();
424
+ range.selectNodeContents(editable);
425
+ selection.removeAllRanges();
426
+ selection.addRange(range);
427
+ }
428
+ /**
429
+ * Keep an existing caret inside `editable`. Otherwise place one at the end.
430
+ * A caret that focus() already left at offset 0 is left alone.
431
+ */
432
+ function ensureCaretInside(editable) {
433
+ const selection = window.getSelection();
434
+ if (!selection) return null;
435
+ if (selection.rangeCount > 0 && editable.contains(selection.getRangeAt(0).startContainer)) return selection;
436
+ const range = document.createRange();
437
+ range.selectNodeContents(editable);
438
+ range.collapse(false);
439
+ selection.removeAllRanges();
440
+ selection.addRange(range);
441
+ return selection;
442
+ }
443
+ function isSelectionAtStart(editable) {
444
+ const selection = window.getSelection();
445
+ if (!selection || selection.rangeCount === 0) return false;
446
+ const range = selection.getRangeAt(0);
447
+ return isBoundaryAtEdge(editable, range.startContainer, range.startOffset, "start");
448
+ }
449
+ function isSelectionAtEnd(editable) {
450
+ const selection = window.getSelection();
451
+ if (!selection || selection.rangeCount === 0) return false;
452
+ const range = selection.getRangeAt(0);
453
+ return isBoundaryAtEdge(editable, range.endContainer, range.endOffset, "end");
454
+ }
455
+ function insertTextAtCursor(editable, text) {
456
+ const selection = ensureCaretInside(editable);
457
+ if (!selection || selection.rangeCount === 0) return false;
458
+ const range = selection.getRangeAt(0);
459
+ range.deleteContents();
460
+ const textNode = document.createTextNode(text);
461
+ range.insertNode(textNode);
462
+ range.setStartAfter(textNode);
463
+ range.collapse(true);
464
+ selection.removeAllRanges();
465
+ selection.addRange(range);
466
+ return true;
467
+ }
468
+ function isBoundaryAtEdge(editable, container, offset, edge) {
469
+ if (!editable.contains(container) || !isAtNodeEdge(container, offset, edge)) return false;
470
+ for (let node = container; node && node !== editable;) {
471
+ if (hasContentSibling(node, edge)) return false;
472
+ node = node.parentNode;
473
+ }
474
+ return true;
475
+ }
476
+ function isAtNodeEdge(node, offset, edge) {
477
+ if (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.CDATA_SECTION_NODE) return edge === "start" ? offset === 0 : offset === (node.nodeValue?.length ?? 0);
478
+ return edge === "start" ? offset === 0 : offset === node.childNodes.length;
479
+ }
480
+ function hasContentSibling(node, edge) {
481
+ for (let sibling = edge === "start" ? node.previousSibling : node.nextSibling; sibling; sibling = edge === "start" ? sibling.previousSibling : sibling.nextSibling) if (sibling.nodeType === Node.ELEMENT_NODE || sibling.nodeType === Node.TEXT_NODE && sibling.nodeValue !== "") return true;
482
+ return false;
483
+ }
484
+ //#endregion
485
+ //#region src/Chat/serialize.ts
486
+ /** Read the draft a contenteditable composer would submit. */
487
+ function serializeComposer(node) {
488
+ let result = "";
489
+ for (const child of Array.from(node.childNodes)) if (child.nodeType === Node.TEXT_NODE) result += child.textContent ?? "";
490
+ else if (child instanceof HTMLElement) {
491
+ if (child.hasAttribute("data-astryx-token")) result += child.getAttribute("data-astryx-token-value") ?? "";
492
+ else if (child.tagName === "BR") result += "\n";
493
+ else result += serializeComposer(child);
494
+ }
495
+ return result;
496
+ }
497
+ function composerHasChip(editable) {
498
+ return editable.querySelector("[data-astryx-token], [data-astryx-dictation-interim]") != null;
499
+ }
500
+ //#endregion
501
+ //#region src/Chat/useChatComposerTokens.ts
502
+ const TRAILING_SPACE = "\xA0";
503
+ function isTokenElement(node) {
504
+ return node instanceof HTMLElement && node.hasAttribute("data-astryx-token");
505
+ }
506
+ function tokenAncestor(node) {
507
+ let current = node;
508
+ while (current) {
509
+ if (isTokenElement(current)) return current;
510
+ current = current.parentNode;
511
+ }
512
+ return null;
513
+ }
514
+ /**
515
+ * Inline chips inside the composer. The DOM span is the source of truth for
516
+ * serialization; Vue teleports the visible badge into it.
517
+ */
518
+ function useChatComposerTokens(options) {
519
+ const portals = ref([]);
520
+ function insertToken(token) {
521
+ const editable = options.editable.value;
522
+ if (!editable) return void 0;
523
+ const selection = ensureCaretInside(editable);
524
+ if (!selection || selection.rangeCount === 0) return void 0;
525
+ const range = selection.getRangeAt(0);
526
+ const span = document.createElement("span");
527
+ const id = `token-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
528
+ span.setAttribute("data-astryx-token", "");
529
+ span.setAttribute("data-astryx-token-value", token.value);
530
+ span.setAttribute("data-astryx-token-id", id);
531
+ span.contentEditable = "false";
532
+ span.style.display = "inline-flex";
533
+ span.style.alignItems = "center";
534
+ span.style.height = "1lh";
535
+ span.style.verticalAlign = "top";
536
+ range.deleteContents();
537
+ range.insertNode(span);
538
+ const space = document.createTextNode(TRAILING_SPACE);
539
+ span.after(space);
540
+ const next = document.createRange();
541
+ next.setStartAfter(space);
542
+ next.collapse(true);
543
+ selection.removeAllRanges();
544
+ selection.addRange(next);
545
+ portals.value = [...portals.value, {
546
+ id,
547
+ span,
548
+ token
549
+ }];
550
+ return id;
551
+ }
552
+ function handleKeyDown(event) {
553
+ if (event.key !== "Backspace") return false;
554
+ const selection = window.getSelection();
555
+ if (!selection || !selection.isCollapsed || selection.rangeCount === 0) return false;
556
+ const { startContainer, startOffset } = selection.getRangeAt(0);
557
+ if (startContainer.nodeType !== Node.TEXT_NODE) return false;
558
+ const previous = startContainer.previousSibling;
559
+ if (!isTokenElement(previous)) return false;
560
+ if (startOffset === 0) return false;
561
+ if (startContainer.textContent === TRAILING_SPACE && startOffset <= 1) {
562
+ event.preventDefault();
563
+ const parent = startContainer.parentNode;
564
+ parent?.removeChild(startContainer);
565
+ parent?.removeChild(previous);
566
+ options.onEmitChange();
567
+ return true;
568
+ }
569
+ return false;
570
+ }
571
+ function handlePaste(event) {
572
+ const editable = options.editable.value;
573
+ if (!editable) return false;
574
+ const selection = ensureCaretInside(editable);
575
+ if (!selection || selection.rangeCount === 0) return false;
576
+ const token = tokenAncestor(selection.getRangeAt(0).startContainer);
577
+ if (!token) return false;
578
+ event.preventDefault();
579
+ const text = event.clipboardData?.getData("text/plain") ?? "";
580
+ const space = token.nextSibling;
581
+ const next = document.createRange();
582
+ if (space && space.nodeType === Node.TEXT_NODE) next.setStartAfter(space);
583
+ else next.setStartAfter(token);
584
+ next.collapse(true);
585
+ selection.removeAllRanges();
586
+ selection.addRange(next);
587
+ insertTextAtCursor(editable, text);
588
+ options.onEmitChange();
589
+ return true;
590
+ }
591
+ function expandToken(id) {
592
+ const editable = options.editable.value;
593
+ const portal = portals.value.find((item) => item.id === id);
594
+ if (!editable || !portal) return;
595
+ const { span } = portal;
596
+ const value = span.getAttribute("data-astryx-token-value") ?? "";
597
+ const textNode = document.createTextNode(value);
598
+ const next = span.nextSibling;
599
+ if (next?.nodeType === Node.TEXT_NODE && next.textContent === TRAILING_SPACE) next.remove();
600
+ span.replaceWith(textNode);
601
+ const selection = window.getSelection();
602
+ if (selection) {
603
+ const range = document.createRange();
604
+ range.setStartAfter(textNode);
605
+ range.collapse(true);
606
+ selection.removeAllRanges();
607
+ selection.addRange(range);
608
+ }
609
+ portals.value = portals.value.filter((item) => item.id !== id);
610
+ options.onEmitChange();
611
+ }
612
+ function cleanupPortals() {
613
+ const editable = options.editable.value;
614
+ portals.value = portals.value.filter((item) => editable?.contains(item.span));
615
+ }
616
+ function clearPortals() {
617
+ portals.value = [];
618
+ }
619
+ return {
620
+ portals,
621
+ insertToken,
622
+ expandToken,
623
+ handleKeyDown,
624
+ handlePaste,
625
+ cleanupPortals,
626
+ clearPortals
627
+ };
628
+ }
629
+ //#endregion
630
+ //#region src/Chat/useComposerHistory.ts
631
+ /**
632
+ * ArrowUp at the start of the draft recalls the previous submission.
633
+ * ArrowDown at the end steps forward. Anywhere else, the caret moves a line.
634
+ */
635
+ function useComposerHistory(options) {
636
+ const history = [];
637
+ let index = -1;
638
+ let draft = "";
639
+ function remember(text) {
640
+ if (!options.enabled.value) return;
641
+ history.push(text);
642
+ index = -1;
643
+ draft = "";
644
+ }
645
+ function onKeyDown(event) {
646
+ if (!options.enabled.value || event.key !== "ArrowUp" && event.key !== "ArrowDown") return false;
647
+ const editable = options.editable.value;
648
+ if (!editable || history.length === 0) return false;
649
+ ensureCaretInside(editable);
650
+ const collapsed = window.getSelection()?.isCollapsed ?? true;
651
+ const atStart = isSelectionAtStart(editable);
652
+ const atEnd = isSelectionAtEnd(editable);
653
+ const canRecallPrevious = atStart && (collapsed || atEnd);
654
+ const canRecallNext = atEnd && (collapsed || atStart);
655
+ if (event.key === "ArrowUp" ? !canRecallPrevious : !canRecallNext) return false;
656
+ if (event.key === "ArrowUp") {
657
+ if (index === -1) draft = serializeComposer(editable);
658
+ index = index === -1 ? history.length - 1 : Math.max(0, index - 1);
659
+ editable.textContent = history[index] ?? "";
660
+ selectAllContents(editable);
661
+ options.onRecall();
662
+ event.preventDefault();
663
+ return true;
664
+ }
665
+ if (index === -1) return false;
666
+ const next = index + 1;
667
+ if (next >= history.length) {
668
+ index = -1;
669
+ editable.textContent = draft;
670
+ if (draft) selectAllContents(editable);
671
+ } else {
672
+ index = next;
673
+ editable.textContent = history[next] ?? "";
674
+ selectAllContents(editable);
675
+ }
676
+ options.onRecall();
677
+ event.preventDefault();
678
+ return true;
679
+ }
680
+ return {
681
+ remember,
682
+ onKeyDown
683
+ };
684
+ }
685
+ //#endregion
686
+ //#region src/Chat/useTriggerMenu.ts
687
+ function textBeforeCursor(editable) {
688
+ const selection = window.getSelection();
689
+ if (!selection || selection.rangeCount === 0 || !selection.getRangeAt(0).collapsed) return null;
690
+ const range = selection.getRangeAt(0);
691
+ if (!editable.contains(range.startContainer) || range.startContainer.nodeType !== Node.TEXT_NODE) return null;
692
+ return (range.startContainer.textContent ?? "").slice(0, range.startOffset);
693
+ }
694
+ function findActiveTrigger(text, triggers) {
695
+ for (let index = text.length - 1; index >= 0; index -= 1) {
696
+ const char = text[index];
697
+ if (char === " " || char === "\n") return null;
698
+ for (const trigger of triggers) {
699
+ if (char !== trigger.character) continue;
700
+ const previous = index > 0 ? text[index - 1] : null;
701
+ if (previous === null || previous === " " || previous === "\n") return {
702
+ trigger,
703
+ query: text.slice(index + 1),
704
+ triggerStart: index
705
+ };
706
+ }
707
+ }
708
+ return null;
709
+ }
710
+ function deleteTriggerText(triggerStart) {
711
+ const selection = window.getSelection();
712
+ if (!selection || selection.rangeCount === 0) return;
713
+ const range = selection.getRangeAt(0);
714
+ const node = range.startContainer;
715
+ if (node.nodeType !== Node.TEXT_NODE) return;
716
+ const text = node.textContent ?? "";
717
+ node.textContent = text.slice(0, triggerStart) + text.slice(range.startOffset);
718
+ const next = document.createRange();
719
+ next.setStart(node, triggerStart);
720
+ next.collapse(true);
721
+ selection.removeAllRanges();
722
+ selection.addRange(next);
723
+ }
724
+ function isPromise(value) {
725
+ return typeof value?.then === "function";
726
+ }
727
+ /**
728
+ * Opens a suggestion list when a trigger character is typed at a word boundary.
729
+ */
730
+ function useTriggerMenu(options) {
731
+ const listboxId = useId();
732
+ const state = reactive({
733
+ isActive: false,
734
+ activeTrigger: null,
735
+ query: "",
736
+ items: [],
737
+ highlightedIndex: 0,
738
+ isLoading: false
739
+ });
740
+ const anchor = ref(null);
741
+ const keyboardMove = ref(false);
742
+ let triggerStart = -1;
743
+ let timer;
744
+ let requestId = 0;
745
+ const asyncSources = /* @__PURE__ */ new WeakSet();
746
+ function removeAnchor() {
747
+ anchor.value?.remove();
748
+ anchor.value = null;
749
+ }
750
+ function placeAnchor() {
751
+ const editable = options.editable.value;
752
+ if (!editable) {
753
+ removeAnchor();
754
+ return;
755
+ }
756
+ const selection = window.getSelection();
757
+ removeAnchor();
758
+ if (!selection || selection.rangeCount === 0) {
759
+ anchor.value = editable;
760
+ return;
761
+ }
762
+ const range = selection.getRangeAt(0).cloneRange();
763
+ range.collapse(true);
764
+ const rect = typeof range.getBoundingClientRect === "function" ? range.getBoundingClientRect() : null;
765
+ if (!rect || rect.width === 0 && rect.height === 0 && rect.top === 0 && rect.left === 0) {
766
+ anchor.value = editable;
767
+ return;
768
+ }
769
+ const span = document.createElement("span");
770
+ span.setAttribute("aria-hidden", "true");
771
+ span.setAttribute("data-astryx-trigger-anchor", "");
772
+ span.style.position = "fixed";
773
+ span.style.left = `${rect.left}px`;
774
+ span.style.top = `${rect.top}px`;
775
+ span.style.width = `${Math.max(rect.width, 1)}px`;
776
+ span.style.height = `${rect.height}px`;
777
+ span.style.pointerEvents = "none";
778
+ span.style.opacity = "0";
779
+ document.body.append(span);
780
+ anchor.value = span;
781
+ }
782
+ function applyItems(items, captured) {
783
+ if (captured !== requestId) return;
784
+ state.items = items;
785
+ state.highlightedIndex = items.length > 0 ? 0 : -1;
786
+ state.isLoading = false;
787
+ }
788
+ function runSearch(trigger, query) {
789
+ const source = trigger.searchSource;
790
+ source.cancel?.();
791
+ const captured = ++requestId;
792
+ state.isLoading = true;
793
+ const result = source.search(query);
794
+ if (isPromise(result)) {
795
+ asyncSources.add(source);
796
+ result.then((items) => applyItems(items, captured), () => applyItems([], captured));
797
+ return;
798
+ }
799
+ applyItems(result, captured);
800
+ }
801
+ function searchItems(trigger, query) {
802
+ if (timer !== void 0) {
803
+ clearTimeout(timer);
804
+ timer = void 0;
805
+ }
806
+ const delay = options.debounceMs?.value ?? 150;
807
+ if (asyncSources.has(trigger.searchSource) && delay > 0) {
808
+ state.isLoading = true;
809
+ timer = setTimeout(() => {
810
+ timer = void 0;
811
+ runSearch(trigger, query);
812
+ }, delay);
813
+ return;
814
+ }
815
+ runSearch(trigger, query);
816
+ }
817
+ function reset() {
818
+ if (timer !== void 0) {
819
+ clearTimeout(timer);
820
+ timer = void 0;
821
+ }
822
+ state.activeTrigger?.searchSource.cancel?.();
823
+ requestId += 1;
824
+ removeAnchor();
825
+ triggerStart = -1;
826
+ state.isActive = false;
827
+ state.activeTrigger = null;
828
+ state.query = "";
829
+ state.items = [];
830
+ state.highlightedIndex = 0;
831
+ state.isLoading = false;
832
+ }
833
+ function selectItem(item) {
834
+ const trigger = state.activeTrigger;
835
+ const editable = options.editable.value;
836
+ if (!trigger || !editable) return;
837
+ removeAnchor();
838
+ deleteTriggerText(triggerStart);
839
+ const result = trigger.onSelect(item);
840
+ if (typeof result === "string") options.onInsertText(result);
841
+ else options.onInsertToken(result);
842
+ options.onEmitChange();
843
+ reset();
844
+ }
845
+ function handleInput() {
846
+ const triggers = options.triggers.value;
847
+ const editable = options.editable.value;
848
+ if (!triggers || triggers.length === 0 || !editable) return;
849
+ const before = textBeforeCursor(editable);
850
+ if (before === null) {
851
+ if (state.isActive) reset();
852
+ return;
853
+ }
854
+ const found = findActiveTrigger(before, triggers);
855
+ if (!found) {
856
+ if (state.isActive) reset();
857
+ return;
858
+ }
859
+ if (!state.isActive || state.activeTrigger !== found.trigger) {
860
+ triggerStart = found.triggerStart;
861
+ state.isActive = true;
862
+ state.activeTrigger = found.trigger;
863
+ state.query = found.query;
864
+ state.highlightedIndex = 0;
865
+ placeAnchor();
866
+ searchItems(found.trigger, found.query);
867
+ return;
868
+ }
869
+ if (state.query !== found.query) {
870
+ state.query = found.query;
871
+ searchItems(found.trigger, found.query);
872
+ }
873
+ }
874
+ function moveHighlight(next) {
875
+ keyboardMove.value = true;
876
+ state.highlightedIndex = next;
877
+ }
878
+ function handleKeyDown(event) {
879
+ if (!state.isActive) return false;
880
+ const last = state.items.length - 1;
881
+ if (event.key === "ArrowDown") {
882
+ event.preventDefault();
883
+ moveHighlight(state.highlightedIndex < last ? state.highlightedIndex + 1 : 0);
884
+ return true;
885
+ }
886
+ if (event.key === "ArrowUp") {
887
+ event.preventDefault();
888
+ moveHighlight(state.highlightedIndex > 0 ? state.highlightedIndex - 1 : last);
889
+ return true;
890
+ }
891
+ if (event.key === "Enter" || event.key === "Tab") {
892
+ const item = state.items[state.highlightedIndex];
893
+ if (!item) return false;
894
+ event.preventDefault();
895
+ selectItem(item);
896
+ return true;
897
+ }
898
+ if (event.key === "Escape") {
899
+ event.preventDefault();
900
+ reset();
901
+ return true;
902
+ }
903
+ return false;
904
+ }
905
+ function highlightOnHover(index) {
906
+ keyboardMove.value = false;
907
+ state.highlightedIndex = index;
908
+ }
909
+ const hasTriggers = computed(() => (options.triggers.value?.length ?? 0) > 0);
910
+ onScopeDispose(() => {
911
+ if (timer !== void 0) clearTimeout(timer);
912
+ removeAnchor();
913
+ });
914
+ return {
915
+ state,
916
+ listboxId,
917
+ anchor,
918
+ keyboardMove,
919
+ hasTriggers,
920
+ handleInput,
921
+ handleKeyDown,
922
+ selectItem,
923
+ highlightOnHover,
924
+ reset
925
+ };
926
+ }
927
+ //#endregion
928
+ //#region src/Chat/ChatComposerInput.vue
929
+ const _hoisted_1$17 = ["data-disabled"];
930
+ const _hoisted_2$12 = {
931
+ key: 0,
932
+ class: "astryx-chat-composer-input__placeholder",
933
+ "aria-hidden": "true"
934
+ };
935
+ const _hoisted_3$10 = [
936
+ "aria-label",
937
+ "contenteditable",
938
+ "aria-disabled",
939
+ "role",
940
+ "aria-multiline",
941
+ "aria-expanded",
942
+ "aria-controls",
943
+ "aria-activedescendant",
944
+ "aria-haspopup"
945
+ ];
946
+ const LINE_HEIGHT = 22;
947
+ const _sfc_main$20 = /*@__PURE__*/ defineComponent({
948
+ name: "AstryxChatComposerInput",
949
+ inheritAttrs: false,
950
+ __name: "ChatComposerInput",
951
+ props: {
952
+ modelValue: {},
953
+ placeholder: {},
954
+ maxRows: { default: 8 },
955
+ triggers: {},
956
+ debounceMs: { default: 150 },
957
+ hasHistory: {
958
+ type: Boolean,
959
+ default: true
960
+ },
961
+ label: {},
962
+ isDisabled: { type: Boolean },
963
+ pasteAsToken: { type: [Object, Boolean] },
964
+ pasteAction: {}
965
+ },
966
+ emits: [
967
+ "change",
968
+ "update:modelValue",
969
+ "submit",
970
+ "files",
971
+ "keydown"
972
+ ],
973
+ setup(__props, { expose: __expose, emit: __emit }) {
974
+ const props = __props;
975
+ const emit = __emit;
976
+ const messages = useAstryxMessages();
977
+ const composer = useChatComposerContext();
978
+ const editableRef = useTemplateRef("editable");
979
+ const self = ref(null);
980
+ const isEmpty = ref(true);
981
+ const pendingEcho = ref(void 0);
982
+ const emitVersion = ref(0);
983
+ const submitListened = hasEmitListener("submit");
984
+ const disabledPassed = wasPropPassed("isDisabled");
985
+ const disabled = computed(() => disabledPassed ? Boolean(props.isDisabled) : composer?.isDisabled.value ?? false);
986
+ const placeholder = computed(() => props.placeholder ?? composer?.placeholder.value ?? messages.value.chat.placeholder);
987
+ const label = computed(() => props.label ?? messages.value.chat.inputLabel);
988
+ const sourceValue = computed(() => props.modelValue !== void 0 ? props.modelValue : composer?.value.value);
989
+ const maxHeight = computed(() => `${props.maxRows * LINE_HEIGHT}px`);
990
+ const triggers = computed(() => props.triggers);
991
+ const debounce = computed(() => props.debounceMs);
992
+ const historyEnabled = computed(() => props.hasHistory);
993
+ function readValue() {
994
+ return serializeComposer(editableRef.value ?? document.createElement("div"));
995
+ }
996
+ const tokens = useChatComposerTokens({
997
+ editable: editableRef,
998
+ onEmitChange: () => publish(readValue())
999
+ });
1000
+ function publish(next) {
1001
+ pendingEcho.value = next;
1002
+ const editable = editableRef.value;
1003
+ const chips = editable ? composerHasChip(editable) : false;
1004
+ const trimmedEmpty = next.trim().length === 0 && !chips;
1005
+ const value = trimmedEmpty ? "" : next;
1006
+ pendingEcho.value = value;
1007
+ isEmpty.value = trimmedEmpty;
1008
+ emitVersion.value += 1;
1009
+ if (props.modelValue !== void 0) {
1010
+ emit("update:modelValue", value);
1011
+ emit("change", value);
1012
+ } else {
1013
+ composer?.onChange(value);
1014
+ emit("update:modelValue", value);
1015
+ emit("change", value);
1016
+ }
1017
+ tokens.cleanupPortals();
1018
+ }
1019
+ const defaultPaste = useChatPasteAsToken({ input: self });
1020
+ const pasteAsToken = computed(() => props.pasteAsToken === false ? null : props.pasteAsToken ?? defaultPaste);
1021
+ function insertText(text) {
1022
+ const editable = editableRef.value;
1023
+ if (!editable) return;
1024
+ insertTextAtCursor(editable, text);
1025
+ publish(readValue());
1026
+ }
1027
+ function focusEditable() {
1028
+ const editable = editableRef.value;
1029
+ if (!editable) return;
1030
+ const existing = getSelectionRangeInside(editable);
1031
+ editable.focus();
1032
+ if (existing) {
1033
+ restoreSelectionRange(existing);
1034
+ return;
1035
+ }
1036
+ placeCaretAtEnd(editable);
1037
+ }
1038
+ function focusAtEnd() {
1039
+ const editable = editableRef.value;
1040
+ if (!editable) return;
1041
+ editable.focus();
1042
+ placeCaretAtEnd(editable);
1043
+ }
1044
+ const handle = {
1045
+ insertToken: (token) => {
1046
+ const id = tokens.insertToken(token);
1047
+ if (id) publish(readValue());
1048
+ return id;
1049
+ },
1050
+ expandToken: (id) => tokens.expandToken(id),
1051
+ insertText,
1052
+ focus: focusEditable,
1053
+ getValue: readValue
1054
+ };
1055
+ self.value = handle;
1056
+ const history = useComposerHistory({
1057
+ enabled: historyEnabled,
1058
+ editable: editableRef,
1059
+ onRecall: () => publish(readValue())
1060
+ });
1061
+ const menu = useTriggerMenu({
1062
+ triggers,
1063
+ editable: editableRef,
1064
+ debounceMs: debounce,
1065
+ onInsertToken: (token) => {
1066
+ tokens.insertToken(token);
1067
+ },
1068
+ onInsertText: (text) => {
1069
+ const editable = editableRef.value;
1070
+ if (editable) insertTextAtCursor(editable, text);
1071
+ },
1072
+ onEmitChange: () => publish(readValue())
1073
+ });
1074
+ const activeDescendant = computed(() => {
1075
+ if (!menu.state.isActive || menu.state.highlightedIndex < 0) return void 0;
1076
+ return `${menu.listboxId}-option-${menu.state.highlightedIndex}`;
1077
+ });
1078
+ const menuLabel = computed(() => menu.state.activeTrigger?.menuLabel ?? messages.value.chat.suggestions);
1079
+ const loadingText = computed(() => menu.state.activeTrigger?.loadingText ?? messages.value.chat.searching);
1080
+ const emptyContent = computed(() => {
1081
+ const value = menu.state.activeTrigger?.emptySearchText;
1082
+ if (value === "") return "";
1083
+ if (value == null) return messages.value.chat.noResults;
1084
+ return value;
1085
+ });
1086
+ function submitValue(text) {
1087
+ if (submitListened || !composer) emit("submit", text);
1088
+ else composer.onSubmit(text);
1089
+ }
1090
+ function onInput() {
1091
+ publish(readValue());
1092
+ menu.handleInput();
1093
+ }
1094
+ function onKeyDown(event) {
1095
+ if (menu.handleKeyDown(event)) return;
1096
+ emit("keydown", event);
1097
+ if (event.defaultPrevented) return;
1098
+ if (tokens.handleKeyDown(event)) return;
1099
+ if (event.key === "Enter" && !event.shiftKey) {
1100
+ if (isImeKeyEvent(event)) return;
1101
+ event.preventDefault();
1102
+ const editable = editableRef.value;
1103
+ if (!editable) return;
1104
+ const text = serializeComposer(editable).trim();
1105
+ if (!text) return;
1106
+ history.remember(text);
1107
+ submitValue(text);
1108
+ tokens.clearPortals();
1109
+ editable.textContent = "";
1110
+ isEmpty.value = true;
1111
+ publish("");
1112
+ return;
1113
+ }
1114
+ history.onKeyDown(event);
1115
+ }
1116
+ function onPaste(event) {
1117
+ const editable = editableRef.value;
1118
+ if (!editable) return;
1119
+ const files = Array.from(event.clipboardData?.files ?? []);
1120
+ if (files.length > 0) {
1121
+ event.preventDefault();
1122
+ emit("files", files);
1123
+ return;
1124
+ }
1125
+ if (tokens.handlePaste(event)) return;
1126
+ event.preventDefault();
1127
+ const text = event.clipboardData?.getData("text/plain") ?? "";
1128
+ const version = emitVersion.value;
1129
+ if (props.pasteAction?.(event, text)) {
1130
+ if (emitVersion.value === version) publish(readValue());
1131
+ return;
1132
+ }
1133
+ if (pasteAsToken.value?.onPaste(text)) {
1134
+ publish(readValue());
1135
+ return;
1136
+ }
1137
+ insertTextAtCursor(editable, text);
1138
+ publish(readValue());
1139
+ }
1140
+ function onDragOver(event) {
1141
+ if (Array.from(event.dataTransfer?.types ?? []).includes("Files")) event.preventDefault();
1142
+ }
1143
+ function onDrop(event) {
1144
+ const files = Array.from(event.dataTransfer?.files ?? []);
1145
+ if (files.length === 0) return;
1146
+ event.preventDefault();
1147
+ if (!disabled.value) emit("files", files);
1148
+ }
1149
+ const visiblePortals = computed(() => tokens.portals.value.filter((portal) => portal.span.isConnected));
1150
+ function showPastedPreview(token) {
1151
+ return !isCustomToken(token) && pasteAsToken.value != null && token.value.length > 200;
1152
+ }
1153
+ watch(sourceValue, (next) => {
1154
+ const editable = editableRef.value;
1155
+ if (next === void 0 || !editable) return;
1156
+ if (next === pendingEcho.value) {
1157
+ pendingEcho.value = void 0;
1158
+ return;
1159
+ }
1160
+ if (serializeComposer(editable) !== next) {
1161
+ pendingEcho.value = void 0;
1162
+ const focused = document.activeElement === editable;
1163
+ tokens.clearPortals();
1164
+ editable.textContent = next;
1165
+ if (focused) placeCaretAtEnd(editable);
1166
+ isEmpty.value = next.length === 0;
1167
+ } else pendingEcho.value = void 0;
1168
+ });
1169
+ onMounted(() => {
1170
+ if (composer) composer.inputControl.value = { focus: focusAtEnd };
1171
+ const next = sourceValue.value;
1172
+ const editable = editableRef.value;
1173
+ if (editable && next && serializeComposer(editable) !== next) {
1174
+ editable.textContent = next;
1175
+ isEmpty.value = next.length === 0;
1176
+ }
1177
+ });
1178
+ onBeforeUnmount(() => {
1179
+ if (composer?.inputControl.value?.focus === focusAtEnd) composer.inputControl.value = null;
1180
+ });
1181
+ __expose(handle);
1182
+ return (_ctx, _cache) => {
1183
+ return openBlock(), createElementBlock("div", mergeProps(_ctx.$attrs, {
1184
+ class: "astryx-chat-composer-input",
1185
+ "data-disabled": disabled.value ? "" : void 0
1186
+ }), [
1187
+ isEmpty.value ? (openBlock(), createElementBlock("div", _hoisted_2$12, toDisplayString(placeholder.value), 1)) : createCommentVNode("v-if", true),
1188
+ createCommentVNode(" contenteditable 就是输入控件;角色和 aria 从脚本绑上。 "),
1189
+ createCommentVNode(" eslint-disable-next-line vue-a11y/no-static-element-interactions "),
1190
+ createElementVNode("div", {
1191
+ ref: "editable",
1192
+ class: "astryx-chat-composer-input__editable",
1193
+ style: normalizeStyle({ maxHeight: maxHeight.value }),
1194
+ "aria-label": label.value,
1195
+ contenteditable: disabled.value ? "false" : "true",
1196
+ "aria-disabled": disabled.value ? "true" : void 0,
1197
+ role: unref(menu).hasTriggers.value ? "combobox" : "textbox",
1198
+ "aria-multiline": unref(menu).hasTriggers.value ? void 0 : "true",
1199
+ "aria-expanded": unref(menu).hasTriggers.value ? unref(menu).state.isActive : void 0,
1200
+ "aria-controls": unref(menu).hasTriggers.value && unref(menu).state.isActive ? unref(menu).listboxId : void 0,
1201
+ "aria-activedescendant": unref(menu).hasTriggers.value && unref(menu).state.isActive ? activeDescendant.value : void 0,
1202
+ "aria-haspopup": unref(menu).hasTriggers.value ? "listbox" : void 0,
1203
+ onInput,
1204
+ onKeydown: onKeyDown,
1205
+ onPaste,
1206
+ onDragover: onDragOver,
1207
+ onDrop
1208
+ }, null, 44, _hoisted_3$10),
1209
+ createVNode(_sfc_main$21, {
1210
+ open: unref(menu).state.isActive,
1211
+ anchor: unref(menu).anchor.value,
1212
+ "listbox-id": unref(menu).listboxId,
1213
+ label: menuLabel.value,
1214
+ items: unref(menu).state.items,
1215
+ "highlighted-index": unref(menu).state.highlightedIndex,
1216
+ loading: unref(menu).state.isLoading,
1217
+ "loading-text": loadingText.value,
1218
+ empty: emptyContent.value,
1219
+ "keyboard-move": unref(menu).keyboardMove.value,
1220
+ "render-item": unref(menu).state.activeTrigger?.renderItem,
1221
+ onSelect: unref(menu).selectItem,
1222
+ onHover: unref(menu).highlightOnHover,
1223
+ onDismiss: unref(menu).reset
1224
+ }, null, 8, [
1225
+ "open",
1226
+ "anchor",
1227
+ "listbox-id",
1228
+ "label",
1229
+ "items",
1230
+ "highlighted-index",
1231
+ "loading",
1232
+ "loading-text",
1233
+ "empty",
1234
+ "keyboard-move",
1235
+ "render-item",
1236
+ "onSelect",
1237
+ "onHover",
1238
+ "onDismiss"
1239
+ ]),
1240
+ (openBlock(true), createElementBlock(Fragment, null, renderList(visiblePortals.value, (portal) => {
1241
+ return openBlock(), createBlock(Teleport, {
1242
+ key: portal.id,
1243
+ to: portal.span
1244
+ }, [showPastedPreview(portal.token) ? (openBlock(), createBlock(_sfc_main$23, {
1245
+ key: 0,
1246
+ text: portal.token.value,
1247
+ onExpand: ($event) => unref(tokens).expandToken(portal.id)
1248
+ }, null, 8, ["text", "onExpand"])) : (openBlock(), createBlock(_sfc_main$22, {
1249
+ key: 1,
1250
+ token: portal.token
1251
+ }, null, 8, ["token"]))], 8, ["to"]);
1252
+ }), 128))
1253
+ ], 16, _hoisted_1$17);
1254
+ };
1255
+ }
1256
+ });
1257
+ //#endregion
1258
+ //#region src/Chat/ChatSendButton.vue
1259
+ const _sfc_main$19 = /*@__PURE__*/ defineComponent({
1260
+ name: "AstryxChatSendButton",
1261
+ inheritAttrs: false,
1262
+ __name: "ChatSendButton",
1263
+ props: {
1264
+ isStopShown: { type: Boolean },
1265
+ isDisabled: { type: Boolean },
1266
+ size: { default: "md" }
1267
+ },
1268
+ emits: [
1269
+ "send",
1270
+ "stop",
1271
+ "click"
1272
+ ],
1273
+ setup(__props, { emit: __emit }) {
1274
+ const props = __props;
1275
+ const emit = __emit;
1276
+ const messages = useAstryxMessages();
1277
+ const composer = useChatComposerContext();
1278
+ const sendListened = hasEmitListener("send");
1279
+ const stopListened = hasEmitListener("stop");
1280
+ const stopPassed = wasPropPassed("isStopShown");
1281
+ const disabledPassed = wasPropPassed("isDisabled");
1282
+ const stopShown = computed(() => stopPassed ? Boolean(props.isStopShown) : composer?.isStopShown.value ?? false);
1283
+ const sendDisabled = computed(() => disabledPassed ? Boolean(props.isDisabled) : !(composer?.canSend.value ?? false));
1284
+ const label = computed(() => stopShown.value ? messages.value.chat.stop : messages.value.chat.send);
1285
+ function onClick(event) {
1286
+ if (stopShown.value) {
1287
+ emit("stop");
1288
+ if (!stopListened) composer?.onStop();
1289
+ } else if (!sendDisabled.value) {
1290
+ emit("send");
1291
+ if (!sendListened) composer?.onSubmit(composer.value.value);
1292
+ }
1293
+ emit("click", event);
1294
+ }
1295
+ return (_ctx, _cache) => {
1296
+ return openBlock(), createBlock(_sfc_main$25, mergeProps(_ctx.$attrs, {
1297
+ class: "astryx-chat-send",
1298
+ label: label.value,
1299
+ variant: stopShown.value ? "secondary" : "primary",
1300
+ size: __props.size,
1301
+ "is-icon-only": "",
1302
+ "is-disabled": !stopShown.value && sendDisabled.value,
1303
+ onClick
1304
+ }), {
1305
+ icon: withCtx(() => [stopShown.value ? renderSlot(_ctx.$slots, "stopIcon", {}, () => [createVNode(_sfc_main$27, { icon: "stop" })], void 0, 0) : renderSlot(_ctx.$slots, "sendIcon", {}, () => [createVNode(_sfc_main$27, { icon: "arrowUp" })], void 0, 1)]),
1306
+ _: 3
1307
+ }, 16, [
1308
+ "label",
1309
+ "variant",
1310
+ "size",
1311
+ "is-disabled"
1312
+ ]);
1313
+ };
1314
+ }
1315
+ });
1316
+ //#endregion
1317
+ //#region src/Chat/modality.ts
1318
+ let modality = "keyboard";
1319
+ let listening = false;
1320
+ /** Pointer focus must not draw the keyboard ring. `:focus-visible` is not enough: programmatic focus matches it too. */
1321
+ function trackInteractionModality() {
1322
+ if (listening || typeof document === "undefined") return;
1323
+ listening = true;
1324
+ document.addEventListener("pointerdown", () => {
1325
+ modality = "pointer";
1326
+ }, true);
1327
+ document.addEventListener("keydown", (event) => {
1328
+ if (event.metaKey || event.altKey || event.ctrlKey) return;
1329
+ modality = "keyboard";
1330
+ }, true);
1331
+ }
1332
+ function isKeyboardModality() {
1333
+ trackInteractionModality();
1334
+ return modality === "keyboard";
1335
+ }
1336
+ //#endregion
1337
+ //#region src/Chat/ChatComposer.vue
1338
+ const _hoisted_1$16 = ["data-disabled", "data-density"];
1339
+ const _hoisted_2$11 = ["data-type", "role"];
1340
+ const _hoisted_3$9 = [
1341
+ "data-density",
1342
+ "data-elevation",
1343
+ "data-keyboard-focus"
1344
+ ];
1345
+ const _hoisted_4$9 = {
1346
+ key: 0,
1347
+ class: "astryx-chat-composer__header"
1348
+ };
1349
+ const _hoisted_5$7 = { class: "astryx-chat-composer__header-start" };
1350
+ const _hoisted_6$6 = { class: "astryx-chat-composer__header-end" };
1351
+ const _hoisted_7$5 = { class: "astryx-chat-composer__input" };
1352
+ const _hoisted_8$3 = { class: "astryx-chat-composer__footer" };
1353
+ const _hoisted_9$3 = { class: "astryx-chat-composer__footer-start" };
1354
+ const _hoisted_10$3 = ["data-interruptible"];
1355
+ const _hoisted_11$2 = ["data-type", "role"];
1356
+ const editorSelector = "textarea, [contenteditable=\"true\"], [role=\"textbox\"], input:not([type]), input[type=\"text\"], input[type=\"search\"], input[type=\"email\"], input[type=\"url\"], input[type=\"tel\"], input[type=\"password\"]";
1357
+ const _sfc_main$18 = /*@__PURE__*/ defineComponent({
1358
+ name: "AstryxChatComposer",
1359
+ inheritAttrs: false,
1360
+ __name: "ChatComposer",
1361
+ props: {
1362
+ modelValue: {},
1363
+ placeholder: {},
1364
+ isDisabled: { type: Boolean },
1365
+ density: { default: "balanced" },
1366
+ elevation: { default: "low" },
1367
+ isStopShown: { type: Boolean },
1368
+ status: {},
1369
+ statusPosition: { default: "bottom" }
1370
+ },
1371
+ emits: [
1372
+ "submit",
1373
+ "stop",
1374
+ "change",
1375
+ "update:modelValue"
1376
+ ],
1377
+ setup(__props, { expose: __expose, emit: __emit }) {
1378
+ const props = __props;
1379
+ const emit = __emit;
1380
+ const slots = useSlots();
1381
+ const messages = useAstryxMessages();
1382
+ const root = useTemplateRef("root");
1383
+ const body = useTemplateRef("body");
1384
+ const localValue = ref("");
1385
+ const controlled = props.modelValue !== void 0;
1386
+ const inputControl = ref(null);
1387
+ const keyboardFocus = ref(false);
1388
+ trackInteractionModality();
1389
+ const value = computed(() => controlled ? props.modelValue ?? "" : localValue.value);
1390
+ const disabled = computed(() => props.isDisabled ?? false);
1391
+ const stopShown = computed(() => props.isStopShown ?? false);
1392
+ const placeholder = computed(() => props.placeholder ?? messages.value.chat.placeholder);
1393
+ const canSend = computed(() => value.value.trim().length > 0 && !disabled.value);
1394
+ const hasHeader = computed(() => slots.headerActions != null || slots.headerContext != null);
1395
+ function setValue(next) {
1396
+ if (!controlled) localValue.value = next;
1397
+ emit("update:modelValue", next);
1398
+ emit("change", next);
1399
+ }
1400
+ function submit(raw) {
1401
+ const trimmed = raw.trim();
1402
+ if (!trimmed || disabled.value) return;
1403
+ emit("submit", trimmed);
1404
+ setValue("");
1405
+ }
1406
+ function stop() {
1407
+ emit("stop");
1408
+ }
1409
+ function onBodyClick(event) {
1410
+ const target = event.target;
1411
+ if (!(target instanceof Element)) return;
1412
+ if (target.closest("button, a, [role=\"button\"], [contenteditable=\"true\"], [data-astryx-token]")) return;
1413
+ if (inputControl.value) {
1414
+ inputControl.value.focus();
1415
+ return;
1416
+ }
1417
+ body.value?.querySelector("[contenteditable=\"true\"], textarea")?.focus();
1418
+ }
1419
+ function onPointerDown() {
1420
+ keyboardFocus.value = false;
1421
+ }
1422
+ function onFocusIn(event) {
1423
+ const target = event.target;
1424
+ keyboardFocus.value = target instanceof HTMLElement && target.matches(editorSelector) && isKeyboardModality();
1425
+ }
1426
+ function onFocusOut(event) {
1427
+ const next = event.relatedTarget;
1428
+ if (!(next instanceof Node) || !event.currentTarget || !event.currentTarget.contains(next)) keyboardFocus.value = false;
1429
+ }
1430
+ provide(chatComposerContextKey, {
1431
+ value,
1432
+ placeholder,
1433
+ isDisabled: disabled,
1434
+ isStopShown: stopShown,
1435
+ canSend,
1436
+ onChange: setValue,
1437
+ onSubmit: submit,
1438
+ onStop: stop,
1439
+ inputControl
1440
+ });
1441
+ __expose({ element: root });
1442
+ return (_ctx, _cache) => {
1443
+ return openBlock(), createElementBlock("div", mergeProps({
1444
+ ref_key: "root",
1445
+ ref: root
1446
+ }, _ctx.$attrs, {
1447
+ class: "astryx-chat-composer",
1448
+ "data-disabled": disabled.value ? "" : void 0,
1449
+ "data-density": __props.density
1450
+ }), [
1451
+ __props.status && __props.statusPosition === "top" ? (openBlock(), createElementBlock("div", {
1452
+ key: 0,
1453
+ class: "astryx-chat-composer__status",
1454
+ "data-place": "top",
1455
+ "data-type": __props.status.type,
1456
+ role: __props.status.type === "error" ? "alert" : "status"
1457
+ }, [createVNode(_sfc_main$27, {
1458
+ icon: __props.status.type === "error" ? "error" : "warning",
1459
+ size: "md",
1460
+ color: __props.status.type === "error" ? "error" : "warning"
1461
+ }, null, 8, ["icon", "color"]), createElementVNode("span", null, toDisplayString(__props.status.message), 1)], 8, _hoisted_2$11)) : createCommentVNode("v-if", true),
1462
+ renderSlot(_ctx.$slots, "drawer"),
1463
+ createCommentVNode(" 点空白处只是把焦点交给编辑器;键盘用户直接 Tab 到输入框。 "),
1464
+ createCommentVNode(" eslint-disable-next-line vue-a11y/click-events-have-key-events, vue-a11y/no-static-element-interactions "),
1465
+ createElementVNode("div", {
1466
+ ref_key: "body",
1467
+ ref: body,
1468
+ class: "astryx-chat-composer__body",
1469
+ "data-density": __props.density,
1470
+ "data-elevation": __props.elevation,
1471
+ "data-keyboard-focus": keyboardFocus.value ? "" : void 0,
1472
+ onClick: onBodyClick,
1473
+ onPointerdown: onPointerDown,
1474
+ onFocusin: onFocusIn,
1475
+ onFocusout: onFocusOut
1476
+ }, [
1477
+ hasHeader.value ? (openBlock(), createElementBlock("div", _hoisted_4$9, [createElementVNode("div", _hoisted_5$7, [renderSlot(_ctx.$slots, "headerActions")]), createElementVNode("div", _hoisted_6$6, [renderSlot(_ctx.$slots, "headerContext")])])) : createCommentVNode("v-if", true),
1478
+ createElementVNode("div", _hoisted_7$5, [renderSlot(_ctx.$slots, "input", {}, () => [createVNode(_sfc_main$20)])]),
1479
+ createElementVNode("div", _hoisted_8$3, [createElementVNode("div", _hoisted_9$3, [renderSlot(_ctx.$slots, "footerActions")]), createElementVNode("div", {
1480
+ class: "astryx-chat-composer__footer-end",
1481
+ "data-interruptible": disabled.value && stopShown.value ? "" : void 0
1482
+ }, [renderSlot(_ctx.$slots, "sendActions"), renderSlot(_ctx.$slots, "sendButton", {}, () => [createVNode(_sfc_main$19)])], 8, _hoisted_10$3)])
1483
+ ], 40, _hoisted_3$9),
1484
+ __props.status && __props.statusPosition === "bottom" ? (openBlock(), createElementBlock("div", {
1485
+ key: 1,
1486
+ class: "astryx-chat-composer__status",
1487
+ "data-place": "bottom",
1488
+ "data-type": __props.status.type,
1489
+ role: __props.status.type === "error" ? "alert" : "status"
1490
+ }, [createVNode(_sfc_main$27, {
1491
+ icon: __props.status.type === "error" ? "error" : "warning",
1492
+ size: "md",
1493
+ color: __props.status.type === "error" ? "error" : "warning"
1494
+ }, null, 8, ["icon", "color"]), createElementVNode("span", null, toDisplayString(__props.status.message), 1)], 8, _hoisted_11$2)) : createCommentVNode("v-if", true)
1495
+ ], 16, _hoisted_1$16);
1496
+ };
1497
+ }
1498
+ });
1499
+ //#endregion
1500
+ //#region src/Chat/ChatComposerDrawer.vue
1501
+ const _hoisted_1$15 = ["data-collapsed"];
1502
+ const _hoisted_2$10 = [
1503
+ "aria-expanded",
1504
+ "aria-controls",
1505
+ "aria-label"
1506
+ ];
1507
+ const _hoisted_3$8 = ["data-hidden"];
1508
+ const _hoisted_4$8 = { class: "astryx-chat-drawer__label" };
1509
+ const _hoisted_5$6 = ["data-hidden"];
1510
+ const _hoisted_6$5 = [
1511
+ "id",
1512
+ "data-collapsed",
1513
+ "inert"
1514
+ ];
1515
+ const _hoisted_7$4 = ["data-collapsed"];
1516
+ const _sfc_main$17 = /*@__PURE__*/ defineComponent({
1517
+ name: "AstryxChatComposerDrawer",
1518
+ inheritAttrs: false,
1519
+ __name: "ChatComposerDrawer",
1520
+ props: /*@__PURE__*/ mergeModels({
1521
+ count: {},
1522
+ label: {},
1523
+ defaultIsCollapsed: {
1524
+ type: Boolean,
1525
+ default: false
1526
+ }
1527
+ }, {
1528
+ "isCollapsed": {
1529
+ type: Boolean,
1530
+ default: void 0
1531
+ },
1532
+ "isCollapsedModifiers": {}
1533
+ }),
1534
+ emits: /*@__PURE__*/ mergeModels(["collapsedChange"], ["update:isCollapsed"]),
1535
+ setup(__props, { emit: __emit }) {
1536
+ const props = __props;
1537
+ const emit = __emit;
1538
+ const model = useModel(__props, "isCollapsed");
1539
+ const messages = useAstryxMessages();
1540
+ const contentId = useId();
1541
+ const internalCollapsed = ref(props.defaultIsCollapsed);
1542
+ const collapsed = computed(() => model.value ?? internalCollapsed.value);
1543
+ const canCollapse = computed(() => props.count != null);
1544
+ const label = computed(() => props.label ?? messages.value.chat.drawerLabel);
1545
+ const actionLabel = computed(() => collapsed.value ? messages.value.chat.drawerExpand(label.value) : messages.value.chat.drawerCollapse(label.value));
1546
+ function toggle() {
1547
+ const next = !collapsed.value;
1548
+ internalCollapsed.value = next;
1549
+ model.value = next;
1550
+ emit("collapsedChange", next);
1551
+ }
1552
+ function onKeyDown(event) {
1553
+ if (event.key !== "Enter" && event.key !== " ") return;
1554
+ event.preventDefault();
1555
+ toggle();
1556
+ }
1557
+ return (_ctx, _cache) => {
1558
+ return openBlock(), createElementBlock("div", mergeProps(_ctx.$attrs, {
1559
+ class: "astryx-chat-drawer",
1560
+ "data-collapsed": canCollapse.value && collapsed.value ? "" : void 0
1561
+ }), [canCollapse.value ? (openBlock(), createElementBlock("div", {
1562
+ key: 0,
1563
+ class: "astryx-chat-drawer__toggle",
1564
+ role: "button",
1565
+ tabindex: "0",
1566
+ "aria-expanded": !collapsed.value,
1567
+ "aria-controls": unref(contentId),
1568
+ "aria-label": actionLabel.value,
1569
+ onClick: toggle,
1570
+ onKeydown: onKeyDown
1571
+ }, [createElementVNode("div", {
1572
+ class: "astryx-chat-drawer__summary",
1573
+ "data-hidden": collapsed.value ? void 0 : "",
1574
+ "aria-hidden": "true",
1575
+ inert: ""
1576
+ }, [renderSlot(_ctx.$slots, "collapsedSummary", {}, () => [createVNode(_sfc_main$26, {
1577
+ variant: "neutral",
1578
+ label: __props.count
1579
+ }, null, 8, ["label"]), createElementVNode("span", _hoisted_4$8, toDisplayString(label.value), 1)])], 8, _hoisted_3$8), createElementVNode("div", {
1580
+ class: "astryx-chat-drawer__handle",
1581
+ "data-hidden": collapsed.value ? "" : void 0
1582
+ }, null, 8, _hoisted_5$6)], 40, _hoisted_2$10)) : createCommentVNode("v-if", true), createElementVNode("div", {
1583
+ id: unref(contentId),
1584
+ class: "astryx-chat-drawer__grid",
1585
+ "data-collapsed": canCollapse.value && collapsed.value ? "" : void 0,
1586
+ inert: canCollapse.value && collapsed.value ? true : void 0
1587
+ }, [createElementVNode("div", {
1588
+ class: "astryx-chat-drawer__content",
1589
+ "data-collapsed": canCollapse.value && collapsed.value ? "" : void 0
1590
+ }, [renderSlot(_ctx.$slots, "default")], 8, _hoisted_7$4)], 8, _hoisted_6$5)], 16, _hoisted_1$15);
1591
+ };
1592
+ }
1593
+ });
1594
+ //#endregion
1595
+ //#region src/Chat/ChatComposerTokenElement.vue
1596
+ const _hoisted_1$14 = ["data-astryx-token-value"];
1597
+ const _sfc_main$16 = /*@__PURE__*/ defineComponent({
1598
+ name: "AstryxChatComposerTokenElement",
1599
+ inheritAttrs: false,
1600
+ __name: "ChatComposerTokenElement",
1601
+ props: { token: {} },
1602
+ setup(__props) {
1603
+ return (_ctx, _cache) => {
1604
+ return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, {
1605
+ class: "astryx-chat-token",
1606
+ "data-astryx-token": "",
1607
+ "data-astryx-token-value": __props.token.value,
1608
+ contenteditable: "false"
1609
+ }), [createVNode(_sfc_main$22, { token: __props.token }, null, 8, ["token"])], 16, _hoisted_1$14);
1610
+ };
1611
+ }
1612
+ });
1613
+ //#endregion
1614
+ //#region src/Chat/ChatDictationButton.vue
1615
+ const BAR_COUNT = 5;
1616
+ const BAR_MIN = .08;
1617
+ const _sfc_main$15 = /*@__PURE__*/ defineComponent({
1618
+ name: "AstryxChatDictationButton",
1619
+ inheritAttrs: false,
1620
+ __name: "ChatDictationButton",
1621
+ props: {
1622
+ dictation: {},
1623
+ size: { default: "md" },
1624
+ isHiddenWhenUnsupported: {
1625
+ type: Boolean,
1626
+ default: true
1627
+ },
1628
+ label: {}
1629
+ },
1630
+ setup(__props) {
1631
+ const props = __props;
1632
+ const messages = useAstryxMessages();
1633
+ const listening = computed(() => props.dictation.isListening);
1634
+ const label = computed(() => props.label ?? (listening.value ? messages.value.chat.stopDictation : messages.value.chat.startDictation));
1635
+ const sizes = {
1636
+ sm: {
1637
+ width: 2,
1638
+ gap: 1.5,
1639
+ height: 14
1640
+ },
1641
+ md: {
1642
+ width: 2.5,
1643
+ gap: 2,
1644
+ height: 18
1645
+ }
1646
+ };
1647
+ const metrics = computed(() => sizes[props.size]);
1648
+ const bars = computed(() => props.dictation.bands.slice(0, BAR_COUNT).map((level, index) => ({
1649
+ key: `band-${index}`,
1650
+ scale: BAR_MIN + Math.min(Math.sqrt(level / .2), 1) * .92
1651
+ })));
1652
+ const clipping = computed(() => {
1653
+ const volume = props.dictation.volume;
1654
+ if (volume < .2) return 0;
1655
+ return Math.min((volume - .2) / .1, 1) * 100;
1656
+ });
1657
+ const barColor = computed(() => clipping.value > 0 ? `color-mix(in srgb, var(--astryx-color-accent), var(--astryx-color-error) ${clipping.value}%)` : "var(--astryx-color-accent)");
1658
+ return (_ctx, _cache) => {
1659
+ return __props.dictation.isSupported || !__props.isHiddenWhenUnsupported ? (openBlock(), createElementBlock("span", mergeProps({ key: 0 }, _ctx.$attrs, { class: "astryx-chat-dictation" }), [listening.value ? (openBlock(), createElementBlock("span", {
1660
+ key: 0,
1661
+ class: "astryx-chat-dictation__bars",
1662
+ "aria-hidden": "true",
1663
+ style: normalizeStyle({
1664
+ gap: `${metrics.value.gap}px`,
1665
+ height: `${metrics.value.height}px`
1666
+ })
1667
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(bars.value, (bar) => {
1668
+ return openBlock(), createElementBlock("span", {
1669
+ key: bar.key,
1670
+ class: "astryx-chat-dictation__bar",
1671
+ style: normalizeStyle({
1672
+ width: `${metrics.value.width}px`,
1673
+ backgroundColor: barColor.value,
1674
+ transform: `scaleY(${bar.scale})`
1675
+ })
1676
+ }, null, 4);
1677
+ }), 128))], 4)) : createCommentVNode("v-if", true), createVNode(_sfc_main$25, {
1678
+ label: label.value,
1679
+ variant: "ghost",
1680
+ size: __props.size,
1681
+ "is-icon-only": "",
1682
+ "is-disabled": !__props.dictation.isSupported,
1683
+ onClick: _cache[0] || (_cache[0] = ($event) => __props.dictation.toggle())
1684
+ }, createSlots({ _: 2 }, [!listening.value ? {
1685
+ name: "icon",
1686
+ fn: withCtx(() => [createVNode(_sfc_main$27, { icon: "microphone" })]),
1687
+ key: "0"
1688
+ } : void 0]), 1032, [
1689
+ "label",
1690
+ "size",
1691
+ "is-disabled"
1692
+ ])], 16)) : createCommentVNode("v-if", true);
1693
+ };
1694
+ }
1695
+ });
1696
+ //#endregion
1697
+ //#region src/Chat/ChatLayoutScrollButton.vue
1698
+ const _hoisted_1$13 = [
1699
+ "data-visible",
1700
+ "data-labeled",
1701
+ "inert"
1702
+ ];
1703
+ const _sfc_main$14 = /*@__PURE__*/ defineComponent({
1704
+ name: "AstryxChatLayoutScrollButton",
1705
+ inheritAttrs: false,
1706
+ __name: "ChatLayoutScrollButton",
1707
+ props: {
1708
+ isVisible: { type: Boolean },
1709
+ label: {}
1710
+ },
1711
+ emits: ["click"],
1712
+ setup(__props, { emit: __emit }) {
1713
+ const props = __props;
1714
+ const emit = __emit;
1715
+ const messages = useAstryxMessages();
1716
+ const accessibleLabel = computed(() => props.label ?? messages.value.chat.scrollToBottom);
1717
+ return (_ctx, _cache) => {
1718
+ return openBlock(), createElementBlock("div", mergeProps(_ctx.$attrs, { class: "astryx-chat-scroll-button" }), [createElementVNode("div", {
1719
+ class: "astryx-chat-scroll-button__pill",
1720
+ "data-visible": __props.isVisible || void 0,
1721
+ "data-labeled": __props.label ? "" : void 0,
1722
+ inert: !__props.isVisible
1723
+ }, [createVNode(_sfc_main$25, {
1724
+ class: "astryx-chat-scroll-button__control",
1725
+ label: accessibleLabel.value,
1726
+ variant: "ghost",
1727
+ size: "md",
1728
+ "is-icon-only": !__props.label,
1729
+ onClick: _cache[0] || (_cache[0] = ($event) => emit("click"))
1730
+ }, {
1731
+ icon: withCtx(() => [createVNode(_sfc_main$27, {
1732
+ icon: "chevronDown",
1733
+ size: "md"
1734
+ })]),
1735
+ _: 1
1736
+ }, 8, ["label", "is-icon-only"])], 8, _hoisted_1$13)], 16);
1737
+ };
1738
+ }
1739
+ });
1740
+ //#endregion
1741
+ //#region src/Chat/ChatLayout.vue
1742
+ const _hoisted_1$12 = ["data-density", "data-scroll"];
1743
+ const _hoisted_2$9 = ["data-density"];
1744
+ const _hoisted_3$7 = {
1745
+ key: 0,
1746
+ class: "astryx-chat-layout__empty"
1747
+ };
1748
+ const _hoisted_4$7 = ["data-density"];
1749
+ const _hoisted_5$5 = ["data-density"];
1750
+ const _hoisted_6$4 = ["data-density"];
1751
+ const _hoisted_7$3 = ["data-density"];
1752
+ const _sfc_main$13 = /*@__PURE__*/ defineComponent({
1753
+ name: "AstryxChatLayout",
1754
+ inheritAttrs: false,
1755
+ __name: "ChatLayout",
1756
+ props: {
1757
+ density: { default: "balanced" },
1758
+ scrollRef: {},
1759
+ showScrollButton: {
1760
+ type: Boolean,
1761
+ default: true
1762
+ }
1763
+ },
1764
+ setup(__props, { expose: __expose }) {
1765
+ const props = __props;
1766
+ const messages = useAstryxMessages();
1767
+ const announce = useAnnounce();
1768
+ const root = useTemplateRef("root");
1769
+ const scrollTarget = computed(() => props.scrollRef ?? root.value);
1770
+ const isSelfScrolling = computed(() => props.scrollRef == null);
1771
+ const scroll = useChatStreamScroll({ scrollRef: scrollTarget });
1772
+ const newMessages = useChatNewMessages({
1773
+ isLocked: scroll.isLocked,
1774
+ onResize: scroll.scrollIfLocked
1775
+ });
1776
+ const buttonVisible = computed(() => scroll.isScrolledUp.value || newMessages.hasNewMessages.value);
1777
+ const buttonLabel = computed(() => newMessages.hasNewMessages.value ? messages.value.chat.newMessages : void 0);
1778
+ function scrollToBottom() {
1779
+ newMessages.dismiss();
1780
+ scroll.scrollToBottom();
1781
+ }
1782
+ watch(() => newMessages.hasNewMessages.value, (hasNew) => {
1783
+ if (hasNew) announce(messages.value.chat.newMessages);
1784
+ });
1785
+ provide(chatLayoutContextKey, {
1786
+ scrollContainer: scrollTarget,
1787
+ contentRef: newMessages.contentRef
1788
+ });
1789
+ __expose({
1790
+ element: root,
1791
+ scrollToBottom
1792
+ });
1793
+ return (_ctx, _cache) => {
1794
+ return openBlock(), createElementBlock("div", mergeProps({
1795
+ ref_key: "root",
1796
+ ref: root
1797
+ }, _ctx.$attrs, {
1798
+ class: "astryx-chat-layout",
1799
+ "data-density": __props.density,
1800
+ "data-scroll": isSelfScrolling.value ? "self" : "external"
1801
+ }), [createElementVNode("div", {
1802
+ class: "astryx-chat-layout__messages",
1803
+ "data-density": __props.density
1804
+ }, [!_ctx.$slots.default && _ctx.$slots.empty ? (openBlock(), createElementBlock("div", _hoisted_3$7, [renderSlot(_ctx.$slots, "empty")])) : renderSlot(_ctx.$slots, "default", {}, void 0, void 0, 1)], 8, _hoisted_2$9), createElementVNode("div", {
1805
+ class: "astryx-chat-layout__dock",
1806
+ "data-density": __props.density
1807
+ }, [
1808
+ renderSlot(_ctx.$slots, "scrollButton", {
1809
+ isVisible: buttonVisible.value,
1810
+ label: buttonLabel.value,
1811
+ scrollToBottom
1812
+ }, () => [__props.showScrollButton ? (openBlock(), createBlock(_sfc_main$14, {
1813
+ key: 0,
1814
+ "is-visible": buttonVisible.value,
1815
+ label: buttonLabel.value,
1816
+ onClick: scrollToBottom
1817
+ }, null, 8, ["is-visible", "label"])) : createCommentVNode("v-if", true)]),
1818
+ createElementVNode("div", {
1819
+ class: "astryx-chat-layout__blur",
1820
+ "data-density": __props.density
1821
+ }, null, 8, _hoisted_5$5),
1822
+ createElementVNode("div", {
1823
+ class: "astryx-chat-layout__composer",
1824
+ "data-density": __props.density
1825
+ }, [createElementVNode("div", {
1826
+ class: "astryx-chat-layout__composer-inner",
1827
+ "data-density": __props.density
1828
+ }, [renderSlot(_ctx.$slots, "composer")], 8, _hoisted_7$3)], 8, _hoisted_6$4)
1829
+ ], 8, _hoisted_4$7)], 16, _hoisted_1$12);
1830
+ };
1831
+ }
1832
+ });
1833
+ //#endregion
1834
+ //#region src/Chat/ChatMessage.vue
1835
+ const _hoisted_1$11 = [
1836
+ "data-sender",
1837
+ "data-density",
1838
+ "aria-label",
1839
+ "aria-labelledby"
1840
+ ];
1841
+ const _hoisted_2$8 = {
1842
+ key: 0,
1843
+ class: "astryx-chat-message__avatar"
1844
+ };
1845
+ const _hoisted_3$6 = ["data-sender"];
1846
+ const _hoisted_4$6 = ["data-sender", "data-density"];
1847
+ const _hoisted_5$4 = {
1848
+ key: 1,
1849
+ class: "astryx-chat-message__metadata"
1850
+ };
1851
+ const _sfc_main$12 = /*@__PURE__*/ defineComponent({
1852
+ name: "AstryxChatMessage",
1853
+ inheritAttrs: false,
1854
+ __name: "ChatMessage",
1855
+ props: {
1856
+ sender: {},
1857
+ name: {},
1858
+ density: {}
1859
+ },
1860
+ setup(__props, { expose: __expose }) {
1861
+ const props = __props;
1862
+ const slots = useSlots();
1863
+ const messages = useAstryxMessages();
1864
+ const list = useChatListContext();
1865
+ const root = useTemplateRef("root");
1866
+ const nameId = `astryx-chat-message-${useId()}`;
1867
+ const density = computed(() => props.density ?? list?.density.value ?? "balanced");
1868
+ const isSystem = computed(() => props.sender === "system");
1869
+ const hasName = computed(() => !isSystem.value && (Boolean(props.name) || Boolean(slots.name)));
1870
+ const senderName = computed(() => {
1871
+ const chat = messages.value.chat;
1872
+ if (props.sender === "user") return chat.senderUser;
1873
+ if (props.sender === "system") return chat.senderSystem;
1874
+ return chat.senderAssistant;
1875
+ });
1876
+ provide(chatMessageContextKey, {
1877
+ sender: computed(() => props.sender),
1878
+ density
1879
+ });
1880
+ __expose({ element: root });
1881
+ return (_ctx, _cache) => {
1882
+ return openBlock(), createElementBlock("article", mergeProps({
1883
+ ref_key: "root",
1884
+ ref: root
1885
+ }, _ctx.$attrs, {
1886
+ class: "astryx-chat-message",
1887
+ "data-sender": __props.sender,
1888
+ "data-density": density.value,
1889
+ "aria-label": hasName.value ? void 0 : unref(messages).chat.messageFrom(senderName.value),
1890
+ "aria-labelledby": hasName.value ? nameId : void 0
1891
+ }), [_ctx.$slots.avatar && !isSystem.value ? (openBlock(), createElementBlock("div", _hoisted_2$8, [renderSlot(_ctx.$slots, "avatar")])) : createCommentVNode("v-if", true), createElementVNode("div", {
1892
+ class: "astryx-chat-message__column",
1893
+ "data-sender": __props.sender
1894
+ }, [
1895
+ hasName.value ? (openBlock(), createElementBlock("div", {
1896
+ key: 0,
1897
+ id: nameId,
1898
+ class: "astryx-chat-message__name",
1899
+ "data-chat-name": ""
1900
+ }, [renderSlot(_ctx.$slots, "name", {}, () => [createTextVNode(toDisplayString(__props.name), 1)])])) : createCommentVNode("v-if", true),
1901
+ createElementVNode("div", {
1902
+ class: "astryx-chat-message__body",
1903
+ "data-sender": __props.sender,
1904
+ "data-density": density.value
1905
+ }, [renderSlot(_ctx.$slots, "default")], 8, _hoisted_4$6),
1906
+ !isSystem.value && _ctx.$slots.metadata ? (openBlock(), createElementBlock("div", _hoisted_5$4, [renderSlot(_ctx.$slots, "metadata")])) : createCommentVNode("v-if", true)
1907
+ ], 8, _hoisted_3$6)], 16, _hoisted_1$11);
1908
+ };
1909
+ }
1910
+ });
1911
+ //#endregion
1912
+ //#region src/Chat/ChatMessageBubble.vue
1913
+ const _hoisted_1$10 = [
1914
+ "data-sender",
1915
+ "data-variant",
1916
+ "data-density"
1917
+ ];
1918
+ const _hoisted_2$7 = ["data-sender", "data-density"];
1919
+ const _hoisted_3$5 = [
1920
+ "data-sender",
1921
+ "data-variant",
1922
+ "data-density",
1923
+ "data-group"
1924
+ ];
1925
+ const _hoisted_4$5 = ["data-sender", "data-density"];
1926
+ const _sfc_main$11 = /*@__PURE__*/ defineComponent({
1927
+ name: "AstryxChatMessageBubble",
1928
+ inheritAttrs: false,
1929
+ __name: "ChatMessageBubble",
1930
+ props: {
1931
+ variant: { default: "filled" },
1932
+ name: {},
1933
+ group: {},
1934
+ width: {}
1935
+ },
1936
+ setup(__props, { expose: __expose }) {
1937
+ const props = __props;
1938
+ const context = useChatMessageContext();
1939
+ const root = useTemplateRef("root");
1940
+ const sender = computed(() => context?.sender.value ?? "assistant");
1941
+ const density = computed(() => context?.density.value ?? "balanced");
1942
+ const frameStyle = computed(() => props.width ? {
1943
+ width: props.width,
1944
+ maxWidth: props.width
1945
+ } : void 0);
1946
+ __expose({ element: root });
1947
+ return (_ctx, _cache) => {
1948
+ return openBlock(), createElementBlock("div", {
1949
+ class: "astryx-chat-message-bubble__frame",
1950
+ "data-sender": sender.value,
1951
+ "data-variant": __props.variant,
1952
+ "data-density": density.value,
1953
+ style: normalizeStyle(frameStyle.value)
1954
+ }, [
1955
+ __props.name || _ctx.$slots.name ? (openBlock(), createElementBlock("div", {
1956
+ key: 0,
1957
+ class: "astryx-chat-message-bubble__name",
1958
+ "data-chat-name": "",
1959
+ "data-sender": sender.value,
1960
+ "data-density": density.value
1961
+ }, [renderSlot(_ctx.$slots, "name", {}, () => [createTextVNode(toDisplayString(__props.name), 1)])], 8, _hoisted_2$7)) : createCommentVNode("v-if", true),
1962
+ createElementVNode("div", mergeProps({
1963
+ ref_key: "root",
1964
+ ref: root
1965
+ }, _ctx.$attrs, {
1966
+ class: "astryx-chat-message-bubble",
1967
+ "data-sender": sender.value,
1968
+ "data-variant": __props.variant,
1969
+ "data-density": density.value,
1970
+ "data-group": __props.group
1971
+ }), [renderSlot(_ctx.$slots, "default")], 16, _hoisted_3$5),
1972
+ _ctx.$slots.metadata ? (openBlock(), createElementBlock("div", {
1973
+ key: 1,
1974
+ class: "astryx-chat-message-bubble__meta",
1975
+ "data-sender": sender.value,
1976
+ "data-density": density.value
1977
+ }, [renderSlot(_ctx.$slots, "metadata")], 8, _hoisted_4$5)) : createCommentVNode("v-if", true)
1978
+ ], 12, _hoisted_1$10);
1979
+ };
1980
+ }
1981
+ });
1982
+ //#endregion
1983
+ //#region src/Chat/ChatMessageList.vue
1984
+ const _hoisted_1$9 = [
1985
+ "aria-busy",
1986
+ "aria-label",
1987
+ "data-density"
1988
+ ];
1989
+ const _hoisted_2$6 = ["data-density", "data-gap"];
1990
+ const _hoisted_3$4 = {
1991
+ key: 1,
1992
+ class: "astryx-chat-message-list__loading"
1993
+ };
1994
+ const _hoisted_4$4 = {
1995
+ key: 3,
1996
+ class: "astryx-chat-message-list__empty"
1997
+ };
1998
+ const _sfc_main$10 = /*@__PURE__*/ defineComponent({
1999
+ name: "AstryxChatMessageList",
2000
+ inheritAttrs: false,
2001
+ __name: "ChatMessageList",
2002
+ props: {
2003
+ density: { default: "balanced" },
2004
+ gap: {},
2005
+ scrollToTopAction: {},
2006
+ isStreaming: {
2007
+ type: Boolean,
2008
+ default: false
2009
+ },
2010
+ label: {}
2011
+ },
2012
+ setup(__props) {
2013
+ const props = __props;
2014
+ const messages = useAstryxMessages();
2015
+ const layout = useChatLayoutContext();
2016
+ const inner = useTemplateRef("inner");
2017
+ const sentinel = useTemplateRef("sentinel");
2018
+ const isLoadingTop = ref(false);
2019
+ const accessibleName = computed(() => props.label ?? messages.value.chat.log);
2020
+ provide(chatListContextKey, { density: computed(() => props.density) });
2021
+ watch(inner, (el, _, onCleanup) => {
2022
+ if (!layout) return;
2023
+ layout.contentRef(el);
2024
+ onCleanup(() => layout.contentRef(null));
2025
+ });
2026
+ watch(() => [
2027
+ props.scrollToTopAction,
2028
+ sentinel.value,
2029
+ layout?.scrollContainer.value
2030
+ ], ([action, sentinelEl], _, onCleanup) => {
2031
+ if (!action || !sentinelEl || typeof IntersectionObserver === "undefined") return;
2032
+ let pending = false;
2033
+ const observer = new IntersectionObserver((entries) => {
2034
+ if (!entries[0]?.isIntersecting || pending) return;
2035
+ pending = true;
2036
+ isLoadingTop.value = true;
2037
+ Promise.resolve(action()).finally(() => {
2038
+ pending = false;
2039
+ isLoadingTop.value = false;
2040
+ });
2041
+ }, {
2042
+ root: layout?.scrollContainer.value ?? null,
2043
+ threshold: 0
2044
+ });
2045
+ observer.observe(sentinelEl);
2046
+ onCleanup(() => observer.disconnect());
2047
+ });
2048
+ return (_ctx, _cache) => {
2049
+ return openBlock(), createElementBlock("div", mergeProps(_ctx.$attrs, {
2050
+ class: "astryx-chat-message-list",
2051
+ role: "log",
2052
+ "aria-live": "polite",
2053
+ "aria-busy": __props.isStreaming || void 0,
2054
+ "aria-label": accessibleName.value,
2055
+ tabindex: "0",
2056
+ "data-density": __props.density
2057
+ }), [createElementVNode("div", {
2058
+ ref_key: "inner",
2059
+ ref: inner,
2060
+ class: "astryx-chat-message-list__inner",
2061
+ "data-density": __props.density,
2062
+ "data-gap": __props.gap
2063
+ }, [
2064
+ __props.scrollToTopAction ? (openBlock(), createElementBlock("div", {
2065
+ key: 0,
2066
+ ref_key: "sentinel",
2067
+ ref: sentinel,
2068
+ class: "astryx-chat-message-list__sentinel",
2069
+ "aria-hidden": "true"
2070
+ }, null, 512)) : createCommentVNode("v-if", true),
2071
+ isLoadingTop.value ? (openBlock(), createElementBlock("div", _hoisted_3$4, [createVNode(_sfc_main$30, {
2072
+ size: "md",
2073
+ "aria-label": unref(messages).chat.toolRunning
2074
+ }, null, 8, ["aria-label"])])) : createCommentVNode("v-if", true),
2075
+ _cache[0] || (_cache[0] = createElementVNode("div", {
2076
+ class: "astryx-chat-message-list__spacer",
2077
+ "aria-hidden": "true"
2078
+ }, null, -1)),
2079
+ _ctx.$slots.default ? renderSlot(_ctx.$slots, "default", {}, void 0, void 0, 2) : _ctx.$slots.empty ? (openBlock(), createElementBlock("div", _hoisted_4$4, [renderSlot(_ctx.$slots, "empty")])) : createCommentVNode("v-if", true)
2080
+ ], 8, _hoisted_2$6)], 16, _hoisted_1$9);
2081
+ };
2082
+ }
2083
+ });
2084
+ //#endregion
2085
+ //#region src/Chat/ChatIcon.vue
2086
+ const _sfc_main$9 = /*@__PURE__*/ defineComponent({
2087
+ __name: "ChatIcon",
2088
+ props: {
2089
+ name: {},
2090
+ size: { default: "sm" }
2091
+ },
2092
+ setup(__props) {
2093
+ return (_ctx, _cache) => {
2094
+ return openBlock(), createBlock(_sfc_main$27, {
2095
+ size: __props.size,
2096
+ color: "inherit"
2097
+ }, {
2098
+ default: withCtx(() => [createElementVNode("span", { class: normalizeClass(["astryx-icon__glyph", `astryx-chat-icon--${__props.name}`]) }, null, 2)]),
2099
+ _: 1
2100
+ }, 8, ["size"]);
2101
+ };
2102
+ }
2103
+ });
2104
+ //#endregion
2105
+ //#region src/Chat/ChatMessageMetadata.vue
2106
+ const _hoisted_1$8 = ["data-sender"];
2107
+ const _hoisted_2$5 = {
2108
+ key: 0,
2109
+ class: "astryx-chat-metadata__timestamp"
2110
+ };
2111
+ const _hoisted_3$3 = {
2112
+ key: 1,
2113
+ class: "astryx-chat-metadata__dot",
2114
+ "aria-hidden": "true"
2115
+ };
2116
+ const _hoisted_4$3 = {
2117
+ key: 2,
2118
+ class: "astryx-chat-metadata__footer"
2119
+ };
2120
+ const _hoisted_5$3 = {
2121
+ key: 3,
2122
+ class: "astryx-chat-metadata__dot",
2123
+ "aria-hidden": "true"
2124
+ };
2125
+ const _hoisted_6$3 = [
2126
+ "data-status",
2127
+ "title",
2128
+ "aria-label"
2129
+ ];
2130
+ const _sfc_main$8 = /*@__PURE__*/ defineComponent({
2131
+ name: "AstryxChatMessageMetadata",
2132
+ inheritAttrs: false,
2133
+ __name: "ChatMessageMetadata",
2134
+ props: {
2135
+ timestamp: {},
2136
+ footer: {},
2137
+ status: {}
2138
+ },
2139
+ setup(__props) {
2140
+ const props = __props;
2141
+ const slots = useSlots();
2142
+ const messages = useAstryxMessages();
2143
+ const locale = useAstryxLocale();
2144
+ const context = useChatMessageContext();
2145
+ const sender = computed(() => context?.sender.value ?? "assistant");
2146
+ const statusLabel = computed(() => {
2147
+ const chat = messages.value.chat;
2148
+ const labels = {
2149
+ sending: chat.statusSending,
2150
+ sent: chat.statusSent,
2151
+ delivered: chat.statusDelivered,
2152
+ read: chat.statusRead,
2153
+ error: chat.statusFailed
2154
+ };
2155
+ return props.status ? labels[props.status] : "";
2156
+ });
2157
+ const spokenStatus = computed(() => {
2158
+ if (locale.value === "en") return statusLabel.value.toLowerCase();
2159
+ return statusLabel.value;
2160
+ });
2161
+ const hasContent = computed(() => Boolean(props.timestamp) || Boolean(slots.timestamp) || Boolean(props.footer) || Boolean(slots.footer) || props.status != null);
2162
+ const showTimestampDot = computed(() => (Boolean(props.timestamp) || Boolean(slots.timestamp)) && (Boolean(props.footer) || Boolean(slots.footer) || props.status != null));
2163
+ const showFooterDot = computed(() => (Boolean(props.footer) || Boolean(slots.footer)) && props.status != null);
2164
+ return (_ctx, _cache) => {
2165
+ return hasContent.value ? (openBlock(), createElementBlock("div", mergeProps({ key: 0 }, _ctx.$attrs, {
2166
+ class: "astryx-chat-metadata",
2167
+ "data-sender": sender.value
2168
+ }), [
2169
+ __props.timestamp || _ctx.$slots.timestamp ? (openBlock(), createElementBlock("span", _hoisted_2$5, [renderSlot(_ctx.$slots, "timestamp", {}, () => [createTextVNode(toDisplayString(__props.timestamp), 1)])])) : createCommentVNode("v-if", true),
2170
+ showTimestampDot.value ? (openBlock(), createElementBlock("span", _hoisted_3$3, "·")) : createCommentVNode("v-if", true),
2171
+ __props.footer || _ctx.$slots.footer ? (openBlock(), createElementBlock("span", _hoisted_4$3, [renderSlot(_ctx.$slots, "footer", {}, () => [createTextVNode(toDisplayString(__props.footer), 1)])])) : createCommentVNode("v-if", true),
2172
+ showFooterDot.value ? (openBlock(), createElementBlock("span", _hoisted_5$3, "·")) : createCommentVNode("v-if", true),
2173
+ __props.status ? (openBlock(), createElementBlock("span", {
2174
+ key: 4,
2175
+ class: "astryx-chat-metadata__status",
2176
+ "data-status": __props.status,
2177
+ title: statusLabel.value,
2178
+ "aria-label": unref(messages).chat.messageStatus(spokenStatus.value)
2179
+ }, [__props.status === "sending" ? (openBlock(), createBlock(_sfc_main$9, {
2180
+ key: 0,
2181
+ name: "clock",
2182
+ size: "xsm"
2183
+ })) : __props.status === "sent" ? (openBlock(), createBlock(_sfc_main$27, {
2184
+ key: 1,
2185
+ icon: "check",
2186
+ size: "xsm",
2187
+ color: "inherit"
2188
+ })) : __props.status === "delivered" || __props.status === "read" ? (openBlock(), createBlock(_sfc_main$9, {
2189
+ key: 2,
2190
+ name: "check-double",
2191
+ size: "xsm"
2192
+ })) : (openBlock(), createBlock(_sfc_main$9, {
2193
+ key: 3,
2194
+ name: "error",
2195
+ size: "xsm"
2196
+ })), createElementVNode("span", null, toDisplayString(statusLabel.value), 1)], 8, _hoisted_6$3)) : createCommentVNode("v-if", true)
2197
+ ], 16, _hoisted_1$8)) : createCommentVNode("v-if", true);
2198
+ };
2199
+ }
2200
+ });
2201
+ //#endregion
2202
+ //#region src/Chat/ChatReasoning.vue
2203
+ const _hoisted_1$7 = ["data-streaming", "data-open"];
2204
+ const _hoisted_2$4 = ["aria-expanded"];
2205
+ const _hoisted_3$2 = { class: "astryx-chat-reasoning__icon" };
2206
+ const _hoisted_4$2 = { class: "astryx-chat-reasoning__label-row" };
2207
+ const _hoisted_5$2 = ["data-streaming"];
2208
+ const _hoisted_6$2 = { class: "astryx-chat-reasoning__duration" };
2209
+ const _hoisted_7$2 = { class: "astryx-chat-reasoning__preview" };
2210
+ const _hoisted_8$2 = ["data-open"];
2211
+ const _hoisted_9$2 = ["data-open", "inert"];
2212
+ const _hoisted_10$2 = { class: "astryx-chat-reasoning__panel-inner" };
2213
+ const _hoisted_11$1 = { class: "astryx-chat-reasoning__body" };
2214
+ const _sfc_main$7 = /*@__PURE__*/ defineComponent({
2215
+ name: "AstryxChatReasoning",
2216
+ inheritAttrs: false,
2217
+ __name: "ChatReasoning",
2218
+ props: /*@__PURE__*/ mergeModels({
2219
+ label: {},
2220
+ duration: {},
2221
+ isStreaming: {
2222
+ type: Boolean,
2223
+ default: false
2224
+ },
2225
+ defaultIsExpanded: {
2226
+ type: Boolean,
2227
+ default: false
2228
+ }
2229
+ }, {
2230
+ "isExpanded": {
2231
+ type: Boolean,
2232
+ default: void 0
2233
+ },
2234
+ "isExpandedModifiers": {}
2235
+ }),
2236
+ emits: /*@__PURE__*/ mergeModels(["expandedChange"], ["update:isExpanded"]),
2237
+ setup(__props, { expose: __expose, emit: __emit }) {
2238
+ const props = __props;
2239
+ const emit = __emit;
2240
+ const slots = useSlots();
2241
+ const messages = useAstryxMessages();
2242
+ const model = useModel(__props, "isExpanded");
2243
+ const internalExpanded = ref(props.defaultIsExpanded);
2244
+ const isExpanded = computed(() => model.value ?? internalExpanded.value);
2245
+ const contentId = `astryx-chat-reasoning-${useId()}`;
2246
+ const root = useTemplateRef("root");
2247
+ const heading = computed(() => props.label ?? messages.value.chat.thinking);
2248
+ function slotPreview() {
2249
+ const nodes = slots.default?.();
2250
+ if (!nodes || nodes.length !== 1) return null;
2251
+ const text = textOf(nodes[0]);
2252
+ return text.trim() === "" ? null : text.trim();
2253
+ }
2254
+ function textOf(node) {
2255
+ if (typeof node.children === "string") return node.children;
2256
+ return "";
2257
+ }
2258
+ function toggle() {
2259
+ const next = !isExpanded.value;
2260
+ internalExpanded.value = next;
2261
+ model.value = next;
2262
+ emit("expandedChange", next);
2263
+ }
2264
+ __expose({ element: root });
2265
+ return (_ctx, _cache) => {
2266
+ return openBlock(), createElementBlock("div", mergeProps({
2267
+ ref_key: "root",
2268
+ ref: root
2269
+ }, _ctx.$attrs, {
2270
+ class: "astryx-chat-reasoning",
2271
+ "data-streaming": __props.isStreaming || void 0,
2272
+ "data-open": isExpanded.value || void 0
2273
+ }), [createElementVNode("button", {
2274
+ type: "button",
2275
+ class: "astryx-chat-reasoning__header",
2276
+ "aria-expanded": isExpanded.value,
2277
+ "aria-controls": contentId,
2278
+ onClick: toggle
2279
+ }, [
2280
+ createElementVNode("span", _hoisted_3$2, [createVNode(_sfc_main$9, {
2281
+ name: "thinking",
2282
+ size: "sm"
2283
+ })]),
2284
+ createElementVNode("span", _hoisted_4$2, [
2285
+ createElementVNode("span", {
2286
+ class: "astryx-chat-reasoning__label",
2287
+ "data-streaming": __props.isStreaming || void 0
2288
+ }, toDisplayString(heading.value), 9, _hoisted_5$2),
2289
+ __props.duration && !__props.isStreaming ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [_cache[0] || (_cache[0] = createElementVNode("span", {
2290
+ class: "astryx-chat-reasoning__separator",
2291
+ "aria-hidden": "true"
2292
+ }, "·", -1)), createElementVNode("span", _hoisted_6$2, toDisplayString(__props.duration), 1)], 64)) : createCommentVNode("v-if", true),
2293
+ !isExpanded.value && !__props.isStreaming && slotPreview() ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [_cache[1] || (_cache[1] = createElementVNode("span", {
2294
+ class: "astryx-chat-reasoning__separator",
2295
+ "aria-hidden": "true"
2296
+ }, "—", -1)), createElementVNode("span", _hoisted_7$2, toDisplayString(slotPreview()), 1)], 64)) : createCommentVNode("v-if", true)
2297
+ ]),
2298
+ createElementVNode("span", {
2299
+ class: "astryx-chat-tool-calls__chevron",
2300
+ "data-open": isExpanded.value || void 0
2301
+ }, [createVNode(_sfc_main$27, {
2302
+ icon: "chevronDown",
2303
+ size: "xsm",
2304
+ color: "inherit"
2305
+ })], 8, _hoisted_8$2)
2306
+ ], 8, _hoisted_2$4), createElementVNode("div", {
2307
+ id: contentId,
2308
+ class: "astryx-chat-reasoning__panel",
2309
+ "data-open": isExpanded.value || void 0,
2310
+ inert: !isExpanded.value
2311
+ }, [createElementVNode("div", _hoisted_10$2, [createElementVNode("div", _hoisted_11$1, [renderSlot(_ctx.$slots, "default")])])], 8, _hoisted_9$2)], 16, _hoisted_1$7);
2312
+ };
2313
+ }
2314
+ });
2315
+ //#endregion
2316
+ //#region src/Chat/ChatSystemMessage.vue
2317
+ const _hoisted_1$6 = { class: "astryx-chat-system-message__content" };
2318
+ const _hoisted_2$3 = {
2319
+ key: 0,
2320
+ class: "astryx-chat-system-message__icon"
2321
+ };
2322
+ const _sfc_main$6 = /*@__PURE__*/ defineComponent({
2323
+ name: "AstryxChatSystemMessage",
2324
+ inheritAttrs: false,
2325
+ __name: "ChatSystemMessage",
2326
+ props: { variant: { default: "default" } },
2327
+ setup(__props) {
2328
+ return (_ctx, _cache) => {
2329
+ return __props.variant === "divider" ? (openBlock(), createElementBlock("div", mergeProps({ key: 0 }, _ctx.$attrs, {
2330
+ class: "astryx-chat-system-message",
2331
+ "data-variant": "divider",
2332
+ role: "status"
2333
+ }), [createVNode(_sfc_main$28, null, {
2334
+ label: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
2335
+ _: 3
2336
+ })], 16)) : (openBlock(), createElementBlock("div", mergeProps({ key: 1 }, _ctx.$attrs, {
2337
+ class: "astryx-chat-system-message",
2338
+ "data-variant": "default",
2339
+ role: "status"
2340
+ }), [createElementVNode("span", _hoisted_1$6, [_ctx.$slots.icon ? (openBlock(), createElementBlock("span", _hoisted_2$3, [renderSlot(_ctx.$slots, "icon")])) : createCommentVNode("v-if", true), renderSlot(_ctx.$slots, "default")])], 16));
2341
+ };
2342
+ }
2343
+ });
2344
+ //#endregion
2345
+ //#region src/Chat/ChatTokenizedText.vue
2346
+ const _hoisted_1$5 = {
2347
+ key: 0,
2348
+ class: "astryx-chat-token"
2349
+ };
2350
+ const _sfc_main$5 = /*@__PURE__*/ defineComponent({
2351
+ name: "AstryxChatTokenizedText",
2352
+ inheritAttrs: false,
2353
+ __name: "ChatTokenizedText",
2354
+ props: {
2355
+ text: {},
2356
+ tokens: {}
2357
+ },
2358
+ setup(__props) {
2359
+ const props = __props;
2360
+ function escapeRegExp(value) {
2361
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2362
+ }
2363
+ const parts = computed(() => {
2364
+ const text = props.text ?? "";
2365
+ const tokens = (props.tokens ?? []).filter((token) => token.value.length > 0);
2366
+ if (text.length === 0 || tokens.length === 0) return [{
2367
+ key: "all",
2368
+ text
2369
+ }];
2370
+ const map = /* @__PURE__ */ new Map();
2371
+ for (const token of tokens) if (!map.has(token.value)) map.set(token.value, token);
2372
+ const pattern = [...map.keys()].map(escapeRegExp).join("|");
2373
+ const regex = new RegExp(`(${pattern})`, "g");
2374
+ const rendered = [];
2375
+ let last = 0;
2376
+ let match = regex.exec(text);
2377
+ let index = 0;
2378
+ while (match) {
2379
+ if (match.index > last) rendered.push({
2380
+ key: `t-${index}`,
2381
+ text: text.slice(last, match.index)
2382
+ });
2383
+ const token = map.get(match[0]);
2384
+ if (token) rendered.push({
2385
+ key: `${match[0]}-${match.index}`,
2386
+ token
2387
+ });
2388
+ last = match.index + match[0].length;
2389
+ index += 1;
2390
+ match = regex.exec(text);
2391
+ }
2392
+ if (last < text.length) rendered.push({
2393
+ key: "tail",
2394
+ text: text.slice(last)
2395
+ });
2396
+ return rendered;
2397
+ });
2398
+ return (_ctx, _cache) => {
2399
+ return openBlock(), createElementBlock("span", mergeProps(_ctx.$attrs, { class: "astryx-chat-tokenized" }), [(openBlock(true), createElementBlock(Fragment, null, renderList(parts.value, (part) => {
2400
+ return openBlock(), createElementBlock(Fragment, { key: part.key }, [part.token ? (openBlock(), createElementBlock("span", _hoisted_1$5, [createVNode(_sfc_main$22, { token: part.token }, null, 8, ["token"])])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(part.text), 1)], 64))], 64);
2401
+ }), 128))], 16);
2402
+ };
2403
+ }
2404
+ });
2405
+ //#endregion
2406
+ //#region src/Chat/ChatStatusMark.vue
2407
+ const _hoisted_1$4 = ["data-status", "title"];
2408
+ const _sfc_main$4 = /*@__PURE__*/ defineComponent({
2409
+ __name: "ChatStatusMark",
2410
+ props: {
2411
+ status: {},
2412
+ errorMessage: {}
2413
+ },
2414
+ setup(__props) {
2415
+ const props = __props;
2416
+ const messages = useAstryxMessages();
2417
+ const label = computed(() => {
2418
+ const chat = messages.value.chat;
2419
+ if (props.status === "pending") return chat.toolPending;
2420
+ if (props.status === "running") return chat.toolRunning;
2421
+ if (props.status === "error") return props.errorMessage ? chat.toolError(props.errorMessage) : chat.statusFailed;
2422
+ return chat.statusSent;
2423
+ });
2424
+ return (_ctx, _cache) => {
2425
+ return openBlock(), createElementBlock("span", {
2426
+ class: "astryx-chat-status",
2427
+ "data-status": __props.status,
2428
+ title: __props.status === "error" ? __props.errorMessage : void 0
2429
+ }, [__props.status === "pending" || __props.status === "running" ? (openBlock(), createBlock(_sfc_main$30, {
2430
+ key: 0,
2431
+ size: "sm",
2432
+ shade: "inherit",
2433
+ "aria-label": label.value
2434
+ }, null, 8, ["aria-label"])) : __props.status === "complete" ? (openBlock(), createBlock(_sfc_main$9, {
2435
+ key: 1,
2436
+ name: "circle-check",
2437
+ size: "xsm"
2438
+ })) : (openBlock(), createBlock(_sfc_main$9, {
2439
+ key: 2,
2440
+ name: "circle-x",
2441
+ size: "xsm"
2442
+ })), __props.status !== "pending" && __props.status !== "running" ? (openBlock(), createBlock(_sfc_main$24, { key: 3 }, {
2443
+ default: withCtx(() => [createTextVNode(toDisplayString(label.value), 1)]),
2444
+ _: 1
2445
+ })) : createCommentVNode("v-if", true)], 8, _hoisted_1$4);
2446
+ };
2447
+ }
2448
+ });
2449
+ //#endregion
2450
+ //#region src/Chat/ChatToolCallRow.vue
2451
+ const _hoisted_1$3 = { class: "astryx-chat-tool-calls__entry" };
2452
+ const _hoisted_2$2 = ["aria-expanded", "aria-controls"];
2453
+ const _hoisted_3$1 = { class: "astryx-chat-tool-calls__name" };
2454
+ const _hoisted_4$1 = {
2455
+ key: 1,
2456
+ class: "astryx-chat-tool-calls__target"
2457
+ };
2458
+ const _hoisted_5$1 = {
2459
+ key: 2,
2460
+ class: "astryx-chat-tool-calls__stats"
2461
+ };
2462
+ const _hoisted_6$1 = {
2463
+ key: 0,
2464
+ class: "astryx-chat-tool-calls__additions"
2465
+ };
2466
+ const _hoisted_7$1 = {
2467
+ key: 1,
2468
+ class: "astryx-chat-tool-calls__deletions"
2469
+ };
2470
+ const _hoisted_8$1 = { key: 2 };
2471
+ const _hoisted_9$1 = {
2472
+ key: 3,
2473
+ class: "astryx-chat-tool-calls__duration"
2474
+ };
2475
+ const _hoisted_10$1 = ["data-open"];
2476
+ const _hoisted_11 = {
2477
+ key: 1,
2478
+ class: "astryx-chat-tool-calls__row"
2479
+ };
2480
+ const _hoisted_12 = { class: "astryx-chat-tool-calls__name" };
2481
+ const _hoisted_13 = {
2482
+ key: 1,
2483
+ class: "astryx-chat-tool-calls__target"
2484
+ };
2485
+ const _hoisted_14 = {
2486
+ key: 2,
2487
+ class: "astryx-chat-tool-calls__stats"
2488
+ };
2489
+ const _hoisted_15 = {
2490
+ key: 0,
2491
+ class: "astryx-chat-tool-calls__additions"
2492
+ };
2493
+ const _hoisted_16 = {
2494
+ key: 1,
2495
+ class: "astryx-chat-tool-calls__deletions"
2496
+ };
2497
+ const _hoisted_17 = { key: 2 };
2498
+ const _hoisted_18 = {
2499
+ key: 3,
2500
+ class: "astryx-chat-tool-calls__duration"
2501
+ };
2502
+ const _sfc_main$3 = /*@__PURE__*/ defineComponent({
2503
+ __name: "ChatToolCallRow",
2504
+ props: {
2505
+ call: {},
2506
+ expandable: { type: Boolean }
2507
+ },
2508
+ setup(__props) {
2509
+ const props = __props;
2510
+ const isDetailOpen = ref(false);
2511
+ const detailId = `astryx-chat-tool-detail-${useId()}`;
2512
+ const status = computed(() => props.call.status ?? "complete");
2513
+ function toggle() {
2514
+ isDetailOpen.value = !isDetailOpen.value;
2515
+ }
2516
+ return (_ctx, _cache) => {
2517
+ return openBlock(), createElementBlock("div", _hoisted_1$3, [__props.expandable ? (openBlock(), createElementBlock("button", {
2518
+ key: 0,
2519
+ type: "button",
2520
+ class: "astryx-chat-tool-calls__row",
2521
+ "data-expandable": "",
2522
+ "aria-expanded": isDetailOpen.value,
2523
+ "aria-controls": isDetailOpen.value ? detailId : void 0,
2524
+ onClick: toggle
2525
+ }, [
2526
+ createVNode(_sfc_main$4, {
2527
+ status: status.value,
2528
+ "error-message": __props.call.errorMessage
2529
+ }, null, 8, ["status", "error-message"]),
2530
+ createElementVNode("span", _hoisted_3$1, toDisplayString(__props.call.name), 1),
2531
+ __props.call.node ? (openBlock(), createBlock(_sfc_main$26, {
2532
+ key: 0,
2533
+ class: "astryx-chat-tool-calls__node",
2534
+ variant: "neutral"
2535
+ }, {
2536
+ default: withCtx(() => [createTextVNode(toDisplayString(__props.call.node), 1)]),
2537
+ _: 1
2538
+ })) : createCommentVNode("v-if", true),
2539
+ __props.call.target ? (openBlock(), createElementBlock("span", _hoisted_4$1, toDisplayString(__props.call.target), 1)) : createCommentVNode("v-if", true),
2540
+ __props.call.additions != null || __props.call.deletions != null || __props.call.stats ? (openBlock(), createElementBlock("span", _hoisted_5$1, [
2541
+ __props.call.additions != null ? (openBlock(), createElementBlock("span", _hoisted_6$1, "+" + toDisplayString(__props.call.additions), 1)) : createCommentVNode("v-if", true),
2542
+ __props.call.deletions != null ? (openBlock(), createElementBlock("span", _hoisted_7$1, "-" + toDisplayString(__props.call.deletions), 1)) : createCommentVNode("v-if", true),
2543
+ __props.call.stats ? (openBlock(), createElementBlock("span", _hoisted_8$1, toDisplayString(__props.call.stats), 1)) : createCommentVNode("v-if", true)
2544
+ ])) : createCommentVNode("v-if", true),
2545
+ __props.call.duration && status.value === "complete" ? (openBlock(), createElementBlock("span", _hoisted_9$1, toDisplayString(__props.call.duration), 1)) : createCommentVNode("v-if", true),
2546
+ createElementVNode("span", {
2547
+ class: "astryx-chat-tool-calls__chevron",
2548
+ "data-open": isDetailOpen.value || void 0
2549
+ }, [createVNode(_sfc_main$27, {
2550
+ icon: "chevronDown",
2551
+ size: "xsm",
2552
+ color: "inherit"
2553
+ })], 8, _hoisted_10$1)
2554
+ ], 8, _hoisted_2$2)) : (openBlock(), createElementBlock("div", _hoisted_11, [
2555
+ createVNode(_sfc_main$4, {
2556
+ status: status.value,
2557
+ "error-message": __props.call.errorMessage
2558
+ }, null, 8, ["status", "error-message"]),
2559
+ createElementVNode("span", _hoisted_12, toDisplayString(__props.call.name), 1),
2560
+ __props.call.node ? (openBlock(), createBlock(_sfc_main$26, {
2561
+ key: 0,
2562
+ class: "astryx-chat-tool-calls__node",
2563
+ variant: "neutral"
2564
+ }, {
2565
+ default: withCtx(() => [createTextVNode(toDisplayString(__props.call.node), 1)]),
2566
+ _: 1
2567
+ })) : createCommentVNode("v-if", true),
2568
+ __props.call.target ? (openBlock(), createElementBlock("span", _hoisted_13, toDisplayString(__props.call.target), 1)) : createCommentVNode("v-if", true),
2569
+ __props.call.additions != null || __props.call.deletions != null || __props.call.stats ? (openBlock(), createElementBlock("span", _hoisted_14, [
2570
+ __props.call.additions != null ? (openBlock(), createElementBlock("span", _hoisted_15, "+" + toDisplayString(__props.call.additions), 1)) : createCommentVNode("v-if", true),
2571
+ __props.call.deletions != null ? (openBlock(), createElementBlock("span", _hoisted_16, "-" + toDisplayString(__props.call.deletions), 1)) : createCommentVNode("v-if", true),
2572
+ __props.call.stats ? (openBlock(), createElementBlock("span", _hoisted_17, toDisplayString(__props.call.stats), 1)) : createCommentVNode("v-if", true)
2573
+ ])) : createCommentVNode("v-if", true),
2574
+ __props.call.duration && status.value === "complete" ? (openBlock(), createElementBlock("span", _hoisted_18, toDisplayString(__props.call.duration), 1)) : createCommentVNode("v-if", true)
2575
+ ])), __props.expandable && isDetailOpen.value ? (openBlock(), createElementBlock("div", {
2576
+ key: 2,
2577
+ id: detailId,
2578
+ class: "astryx-chat-tool-calls__detail"
2579
+ }, [renderSlot(_ctx.$slots, "detail")])) : createCommentVNode("v-if", true)]);
2580
+ };
2581
+ }
2582
+ });
2583
+ //#endregion
2584
+ //#region src/Chat/ChatToolCalls.vue
2585
+ const _hoisted_1$2 = ["aria-expanded"];
2586
+ const _hoisted_2$1 = { class: "astryx-chat-tool-calls__group-icon" };
2587
+ const _hoisted_3 = { class: "astryx-chat-tool-calls__group-label" };
2588
+ const _hoisted_4 = { class: "astryx-chat-tool-calls__name" };
2589
+ const _hoisted_5 = {
2590
+ key: 0,
2591
+ class: "astryx-chat-tool-calls__target"
2592
+ };
2593
+ const _hoisted_6 = { class: "astryx-chat-tool-calls__count" };
2594
+ const _hoisted_7 = ["data-open"];
2595
+ const _hoisted_8 = ["data-open", "inert"];
2596
+ const _hoisted_9 = { class: "astryx-chat-tool-calls__panel-inner" };
2597
+ const _hoisted_10 = { class: "astryx-chat-tool-calls__list" };
2598
+ const _sfc_main$2 = /*@__PURE__*/ defineComponent({
2599
+ name: "AstryxChatToolCalls",
2600
+ inheritAttrs: false,
2601
+ __name: "ChatToolCalls",
2602
+ props: /*@__PURE__*/ mergeModels({
2603
+ calls: {},
2604
+ label: {},
2605
+ defaultIsExpanded: {
2606
+ type: Boolean,
2607
+ default: false
2608
+ }
2609
+ }, {
2610
+ "isExpanded": {
2611
+ type: Boolean,
2612
+ default: void 0
2613
+ },
2614
+ "isExpandedModifiers": {}
2615
+ }),
2616
+ emits: /*@__PURE__*/ mergeModels(["expandedChange"], ["update:isExpanded"]),
2617
+ setup(__props, { expose: __expose, emit: __emit }) {
2618
+ const props = __props;
2619
+ const emit = __emit;
2620
+ const slots = useSlots();
2621
+ const messages = useAstryxMessages();
2622
+ const model = useModel(__props, "isExpanded");
2623
+ const internalExpanded = ref(props.defaultIsExpanded);
2624
+ const isExpanded = computed(() => model.value ?? internalExpanded.value);
2625
+ const contentId = `astryx-chat-tool-calls-${useId()}`;
2626
+ const root = useTemplateRef("root");
2627
+ const summary = computed(() => props.label ?? messages.value.chat.toolGroup(props.calls.length));
2628
+ const latest = computed(() => props.calls[props.calls.length - 1]);
2629
+ const single = computed(() => props.calls.length === 1 ? props.calls[0] : void 0);
2630
+ function toggle() {
2631
+ const next = !isExpanded.value;
2632
+ internalExpanded.value = next;
2633
+ model.value = next;
2634
+ emit("expandedChange", next);
2635
+ }
2636
+ function rowKey(call, index) {
2637
+ return call.key ?? `${call.name}\u001F${call.target ?? ""}\u001F${call.node ?? ""}\u001F${index}`;
2638
+ }
2639
+ function expandable(call) {
2640
+ return Boolean(call.detail) || Boolean(slots.detail);
2641
+ }
2642
+ __expose({ element: root });
2643
+ return (_ctx, _cache) => {
2644
+ return __props.calls.length ? (openBlock(), createElementBlock("div", mergeProps({
2645
+ key: 0,
2646
+ ref_key: "root",
2647
+ ref: root
2648
+ }, _ctx.$attrs, { class: "astryx-chat-tool-calls" }), [single.value ? (openBlock(), createBlock(_sfc_main$3, {
2649
+ key: 0,
2650
+ call: single.value,
2651
+ expandable: expandable(single.value)
2652
+ }, createSlots({ _: 2 }, [expandable(single.value) ? {
2653
+ name: "detail",
2654
+ fn: withCtx(() => [renderSlot(_ctx.$slots, "detail", { call: single.value }, () => [createTextVNode(toDisplayString(single.value.detail), 1)])]),
2655
+ key: "0"
2656
+ } : void 0]), 1032, ["call", "expandable"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createElementVNode("button", {
2657
+ type: "button",
2658
+ class: "astryx-chat-tool-calls__row",
2659
+ "aria-expanded": isExpanded.value,
2660
+ "aria-controls": contentId,
2661
+ onClick: toggle
2662
+ }, [
2663
+ isExpanded.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_2$1, [createVNode(_sfc_main$9, {
2664
+ name: "wrench",
2665
+ size: "sm"
2666
+ })]), createElementVNode("span", _hoisted_3, toDisplayString(summary.value), 1)], 64)) : latest.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
2667
+ createVNode(_sfc_main$4, {
2668
+ status: latest.value.status ?? "complete",
2669
+ "error-message": latest.value.errorMessage
2670
+ }, null, 8, ["status", "error-message"]),
2671
+ createElementVNode("span", _hoisted_4, toDisplayString(latest.value.name), 1),
2672
+ latest.value.target ? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(latest.value.target), 1)) : createCommentVNode("v-if", true)
2673
+ ], 64)) : createCommentVNode("v-if", true),
2674
+ createElementVNode("span", _hoisted_6, [!isExpanded.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createVNode(_sfc_main$9, {
2675
+ name: "wrench",
2676
+ size: "xsm"
2677
+ }), createTextVNode(" " + toDisplayString(__props.calls.length), 1)], 64)) : createCommentVNode("v-if", true)]),
2678
+ createElementVNode("span", {
2679
+ class: "astryx-chat-tool-calls__chevron",
2680
+ "data-open": isExpanded.value || void 0
2681
+ }, [createVNode(_sfc_main$27, {
2682
+ icon: "chevronDown",
2683
+ size: "xsm",
2684
+ color: "inherit"
2685
+ })], 8, _hoisted_7)
2686
+ ], 8, _hoisted_1$2), createElementVNode("div", {
2687
+ id: contentId,
2688
+ class: "astryx-chat-tool-calls__panel",
2689
+ "data-open": isExpanded.value || void 0,
2690
+ inert: !isExpanded.value
2691
+ }, [createElementVNode("div", _hoisted_9, [createElementVNode("div", _hoisted_10, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.calls, (call, index) => {
2692
+ return openBlock(), createBlock(_sfc_main$3, {
2693
+ key: rowKey(call, index),
2694
+ call,
2695
+ expandable: expandable(call)
2696
+ }, createSlots({ _: 2 }, [expandable(call) ? {
2697
+ name: "detail",
2698
+ fn: withCtx(() => [renderSlot(_ctx.$slots, "detail", { call }, () => [createTextVNode(toDisplayString(call.detail), 1)])]),
2699
+ key: "0"
2700
+ } : void 0]), 1032, ["call", "expandable"]);
2701
+ }), 128))])])], 8, _hoisted_8)], 64))], 16)) : createCommentVNode("v-if", true);
2702
+ };
2703
+ }
2704
+ });
2705
+ //#endregion
2706
+ //#region src/Chat/ChatTypingIndicator.vue
2707
+ const _hoisted_1$1 = {
2708
+ key: 0,
2709
+ class: "astryx-chat-typing__label"
2710
+ };
2711
+ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
2712
+ name: "AstryxChatTypingIndicator",
2713
+ inheritAttrs: false,
2714
+ __name: "ChatTypingIndicator",
2715
+ props: { names: {} },
2716
+ setup(__props, { expose: __expose }) {
2717
+ const props = __props;
2718
+ const messages = useAstryxMessages();
2719
+ const root = useTemplateRef("root");
2720
+ const label = computed(() => {
2721
+ const names = props.names?.filter((name) => name.trim() !== "") ?? [];
2722
+ const chat = messages.value.chat;
2723
+ if (names.length === 0) return null;
2724
+ if (names.length === 1) return chat.typingOne(names[0]);
2725
+ if (names.length === 2) return chat.typingTwo(names[0], names[1]);
2726
+ return chat.typingMany(names[0], names.length - 1);
2727
+ });
2728
+ __expose({ element: root });
2729
+ return (_ctx, _cache) => {
2730
+ return openBlock(), createElementBlock("div", mergeProps({
2731
+ ref_key: "root",
2732
+ ref: root
2733
+ }, _ctx.$attrs, {
2734
+ class: "astryx-chat-typing",
2735
+ role: "status",
2736
+ "aria-live": "polite"
2737
+ }), [_cache[0] || (_cache[0] = createElementVNode("span", {
2738
+ class: "astryx-chat-typing__dots",
2739
+ "aria-hidden": "true"
2740
+ }, [
2741
+ createElementVNode("span", { class: "astryx-chat-typing__dot" }),
2742
+ createElementVNode("span", {
2743
+ class: "astryx-chat-typing__dot",
2744
+ "data-index": "2"
2745
+ }),
2746
+ createElementVNode("span", {
2747
+ class: "astryx-chat-typing__dot",
2748
+ "data-index": "3"
2749
+ })
2750
+ ], -1)), label.value ? (openBlock(), createElementBlock("span", _hoisted_1$1, toDisplayString(label.value), 1)) : createCommentVNode("v-if", true)], 16);
2751
+ };
2752
+ }
2753
+ });
2754
+ //#endregion
2755
+ //#region src/Chat/ChatUnreadDivider.vue
2756
+ const _hoisted_1 = ["aria-label"];
2757
+ const _hoisted_2 = { class: "astryx-chat-unread__label" };
2758
+ const _sfc_main = /*@__PURE__*/ defineComponent({
2759
+ name: "AstryxChatUnreadDivider",
2760
+ inheritAttrs: false,
2761
+ __name: "ChatUnreadDivider",
2762
+ props: { label: {} },
2763
+ setup(__props, { expose: __expose }) {
2764
+ const props = __props;
2765
+ const messages = useAstryxMessages();
2766
+ const root = useTemplateRef("root");
2767
+ const text = computed(() => props.label ?? messages.value.chat.unreadLabel);
2768
+ const accessibleName = computed(() => messages.value.chat.unreadBelow(text.value));
2769
+ __expose({ element: root });
2770
+ return (_ctx, _cache) => {
2771
+ return openBlock(), createElementBlock("div", mergeProps({
2772
+ ref_key: "root",
2773
+ ref: root
2774
+ }, _ctx.$attrs, {
2775
+ class: "astryx-chat-unread",
2776
+ role: "separator",
2777
+ "aria-label": accessibleName.value
2778
+ }), [_cache[0] || (_cache[0] = createElementVNode("span", { class: "astryx-chat-unread__line" }, null, -1)), createElementVNode("span", _hoisted_2, toDisplayString(text.value), 1)], 16, _hoisted_1);
2779
+ };
2780
+ }
2781
+ });
2782
+ //#endregion
2783
+ //#region src/Chat/volumeAnalyser.ts
2784
+ const VOICE_SPLITS = [
2785
+ 3,
2786
+ 6,
2787
+ 11,
2788
+ 18
2789
+ ];
2790
+ const CALIBRATION_FRAMES = 60;
2791
+ let sharedAudio = null;
2792
+ function getSharedAudioContext() {
2793
+ if (!sharedAudio || sharedAudio.state === "closed") sharedAudio = new AudioContext();
2794
+ if (sharedAudio.state === "suspended") sharedAudio.resume();
2795
+ return sharedAudio;
2796
+ }
2797
+ async function createVolumeAnalyser(getContext) {
2798
+ try {
2799
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
2800
+ const audioContext = getContext();
2801
+ const source = audioContext.createMediaStreamSource(stream);
2802
+ const analyser = audioContext.createAnalyser();
2803
+ analyser.fftSize = 256;
2804
+ analyser.smoothingTimeConstant = .5;
2805
+ source.connect(analyser);
2806
+ const data = new Uint8Array(analyser.frequencyBinCount);
2807
+ const noiseFloor = new Float32Array(analyser.frequencyBinCount);
2808
+ let samples = 0;
2809
+ function calibrate() {
2810
+ analyser.getByteFrequencyData(data);
2811
+ samples += 1;
2812
+ for (let index = 0; index < data.length; index += 1) {
2813
+ const value = (data[index] ?? 0) / 255;
2814
+ if (value > (noiseFloor[index] ?? 0)) noiseFloor[index] = value;
2815
+ }
2816
+ }
2817
+ function cleanBin(index) {
2818
+ const raw = (data[index] ?? 0) / 255;
2819
+ if (samples < CALIBRATION_FRAMES) return 0;
2820
+ return Math.max(0, raw - (noiseFloor[index] ?? 0) * 1.1);
2821
+ }
2822
+ function splitsFor(count) {
2823
+ const voice = [...VOICE_SPLITS, data.length];
2824
+ return count <= voice.length ? voice.slice(0, count) : voice;
2825
+ }
2826
+ function bandsFrom(count, read) {
2827
+ const bands = [];
2828
+ const splits = splitsFor(count);
2829
+ let start = 1;
2830
+ for (const end of splits) {
2831
+ let sum = 0;
2832
+ for (let index = start; index < end; index += 1) sum += read(index);
2833
+ bands.push(sum / (end - start));
2834
+ start = end;
2835
+ }
2836
+ return bands;
2837
+ }
2838
+ return {
2839
+ getVolume() {
2840
+ analyser.getByteFrequencyData(data);
2841
+ if (samples < CALIBRATION_FRAMES) calibrate();
2842
+ let sum = 0;
2843
+ for (let index = 0; index < data.length; index += 1) sum += cleanBin(index);
2844
+ return sum / data.length;
2845
+ },
2846
+ getBands(count) {
2847
+ analyser.getByteFrequencyData(data);
2848
+ if (samples < CALIBRATION_FRAMES) calibrate();
2849
+ return bandsFrom(count, cleanBin);
2850
+ },
2851
+ getRawBands(count) {
2852
+ analyser.getByteFrequencyData(data);
2853
+ return bandsFrom(count, (index) => (data[index] ?? 0) / 255);
2854
+ },
2855
+ cleanup() {
2856
+ source.disconnect();
2857
+ for (const track of stream.getTracks()) track.stop();
2858
+ }
2859
+ };
2860
+ } catch {
2861
+ return null;
2862
+ }
2863
+ }
2864
+ const isIOS = typeof navigator !== "undefined" && /iPad|iPhone|iPod/.test(navigator.userAgent);
2865
+ function playPlop(frequency, delay, getContext, volume = .25) {
2866
+ try {
2867
+ const context = getContext();
2868
+ const now = context.currentTime;
2869
+ const duration = frequency < 200 ? .18 : .06;
2870
+ const oscillator = context.createOscillator();
2871
+ const gain = context.createGain();
2872
+ oscillator.type = "sine";
2873
+ oscillator.frequency.setValueAtTime(frequency * 1.3, now + delay);
2874
+ oscillator.frequency.exponentialRampToValueAtTime(frequency, now + delay + .01);
2875
+ oscillator.frequency.exponentialRampToValueAtTime(frequency * .93, now + delay + duration);
2876
+ gain.gain.setValueAtTime(.001, now);
2877
+ gain.gain.setValueAtTime(volume, now + delay);
2878
+ gain.gain.exponentialRampToValueAtTime(volume * .2, now + delay + duration * .12);
2879
+ gain.gain.exponentialRampToValueAtTime(.001, now + delay + duration);
2880
+ oscillator.connect(gain);
2881
+ gain.connect(context.destination);
2882
+ oscillator.start(now + delay);
2883
+ oscillator.stop(now + delay + duration);
2884
+ } catch {}
2885
+ }
2886
+ function playDictationStart(getContext) {
2887
+ if (isIOS) return;
2888
+ playPlop(392, 0, getContext);
2889
+ playPlop(523, .07, getContext);
2890
+ }
2891
+ function playDictationStop(getContext) {
2892
+ if (isIOS) return;
2893
+ playPlop(523, 0, getContext);
2894
+ playPlop(392, .07, getContext);
2895
+ }
2896
+ //#endregion
2897
+ //#region src/Chat/useSpeechRecognition.ts
2898
+ const SILENT_BANDS = [
2899
+ 0,
2900
+ 0,
2901
+ 0,
2902
+ 0,
2903
+ 0
2904
+ ];
2905
+ function speechRecognitionCtor() {
2906
+ if (typeof window === "undefined") return null;
2907
+ const host = window;
2908
+ return host.SpeechRecognition ?? host.webkitSpeechRecognition ?? null;
2909
+ }
2910
+ /**
2911
+ * Browser speech recognition plus a microphone level.
2912
+ * No speech service is bundled: unsupported browsers report `isSupported` false.
2913
+ */
2914
+ function useSpeechRecognition(options = {}) {
2915
+ const locale = useAstryxLocale();
2916
+ const messages = useAstryxMessages();
2917
+ const continuous = options.continuous ?? true;
2918
+ const interimResults = options.interimResults ?? true;
2919
+ const trackVolume = options.trackVolume ?? true;
2920
+ const isSupported = computed(() => speechRecognitionCtor() != null);
2921
+ const isListening = ref(false);
2922
+ const isSpeaking = ref(false);
2923
+ const volume = ref(0);
2924
+ const bands = shallowRef([...SILENT_BANDS]);
2925
+ const rawBands = shallowRef([...SILENT_BANDS]);
2926
+ const interimTranscript = ref("");
2927
+ let recognition = null;
2928
+ let analyser = null;
2929
+ let frame = 0;
2930
+ const callbacks = {
2931
+ onTranscript: options.onTranscript,
2932
+ onResult: options.onResult,
2933
+ onError: options.onError,
2934
+ onStart: options.onStart,
2935
+ onEnd: options.onEnd,
2936
+ onVolumeSample: options.onVolumeSample,
2937
+ transformTranscript: options.transformTranscript
2938
+ };
2939
+ function stopVolume() {
2940
+ cancelAnimationFrame(frame);
2941
+ volume.value = 0;
2942
+ bands.value = [...SILENT_BANDS];
2943
+ rawBands.value = [...SILENT_BANDS];
2944
+ analyser?.cleanup();
2945
+ analyser = null;
2946
+ }
2947
+ function pollVolume() {
2948
+ const current = analyser;
2949
+ if (current) {
2950
+ const level = current.getVolume();
2951
+ volume.value = level;
2952
+ bands.value = current.getBands(5);
2953
+ rawBands.value = current.getRawBands(5);
2954
+ callbacks.onVolumeSample?.(level);
2955
+ }
2956
+ frame = requestAnimationFrame(pollVolume);
2957
+ }
2958
+ function getContext() {
2959
+ return options.audioContext ?? getSharedAudioContext();
2960
+ }
2961
+ function start() {
2962
+ const Ctor = speechRecognitionCtor();
2963
+ if (!Ctor) return;
2964
+ recognition?.abort();
2965
+ const next = new Ctor();
2966
+ next.lang = options.lang ?? locale.value;
2967
+ next.continuous = continuous;
2968
+ next.interimResults = interimResults;
2969
+ next.onstart = () => {
2970
+ isListening.value = true;
2971
+ callbacks.onStart?.();
2972
+ if (!trackVolume) return;
2973
+ createVolumeAnalyser(getContext).then((created) => {
2974
+ if (!created || recognition !== next) return;
2975
+ analyser = created;
2976
+ pollVolume();
2977
+ });
2978
+ };
2979
+ next.onend = () => {
2980
+ isListening.value = false;
2981
+ isSpeaking.value = false;
2982
+ interimTranscript.value = "";
2983
+ stopVolume();
2984
+ callbacks.onEnd?.();
2985
+ };
2986
+ next.onspeechstart = () => {
2987
+ isSpeaking.value = true;
2988
+ };
2989
+ next.onspeechend = () => {
2990
+ isSpeaking.value = false;
2991
+ };
2992
+ next.onresult = (event) => {
2993
+ let interim = "";
2994
+ for (let index = event.resultIndex; index < event.results.length; index += 1) {
2995
+ const result = event.results[index];
2996
+ if (!result) continue;
2997
+ let transcript = result[0].transcript;
2998
+ if (callbacks.transformTranscript) transcript = callbacks.transformTranscript(transcript);
2999
+ if (result.isFinal) {
3000
+ callbacks.onResult?.(transcript);
3001
+ callbacks.onTranscript?.(transcript, true);
3002
+ interimTranscript.value = "";
3003
+ } else interim += transcript;
3004
+ }
3005
+ if (interim) {
3006
+ interimTranscript.value = interim;
3007
+ callbacks.onTranscript?.(interim, false);
3008
+ }
3009
+ };
3010
+ next.onerror = (event) => {
3011
+ callbacks.onError?.(event);
3012
+ };
3013
+ next.onnomatch = () => {
3014
+ callbacks.onError?.({
3015
+ error: "no-speech",
3016
+ message: messages.value.chat.noSpeechDetected
3017
+ });
3018
+ };
3019
+ recognition = next;
3020
+ next.start();
3021
+ }
3022
+ function stop() {
3023
+ recognition?.stop();
3024
+ }
3025
+ function abort() {
3026
+ recognition?.abort();
3027
+ stopVolume();
3028
+ }
3029
+ function toggle() {
3030
+ if (isListening.value) stop();
3031
+ else start();
3032
+ }
3033
+ onScopeDispose(() => {
3034
+ recognition?.abort();
3035
+ recognition = null;
3036
+ stopVolume();
3037
+ });
3038
+ return {
3039
+ isSupported,
3040
+ isListening: computed(() => isListening.value),
3041
+ isSpeaking: computed(() => isSpeaking.value),
3042
+ volume: computed(() => volume.value),
3043
+ bands: computed(() => bands.value),
3044
+ rawBands: computed(() => rawBands.value),
3045
+ interimTranscript: computed(() => interimTranscript.value),
3046
+ start,
3047
+ stop,
3048
+ abort,
3049
+ toggle
3050
+ };
3051
+ }
3052
+ //#endregion
3053
+ //#region src/Chat/useChatDictation.ts
3054
+ const CAPS_VOLUME = .15;
3055
+ const CAPS_SAMPLES = 10;
3056
+ const VOLUME_HISTORY = 30;
3057
+ function composerEditable() {
3058
+ const active = document.activeElement;
3059
+ if (active instanceof HTMLElement && active.getAttribute("contenteditable") === "true") return active;
3060
+ return document.querySelector(".astryx-chat-composer-input [contenteditable=\"true\"], [role=\"textbox\"][contenteditable=\"true\"]");
3061
+ }
3062
+ function audioContextOf(explicit) {
3063
+ return explicit ?? getSharedAudioContext();
3064
+ }
3065
+ /**
3066
+ * Voice input for the composer.
3067
+ *
3068
+ * Uses the browser SpeechRecognition API. Sustained loud speech is written in
3069
+ * capitals. Pass `input` to drop interim text into ChatComposerInput.
3070
+ */
3071
+ function useChatDictation(options = {}) {
3072
+ const history = [];
3073
+ const interimSpan = ref(null);
3074
+ const hasSounds = options.hasSounds ?? false;
3075
+ function onVolumeSample(level) {
3076
+ history.push(level);
3077
+ if (history.length > VOLUME_HISTORY) history.shift();
3078
+ }
3079
+ function transformTranscript(text) {
3080
+ const rewritten = options.transformTranscript ? options.transformTranscript(text) : text;
3081
+ if ((history.length > 0 ? history.reduce((sum, value) => sum + value, 0) / history.length : 0) >= CAPS_VOLUME && history.length >= CAPS_SAMPLES) return rewritten.toUpperCase();
3082
+ return rewritten;
3083
+ }
3084
+ function removeInterim() {
3085
+ const span = interimSpan.value;
3086
+ if (span?.isConnected) span.remove();
3087
+ interimSpan.value = null;
3088
+ }
3089
+ function insertInterim() {
3090
+ const editable = composerEditable();
3091
+ if (!editable) return null;
3092
+ const span = document.createElement("span");
3093
+ span.setAttribute("data-astryx-dictation-interim", "");
3094
+ span.contentEditable = "false";
3095
+ span.className = "astryx-chat-composer-input__interim";
3096
+ editable.append(span);
3097
+ interimSpan.value = span;
3098
+ editable.dispatchEvent(new Event("input", { bubbles: true }));
3099
+ return span;
3100
+ }
3101
+ const speech = useSpeechRecognition({
3102
+ lang: options.lang,
3103
+ continuous: options.continuous,
3104
+ interimResults: options.interimResults,
3105
+ audioContext: options.audioContext,
3106
+ onError: options.onError,
3107
+ transformTranscript,
3108
+ onVolumeSample,
3109
+ onTranscript: (transcript, isFinal) => {
3110
+ if (options.input) {
3111
+ if (isFinal) {
3112
+ removeInterim();
3113
+ const handle = options.input.value;
3114
+ if (handle) {
3115
+ handle.focus();
3116
+ handle.insertText(`${transcript} `);
3117
+ }
3118
+ options.onResult?.(transcript);
3119
+ insertInterim();
3120
+ } else if (interimSpan.value) interimSpan.value.textContent = transcript;
3121
+ else {
3122
+ const span = insertInterim();
3123
+ if (span) span.textContent = transcript;
3124
+ }
3125
+ }
3126
+ options.onTranscript?.(transcript, isFinal);
3127
+ },
3128
+ onResult: options.input ? void 0 : options.onResult,
3129
+ onStart: () => {
3130
+ if (hasSounds) playDictationStart(() => audioContextOf(options.audioContext));
3131
+ if (options.input) insertInterim();
3132
+ options.onStart?.();
3133
+ },
3134
+ onEnd: () => {
3135
+ if (hasSounds) playDictationStop(() => audioContextOf(options.audioContext));
3136
+ if (options.input) {
3137
+ removeInterim();
3138
+ composerEditable()?.dispatchEvent(new Event("input", { bubbles: true }));
3139
+ }
3140
+ history.length = 0;
3141
+ options.onEnd?.();
3142
+ }
3143
+ });
3144
+ onScopeDispose(removeInterim);
3145
+ return speech;
3146
+ }
3147
+ //#endregion
3148
+ export { useChatLayoutContext as C, pastedTextLabel as D, PASTE_TOKEN_THRESHOLD as E, useChatPasteAsToken as O, useChatComposerContext as S, useChatMessageContext as T, _sfc_main$17 as _, _sfc_main$2 as a, _sfc_main$20 as b, _sfc_main$7 as c, _sfc_main$11 as d, _sfc_main$12 as f, _sfc_main$16 as g, _sfc_main$15 as h, _sfc_main$1 as i, _sfc_main$8 as l, _sfc_main$14 as m, useSpeechRecognition as n, _sfc_main$5 as o, _sfc_main$13 as p, _sfc_main as r, _sfc_main$6 as s, useChatDictation as t, _sfc_main$10 as u, _sfc_main$18 as v, useChatListContext as w, useChatComposerTokens as x, _sfc_main$19 as y };