@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.
- package/dist/AppShell/index.js +1 -1
- package/dist/{AppShell-EiGyPFbA.js → AppShell-Cb41gIAj.js} +2 -2
- package/dist/Avatar/index.d.ts +1 -1
- package/dist/Avatar/index.js +1 -1
- package/dist/Avatar-jmHInHwj.js +244 -0
- package/dist/AvatarGroup/index.d.ts +1 -1
- package/dist/AvatarGroup/index.js +1 -1
- package/dist/{AvatarGroup-CZcgAMjM.js → AvatarGroup-QyzSrpYA.js} +8 -7
- package/dist/Badge/index.d.ts +2 -1
- package/dist/Badge/index.js +1 -1
- package/dist/Badge-CjDQPF5g.js +30 -0
- package/dist/Banner/index.d.ts +1 -1
- package/dist/Breadcrumbs/index.d.ts +2 -2
- package/dist/Breadcrumbs/index.js +1 -1
- package/dist/{Breadcrumbs-C3YZFQ9w.js → Breadcrumbs-CTQ7fjDn.js} +45 -11
- package/dist/ButtonGroup/index.d.ts +1 -1
- package/dist/Calendar/index.d.ts +2 -2
- package/dist/Calendar/index.js +1 -1
- package/dist/Calendar-BFaI5AMv.js +352 -0
- package/dist/Card/index.d.ts +1 -1
- package/dist/Carousel/index.js +1 -1
- package/dist/{Carousel-BTtHkKea.js → Carousel-X0cuQkvg.js} +2 -2
- package/dist/Chat/index.d.ts +2 -2
- package/dist/Chat/index.js +2 -2
- package/dist/Chat-M0tiGrNT.js +3148 -0
- package/dist/CheckboxInput/index.d.ts +1 -1
- package/dist/CheckboxList/index.d.ts +1 -1
- package/dist/CheckboxList/index.js +1 -1
- package/dist/Citation/index.js +1 -1
- package/dist/{Citation-DijdbUUu.js → Citation-C1veUk5p.js} +2 -2
- package/dist/ClickableCard/index.d.ts +1 -1
- package/dist/Code/index.js +1 -1
- package/dist/{CodeBlock-CxTb6j9_.js → CodeBlock-DesTJAkm.js} +1 -1
- package/dist/Collapsible/index.js +1 -1
- package/dist/{Collapsible-9MC08X39.js → Collapsible-BestK35V.js} +1 -1
- package/dist/CommandPalette/index.js +1 -1
- package/dist/{CommandPalette-BD2iODF5.js → CommandPalette-BArt97V8.js} +1 -1
- package/dist/ComplexSelector/index.d.ts +1 -1
- package/dist/ComplexSelector/index.js +1 -1
- package/dist/{ComplexSelector-D9iXd5cH.js → ComplexSelector-CAoig1RK.js} +2 -2
- package/dist/ContextMenu/index.d.ts +2 -2
- package/dist/ContextMenu/index.js +1 -1
- package/dist/{ContextMenu-HS_WqYIg.js → ContextMenu-9Im6i43E.js} +101 -63
- package/dist/DateInput/index.d.ts +2 -2
- package/dist/DateInput/index.js +1 -1
- package/dist/DateInput-BLocxqC3.js +376 -0
- package/dist/DateRangeInput/index.d.ts +1 -1
- package/dist/DateRangeInput/index.js +1 -1
- package/dist/{DateRangeInput-1bhPGG2s.js → DateRangeInput-DwDy3eRo.js} +2 -2
- package/dist/DateTimeInput/index.d.ts +1 -1
- package/dist/DateTimeInput/index.js +1 -1
- package/dist/{DateTimeInput-SnPdgk6P.js → DateTimeInput-BYiaC9bn.js} +5 -3
- package/dist/DropdownMenu/index.d.ts +3 -2
- package/dist/DropdownMenu/index.js +3 -2
- package/dist/DropdownMenu-Ddfr-aEC.js +164 -0
- package/dist/{DropdownMenu-UJGych6v.js → DropdownMenu-Dh-WAHKb.js} +63 -16
- package/dist/FileInput/index.d.ts +1 -1
- package/dist/FileInput/index.js +1 -1
- package/dist/{FileInput-BQiwE-GB.js → FileInput-DLO6V12n.js} +2 -2
- package/dist/Icon/index.d.ts +4 -3
- package/dist/Icon/index.js +3 -2
- package/dist/Icon/plugin.d.ts +45 -0
- package/dist/Icon/plugin.js +177 -0
- package/dist/Icon-CJKjdJy-.js +198 -0
- package/dist/Lightbox/index.js +1 -1
- package/dist/{Lightbox-CshKoB0r.js → Lightbox-CdFCS_EA.js} +2 -2
- package/dist/Link/index.d.ts +1 -1
- package/dist/Link/index.js +1 -1
- package/dist/Link-DqdQZ37C.js +141 -0
- package/dist/Markdown/index.js +1 -1
- package/dist/{Markdown-D-Ua055V.js → Markdown-BqKloI3w.js} +3 -3
- package/dist/MetadataList/index.js +1 -1
- package/dist/{MetadataList-sYph_d8Q.js → MetadataList-CvZkmisf.js} +1 -1
- package/dist/MobileNav/index.js +1 -1
- package/dist/{MobileNavToggle-DD9D01aj.js → MobileNavToggle-Bwi0qXXM.js} +1 -1
- package/dist/MoreMenu/index.d.ts +1 -1
- package/dist/MoreMenu/index.js +1 -1
- package/dist/{MoreMenu-BVrK5kkk.js → MoreMenu-BwJaJrkB.js} +2 -2
- package/dist/MultiSelector/index.js +1 -1
- package/dist/{MultiSelector-DiYov9sF.js → MultiSelector-C-ETfFy_.js} +2 -2
- package/dist/NumberInput/index.d.ts +2 -2
- package/dist/NumberInput/index.js +1 -1
- package/dist/NumberInput-DjetVB8O.js +210 -0
- package/dist/Outline/index.js +1 -1
- package/dist/{Outline-DQfEBxlc.js → Outline-Bz9c-u5y.js} +1 -1
- package/dist/OverflowList/index.d.ts +1 -1
- package/dist/OverflowList/index.js +1 -1
- package/dist/{OverflowList-BU5VCntl.js → OverflowList-ED-KN9Y5.js} +1 -1
- package/dist/Overlay/index.d.ts +1 -1
- package/dist/Pagination/index.d.ts +2 -2
- package/dist/Pagination/index.js +1 -1
- package/dist/{Pagination-HdhtykeF.js → Pagination-Cb66HMEf.js} +76 -22
- package/dist/PowerSearch/index.d.ts +2 -0
- package/dist/PowerSearch/index.js +2 -0
- package/dist/PowerSearch-CjU8y4mi.js +2386 -0
- package/dist/ProgressBar/index.d.ts +1 -1
- package/dist/ProgressBar/index.js +1 -1
- package/dist/{ProgressBar-ChDp-7oe.js → ProgressBar-DO9Chic6.js} +31 -11
- package/dist/Radio/index.d.ts +1 -1
- package/dist/RadioList/index.d.ts +1 -1
- package/dist/Resizable/index.d.ts +1 -1
- package/dist/Resizable/index.js +1 -1
- package/dist/{Resizable-BhSMfO5B.js → Resizable-D-TBCg0Y.js} +1 -1
- package/dist/ScrollableArea/index.d.ts +1 -1
- package/dist/SelectableCard/index.d.ts +1 -1
- package/dist/Selector/index.d.ts +2 -2
- package/dist/Selector/index.js +1 -1
- package/dist/Selector-DE2IEFML.js +468 -0
- package/dist/SideNav/index.d.ts +1 -1
- package/dist/SideNav/index.js +1 -1
- package/dist/{SideNav-B-Om6Bz3.js → SideNav-DonEfiAx.js} +17 -17
- package/dist/Stepper/index.js +1 -1
- package/dist/{Stepper-BZ_8SEZt.js → Stepper-D2ZIcUNx.js} +1 -1
- package/dist/Switch/index.d.ts +1 -1
- package/dist/TabList/index.d.ts +1 -1
- package/dist/TabList/index.js +1 -1
- package/dist/{TabList-Cnc2Hs1H.js → TabList-D3F-stq-.js} +10 -3
- package/dist/Table/index.d.ts +3 -3
- package/dist/Table/index.js +2 -2
- package/dist/Table-CrP2Uffq.js +1982 -0
- package/dist/TextArea/index.d.ts +1 -1
- package/dist/TextArea/index.js +1 -1
- package/dist/{TextArea-Cv9UVbzS.js → TextArea-CPwSytdO.js} +12 -6
- package/dist/TextInput/index.d.ts +1 -1
- package/dist/Thumbnail/index.js +1 -1
- package/dist/{Thumbnail-Dc1UpykG.js → Thumbnail-BC5JGPGZ.js} +11 -11
- package/dist/{TimeField-C7t9Knq9.js → TimeField-DpHFrNv0.js} +2 -2
- package/dist/TimeInput/index.d.ts +1 -1
- package/dist/TimeInput/index.js +1 -1
- package/dist/{TimeInput-BXxCOY98.js → TimeInput-DJLV0_4z.js} +1 -1
- package/dist/Timestamp/index.d.ts +1 -1
- package/dist/Timestamp/index.js +2 -1
- package/dist/{Timestamp-DlZSmgCJ.js → Timestamp-GKNjjt5g.js} +4 -121
- package/dist/Toast/index.d.ts +2 -2
- package/dist/Toast/index.js +1 -1
- package/dist/{Toast-BKXPvEMp.js → Toast-C2ML-bmi.js} +37 -7
- package/dist/ToggleButton/index.d.ts +1 -1
- package/dist/Token/index.js +1 -1
- package/dist/{Token-wt4e80IM.js → Token-Chu_eCVs.js} +1 -1
- package/dist/Tokenizer/index.js +1 -1
- package/dist/{Tokenizer-nmTqdS9n.js → Tokenizer-DRDpvQ_l.js} +3 -3
- package/dist/Toolbar/index.d.ts +1 -1
- package/dist/TopNav/index.d.ts +1 -1
- package/dist/TopNav/index.js +1 -1
- package/dist/{TopNav-notp-mJx.js → TopNav-CEyWddtA.js} +3 -3
- package/dist/TreeList/index.js +1 -1
- package/dist/{TreeList-s1xBQ0qq.js → TreeList-COS8US8e.js} +3 -3
- package/dist/Typeahead/index.js +1 -1
- package/dist/{Typeahead-VAuiCurV.js → Typeahead-P7cCqtaQ.js} +3 -3
- package/dist/{useTypeaheadMenu-Ba7XpcZl.js → TypeaheadMenu-CGvo0Tud.js} +107 -107
- package/dist/composables/index.d.ts +3 -3
- package/dist/{context-Dbo22sck.js → context-CiKncNSH.js} +184 -6
- package/dist/formatInstant-BDWUIxS7.js +119 -0
- package/dist/{index-SMh42NI8.d.ts → index-B1TG2GPS.d.ts} +2 -2
- package/dist/{index-DRmb-oUa.d.ts → index-B9WzWqzk.d.ts} +3 -29
- package/dist/{index-BuubzuE8.d.ts → index-BDZCJ9ri.d.ts} +2 -2
- package/dist/{index-z4p2k9VR.d.ts → index-Bgv4ZmBl.d.ts} +20 -2
- package/dist/{index-BoRIw-bP.d.ts → index-Bt7ewoz2.d.ts} +32 -2
- package/dist/{index-uQrLBZfF.d.ts → index-Bu3oXIIS.d.ts} +24 -0
- package/dist/index-CJlBAXwj.d.ts +100 -0
- package/dist/{index-DT9S92sh.d.ts → index-CNjbOgty.d.ts} +15 -0
- package/dist/{index-DXSs3rwa.d.ts → index-CngORuiq.d.ts} +137 -2
- package/dist/index-Cs5pcwY3.d.ts +416 -0
- package/dist/{index-NzLDrOkk.d.ts → index-D4I_obqR.d.ts} +14 -1
- package/dist/{index-BtPZKO20.d.ts → index-DGXviOlp.d.ts} +49 -7
- package/dist/{index-CTolyeii.d.ts → index-DN7MXPHM.d.ts} +15 -1
- package/dist/{index-C6iJwTL0.d.ts → index-DNRBbwp_.d.ts} +165 -55
- package/dist/{index-C59PdX9L.d.ts → index-DWH_HzXi.d.ts} +1 -1
- package/dist/{index-BfOtLyNH.d.ts → index-D_nYxnNv.d.ts} +1 -1
- package/dist/{index-Bn_odHQ9.d.ts → index-Dg3ZmwEg.d.ts} +12 -1
- package/dist/{index-BiMGXtD-.d.ts → index-DimuSES0.d.ts} +14 -1
- package/dist/{index-DEeJowZf.d.ts → index-cGagYM1g.d.ts} +15 -2
- package/dist/{index-Ck8qy6Rf.d.ts → index-c_TaW8mM.d.ts} +24 -2
- package/dist/index-kVhSuA9t.d.ts +902 -0
- package/dist/{index-1uRMrk63.d.ts → index-nCEmW6wa.d.ts} +1 -1
- package/dist/{index-DoFCm2Q2.d.ts → index-qiZtHnGK.d.ts} +26 -0
- package/dist/{index-Bainr_3n.d.ts → index-u5ozu0G7.d.ts} +2 -2
- package/dist/index.d.ts +79 -76
- package/dist/index.js +111 -100
- package/dist/locale/index.d.ts +1 -1
- package/dist/locale/index.js +1 -1
- package/dist/power-search-DrDfcoL1.d.ts +68 -0
- package/dist/registry-B1ObuP--.d.ts +45 -0
- package/dist/registry-C7OZb0PJ.js +135 -0
- package/dist/{types-JktRxujg.d.ts → types-Cp4liug-.d.ts} +66 -3
- package/dist/{index-B8MbfMwV.d.ts → types-DDAAT1Xd.d.ts} +36 -82
- package/dist/types-DIYwbkpe.d.ts +33 -0
- package/dist/{types-DZoEm_DG.d.ts → types-pnljWjEE.d.ts} +23 -6
- package/dist/{useResizable-CeESZS1v.d.ts → useResizable-CUoNPmUw.d.ts} +1 -1
- package/package.json +10 -1
- package/styles/avatar.css +14 -0
- package/styles/badge.css +8 -0
- package/styles/breadcrumbs.css +6 -1
- package/styles/button.css +13 -0
- package/styles/calendar.css +16 -0
- package/styles/card.css +2 -0
- package/styles/chat.css +474 -3
- package/styles/dropdown-menu.css +11 -0
- package/styles/icon.css +58 -8
- package/styles/link.css +5 -0
- package/styles/number-input.css +22 -0
- package/styles/pagination.css +45 -1
- package/styles/power-search.css +303 -0
- package/styles/progress-bar.css +4 -0
- package/styles/selector.css +58 -0
- package/styles/tab-list.css +15 -0
- package/styles/table.css +304 -1
- package/styles/text-area.css +17 -0
- package/styles/text-input.css +5 -0
- package/styles/toast.css +7 -0
- package/styles/toolbar.css +11 -0
- package/styles.css +1 -0
- package/dist/Avatar-5s3HSrtN.js +0 -136
- package/dist/Badge-Ba0EwsTj.js +0 -19
- package/dist/Calendar-BG7wEsro.js +0 -277
- package/dist/Chat-DjNxw5gv.js +0 -1051
- package/dist/DateInput-DE2TN1Rq.js +0 -250
- package/dist/Icon-Dfr4k6-S.js +0 -86
- package/dist/Link-CprjOuTI.js +0 -92
- package/dist/NumberInput-CTYiQxHq.js +0 -147
- package/dist/Selector-Da-2BO3q.js +0 -325
- package/dist/Table-D-LhAy_J.js +0 -967
- package/dist/index-DJo-BmXZ.d.ts +0 -431
- package/dist/index-DtjAdYEc.d.ts +0 -68
- 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 };
|