@anvia/react-ui 1.3.0 → 1.3.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/attachment/index.d.ts +3 -21
- package/dist/attachment/index.js +3 -12
- package/dist/attachment-By2pged3.js +91 -0
- package/dist/attachment-By2pged3.js.map +1 -0
- package/dist/chat/index.d.ts +3 -152
- package/dist/chat/index.js +3 -25
- package/dist/chat-Dgz8hsVT.js +1210 -0
- package/dist/chat-Dgz8hsVT.js.map +1 -0
- package/dist/completion/index.d.ts +3 -29
- package/dist/completion/index.js +3 -16
- package/dist/completion-DDTKoZRJ.js +124 -0
- package/dist/completion-DDTKoZRJ.js.map +1 -0
- package/dist/connection-DeNq8wHS.js +203 -0
- package/dist/connection-DeNq8wHS.js.map +1 -0
- package/dist/contexts-B4ijG24z.js +212 -0
- package/dist/contexts-B4ijG24z.js.map +1 -0
- package/dist/elements-D4vmkkF8.js +19 -0
- package/dist/elements-D4vmkkF8.js.map +1 -0
- package/dist/graph-explorer/index.d.ts +54 -73
- package/dist/graph-explorer/index.d.ts.map +1 -0
- package/dist/graph-explorer/index.js +181 -248
- package/dist/graph-explorer/index.js.map +1 -1
- package/dist/human-input/index.d.ts +3 -78
- package/dist/human-input/index.js +3 -21
- package/dist/human-input-CsCbZomZ.js +314 -0
- package/dist/human-input-CsCbZomZ.js.map +1 -0
- package/dist/image/index.d.ts +3 -40
- package/dist/image/index.js +3 -12
- package/dist/image-aCWm6hFe.js +303 -0
- package/dist/image-aCWm6hFe.js.map +1 -0
- package/dist/index-B1Qs2Cjv.d.ts +145 -0
- package/dist/index-B1Qs2Cjv.d.ts.map +1 -0
- package/dist/index-BCBVcl0D.d.ts +277 -0
- package/dist/index-BCBVcl0D.d.ts.map +1 -0
- package/dist/index-CE22tsua.d.ts +20 -0
- package/dist/index-CE22tsua.d.ts.map +1 -0
- package/dist/index-CGqtpc6b.d.ts +25 -0
- package/dist/index-CGqtpc6b.d.ts.map +1 -0
- package/dist/index-DqdyM0Jm.d.ts +7 -0
- package/dist/index-DqdyM0Jm.d.ts.map +1 -0
- package/dist/index-Mw3ltwSG.d.ts +318 -0
- package/dist/index-Mw3ltwSG.d.ts.map +1 -0
- package/dist/index.d.ts +7 -40
- package/dist/index.js +34 -118
- package/dist/index.js.map +1 -1
- package/dist/message/index.d.ts +3 -105
- package/dist/message/index.js +3 -20
- package/dist/message-CElFEKgL.js +783 -0
- package/dist/message-CElFEKgL.js.map +1 -0
- package/dist/primitives-xfE7zfwJ.js +15 -0
- package/dist/primitives-xfE7zfwJ.js.map +1 -0
- package/dist/refs-Djj9rSvB.js +10 -0
- package/dist/refs-Djj9rSvB.js.map +1 -0
- package/dist/sandbox-desktop/index.d.ts +100 -104
- package/dist/sandbox-desktop/index.d.ts.map +1 -0
- package/dist/sandbox-desktop/index.js +344 -342
- package/dist/sandbox-desktop/index.js.map +1 -1
- package/dist/sandbox-terminal/index.d.ts +52 -56
- package/dist/sandbox-terminal/index.d.ts.map +1 -0
- package/dist/sandbox-terminal/index.js +217 -208
- package/dist/sandbox-terminal/index.js.map +1 -1
- package/dist/selection-toolbar/index.d.ts +3 -21
- package/dist/selection-toolbar/index.js +3 -13
- package/dist/selection-toolbar-CIkOoM-7.js +171 -0
- package/dist/selection-toolbar-CIkOoM-7.js.map +1 -0
- package/dist/shared-BSYElFU2.d.ts +2 -0
- package/dist/shared.d.ts +4 -14
- package/dist/shared.js +1 -43
- package/dist/stream/index.d.ts +2 -22
- package/dist/stream/index.js +1 -8
- package/dist/stream-CAO0x8_1.js +298 -0
- package/dist/stream-CAO0x8_1.js.map +1 -0
- package/dist/thread-list/index.d.ts +3 -47
- package/dist/thread-list/index.js +3 -18
- package/dist/thread-list-xJbGXFrE.js +228 -0
- package/dist/thread-list-xJbGXFrE.js.map +1 -0
- package/dist/ticket-CoJHBpdF.d.ts +32 -0
- package/dist/ticket-CoJHBpdF.d.ts.map +1 -0
- package/dist/url-CiYZkDks.js +52 -0
- package/dist/url-CiYZkDks.js.map +1 -0
- package/dist/value-BGGrx72L.js +13 -0
- package/dist/value-BGGrx72L.js.map +1 -0
- package/package.json +22 -22
- package/dist/attachment/index.js.map +0 -1
- package/dist/attachment-mbjsroli.d.ts +0 -9
- package/dist/chat/index.js.map +0 -1
- package/dist/chat-PSj1Fo4W.d.ts +0 -88
- package/dist/chunk-22EZJUPM.js +0 -120
- package/dist/chunk-22EZJUPM.js.map +0 -1
- package/dist/chunk-5FZKTDZR.js +0 -201
- package/dist/chunk-5FZKTDZR.js.map +0 -1
- package/dist/chunk-6XOJFLX4.js +0 -22
- package/dist/chunk-6XOJFLX4.js.map +0 -1
- package/dist/chunk-CKA4CE26.js +0 -25
- package/dist/chunk-CKA4CE26.js.map +0 -1
- package/dist/chunk-DLBNFBXQ.js +0 -425
- package/dist/chunk-DLBNFBXQ.js.map +0 -1
- package/dist/chunk-E73S3SOF.js +0 -404
- package/dist/chunk-E73S3SOF.js.map +0 -1
- package/dist/chunk-F6AKBUO3.js +0 -380
- package/dist/chunk-F6AKBUO3.js.map +0 -1
- package/dist/chunk-HGOPGZNQ.js +0 -195
- package/dist/chunk-HGOPGZNQ.js.map +0 -1
- package/dist/chunk-KHB5CHAU.js +0 -220
- package/dist/chunk-KHB5CHAU.js.map +0 -1
- package/dist/chunk-LHABOAOP.js +0 -316
- package/dist/chunk-LHABOAOP.js.map +0 -1
- package/dist/chunk-N5XQU36W.js +0 -410
- package/dist/chunk-N5XQU36W.js.map +0 -1
- package/dist/chunk-T3ZFZ3NP.js +0 -30
- package/dist/chunk-T3ZFZ3NP.js.map +0 -1
- package/dist/chunk-T4IYUEPH.js +0 -1612
- package/dist/chunk-T4IYUEPH.js.map +0 -1
- package/dist/chunk-W2AHKMRS.js +0 -1092
- package/dist/chunk-W2AHKMRS.js.map +0 -1
- package/dist/completion/index.js.map +0 -1
- package/dist/completion-input-DHlwrhxB.d.ts +0 -24
- package/dist/composer-6iCSIq6L.d.ts +0 -111
- package/dist/human-input/index.js.map +0 -1
- package/dist/human-input-CkLrZ8IC.d.ts +0 -27
- package/dist/image/index.js.map +0 -1
- package/dist/image-VZv6Ddsn.d.ts +0 -14
- package/dist/message/index.js.map +0 -1
- package/dist/message-CNRnZQwU.d.ts +0 -14
- package/dist/render-CRKWam2v.d.ts +0 -8
- package/dist/selection-toolbar/index.js.map +0 -1
- package/dist/selection-toolbar-Cwjbuv5S.d.ts +0 -14
- package/dist/shared.js.map +0 -1
- package/dist/stream/index.js.map +0 -1
- package/dist/thread-Bz6-1zaJ.d.ts +0 -12
- package/dist/thread-list/index.js.map +0 -1
- package/dist/thread-list-On8db4XL.d.ts +0 -34
- package/dist/ticket-pexI1zIa.d.ts +0 -33
|
@@ -0,0 +1,1210 @@
|
|
|
1
|
+
import { I as useChatContext, R as InternalAttachmentProvider, T as useComposer, c as useThread, f as InternalMessageProvider, s as InternalThreadProvider, w as InternalComposerProvider } from "./contexts-B4ijG24z.js";
|
|
2
|
+
import { t as composeRefs } from "./refs-Djj9rSvB.js";
|
|
3
|
+
import { t as AttachmentPrimitive } from "./attachment-By2pged3.js";
|
|
4
|
+
import { n as shiftComposerEntityRanges, r as isComposerEntityData, t as MessagePrimitive } from "./message-CElFEKgL.js";
|
|
5
|
+
import { t as renderPrimitive } from "./primitives-xfE7zfwJ.js";
|
|
6
|
+
import { Fragment, forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { mergeAttributes } from "@tiptap/core";
|
|
9
|
+
import Document from "@tiptap/extension-document";
|
|
10
|
+
import HardBreak from "@tiptap/extension-hard-break";
|
|
11
|
+
import Mention from "@tiptap/extension-mention";
|
|
12
|
+
import Paragraph from "@tiptap/extension-paragraph";
|
|
13
|
+
import Placeholder from "@tiptap/extension-placeholder";
|
|
14
|
+
import Text from "@tiptap/extension-text";
|
|
15
|
+
import { PluginKey } from "@tiptap/pm/state";
|
|
16
|
+
import { EditorContent, NodeViewWrapper, ReactNodeViewRenderer, useEditor } from "@tiptap/react";
|
|
17
|
+
// src/chat/composer.tsx
|
|
18
|
+
const ComposerRoot = forwardRef(function ComposerRoot({ attachments: attachmentsProp, defaultAttachments, defaultEntities, defaultInput = "", defaultQuote, entities: entitiesProp, input: inputProp, onAttachmentsChange, onEntitiesChange, onInputChange, onQuoteChange, quote: quoteProp, submitMessage, triggers: triggersProp, onSubmit, ...props }, ref) {
|
|
19
|
+
const chat = useChatContext();
|
|
20
|
+
const [uncontrolledInput, setUncontrolledInput] = useState(defaultInput);
|
|
21
|
+
const [uncontrolledAttachments, setUncontrolledAttachments] = useState(() => [...defaultAttachments ?? []]);
|
|
22
|
+
const [uncontrolledEntities, setUncontrolledEntities] = useState(() => [...defaultEntities ?? []]);
|
|
23
|
+
const [uncontrolledQuote, setUncontrolledQuote] = useState(defaultQuote);
|
|
24
|
+
const [activeTrigger, setActiveTriggerState] = useState();
|
|
25
|
+
const input = inputProp ?? uncontrolledInput;
|
|
26
|
+
const attachments = attachmentsProp ?? uncontrolledAttachments;
|
|
27
|
+
const entities = entitiesProp ?? uncontrolledEntities;
|
|
28
|
+
const triggers = triggersProp ?? EMPTY_COMPOSER_TRIGGERS;
|
|
29
|
+
const rawQuote = quoteProp ?? uncontrolledQuote;
|
|
30
|
+
const rawQuoteMessageId = rawQuote?.messageId;
|
|
31
|
+
const rawQuoteText = rawQuote?.text;
|
|
32
|
+
const quote = useMemo(() => rawQuoteMessageId === void 0 || rawQuoteText === void 0 ? void 0 : {
|
|
33
|
+
messageId: rawQuoteMessageId,
|
|
34
|
+
text: rawQuoteText
|
|
35
|
+
}, [rawQuoteMessageId, rawQuoteText]);
|
|
36
|
+
const attachmentsRef = useRef(attachments);
|
|
37
|
+
attachmentsRef.current = attachments;
|
|
38
|
+
const entitiesRef = useRef(entities);
|
|
39
|
+
entitiesRef.current = entities;
|
|
40
|
+
const defaultSubmissionInFlightRef = useRef(false);
|
|
41
|
+
const inputControlled = inputProp !== void 0;
|
|
42
|
+
const attachmentsControlled = attachmentsProp !== void 0;
|
|
43
|
+
const entitiesControlled = entitiesProp !== void 0;
|
|
44
|
+
const quoteControlled = quoteProp !== void 0;
|
|
45
|
+
const hasMessageContent = input.trim().length > 0 || attachments.length > 0 || entities.length > 0 || quote !== void 0;
|
|
46
|
+
const active = chat.status === "submitted" || chat.status === "streaming";
|
|
47
|
+
const canSubmit = hasMessageContent && !active;
|
|
48
|
+
const canStop = active;
|
|
49
|
+
const setInput = useCallback((nextInput) => {
|
|
50
|
+
if (!inputControlled) setUncontrolledInput(nextInput);
|
|
51
|
+
onInputChange?.(nextInput);
|
|
52
|
+
}, [inputControlled, onInputChange]);
|
|
53
|
+
const setAttachments = useCallback((update) => {
|
|
54
|
+
const nextAttachments = typeof update === "function" ? update(attachmentsRef.current) : update;
|
|
55
|
+
attachmentsRef.current = nextAttachments;
|
|
56
|
+
if (!attachmentsControlled) setUncontrolledAttachments(nextAttachments);
|
|
57
|
+
onAttachmentsChange?.(nextAttachments);
|
|
58
|
+
}, [attachmentsControlled, onAttachmentsChange]);
|
|
59
|
+
const setEntities = useCallback((update) => {
|
|
60
|
+
const nextEntities = typeof update === "function" ? update(entitiesRef.current) : update;
|
|
61
|
+
entitiesRef.current = nextEntities;
|
|
62
|
+
if (!entitiesControlled) setUncontrolledEntities(nextEntities);
|
|
63
|
+
onEntitiesChange?.(nextEntities);
|
|
64
|
+
}, [entitiesControlled, onEntitiesChange]);
|
|
65
|
+
const setQuote = useCallback((nextQuote) => {
|
|
66
|
+
const normalizedQuote = normalizeQuote(nextQuote);
|
|
67
|
+
if (!quoteControlled) setUncontrolledQuote(normalizedQuote);
|
|
68
|
+
onQuoteChange?.(normalizedQuote);
|
|
69
|
+
}, [onQuoteChange, quoteControlled]);
|
|
70
|
+
const addAttachment = useCallback(async (attachment) => {
|
|
71
|
+
const normalized = isFileAttachmentInput(attachment) ? await attachmentFromFile(attachment) : createAttachment(attachment);
|
|
72
|
+
setAttachments((current) => [...current, normalized]);
|
|
73
|
+
}, [setAttachments]);
|
|
74
|
+
const removeAttachment = useCallback((id) => {
|
|
75
|
+
setAttachments((current) => current.filter((attachment) => attachment.id !== id));
|
|
76
|
+
}, [setAttachments]);
|
|
77
|
+
const clearAttachments = useCallback(() => {
|
|
78
|
+
setAttachments([]);
|
|
79
|
+
}, [setAttachments]);
|
|
80
|
+
const clearQuote = useCallback(() => {
|
|
81
|
+
setQuote(void 0);
|
|
82
|
+
}, [setQuote]);
|
|
83
|
+
const clear = useCallback(() => {
|
|
84
|
+
setInput("");
|
|
85
|
+
setAttachments([]);
|
|
86
|
+
setEntities([]);
|
|
87
|
+
setActiveTriggerState(void 0);
|
|
88
|
+
clearQuote();
|
|
89
|
+
}, [
|
|
90
|
+
clearQuote,
|
|
91
|
+
setAttachments,
|
|
92
|
+
setEntities,
|
|
93
|
+
setInput
|
|
94
|
+
]);
|
|
95
|
+
const submit = useCallback(async () => {
|
|
96
|
+
const prompt = input;
|
|
97
|
+
if (prompt.trim().length === 0 && attachments.length === 0 && entities.length === 0 && quote === void 0 || chat.status === "submitted" || chat.status === "streaming") return;
|
|
98
|
+
assertComposerEntitiesData(entities);
|
|
99
|
+
if (submitMessage !== void 0) {
|
|
100
|
+
await submitMessage({
|
|
101
|
+
input: prompt,
|
|
102
|
+
attachments,
|
|
103
|
+
entities,
|
|
104
|
+
chat,
|
|
105
|
+
quote,
|
|
106
|
+
clear
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (defaultSubmissionInFlightRef.current) return;
|
|
111
|
+
const submittedAttachments = [...attachments];
|
|
112
|
+
const submittedQuote = quote === void 0 ? void 0 : { ...quote };
|
|
113
|
+
const formattedPrompt = submittedQuote === void 0 ? {
|
|
114
|
+
text: prompt,
|
|
115
|
+
prefixLength: 0
|
|
116
|
+
} : promptWithQuote(prompt, submittedQuote);
|
|
117
|
+
const submittedEntities = shiftComposerEntityRanges([...entities], formattedPrompt.prefixLength);
|
|
118
|
+
const submittedText = formattedPrompt.text;
|
|
119
|
+
const metadata = composerSubmitMetadata(submittedQuote, submittedEntities);
|
|
120
|
+
let payload;
|
|
121
|
+
if (submittedAttachments.length === 0) payload = metadata === void 0 ? { text: submittedText } : {
|
|
122
|
+
text: submittedText,
|
|
123
|
+
metadata
|
|
124
|
+
};
|
|
125
|
+
else {
|
|
126
|
+
const attachmentPayload = {
|
|
127
|
+
text: submittedText,
|
|
128
|
+
attachments: submittedAttachments
|
|
129
|
+
};
|
|
130
|
+
if (metadata !== void 0) attachmentPayload.metadata = metadata;
|
|
131
|
+
payload = attachmentPayload;
|
|
132
|
+
}
|
|
133
|
+
defaultSubmissionInFlightRef.current = true;
|
|
134
|
+
try {
|
|
135
|
+
const submission = chat.sendMessage(payload);
|
|
136
|
+
clear();
|
|
137
|
+
await submission;
|
|
138
|
+
} finally {
|
|
139
|
+
defaultSubmissionInFlightRef.current = false;
|
|
140
|
+
}
|
|
141
|
+
}, [
|
|
142
|
+
attachments,
|
|
143
|
+
chat,
|
|
144
|
+
clear,
|
|
145
|
+
entities,
|
|
146
|
+
input,
|
|
147
|
+
quote,
|
|
148
|
+
submitMessage
|
|
149
|
+
]);
|
|
150
|
+
const setActiveTrigger = useCallback((update) => {
|
|
151
|
+
setActiveTriggerState(update);
|
|
152
|
+
}, []);
|
|
153
|
+
const value = useMemo(() => ({
|
|
154
|
+
input,
|
|
155
|
+
setInput,
|
|
156
|
+
attachments,
|
|
157
|
+
setAttachments,
|
|
158
|
+
addAttachment,
|
|
159
|
+
removeAttachment,
|
|
160
|
+
clearAttachments,
|
|
161
|
+
entities,
|
|
162
|
+
setEntities,
|
|
163
|
+
quote,
|
|
164
|
+
setQuote,
|
|
165
|
+
clearQuote,
|
|
166
|
+
triggers,
|
|
167
|
+
activeTrigger,
|
|
168
|
+
setActiveTrigger,
|
|
169
|
+
submit,
|
|
170
|
+
stop: chat.stop,
|
|
171
|
+
status: chat.status,
|
|
172
|
+
canSubmit,
|
|
173
|
+
canStop
|
|
174
|
+
}), [
|
|
175
|
+
addAttachment,
|
|
176
|
+
attachments,
|
|
177
|
+
activeTrigger,
|
|
178
|
+
canStop,
|
|
179
|
+
canSubmit,
|
|
180
|
+
chat.status,
|
|
181
|
+
chat.stop,
|
|
182
|
+
clearAttachments,
|
|
183
|
+
clearQuote,
|
|
184
|
+
entities,
|
|
185
|
+
input,
|
|
186
|
+
quote,
|
|
187
|
+
removeAttachment,
|
|
188
|
+
setAttachments,
|
|
189
|
+
setActiveTrigger,
|
|
190
|
+
setEntities,
|
|
191
|
+
setInput,
|
|
192
|
+
setQuote,
|
|
193
|
+
submit,
|
|
194
|
+
triggers
|
|
195
|
+
]);
|
|
196
|
+
const handleSubmit = useCallback((event) => {
|
|
197
|
+
onSubmit?.(event);
|
|
198
|
+
if (event.defaultPrevented) return;
|
|
199
|
+
event.preventDefault();
|
|
200
|
+
submit();
|
|
201
|
+
}, [onSubmit, submit]);
|
|
202
|
+
return /* @__PURE__ */ jsx(InternalComposerProvider, {
|
|
203
|
+
value,
|
|
204
|
+
children: renderPrimitive("form", {
|
|
205
|
+
...props,
|
|
206
|
+
onSubmit: handleSubmit,
|
|
207
|
+
"data-state": chat.status
|
|
208
|
+
}, ref)
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
const ComposerQuotePreview = forwardRef(function ComposerQuotePreview({ children, ...props }, ref) {
|
|
212
|
+
const quote = useComposer().quote;
|
|
213
|
+
if (quote === void 0) return null;
|
|
214
|
+
const renderedChildren = typeof children === "function" ? children(quote) : children ?? quote.text;
|
|
215
|
+
return renderPrimitive("blockquote", {
|
|
216
|
+
...props,
|
|
217
|
+
children: renderedChildren
|
|
218
|
+
}, ref);
|
|
219
|
+
});
|
|
220
|
+
const ComposerClearQuote = forwardRef(function ComposerClearQuote({ onClick, ...props }, ref) {
|
|
221
|
+
const composer = useComposer();
|
|
222
|
+
const disabled = props.disabled ?? composer.quote === void 0;
|
|
223
|
+
const handleClick = useCallback((event) => {
|
|
224
|
+
onClick?.(event);
|
|
225
|
+
if (event.defaultPrevented || disabled) return;
|
|
226
|
+
composer.clearQuote();
|
|
227
|
+
}, [
|
|
228
|
+
composer,
|
|
229
|
+
disabled,
|
|
230
|
+
onClick
|
|
231
|
+
]);
|
|
232
|
+
return renderPrimitive("button", {
|
|
233
|
+
...props,
|
|
234
|
+
children: props.children ?? "Clear quote",
|
|
235
|
+
disabled,
|
|
236
|
+
onClick: handleClick,
|
|
237
|
+
type: props.type ?? "button",
|
|
238
|
+
"data-state": disabled ? "disabled" : "enabled"
|
|
239
|
+
}, ref);
|
|
240
|
+
});
|
|
241
|
+
const ComposerInput = forwardRef(function ComposerInput({ autoResize = true, disabled, maxRows: maxRowsProp, minRows: minRowsProp, onKeyDown, placeholder, renderEntity, style, ...props }, ref) {
|
|
242
|
+
const composer = useComposer();
|
|
243
|
+
const composerRef = useRef(composer);
|
|
244
|
+
composerRef.current = composer;
|
|
245
|
+
const renderEntityRef = useRef(renderEntity);
|
|
246
|
+
renderEntityRef.current = renderEntity;
|
|
247
|
+
const customEntityView = renderEntity !== void 0;
|
|
248
|
+
const editorReadyRef = useRef(false);
|
|
249
|
+
const syncingFromEditorRef = useRef(false);
|
|
250
|
+
const triggers = composer.triggers;
|
|
251
|
+
const minRows = normalizeRows(minRowsProp ?? 1, 1);
|
|
252
|
+
const maxRows = normalizeRows(maxRowsProp ?? 6, minRows);
|
|
253
|
+
const extensions = useMemo(() => composerInputExtensions({
|
|
254
|
+
composerRef,
|
|
255
|
+
placeholder,
|
|
256
|
+
renderEntityRef: customEntityView ? renderEntityRef : void 0,
|
|
257
|
+
triggers
|
|
258
|
+
}), [
|
|
259
|
+
customEntityView,
|
|
260
|
+
placeholder,
|
|
261
|
+
triggers
|
|
262
|
+
]);
|
|
263
|
+
const editor = useEditor({
|
|
264
|
+
extensions,
|
|
265
|
+
content: plainTextToComposerContent(composer.input),
|
|
266
|
+
editable: composer.status !== "submitted" && composer.status !== "streaming",
|
|
267
|
+
editorProps: {
|
|
268
|
+
attributes: {
|
|
269
|
+
"aria-label": props["aria-label"] ?? "Message",
|
|
270
|
+
"aria-multiline": "true",
|
|
271
|
+
role: "textbox"
|
|
272
|
+
},
|
|
273
|
+
handleKeyDown: (_view, event) => submitComposerFromEditorKeyDown(event, composerRef)
|
|
274
|
+
},
|
|
275
|
+
onUpdate: ({ editor: updatedEditor }) => {
|
|
276
|
+
if (!editorReadyRef.current) return;
|
|
277
|
+
const snapshot = composerSnapshotFromEditor(updatedEditor, composerRef.current.entities, composerRef.current.triggers);
|
|
278
|
+
syncingFromEditorRef.current = true;
|
|
279
|
+
composerRef.current.setInput(snapshot.input);
|
|
280
|
+
composerRef.current.setEntities(snapshot.entities);
|
|
281
|
+
}
|
|
282
|
+
}, [extensions]);
|
|
283
|
+
const ariaDisabled = props["aria-disabled"] === true || props["aria-disabled"] === "true";
|
|
284
|
+
const composerDisabled = disabled === true || ariaDisabled || composer.status === "submitted" || composer.status === "streaming";
|
|
285
|
+
const inputStyle = useMemo(() => {
|
|
286
|
+
if (!autoResize) return style;
|
|
287
|
+
const rowsStyle = {
|
|
288
|
+
["--anvia-composer-min-rows"]: minRows,
|
|
289
|
+
["--anvia-composer-max-rows"]: maxRows
|
|
290
|
+
};
|
|
291
|
+
return style === void 0 ? rowsStyle : {
|
|
292
|
+
...rowsStyle,
|
|
293
|
+
...style
|
|
294
|
+
};
|
|
295
|
+
}, [
|
|
296
|
+
autoResize,
|
|
297
|
+
maxRows,
|
|
298
|
+
minRows,
|
|
299
|
+
style
|
|
300
|
+
]);
|
|
301
|
+
const handleKeyDown = useCallback((event) => {
|
|
302
|
+
onKeyDown?.(event);
|
|
303
|
+
if (event.defaultPrevented || event.key !== "Enter" || event.shiftKey || event.nativeEvent.isComposing) return;
|
|
304
|
+
event.preventDefault();
|
|
305
|
+
composerRef.current.submit();
|
|
306
|
+
}, [onKeyDown]);
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
if (editor === null) return;
|
|
309
|
+
editor.setEditable(!composerDisabled, false);
|
|
310
|
+
}, [composerDisabled, editor]);
|
|
311
|
+
useEffect(() => {
|
|
312
|
+
if (editor === null) return;
|
|
313
|
+
editor.commands.setContent(plainTextToComposerContent(composerRef.current.input), { emitUpdate: false });
|
|
314
|
+
editorReadyRef.current = true;
|
|
315
|
+
return () => {
|
|
316
|
+
editorReadyRef.current = false;
|
|
317
|
+
};
|
|
318
|
+
}, [editor]);
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
if (editor === null || !editorReadyRef.current) return;
|
|
321
|
+
if (syncingFromEditorRef.current) {
|
|
322
|
+
syncingFromEditorRef.current = false;
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (composerSnapshotFromEditor(editor, composer.entities, composer.triggers).input === composer.input) return;
|
|
326
|
+
editor.commands.setContent(plainTextToComposerContent(composer.input), { emitUpdate: false });
|
|
327
|
+
}, [composer, editor]);
|
|
328
|
+
return renderPrimitive("div", {
|
|
329
|
+
...props,
|
|
330
|
+
children: /* @__PURE__ */ jsx(EditorContent, { editor }),
|
|
331
|
+
"aria-disabled": composerDisabled ? true : props["aria-disabled"],
|
|
332
|
+
"data-role": "editor",
|
|
333
|
+
"data-state": composerDisabled ? "disabled" : "enabled",
|
|
334
|
+
onKeyDown: handleKeyDown,
|
|
335
|
+
style: inputStyle
|
|
336
|
+
}, ref);
|
|
337
|
+
});
|
|
338
|
+
const ComposerTextareaInput = forwardRef(function ComposerTextareaInput({ autoResize = true, maxRows: maxRowsProp, minRows: minRowsProp, onChange, onKeyDown, rows, ...props }, ref) {
|
|
339
|
+
const composer = useComposer();
|
|
340
|
+
const inputRef = useRef(null);
|
|
341
|
+
const wasAutoResizeRef = useRef(autoResize);
|
|
342
|
+
const composedInputRef = useMemo(() => composeRefs(inputRef, ref), [ref]);
|
|
343
|
+
const minRows = normalizeRows(minRowsProp ?? rows ?? 1, 1);
|
|
344
|
+
const maxRows = normalizeRows(maxRowsProp ?? 6, minRows);
|
|
345
|
+
useEffect(() => {
|
|
346
|
+
const wasAutoResize = wasAutoResizeRef.current;
|
|
347
|
+
wasAutoResizeRef.current = autoResize;
|
|
348
|
+
if (!autoResize) {
|
|
349
|
+
if (wasAutoResize) resetComposerInputSize(inputRef.current);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
resizeComposerInput(inputRef.current, {
|
|
353
|
+
maxRows,
|
|
354
|
+
minRows
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
const handleChange = useCallback((event) => {
|
|
358
|
+
onChange?.(event);
|
|
359
|
+
if (!event.defaultPrevented) {
|
|
360
|
+
composer.setInput(event.currentTarget.value);
|
|
361
|
+
composer.setEntities([]);
|
|
362
|
+
if (autoResize) resizeComposerInput(event.currentTarget, {
|
|
363
|
+
maxRows,
|
|
364
|
+
minRows
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}, [
|
|
368
|
+
autoResize,
|
|
369
|
+
composer,
|
|
370
|
+
maxRows,
|
|
371
|
+
minRows,
|
|
372
|
+
onChange
|
|
373
|
+
]);
|
|
374
|
+
const handleKeyDown = useCallback((event) => {
|
|
375
|
+
onKeyDown?.(event);
|
|
376
|
+
if (event.defaultPrevented || event.key !== "Enter" || event.shiftKey || event.nativeEvent.isComposing) return;
|
|
377
|
+
event.preventDefault();
|
|
378
|
+
composer.submit();
|
|
379
|
+
}, [composer, onKeyDown]);
|
|
380
|
+
return renderPrimitive("textarea", {
|
|
381
|
+
...props,
|
|
382
|
+
"aria-label": props["aria-label"] ?? "Message",
|
|
383
|
+
disabled: props.disabled ?? (composer.status === "submitted" || composer.status === "streaming"),
|
|
384
|
+
onChange: handleChange,
|
|
385
|
+
onKeyDown: handleKeyDown,
|
|
386
|
+
rows: autoResize ? minRows : rows,
|
|
387
|
+
value: composer.input
|
|
388
|
+
}, composedInputRef);
|
|
389
|
+
});
|
|
390
|
+
const COMPOSER_ENTITY_NODE = "composerEntity";
|
|
391
|
+
const EMPTY_COMPOSER_TRIGGERS = [];
|
|
392
|
+
const ComposerEntityExtension = Mention.extend({
|
|
393
|
+
name: COMPOSER_ENTITY_NODE,
|
|
394
|
+
addAttributes() {
|
|
395
|
+
return {
|
|
396
|
+
id: {
|
|
397
|
+
default: null,
|
|
398
|
+
parseHTML: (element) => element.getAttribute("data-id"),
|
|
399
|
+
renderHTML: (attributes) => attributes.id === void 0 || attributes.id === null ? {} : { "data-id": String(attributes.id) }
|
|
400
|
+
},
|
|
401
|
+
label: {
|
|
402
|
+
default: null,
|
|
403
|
+
parseHTML: (element) => element.getAttribute("data-label"),
|
|
404
|
+
renderHTML: (attributes) => attributes.label === void 0 || attributes.label === null ? {} : { "data-label": String(attributes.label) }
|
|
405
|
+
},
|
|
406
|
+
mentionSuggestionChar: {
|
|
407
|
+
default: "@",
|
|
408
|
+
parseHTML: (element) => element.getAttribute("data-mention-suggestion-char"),
|
|
409
|
+
renderHTML: (attributes) => ({ "data-mention-suggestion-char": attributes.mentionSuggestionChar })
|
|
410
|
+
},
|
|
411
|
+
triggerId: {
|
|
412
|
+
default: null,
|
|
413
|
+
parseHTML: (element) => element.getAttribute("data-trigger-id"),
|
|
414
|
+
renderHTML: (attributes) => attributes.triggerId === void 0 || attributes.triggerId === null ? {} : { "data-trigger-id": String(attributes.triggerId) }
|
|
415
|
+
},
|
|
416
|
+
text: {
|
|
417
|
+
default: null,
|
|
418
|
+
parseHTML: (element) => element.getAttribute("data-text"),
|
|
419
|
+
renderHTML: (attributes) => attributes.text === void 0 || attributes.text === null ? {} : { "data-text": String(attributes.text) }
|
|
420
|
+
},
|
|
421
|
+
data: {
|
|
422
|
+
default: void 0,
|
|
423
|
+
parseHTML: (element) => dataFromAttribute(element.getAttribute("data-entity-data")),
|
|
424
|
+
renderHTML: (attributes) => {
|
|
425
|
+
if (attributes.data === void 0) return {};
|
|
426
|
+
if (!isComposerEntityData(attributes.data)) throw new TypeError("Composer entity data must be a JSON value.");
|
|
427
|
+
return { "data-entity-data": JSON.stringify(attributes.data) };
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
function composerEntityView(renderEntityRef, composerRef) {
|
|
434
|
+
return function ComposerEntityView({ node }) {
|
|
435
|
+
const attrs = node.attrs;
|
|
436
|
+
const { range: _range, ...entity } = composerEntityFromAttrs(attrs, 0, composerRef.current.entities, composerRef.current.triggers);
|
|
437
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, {
|
|
438
|
+
as: "span",
|
|
439
|
+
children: renderEntityRef.current?.(entity) ?? entity.text
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function composerInputExtensions({ composerRef, placeholder, renderEntityRef, triggers }) {
|
|
444
|
+
const EntityExtension = renderEntityRef === void 0 ? ComposerEntityExtension : ComposerEntityExtension.extend({ addNodeView() {
|
|
445
|
+
return ReactNodeViewRenderer(composerEntityView(renderEntityRef, composerRef), { as: "span" });
|
|
446
|
+
} });
|
|
447
|
+
return [
|
|
448
|
+
Document,
|
|
449
|
+
Paragraph,
|
|
450
|
+
Text,
|
|
451
|
+
HardBreak,
|
|
452
|
+
Placeholder.configure(placeholder === void 0 ? {} : { placeholder }),
|
|
453
|
+
EntityExtension.configure({
|
|
454
|
+
HTMLAttributes: {},
|
|
455
|
+
deleteTriggerWithBackspace: true,
|
|
456
|
+
renderText: ({ node }) => composerEntityText(node.attrs),
|
|
457
|
+
renderHTML: ({ node, options }) => [
|
|
458
|
+
"span",
|
|
459
|
+
mergeAttributes(options.HTMLAttributes, {}),
|
|
460
|
+
composerEntityText(node.attrs)
|
|
461
|
+
],
|
|
462
|
+
suggestions: triggers.map((trigger) => composerSuggestion(trigger, composerRef))
|
|
463
|
+
})
|
|
464
|
+
];
|
|
465
|
+
}
|
|
466
|
+
function submitComposerFromEditorKeyDown(event, composerRef) {
|
|
467
|
+
if (event.defaultPrevented || event.key !== "Enter" || event.shiftKey || event.isComposing || composerRef.current.activeTrigger !== void 0) return false;
|
|
468
|
+
event.preventDefault();
|
|
469
|
+
composerRef.current.submit();
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
function composerSuggestion(trigger, composerRef) {
|
|
473
|
+
const suggestion = {
|
|
474
|
+
pluginKey: new PluginKey(`anvia-composer-trigger-${trigger.id}`),
|
|
475
|
+
char: trigger.char,
|
|
476
|
+
items: ({ query, signal }) => resolveTriggerItems(trigger, query, composerRef.current, signal),
|
|
477
|
+
command: ({ editor, range, props }) => {
|
|
478
|
+
const overrideSpace = editor.state.selection.$to.nodeAfter?.text?.startsWith(" ");
|
|
479
|
+
const insertRange = {
|
|
480
|
+
from: range.from,
|
|
481
|
+
to: overrideSpace ? range.to + 1 : range.to
|
|
482
|
+
};
|
|
483
|
+
editor.chain().focus().insertContentAt(insertRange, [{
|
|
484
|
+
type: COMPOSER_ENTITY_NODE,
|
|
485
|
+
attrs: props
|
|
486
|
+
}, {
|
|
487
|
+
type: "text",
|
|
488
|
+
text: " "
|
|
489
|
+
}]).run();
|
|
490
|
+
composerEditorOwnerDocument(editor)?.defaultView?.getSelection()?.collapseToEnd();
|
|
491
|
+
composerRef.current.setActiveTrigger(void 0);
|
|
492
|
+
},
|
|
493
|
+
render: () => ({
|
|
494
|
+
onStart: (props) => {
|
|
495
|
+
updateComposerTriggerState(trigger, props, composerRef);
|
|
496
|
+
},
|
|
497
|
+
onUpdate: (props) => {
|
|
498
|
+
updateComposerTriggerState(trigger, props, composerRef);
|
|
499
|
+
},
|
|
500
|
+
onExit: () => {
|
|
501
|
+
composerRef.current.setActiveTrigger((current) => current?.trigger.id === trigger.id ? void 0 : current);
|
|
502
|
+
},
|
|
503
|
+
onKeyDown: (props) => handleComposerTriggerKeyDown(trigger, props, composerRef)
|
|
504
|
+
})
|
|
505
|
+
};
|
|
506
|
+
if (trigger.allowSpaces !== void 0) suggestion.allowSpaces = trigger.allowSpaces;
|
|
507
|
+
if (trigger.allowedPrefixes !== void 0) suggestion.allowedPrefixes = trigger.allowedPrefixes;
|
|
508
|
+
if (trigger.startOfLine !== void 0) suggestion.startOfLine = trigger.startOfLine;
|
|
509
|
+
if (trigger.minQueryLength !== void 0) suggestion.minQueryLength = trigger.minQueryLength;
|
|
510
|
+
return suggestion;
|
|
511
|
+
}
|
|
512
|
+
function resolveTriggerItems(trigger, query, composer, signal) {
|
|
513
|
+
if (Array.isArray(trigger.items)) {
|
|
514
|
+
const normalizedQuery = query.toLowerCase();
|
|
515
|
+
return trigger.items.filter((item) => item.label.toLowerCase().includes(normalizedQuery));
|
|
516
|
+
}
|
|
517
|
+
return trigger.items({
|
|
518
|
+
trigger,
|
|
519
|
+
query,
|
|
520
|
+
input: composer.input,
|
|
521
|
+
entities: composer.entities,
|
|
522
|
+
signal
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
function composerEditorOwnerDocument(editor) {
|
|
526
|
+
const element = editor.options.element;
|
|
527
|
+
if (element === null || typeof element === "function") return;
|
|
528
|
+
return "mount" in element ? element.mount.ownerDocument : element.ownerDocument;
|
|
529
|
+
}
|
|
530
|
+
function updateComposerTriggerState(trigger, props, composerRef) {
|
|
531
|
+
const current = composerRef.current.activeTrigger;
|
|
532
|
+
const selectedIndex = current?.trigger.id === trigger.id ? clampIndex(current.selectedIndex, props.items.length) : 0;
|
|
533
|
+
const rect = rectFromSuggestion(props);
|
|
534
|
+
const state = {
|
|
535
|
+
trigger,
|
|
536
|
+
query: props.query,
|
|
537
|
+
items: props.items,
|
|
538
|
+
loading: props.loading,
|
|
539
|
+
selectedIndex,
|
|
540
|
+
selectItem: (item) => {
|
|
541
|
+
if (item.disabled) return;
|
|
542
|
+
props.command(triggerItemToEntityAttrs(trigger, item));
|
|
543
|
+
},
|
|
544
|
+
setSelectedIndex: (index) => {
|
|
545
|
+
composerRef.current.setActiveTrigger((active) => active?.trigger.id === trigger.id ? {
|
|
546
|
+
...active,
|
|
547
|
+
selectedIndex: clampIndex(index, active.items.length)
|
|
548
|
+
} : active);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
if (rect !== void 0) state.rect = rect;
|
|
552
|
+
composerRef.current.setActiveTrigger(state);
|
|
553
|
+
}
|
|
554
|
+
function handleComposerTriggerKeyDown(trigger, { event }, composerRef) {
|
|
555
|
+
const activeTrigger = composerRef.current.activeTrigger;
|
|
556
|
+
if (activeTrigger?.trigger.id !== trigger.id) return false;
|
|
557
|
+
if (event.key === "ArrowDown") {
|
|
558
|
+
event.preventDefault();
|
|
559
|
+
activeTrigger.setSelectedIndex(activeTrigger.selectedIndex + 1);
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
if (event.key === "ArrowUp") {
|
|
563
|
+
event.preventDefault();
|
|
564
|
+
activeTrigger.setSelectedIndex(activeTrigger.selectedIndex - 1);
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
if (event.key === "Enter" || event.key === "Tab") {
|
|
568
|
+
event.preventDefault();
|
|
569
|
+
const item = activeTrigger.items[activeTrigger.selectedIndex];
|
|
570
|
+
if (item !== void 0) activeTrigger.selectItem(item);
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
if (event.key === "Escape") {
|
|
574
|
+
composerRef.current.setActiveTrigger(void 0);
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
function triggerItemToEntityAttrs(trigger, item) {
|
|
580
|
+
if (item.data !== void 0 && !isComposerEntityData(item.data)) throw new TypeError("Composer trigger item data must be a JSON value.");
|
|
581
|
+
return {
|
|
582
|
+
id: item.id,
|
|
583
|
+
label: item.label,
|
|
584
|
+
mentionSuggestionChar: trigger.char,
|
|
585
|
+
triggerId: trigger.id,
|
|
586
|
+
text: triggerItemText(trigger, item),
|
|
587
|
+
data: item.data
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
function triggerItemText(trigger, item) {
|
|
591
|
+
return item.text ?? `${trigger.char}${item.label}`;
|
|
592
|
+
}
|
|
593
|
+
function rectFromSuggestion(props) {
|
|
594
|
+
const rect = props.clientRect?.();
|
|
595
|
+
if (rect === void 0 || rect === null) return;
|
|
596
|
+
return {
|
|
597
|
+
top: rect.top,
|
|
598
|
+
right: rect.right,
|
|
599
|
+
bottom: rect.bottom,
|
|
600
|
+
left: rect.left,
|
|
601
|
+
width: rect.width,
|
|
602
|
+
height: rect.height
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function clampIndex(index, length) {
|
|
606
|
+
if (length <= 0) return 0;
|
|
607
|
+
return (index + length) % length;
|
|
608
|
+
}
|
|
609
|
+
function plainTextToComposerContent(input) {
|
|
610
|
+
return {
|
|
611
|
+
type: "doc",
|
|
612
|
+
content: (input.length === 0 ? [""] : input.split(/\r?\n/)).map((line) => {
|
|
613
|
+
const paragraph = { type: "paragraph" };
|
|
614
|
+
if (line.length > 0) paragraph.content = [{
|
|
615
|
+
type: "text",
|
|
616
|
+
text: line
|
|
617
|
+
}];
|
|
618
|
+
return paragraph;
|
|
619
|
+
})
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
function composerSnapshotFromEditor(editor, previousEntities, triggers) {
|
|
623
|
+
return composerSnapshotFromContent(editor.getJSON(), previousEntities, triggers);
|
|
624
|
+
}
|
|
625
|
+
function composerSnapshotFromContent(content, previousEntities, triggers) {
|
|
626
|
+
const entities = [];
|
|
627
|
+
let input = "";
|
|
628
|
+
let blockIndex = 0;
|
|
629
|
+
const appendNode = (node) => {
|
|
630
|
+
if (node.type === "doc") {
|
|
631
|
+
for (const child of node.content ?? []) appendNode(child);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (node.type === "paragraph") {
|
|
635
|
+
if (blockIndex > 0) input += "\n";
|
|
636
|
+
blockIndex += 1;
|
|
637
|
+
for (const child of node.content ?? []) appendNode(child);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
if (node.type === "text") {
|
|
641
|
+
input += node.text ?? "";
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (node.type === "hardBreak") {
|
|
645
|
+
input += "\n";
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (node.type === COMPOSER_ENTITY_NODE) {
|
|
649
|
+
const attrs = node.attrs;
|
|
650
|
+
const entity = composerEntityFromAttrs(attrs, input.length, previousEntities, triggers);
|
|
651
|
+
input += entity.text;
|
|
652
|
+
entities.push(entity);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
for (const child of node.content ?? []) appendNode(child);
|
|
656
|
+
};
|
|
657
|
+
appendNode(content);
|
|
658
|
+
return {
|
|
659
|
+
input,
|
|
660
|
+
entities
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
function composerEntityFromAttrs(attrs, from, previousEntities, triggers) {
|
|
664
|
+
const id = stringAttr(attrs?.id) ?? "";
|
|
665
|
+
const trigger = stringAttr(attrs?.mentionSuggestionChar) ?? "@";
|
|
666
|
+
const triggerId = stringAttr(attrs?.triggerId) ?? triggers.find((candidate) => candidate.char === trigger)?.id ?? trigger;
|
|
667
|
+
const label = stringAttr(attrs?.label) ?? id;
|
|
668
|
+
const text = composerEntityText(attrs);
|
|
669
|
+
const previous = previousEntities.find((entity) => entity.id === id && entity.triggerId === triggerId && entity.trigger === trigger);
|
|
670
|
+
const data = attrs?.data === void 0 ? previous?.data : attrs.data;
|
|
671
|
+
const entity = {
|
|
672
|
+
id,
|
|
673
|
+
triggerId,
|
|
674
|
+
trigger,
|
|
675
|
+
label,
|
|
676
|
+
text,
|
|
677
|
+
range: {
|
|
678
|
+
from,
|
|
679
|
+
to: from + text.length
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
if (data !== void 0) entity.data = data;
|
|
683
|
+
return entity;
|
|
684
|
+
}
|
|
685
|
+
function composerEntityText(attrs) {
|
|
686
|
+
const text = stringAttr(attrs?.text);
|
|
687
|
+
if (text !== void 0) return text;
|
|
688
|
+
return `${stringAttr(attrs?.mentionSuggestionChar) ?? "@"}${stringAttr(attrs?.label) ?? stringAttr(attrs?.id) ?? ""}`;
|
|
689
|
+
}
|
|
690
|
+
function stringAttr(value) {
|
|
691
|
+
return typeof value === "string" ? value : void 0;
|
|
692
|
+
}
|
|
693
|
+
function dataFromAttribute(value) {
|
|
694
|
+
if (value === null) return;
|
|
695
|
+
try {
|
|
696
|
+
const parsed = JSON.parse(value);
|
|
697
|
+
return isComposerEntityData(parsed) ? parsed : void 0;
|
|
698
|
+
} catch {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function resizeComposerInput(input, { maxRows, minRows }) {
|
|
703
|
+
if (input === null) return;
|
|
704
|
+
const style = getComputedStyle(input);
|
|
705
|
+
const lineHeight = lineHeightFromStyle(style);
|
|
706
|
+
const paddingHeight = pixelValue(style.paddingTop) + pixelValue(style.paddingBottom);
|
|
707
|
+
const borderHeight = pixelValue(style.borderTopWidth) + pixelValue(style.borderBottomWidth);
|
|
708
|
+
const minContentHeight = lineHeight * minRows;
|
|
709
|
+
const maxContentHeight = Math.max(minContentHeight, lineHeight * maxRows);
|
|
710
|
+
input.style.height = "auto";
|
|
711
|
+
const scrollContentHeight = Math.max(input.scrollHeight - paddingHeight, 0);
|
|
712
|
+
const contentHeight = clamp(scrollContentHeight, minContentHeight, maxContentHeight);
|
|
713
|
+
const nextHeight = style.boxSizing === "border-box" ? contentHeight + paddingHeight + borderHeight : contentHeight;
|
|
714
|
+
input.style.height = `${Math.ceil(nextHeight)}px`;
|
|
715
|
+
input.style.overflowY = scrollContentHeight > maxContentHeight ? "auto" : "hidden";
|
|
716
|
+
}
|
|
717
|
+
function resetComposerInputSize(input) {
|
|
718
|
+
if (input === null) return;
|
|
719
|
+
input.style.height = "";
|
|
720
|
+
input.style.overflowY = "";
|
|
721
|
+
}
|
|
722
|
+
function normalizeRows(value, min) {
|
|
723
|
+
if (!Number.isFinite(value)) return min;
|
|
724
|
+
return Math.max(min, Math.floor(value));
|
|
725
|
+
}
|
|
726
|
+
function lineHeightFromStyle(style) {
|
|
727
|
+
return pixelValue(style.lineHeight) || (pixelValue(style.fontSize) || 16) * 1.2;
|
|
728
|
+
}
|
|
729
|
+
function pixelValue(value) {
|
|
730
|
+
const parsed = Number.parseFloat(value);
|
|
731
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
732
|
+
}
|
|
733
|
+
function clamp(value, min, max) {
|
|
734
|
+
return Math.min(Math.max(value, min), max);
|
|
735
|
+
}
|
|
736
|
+
function promptWithQuote(prompt, quote) {
|
|
737
|
+
const quotedText = quote.text.split(/\r?\n/).map((line) => `> ${line}`).join("\n");
|
|
738
|
+
if (prompt.trim().length === 0) return {
|
|
739
|
+
text: quotedText,
|
|
740
|
+
prefixLength: 0
|
|
741
|
+
};
|
|
742
|
+
const prefix = `${quotedText}\n\n`;
|
|
743
|
+
return {
|
|
744
|
+
text: `${prefix}${prompt}`,
|
|
745
|
+
prefixLength: prefix.length
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function normalizeQuote(quote) {
|
|
749
|
+
if (quote === void 0) return;
|
|
750
|
+
return {
|
|
751
|
+
text: quote.text,
|
|
752
|
+
messageId: quote.messageId
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function composerSubmitMetadata(quote, entities) {
|
|
756
|
+
if (quote === void 0 && entities.length === 0) return;
|
|
757
|
+
const metadata = {};
|
|
758
|
+
if (quote !== void 0) metadata.quote = quote;
|
|
759
|
+
if (entities.length > 0) metadata.composer = { entities: entities.map(composerEntityMetadata) };
|
|
760
|
+
return metadata;
|
|
761
|
+
}
|
|
762
|
+
function assertComposerEntitiesData(entities) {
|
|
763
|
+
for (const entity of entities) if (entity.data !== void 0 && !isComposerEntityData(entity.data)) throw new TypeError("Composer entity data must be a JSON value.");
|
|
764
|
+
}
|
|
765
|
+
function composerEntityMetadata(entity) {
|
|
766
|
+
const metadata = {
|
|
767
|
+
id: entity.id,
|
|
768
|
+
triggerId: entity.triggerId,
|
|
769
|
+
trigger: entity.trigger,
|
|
770
|
+
label: entity.label,
|
|
771
|
+
text: entity.text,
|
|
772
|
+
range: {
|
|
773
|
+
from: entity.range.from,
|
|
774
|
+
to: entity.range.to
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
if (entity.data !== void 0) metadata.data = entity.data;
|
|
778
|
+
return metadata;
|
|
779
|
+
}
|
|
780
|
+
const ComposerSubmit = forwardRef(function ComposerSubmit(props, ref) {
|
|
781
|
+
const composer = useComposer();
|
|
782
|
+
const disabled = props.disabled ?? !composer.canSubmit;
|
|
783
|
+
return renderPrimitive("button", {
|
|
784
|
+
...props,
|
|
785
|
+
children: props.children ?? "Send",
|
|
786
|
+
disabled,
|
|
787
|
+
type: props.type ?? "submit",
|
|
788
|
+
"data-state": disabled ? "disabled" : "enabled"
|
|
789
|
+
}, ref);
|
|
790
|
+
});
|
|
791
|
+
const ComposerStop = forwardRef(function ComposerStop({ onClick, ...props }, ref) {
|
|
792
|
+
const composer = useComposer();
|
|
793
|
+
const disabled = props.disabled ?? !composer.canStop;
|
|
794
|
+
const handleClick = useCallback((event) => {
|
|
795
|
+
onClick?.(event);
|
|
796
|
+
if (event.defaultPrevented || disabled) return;
|
|
797
|
+
composer.stop();
|
|
798
|
+
}, [
|
|
799
|
+
composer,
|
|
800
|
+
disabled,
|
|
801
|
+
onClick
|
|
802
|
+
]);
|
|
803
|
+
return renderPrimitive("button", {
|
|
804
|
+
...props,
|
|
805
|
+
children: props.children ?? "Stop",
|
|
806
|
+
disabled,
|
|
807
|
+
onClick: handleClick,
|
|
808
|
+
type: props.type ?? "button",
|
|
809
|
+
"data-state": disabled ? "disabled" : "enabled"
|
|
810
|
+
}, ref);
|
|
811
|
+
});
|
|
812
|
+
const ComposerTriggerMenu = forwardRef(function ComposerTriggerMenu({ children, keepMounted = false, style, ...props }, ref) {
|
|
813
|
+
const activeTrigger = useComposer().activeTrigger;
|
|
814
|
+
const empty = activeTrigger === void 0;
|
|
815
|
+
if (empty && !keepMounted) return null;
|
|
816
|
+
const menuStyle = activeTrigger?.rect === void 0 ? style : {
|
|
817
|
+
position: "fixed",
|
|
818
|
+
top: activeTrigger.rect.bottom,
|
|
819
|
+
left: activeTrigger.rect.left,
|
|
820
|
+
...style
|
|
821
|
+
};
|
|
822
|
+
const renderedChildren = activeTrigger === void 0 ? typeof children === "function" ? null : children : typeof children === "function" ? children(activeTrigger) : children ?? activeTrigger.items.map((item, index) => /* @__PURE__ */ jsx(ComposerTriggerItem, { index }, `${item.id}:${index.toString()}`));
|
|
823
|
+
return renderPrimitive("div", {
|
|
824
|
+
...props,
|
|
825
|
+
children: renderedChildren,
|
|
826
|
+
role: props.role ?? "listbox",
|
|
827
|
+
style: menuStyle,
|
|
828
|
+
"data-state": activeTrigger === void 0 ? "closed" : activeTrigger.loading ? "loading" : empty ? "empty" : "open"
|
|
829
|
+
}, ref);
|
|
830
|
+
});
|
|
831
|
+
const ComposerTriggerItem = forwardRef(function ComposerTriggerItem({ children, index, item: itemProp, onClick, onMouseEnter, ...props }, ref) {
|
|
832
|
+
const activeTrigger = useComposer().activeTrigger;
|
|
833
|
+
const itemIndex = index ?? 0;
|
|
834
|
+
const item = itemProp ?? activeTrigger?.items[itemIndex];
|
|
835
|
+
if (activeTrigger === void 0 || item === void 0) return null;
|
|
836
|
+
const selected = activeTrigger.selectedIndex === itemIndex;
|
|
837
|
+
const disabled = props.disabled ?? item.disabled ?? false;
|
|
838
|
+
const renderedChildren = typeof children === "function" ? children(item, activeTrigger) : children ?? item.label;
|
|
839
|
+
const handleMouseEnter = (event) => {
|
|
840
|
+
onMouseEnter?.(event);
|
|
841
|
+
if (!event.defaultPrevented && !disabled) activeTrigger.setSelectedIndex(itemIndex);
|
|
842
|
+
};
|
|
843
|
+
const handleClick = (event) => {
|
|
844
|
+
onClick?.(event);
|
|
845
|
+
if (!event.defaultPrevented && !disabled) activeTrigger.selectItem(item);
|
|
846
|
+
};
|
|
847
|
+
return renderPrimitive("button", {
|
|
848
|
+
...props,
|
|
849
|
+
children: renderedChildren,
|
|
850
|
+
disabled,
|
|
851
|
+
onClick: handleClick,
|
|
852
|
+
onMouseEnter: handleMouseEnter,
|
|
853
|
+
role: props.role ?? "option",
|
|
854
|
+
type: props.type ?? "button",
|
|
855
|
+
"aria-selected": selected,
|
|
856
|
+
"data-state": disabled ? "disabled" : selected ? "selected" : "idle"
|
|
857
|
+
}, ref);
|
|
858
|
+
});
|
|
859
|
+
const ComposerAttachments = forwardRef(function ComposerAttachments({ children, keepMounted = false, ...props }, ref) {
|
|
860
|
+
const composer = useComposer();
|
|
861
|
+
const empty = composer.attachments.length === 0;
|
|
862
|
+
if (empty && !keepMounted) return null;
|
|
863
|
+
return renderPrimitive("div", {
|
|
864
|
+
...props,
|
|
865
|
+
children: composer.attachments.map((attachment) => /* @__PURE__ */ jsx(InternalAttachmentProvider, {
|
|
866
|
+
value: {
|
|
867
|
+
attachment,
|
|
868
|
+
remove: () => {
|
|
869
|
+
composer.removeAttachment(attachment.id);
|
|
870
|
+
}
|
|
871
|
+
},
|
|
872
|
+
children: typeof children === "function" ? children(attachment) : children ?? /* @__PURE__ */ jsx(AttachmentPrimitive.Root, {})
|
|
873
|
+
}, attachment.id)),
|
|
874
|
+
"data-role": "attachments",
|
|
875
|
+
"data-state": empty ? "empty" : "populated"
|
|
876
|
+
}, ref);
|
|
877
|
+
});
|
|
878
|
+
const ComposerAttachmentInput = forwardRef(function ComposerAttachmentInput({ onChange, ...props }, ref) {
|
|
879
|
+
const composer = useComposer();
|
|
880
|
+
const disabled = props.disabled ?? (composer.status === "submitted" || composer.status === "streaming");
|
|
881
|
+
const handleChange = useCallback(async (event) => {
|
|
882
|
+
onChange?.(event);
|
|
883
|
+
if (event.defaultPrevented) return;
|
|
884
|
+
const files = Array.from(event.currentTarget.files ?? []);
|
|
885
|
+
event.currentTarget.value = "";
|
|
886
|
+
for (const file of files) await composer.addAttachment(file);
|
|
887
|
+
}, [composer, onChange]);
|
|
888
|
+
return renderPrimitive("input", {
|
|
889
|
+
...props,
|
|
890
|
+
disabled,
|
|
891
|
+
onChange: (event) => {
|
|
892
|
+
handleChange(event);
|
|
893
|
+
},
|
|
894
|
+
type: "file",
|
|
895
|
+
"data-state": disabled ? "disabled" : "enabled"
|
|
896
|
+
}, ref);
|
|
897
|
+
});
|
|
898
|
+
const ComposerAddAttachment = forwardRef(function ComposerAddAttachment({ accept, multiple = false, onClick, ...props }, ref) {
|
|
899
|
+
const composer = useComposer();
|
|
900
|
+
const inputRef = useRef(null);
|
|
901
|
+
const disabled = props.disabled ?? (composer.status === "submitted" || composer.status === "streaming");
|
|
902
|
+
const handleClick = useCallback((event) => {
|
|
903
|
+
onClick?.(event);
|
|
904
|
+
if (event.defaultPrevented || disabled) return;
|
|
905
|
+
inputRef.current?.click();
|
|
906
|
+
}, [disabled, onClick]);
|
|
907
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [renderPrimitive("button", {
|
|
908
|
+
...props,
|
|
909
|
+
children: props.children ?? "Attach",
|
|
910
|
+
disabled,
|
|
911
|
+
onClick: handleClick,
|
|
912
|
+
type: props.type ?? "button",
|
|
913
|
+
"data-state": disabled ? "disabled" : "enabled"
|
|
914
|
+
}, ref), /* @__PURE__ */ jsx(ComposerAttachmentInput, {
|
|
915
|
+
accept,
|
|
916
|
+
"aria-hidden": "true",
|
|
917
|
+
hidden: true,
|
|
918
|
+
multiple,
|
|
919
|
+
ref: inputRef,
|
|
920
|
+
tabIndex: -1
|
|
921
|
+
})] });
|
|
922
|
+
});
|
|
923
|
+
const ComposerAttachmentDropzone = forwardRef(function ComposerAttachmentDropzone({ disabled: disabledProp = false, onDragLeave, onDragOver, onDrop, ...props }, ref) {
|
|
924
|
+
const composer = useComposer();
|
|
925
|
+
const [dragging, setDragging] = useState(false);
|
|
926
|
+
const disabled = disabledProp || composer.status === "submitted" || composer.status === "streaming";
|
|
927
|
+
const handleDragOver = useCallback((event) => {
|
|
928
|
+
onDragOver?.(event);
|
|
929
|
+
if (event.defaultPrevented || disabled) return;
|
|
930
|
+
event.preventDefault();
|
|
931
|
+
setDragging(true);
|
|
932
|
+
}, [disabled, onDragOver]);
|
|
933
|
+
const handleDragLeave = useCallback((event) => {
|
|
934
|
+
onDragLeave?.(event);
|
|
935
|
+
if (!event.defaultPrevented) setDragging(false);
|
|
936
|
+
}, [onDragLeave]);
|
|
937
|
+
const handleDrop = useCallback((event) => {
|
|
938
|
+
onDrop?.(event);
|
|
939
|
+
if (event.defaultPrevented || disabled) return;
|
|
940
|
+
event.preventDefault();
|
|
941
|
+
setDragging(false);
|
|
942
|
+
const files = Array.from(event.dataTransfer.files);
|
|
943
|
+
Promise.all(files.map((file) => composer.addAttachment(file)));
|
|
944
|
+
}, [
|
|
945
|
+
composer,
|
|
946
|
+
disabled,
|
|
947
|
+
onDrop
|
|
948
|
+
]);
|
|
949
|
+
return renderPrimitive("div", {
|
|
950
|
+
...props,
|
|
951
|
+
onDragLeave: handleDragLeave,
|
|
952
|
+
onDragOver: handleDragOver,
|
|
953
|
+
onDrop: handleDrop,
|
|
954
|
+
"data-state": disabled ? "disabled" : dragging ? "dragging" : "enabled"
|
|
955
|
+
}, ref);
|
|
956
|
+
});
|
|
957
|
+
function createAttachment(attachment) {
|
|
958
|
+
return {
|
|
959
|
+
...attachment,
|
|
960
|
+
id: attachment.id ?? createAttachmentId()
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function isFileAttachmentInput(attachment) {
|
|
964
|
+
return typeof File !== "undefined" && attachment instanceof File;
|
|
965
|
+
}
|
|
966
|
+
async function attachmentFromFile(file) {
|
|
967
|
+
return {
|
|
968
|
+
id: createAttachmentId(),
|
|
969
|
+
type: file.type.startsWith("image/") ? "image" : "document",
|
|
970
|
+
name: file.name,
|
|
971
|
+
mediaType: file.type.length > 0 ? file.type : "application/octet-stream",
|
|
972
|
+
data: await readFileData(file)
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
async function readFileData(file) {
|
|
976
|
+
const dataUrl = await new Promise((resolve, reject) => {
|
|
977
|
+
const reader = new FileReader();
|
|
978
|
+
reader.addEventListener("load", () => {
|
|
979
|
+
resolve(String(reader.result ?? ""));
|
|
980
|
+
});
|
|
981
|
+
reader.addEventListener("error", () => {
|
|
982
|
+
reject(reader.error ?? /* @__PURE__ */ new Error("Failed to read attachment."));
|
|
983
|
+
});
|
|
984
|
+
reader.readAsDataURL(file);
|
|
985
|
+
});
|
|
986
|
+
return dataUrl.includes(",") ? dataUrl.slice(dataUrl.indexOf(",") + 1) : dataUrl;
|
|
987
|
+
}
|
|
988
|
+
let nextAttachmentId = 0;
|
|
989
|
+
function createAttachmentId() {
|
|
990
|
+
const random = globalThis.crypto?.randomUUID?.();
|
|
991
|
+
if (random !== void 0) return `attachment_${random}`;
|
|
992
|
+
nextAttachmentId += 1;
|
|
993
|
+
return `attachment_${nextAttachmentId.toString(36)}`;
|
|
994
|
+
}
|
|
995
|
+
const ComposerPrimitive = {
|
|
996
|
+
Root: ComposerRoot,
|
|
997
|
+
Quote: ComposerQuotePreview,
|
|
998
|
+
ClearQuote: ComposerClearQuote,
|
|
999
|
+
Input: ComposerInput,
|
|
1000
|
+
TextareaInput: ComposerTextareaInput,
|
|
1001
|
+
TriggerMenu: ComposerTriggerMenu,
|
|
1002
|
+
TriggerItem: ComposerTriggerItem,
|
|
1003
|
+
Attachments: ComposerAttachments,
|
|
1004
|
+
AddAttachment: ComposerAddAttachment,
|
|
1005
|
+
AttachmentInput: ComposerAttachmentInput,
|
|
1006
|
+
AttachmentDropzone: ComposerAttachmentDropzone,
|
|
1007
|
+
Submit: ComposerSubmit,
|
|
1008
|
+
Stop: ComposerStop
|
|
1009
|
+
};
|
|
1010
|
+
// src/chat/thread.tsx
|
|
1011
|
+
const ThreadRoot = forwardRef(function ThreadRoot(props, ref) {
|
|
1012
|
+
const chat = useChatContext();
|
|
1013
|
+
const viewportRef = useRef(null);
|
|
1014
|
+
const messagesRef = useRef(null);
|
|
1015
|
+
const [atBottom, setAtBottom] = useState(true);
|
|
1016
|
+
const scrollToBottom = useCallback((behavior = "smooth") => {
|
|
1017
|
+
const viewport = viewportRef.current;
|
|
1018
|
+
if (viewport === null) return;
|
|
1019
|
+
viewport.scrollTo({
|
|
1020
|
+
top: viewport.scrollHeight,
|
|
1021
|
+
behavior
|
|
1022
|
+
});
|
|
1023
|
+
}, []);
|
|
1024
|
+
const thread = useMemo(() => ({
|
|
1025
|
+
viewportRef,
|
|
1026
|
+
messagesRef,
|
|
1027
|
+
atBottom,
|
|
1028
|
+
setAtBottom,
|
|
1029
|
+
scrollToBottom
|
|
1030
|
+
}), [atBottom, scrollToBottom]);
|
|
1031
|
+
return /* @__PURE__ */ jsx(InternalThreadProvider, {
|
|
1032
|
+
value: thread,
|
|
1033
|
+
children: renderPrimitive("div", {
|
|
1034
|
+
...props,
|
|
1035
|
+
"data-state": chat.status
|
|
1036
|
+
}, ref)
|
|
1037
|
+
});
|
|
1038
|
+
});
|
|
1039
|
+
const ThreadViewport = forwardRef(function ThreadViewport({ autoScroll = true, onScroll, ...props }, ref) {
|
|
1040
|
+
const chat = useChatContext();
|
|
1041
|
+
const thread = useThread();
|
|
1042
|
+
const messages = chat.messages;
|
|
1043
|
+
const composedRef = useMemo(() => composeRefs(ref, (node) => {
|
|
1044
|
+
thread.viewportRef.current = node;
|
|
1045
|
+
}), [ref, thread.viewportRef]);
|
|
1046
|
+
useEffect(() => {
|
|
1047
|
+
const hasMessages = messages.length > 0;
|
|
1048
|
+
if (autoScroll && (thread.atBottom || !hasMessages)) thread.scrollToBottom("auto");
|
|
1049
|
+
}, [
|
|
1050
|
+
autoScroll,
|
|
1051
|
+
messages,
|
|
1052
|
+
thread
|
|
1053
|
+
]);
|
|
1054
|
+
useEffect(() => {
|
|
1055
|
+
const viewport = thread.viewportRef.current;
|
|
1056
|
+
if (viewport === null || !autoScroll || typeof ResizeObserver !== "function") return;
|
|
1057
|
+
const content = thread.messagesRef.current;
|
|
1058
|
+
if (content === null) return;
|
|
1059
|
+
const observer = new ResizeObserver(() => {
|
|
1060
|
+
if (thread.atBottom) thread.scrollToBottom("auto");
|
|
1061
|
+
});
|
|
1062
|
+
observer.observe(viewport);
|
|
1063
|
+
observer.observe(content);
|
|
1064
|
+
return () => observer.disconnect();
|
|
1065
|
+
}, [autoScroll, thread]);
|
|
1066
|
+
const handleScroll = useCallback((event) => {
|
|
1067
|
+
onScroll?.(event);
|
|
1068
|
+
const node = event.currentTarget;
|
|
1069
|
+
const distanceFromBottom = node.scrollHeight - node.scrollTop - node.clientHeight;
|
|
1070
|
+
thread.setAtBottom(distanceFromBottom < 80);
|
|
1071
|
+
}, [onScroll, thread]);
|
|
1072
|
+
return renderPrimitive("div", {
|
|
1073
|
+
...props,
|
|
1074
|
+
onScroll: handleScroll,
|
|
1075
|
+
"data-state": thread.atBottom ? "bottom" : "away"
|
|
1076
|
+
}, composedRef);
|
|
1077
|
+
});
|
|
1078
|
+
const ThreadViewportFooter = forwardRef(function ThreadViewportFooter(props, ref) {
|
|
1079
|
+
return renderPrimitive("div", { ...props }, ref);
|
|
1080
|
+
});
|
|
1081
|
+
const ThreadEmpty = forwardRef(function ThreadEmpty(props, ref) {
|
|
1082
|
+
if (useChatContext().messages.length > 0) return null;
|
|
1083
|
+
return renderPrimitive("div", { ...props }, ref);
|
|
1084
|
+
});
|
|
1085
|
+
const ThreadStatus = forwardRef(function ThreadStatus({ children, ...props }, ref) {
|
|
1086
|
+
const chat = useChatContext();
|
|
1087
|
+
const renderedChildren = typeof children === "function" ? children(chat.status) : children;
|
|
1088
|
+
return renderPrimitive("div", {
|
|
1089
|
+
...props,
|
|
1090
|
+
children: renderedChildren ?? chat.status,
|
|
1091
|
+
"data-state": chat.status
|
|
1092
|
+
}, ref);
|
|
1093
|
+
});
|
|
1094
|
+
const ThreadLoading = forwardRef(function ThreadLoading(props, ref) {
|
|
1095
|
+
const chat = useChatContext();
|
|
1096
|
+
if (chat.status !== "submitted" && chat.status !== "streaming") return null;
|
|
1097
|
+
return renderPrimitive("div", {
|
|
1098
|
+
...props,
|
|
1099
|
+
children: props.children ?? "Loading"
|
|
1100
|
+
}, ref);
|
|
1101
|
+
});
|
|
1102
|
+
const ThreadError = forwardRef(function ThreadError({ children, ...props }, ref) {
|
|
1103
|
+
const chat = useChatContext();
|
|
1104
|
+
if (chat.status !== "error" && chat.error === void 0) return null;
|
|
1105
|
+
const renderedChildren = typeof children === "function" ? children(chat.error) : children;
|
|
1106
|
+
return renderPrimitive("div", {
|
|
1107
|
+
...props,
|
|
1108
|
+
children: renderedChildren ?? errorMessage(chat.error),
|
|
1109
|
+
role: props.role ?? "alert"
|
|
1110
|
+
}, ref);
|
|
1111
|
+
});
|
|
1112
|
+
const ThreadMessages = forwardRef(function ThreadMessages({ children, keepMounted = true, ...props }, ref) {
|
|
1113
|
+
const chat = useChatContext();
|
|
1114
|
+
const thread = useThread();
|
|
1115
|
+
const composedRef = useMemo(() => composeRefs(ref, (node) => {
|
|
1116
|
+
thread.messagesRef.current = node;
|
|
1117
|
+
}), [ref, thread.messagesRef]);
|
|
1118
|
+
const empty = chat.messages.length === 0;
|
|
1119
|
+
if (empty && !keepMounted) return null;
|
|
1120
|
+
return renderPrimitive("div", {
|
|
1121
|
+
...props,
|
|
1122
|
+
children: chat.messages.map((message) => /* @__PURE__ */ jsx(InternalMessageProvider, {
|
|
1123
|
+
message,
|
|
1124
|
+
children: typeof children === "function" ? children(message) : children ?? defaultMessage()
|
|
1125
|
+
}, message.id)),
|
|
1126
|
+
"data-state": empty ? "empty" : "populated"
|
|
1127
|
+
}, composedRef);
|
|
1128
|
+
});
|
|
1129
|
+
const ThreadSuggestions = forwardRef(function ThreadSuggestions({ children, keepMounted = false, ...props }, ref) {
|
|
1130
|
+
const suggestions = useChatContext().suggestions ?? [];
|
|
1131
|
+
const empty = suggestions.length === 0;
|
|
1132
|
+
if (empty && !keepMounted) return null;
|
|
1133
|
+
return renderPrimitive("div", {
|
|
1134
|
+
...props,
|
|
1135
|
+
children: suggestions.map((suggestion) => typeof children === "function" ? /* @__PURE__ */ jsx(Fragment, { children: children(suggestion) }, suggestion.id) : /* @__PURE__ */ jsx(ThreadSuggestion, {
|
|
1136
|
+
suggestion,
|
|
1137
|
+
children
|
|
1138
|
+
}, suggestion.id)),
|
|
1139
|
+
"data-role": "suggestions",
|
|
1140
|
+
"data-state": empty ? "empty" : "populated"
|
|
1141
|
+
}, ref);
|
|
1142
|
+
});
|
|
1143
|
+
const ThreadSuggestion = forwardRef(function ThreadSuggestion({ onClick, prompt, suggestion, ...props }, ref) {
|
|
1144
|
+
const chat = useChatContext();
|
|
1145
|
+
const suggestionPrompt = prompt ?? suggestion?.prompt ?? "";
|
|
1146
|
+
const disabled = props.disabled ?? (suggestionPrompt.length === 0 || chat.status === "submitted" || chat.status === "streaming");
|
|
1147
|
+
const handleClick = useCallback((event) => {
|
|
1148
|
+
onClick?.(event);
|
|
1149
|
+
if (event.defaultPrevented || disabled) return;
|
|
1150
|
+
chat.sendMessage({ text: suggestionPrompt });
|
|
1151
|
+
}, [
|
|
1152
|
+
chat,
|
|
1153
|
+
disabled,
|
|
1154
|
+
onClick,
|
|
1155
|
+
suggestionPrompt
|
|
1156
|
+
]);
|
|
1157
|
+
return renderPrimitive("button", {
|
|
1158
|
+
...props,
|
|
1159
|
+
children: props.children ?? suggestion?.label ?? suggestionPrompt,
|
|
1160
|
+
disabled,
|
|
1161
|
+
onClick: handleClick,
|
|
1162
|
+
type: props.type ?? "button",
|
|
1163
|
+
"data-state": disabled ? "disabled" : "enabled"
|
|
1164
|
+
}, ref);
|
|
1165
|
+
});
|
|
1166
|
+
const ThreadScrollToBottom = forwardRef(function ThreadScrollToBottom({ onClick, ...props }, ref) {
|
|
1167
|
+
const thread = useThread();
|
|
1168
|
+
const disabled = props.disabled ?? thread.atBottom;
|
|
1169
|
+
const handleClick = useCallback((event) => {
|
|
1170
|
+
onClick?.(event);
|
|
1171
|
+
if (event.defaultPrevented || disabled) return;
|
|
1172
|
+
thread.scrollToBottom();
|
|
1173
|
+
}, [
|
|
1174
|
+
disabled,
|
|
1175
|
+
onClick,
|
|
1176
|
+
thread
|
|
1177
|
+
]);
|
|
1178
|
+
return renderPrimitive("button", {
|
|
1179
|
+
...props,
|
|
1180
|
+
children: props.children ?? "Scroll to bottom",
|
|
1181
|
+
disabled,
|
|
1182
|
+
onClick: handleClick,
|
|
1183
|
+
type: props.type ?? "button",
|
|
1184
|
+
"data-state": thread.atBottom ? "bottom" : "away"
|
|
1185
|
+
}, ref);
|
|
1186
|
+
});
|
|
1187
|
+
function defaultMessage() {
|
|
1188
|
+
return /* @__PURE__ */ jsxs(MessagePrimitive.Root, { children: [/* @__PURE__ */ jsx(MessagePrimitive.Content, { children: /* @__PURE__ */ jsx(MessagePrimitive.Parts, {}) }), /* @__PURE__ */ jsx(MessagePrimitive.Actions, {})] });
|
|
1189
|
+
}
|
|
1190
|
+
function errorMessage(error) {
|
|
1191
|
+
if (error instanceof Error) return error.message;
|
|
1192
|
+
if (typeof error === "string") return error;
|
|
1193
|
+
return "Something went wrong.";
|
|
1194
|
+
}
|
|
1195
|
+
const ThreadPrimitive = {
|
|
1196
|
+
Root: ThreadRoot,
|
|
1197
|
+
Viewport: ThreadViewport,
|
|
1198
|
+
ViewportFooter: ThreadViewportFooter,
|
|
1199
|
+
Messages: ThreadMessages,
|
|
1200
|
+
Empty: ThreadEmpty,
|
|
1201
|
+
Status: ThreadStatus,
|
|
1202
|
+
Loading: ThreadLoading,
|
|
1203
|
+
Error: ThreadError,
|
|
1204
|
+
Suggestions: ThreadSuggestions,
|
|
1205
|
+
Suggestion: ThreadSuggestion,
|
|
1206
|
+
ScrollToBottom: ThreadScrollToBottom
|
|
1207
|
+
};
|
|
1208
|
+
export { ComposerPrimitive as n, ThreadPrimitive as t };
|
|
1209
|
+
|
|
1210
|
+
//# sourceMappingURL=chat-Dgz8hsVT.js.map
|