@assistant-ui/react 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.mjs ADDED
@@ -0,0 +1,1572 @@
1
+ import {
2
+ AssistantContext,
3
+ ContentPartContext,
4
+ MessageContext,
5
+ ThreadContext,
6
+ __export,
7
+ useAssistantContext,
8
+ useComposerContext,
9
+ useContentPartContext,
10
+ useMessageContext,
11
+ useThreadContext
12
+ } from "./chunk-KIP3YFVM.mjs";
13
+
14
+ // src/actions/useCopyMessage.tsx
15
+ import { useCallback } from "react";
16
+
17
+ // src/utils/combined/useCombinedStore.ts
18
+ import { useMemo } from "react";
19
+
20
+ // src/utils/combined/createCombinedStore.ts
21
+ import { useSyncExternalStore } from "react";
22
+ var createCombinedStore = (stores) => {
23
+ const subscribe = (callback) => {
24
+ const unsubscribes = stores.map((store) => store.subscribe(callback));
25
+ return () => {
26
+ for (const unsub of unsubscribes) {
27
+ unsub();
28
+ }
29
+ };
30
+ };
31
+ return (selector) => {
32
+ const getSnapshot = () => selector(...stores.map((store) => store.getState()));
33
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
34
+ };
35
+ };
36
+
37
+ // src/utils/combined/useCombinedStore.ts
38
+ var useCombinedStore = (stores, selector) => {
39
+ const useCombined = useMemo(() => createCombinedStore(stores), stores);
40
+ return useCombined(selector);
41
+ };
42
+
43
+ // src/utils/getMessageText.tsx
44
+ var getMessageText = (message) => {
45
+ const textParts = message.content.filter(
46
+ (part) => part.type === "text"
47
+ );
48
+ return textParts.map((part) => part.text).join("\n\n");
49
+ };
50
+
51
+ // src/actions/useCopyMessage.tsx
52
+ var useCopyMessage = ({ copiedDuration = 3e3 }) => {
53
+ const { useMessage, useComposer } = useMessageContext();
54
+ const hasCopyableContent = useCombinedStore(
55
+ [useMessage, useComposer],
56
+ (m, c) => {
57
+ return c.isEditing || m.message.content.some((c2) => c2.type === "text");
58
+ }
59
+ );
60
+ const callback = useCallback(() => {
61
+ const { isEditing, value: composerValue } = useComposer.getState();
62
+ const { message, setIsCopied } = useMessage.getState();
63
+ const valueToCopy = isEditing ? composerValue : getMessageText(message);
64
+ navigator.clipboard.writeText(valueToCopy);
65
+ setIsCopied(true);
66
+ setTimeout(() => setIsCopied(false), copiedDuration);
67
+ }, [useComposer, useMessage, copiedDuration]);
68
+ if (!hasCopyableContent) return null;
69
+ return callback;
70
+ };
71
+
72
+ // src/actions/useReloadMessage.tsx
73
+ import { useCallback as useCallback2 } from "react";
74
+ var useReloadMessage = () => {
75
+ const { useThread, useViewport } = useThreadContext();
76
+ const { useMessage } = useMessageContext();
77
+ const disabled = useCombinedStore(
78
+ [useThread, useMessage],
79
+ (t, m) => t.isRunning || m.message.role !== "assistant"
80
+ );
81
+ const callback = useCallback2(() => {
82
+ const { parentId } = useMessage.getState();
83
+ useThread.getState().startRun(parentId);
84
+ useViewport.getState().scrollToBottom();
85
+ }, [useMessage, useThread, useViewport]);
86
+ if (disabled) return null;
87
+ return callback;
88
+ };
89
+
90
+ // src/actions/useBeginMessageEdit.tsx
91
+ import { useCallback as useCallback3 } from "react";
92
+ var useBeginMessageEdit = () => {
93
+ const { useMessage, useComposer } = useMessageContext();
94
+ const disabled = useCombinedStore(
95
+ [useMessage, useComposer],
96
+ (m, c) => m.message.role !== "user" || c.isEditing
97
+ );
98
+ const callback = useCallback3(() => {
99
+ const { edit } = useComposer.getState();
100
+ edit();
101
+ }, [useComposer]);
102
+ if (disabled) return null;
103
+ return callback;
104
+ };
105
+
106
+ // src/actions/useGoToNextBranch.tsx
107
+ import { useCallback as useCallback4 } from "react";
108
+ var useGoToNextBranch = () => {
109
+ const { useThread } = useThreadContext();
110
+ const { useMessage, useComposer } = useMessageContext();
111
+ const disabled = useCombinedStore(
112
+ [useMessage, useComposer],
113
+ (m, c) => c.isEditing || m.branches.indexOf(m.message.id) + 1 >= m.branches.length
114
+ );
115
+ const callback = useCallback4(() => {
116
+ const { message, branches } = useMessage.getState();
117
+ useThread.getState().switchToBranch(branches[branches.indexOf(message.id) + 1]);
118
+ }, [useMessage, useThread]);
119
+ if (disabled) return null;
120
+ return callback;
121
+ };
122
+
123
+ // src/actions/useGoToPreviousBranch.tsx
124
+ import { useCallback as useCallback5 } from "react";
125
+ var useGoToPreviousBranch = () => {
126
+ const { useThread } = useThreadContext();
127
+ const { useMessage, useComposer } = useMessageContext();
128
+ const disabled = useCombinedStore(
129
+ [useMessage, useComposer],
130
+ (m, c) => c.isEditing || m.branches.indexOf(m.message.id) <= 0
131
+ );
132
+ const callback = useCallback5(() => {
133
+ const { message, branches } = useMessage.getState();
134
+ useThread.getState().switchToBranch(branches[branches.indexOf(message.id) - 1]);
135
+ }, [useMessage, useThread]);
136
+ if (disabled) return null;
137
+ return callback;
138
+ };
139
+
140
+ // src/primitives/thread/index.ts
141
+ var thread_exports = {};
142
+ __export(thread_exports, {
143
+ Empty: () => ThreadEmpty,
144
+ If: () => ThreadIf,
145
+ Messages: () => ThreadMessages,
146
+ Root: () => ThreadRoot,
147
+ ScrollToBottom: () => ThreadScrollToBottom,
148
+ Suggestion: () => ThreadSuggestion,
149
+ Viewport: () => ThreadViewport
150
+ });
151
+
152
+ // src/primitives/thread/ThreadRoot.tsx
153
+ import {
154
+ Primitive
155
+ } from "@radix-ui/react-primitive";
156
+ import { forwardRef } from "react";
157
+ import { jsx } from "react/jsx-runtime";
158
+ var ThreadRoot = forwardRef(
159
+ (props, ref) => {
160
+ return /* @__PURE__ */ jsx(Primitive.div, { ...props, ref });
161
+ }
162
+ );
163
+
164
+ // src/primitives/thread/ThreadIf.tsx
165
+ var useThreadIf = (props) => {
166
+ const { useThread } = useThreadContext();
167
+ return useThread((thread) => {
168
+ if (props.empty === true && thread.messages.length !== 0) return false;
169
+ if (props.empty === false && thread.messages.length === 0) return false;
170
+ if (props.running === true && !thread.isRunning) return false;
171
+ if (props.running === false && thread.isRunning) return false;
172
+ return true;
173
+ });
174
+ };
175
+ var ThreadIf = ({ children, ...query }) => {
176
+ const result = useThreadIf(query);
177
+ return result ? children : null;
178
+ };
179
+
180
+ // src/primitives/thread/ThreadEmpty.tsx
181
+ import { jsx as jsx2 } from "react/jsx-runtime";
182
+ var ThreadEmpty = ({ children }) => {
183
+ return /* @__PURE__ */ jsx2(ThreadIf, { empty: true, children });
184
+ };
185
+
186
+ // src/primitives/thread/ThreadViewport.tsx
187
+ import { composeEventHandlers } from "@radix-ui/primitive";
188
+ import { useComposedRefs } from "@radix-ui/react-compose-refs";
189
+ import {
190
+ Primitive as Primitive2
191
+ } from "@radix-ui/react-primitive";
192
+ import { forwardRef as forwardRef2, useRef } from "react";
193
+
194
+ // src/utils/hooks/useOnResizeContent.tsx
195
+ import { useCallbackRef } from "@radix-ui/react-use-callback-ref";
196
+ import { useEffect } from "react";
197
+ var useOnResizeContent = (ref, callback) => {
198
+ const callbackRef = useCallbackRef(callback);
199
+ useEffect(() => {
200
+ const el = ref.current;
201
+ if (!el) return;
202
+ const resizeObserver = new ResizeObserver(() => {
203
+ callbackRef();
204
+ });
205
+ const mutationObserver = new MutationObserver((mutations) => {
206
+ for (const mutation of mutations) {
207
+ for (const node of mutation.addedNodes) {
208
+ if (node instanceof Element) {
209
+ resizeObserver.observe(node);
210
+ }
211
+ }
212
+ for (const node of mutation.removedNodes) {
213
+ if (node instanceof Element) {
214
+ resizeObserver.unobserve(node);
215
+ }
216
+ }
217
+ }
218
+ callbackRef();
219
+ });
220
+ resizeObserver.observe(el);
221
+ mutationObserver.observe(el, { childList: true });
222
+ for (const child of el.children) {
223
+ resizeObserver.observe(child);
224
+ }
225
+ return () => {
226
+ resizeObserver.disconnect();
227
+ mutationObserver.disconnect();
228
+ };
229
+ }, [ref.current, callbackRef]);
230
+ };
231
+
232
+ // src/utils/hooks/useOnScrollToBottom.tsx
233
+ import { useCallbackRef as useCallbackRef2 } from "@radix-ui/react-use-callback-ref";
234
+ import { useEffect as useEffect2 } from "react";
235
+ var useOnScrollToBottom = (callback) => {
236
+ const callbackRef = useCallbackRef2(callback);
237
+ const { useViewport } = useThreadContext();
238
+ useEffect2(() => {
239
+ return useViewport.getState().onScrollToBottom(() => {
240
+ callbackRef();
241
+ });
242
+ }, [useViewport, callbackRef]);
243
+ };
244
+
245
+ // src/primitives/thread/ThreadViewport.tsx
246
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
247
+ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...rest }, forwardedRef) => {
248
+ const messagesEndRef = useRef(null);
249
+ const divRef = useRef(null);
250
+ const ref = useComposedRefs(forwardedRef, divRef);
251
+ const { useViewport } = useThreadContext();
252
+ const firstRenderRef = useRef(true);
253
+ const isScrollingToBottomRef = useRef(false);
254
+ const lastScrollTop = useRef(0);
255
+ const scrollToBottom = () => {
256
+ const div = messagesEndRef.current;
257
+ if (!div || !autoScroll) return;
258
+ const behavior = firstRenderRef.current ? "instant" : "auto";
259
+ firstRenderRef.current = false;
260
+ isScrollingToBottomRef.current = true;
261
+ div.scrollIntoView({ behavior });
262
+ };
263
+ useOnResizeContent(divRef, () => {
264
+ if (!isScrollingToBottomRef.current && !useViewport.getState().isAtBottom) {
265
+ handleScroll();
266
+ } else {
267
+ scrollToBottom();
268
+ }
269
+ });
270
+ useOnScrollToBottom(() => {
271
+ scrollToBottom();
272
+ });
273
+ const handleScroll = () => {
274
+ const div = divRef.current;
275
+ if (!div) return;
276
+ const isAtBottom = useViewport.getState().isAtBottom;
277
+ const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
278
+ if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
279
+ } else if (newIsAtBottom !== isAtBottom) {
280
+ isScrollingToBottomRef.current = false;
281
+ useViewport.setState({ isAtBottom: newIsAtBottom });
282
+ }
283
+ lastScrollTop.current = div.scrollTop;
284
+ };
285
+ return /* @__PURE__ */ jsxs(
286
+ Primitive2.div,
287
+ {
288
+ ...rest,
289
+ onScroll: composeEventHandlers(onScroll, handleScroll),
290
+ ref,
291
+ children: [
292
+ children,
293
+ /* @__PURE__ */ jsx3("div", { ref: messagesEndRef })
294
+ ]
295
+ }
296
+ );
297
+ });
298
+
299
+ // src/primitives/thread/ThreadMessages.tsx
300
+ import { memo } from "react";
301
+
302
+ // src/context/providers/MessageProvider.tsx
303
+ import { useEffect as useEffect3, useState } from "react";
304
+ import { create as create2 } from "zustand";
305
+
306
+ // src/context/stores/MessageComposer.ts
307
+ import { create } from "zustand";
308
+
309
+ // src/context/stores/BaseComposer.ts
310
+ var makeBaseComposer = (set) => ({
311
+ value: "",
312
+ setValue: (value) => {
313
+ set({ value });
314
+ }
315
+ });
316
+
317
+ // src/context/stores/MessageComposer.ts
318
+ var makeEditComposerStore = ({
319
+ onEdit,
320
+ onSend
321
+ }) => create()((set, get, store) => ({
322
+ ...makeBaseComposer(set, get, store),
323
+ isEditing: false,
324
+ edit: () => {
325
+ const value = onEdit();
326
+ set({ isEditing: true, value });
327
+ },
328
+ send: () => {
329
+ const value = get().value;
330
+ set({ isEditing: false });
331
+ onSend(value);
332
+ },
333
+ cancel: () => {
334
+ if (!get().isEditing) return false;
335
+ set({ isEditing: false });
336
+ return true;
337
+ }
338
+ }));
339
+
340
+ // src/context/providers/MessageProvider.tsx
341
+ import { jsx as jsx4 } from "react/jsx-runtime";
342
+ var getIsLast = (thread, message) => {
343
+ return thread.messages[thread.messages.length - 1]?.id === message.id;
344
+ };
345
+ var syncMessage = (thread, useMessage, messageIndex) => {
346
+ const parentId = thread.messages[messageIndex - 1]?.id ?? null;
347
+ const message = thread.messages[messageIndex];
348
+ if (!message) return;
349
+ const isLast = getIsLast(thread, message);
350
+ const branches = thread.getBranches(message.id);
351
+ const currentState = useMessage.getState();
352
+ if (currentState.message === message && currentState.parentId === parentId && currentState.branches === branches && currentState.isLast === isLast)
353
+ return;
354
+ useMessage.setState({
355
+ message,
356
+ parentId,
357
+ branches,
358
+ isLast
359
+ });
360
+ };
361
+ var useMessageContext2 = (messageIndex) => {
362
+ const { useThread } = useThreadContext();
363
+ const [context] = useState(() => {
364
+ const useMessage = create2((set) => ({
365
+ message: null,
366
+ parentId: null,
367
+ branches: [],
368
+ isLast: false,
369
+ inProgressIndicator: null,
370
+ isCopied: false,
371
+ isHovering: false,
372
+ setInProgressIndicator: (value) => {
373
+ set({ inProgressIndicator: value });
374
+ },
375
+ setIsCopied: (value) => {
376
+ set({ isCopied: value });
377
+ },
378
+ setIsHovering: (value) => {
379
+ set({ isHovering: value });
380
+ }
381
+ }));
382
+ const useComposer = makeEditComposerStore({
383
+ onEdit: () => {
384
+ const message = useMessage.getState().message;
385
+ if (message.role !== "user")
386
+ throw new Error(
387
+ "Tried to edit a non-user message. Editing is only supported for user messages. This is likely an internal bug in assistant-ui."
388
+ );
389
+ const text = getMessageText(message);
390
+ return text;
391
+ },
392
+ onSend: (text) => {
393
+ const { message, parentId } = useMessage.getState();
394
+ if (message.role !== "user")
395
+ throw new Error(
396
+ "Tried to edit a non-user message. Editing is only supported for user messages. This is likely an internal bug in assistant-ui."
397
+ );
398
+ const nonTextParts = message.content.filter(
399
+ (part) => part.type !== "text" && part.type !== "ui"
400
+ );
401
+ useThread.getState().append({
402
+ parentId,
403
+ content: [{ type: "text", text }, ...nonTextParts]
404
+ });
405
+ }
406
+ });
407
+ syncMessage(useThread.getState(), useMessage, messageIndex);
408
+ return { useMessage, useComposer };
409
+ });
410
+ useEffect3(() => {
411
+ return useThread.subscribe((thread) => {
412
+ syncMessage(thread, context.useMessage, messageIndex);
413
+ });
414
+ }, [context, useThread, messageIndex]);
415
+ return context;
416
+ };
417
+ var MessageProvider = ({
418
+ messageIndex,
419
+ children
420
+ }) => {
421
+ const context = useMessageContext2(messageIndex);
422
+ return /* @__PURE__ */ jsx4(MessageContext.Provider, { value: context, children });
423
+ };
424
+
425
+ // src/primitives/composer/ComposerIf.tsx
426
+ var useComposerIf = (props) => {
427
+ const { useComposer } = useComposerContext();
428
+ return useComposer((composer) => {
429
+ if (props.editing === true && !composer.isEditing) return false;
430
+ if (props.editing === false && composer.isEditing) return false;
431
+ return true;
432
+ });
433
+ };
434
+ var ComposerIf = ({ children, ...query }) => {
435
+ const result = useComposerIf(query);
436
+ return result ? children : null;
437
+ };
438
+
439
+ // src/primitives/message/MessageIf.tsx
440
+ var useMessageIf = (props) => {
441
+ const { useMessage } = useMessageContext();
442
+ return useMessage(({ message, branches, isLast, isCopied, isHovering }) => {
443
+ if (props.hasBranches === true && branches.length < 2) return false;
444
+ if (props.user && message.role !== "user") return false;
445
+ if (props.assistant && message.role !== "assistant") return false;
446
+ if (props.lastOrHover === true && !isHovering && !isLast) return false;
447
+ if (props.copied === true && !isCopied) return false;
448
+ if (props.copied === false && isCopied) return false;
449
+ return true;
450
+ });
451
+ };
452
+ var MessageIf = ({ children, ...query }) => {
453
+ const result = useMessageIf(query);
454
+ return result ? children : null;
455
+ };
456
+
457
+ // src/primitives/thread/ThreadMessages.tsx
458
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
459
+ var getComponents = (components) => {
460
+ return {
461
+ EditComposer: components.EditComposer ?? components.UserMessage ?? components.Message,
462
+ UserMessage: components.UserMessage ?? components.Message,
463
+ AssistantMessage: components.AssistantMessage ?? components.Message
464
+ };
465
+ };
466
+ var ThreadMessageImpl = ({
467
+ messageIndex,
468
+ components
469
+ }) => {
470
+ const { UserMessage, EditComposer, AssistantMessage } = getComponents(components);
471
+ return /* @__PURE__ */ jsxs2(MessageProvider, { messageIndex, children: [
472
+ /* @__PURE__ */ jsxs2(MessageIf, { user: true, children: [
473
+ /* @__PURE__ */ jsx5(ComposerIf, { editing: false, children: /* @__PURE__ */ jsx5(UserMessage, {}) }),
474
+ /* @__PURE__ */ jsx5(ComposerIf, { editing: true, children: /* @__PURE__ */ jsx5(EditComposer, {}) })
475
+ ] }),
476
+ /* @__PURE__ */ jsx5(MessageIf, { assistant: true, children: /* @__PURE__ */ jsx5(AssistantMessage, {}) })
477
+ ] });
478
+ };
479
+ var ThreadMessage = memo(
480
+ ThreadMessageImpl,
481
+ (prev, next) => prev.messageIndex === next.messageIndex && prev.components.UserMessage === next.components.UserMessage && prev.components.EditComposer === next.components.EditComposer && prev.components.AssistantMessage === next.components.AssistantMessage
482
+ );
483
+ var ThreadMessages = ({ components }) => {
484
+ const { useThread } = useThreadContext();
485
+ const messagesLength = useThread((t) => t.messages.length);
486
+ if (messagesLength === 0) return null;
487
+ return new Array(messagesLength).fill(null).map((_, idx) => {
488
+ const messageIndex = idx;
489
+ return /* @__PURE__ */ jsx5(
490
+ ThreadMessage,
491
+ {
492
+ messageIndex,
493
+ components
494
+ },
495
+ messageIndex
496
+ );
497
+ });
498
+ };
499
+
500
+ // src/primitives/thread/ThreadScrollToBottom.tsx
501
+ import { composeEventHandlers as composeEventHandlers2 } from "@radix-ui/primitive";
502
+ import {
503
+ Primitive as Primitive3
504
+ } from "@radix-ui/react-primitive";
505
+ import { forwardRef as forwardRef3 } from "react";
506
+ import { jsx as jsx6 } from "react/jsx-runtime";
507
+ var ThreadScrollToBottom = forwardRef3(({ onClick, ...rest }, ref) => {
508
+ const { useViewport } = useThreadContext();
509
+ const isAtBottom = useViewport((s) => s.isAtBottom);
510
+ const handleScrollToBottom = () => {
511
+ useViewport.getState().scrollToBottom();
512
+ };
513
+ return /* @__PURE__ */ jsx6(
514
+ Primitive3.button,
515
+ {
516
+ ...rest,
517
+ disabled: isAtBottom,
518
+ ref,
519
+ onClick: composeEventHandlers2(onClick, handleScrollToBottom)
520
+ }
521
+ );
522
+ });
523
+
524
+ // src/primitives/thread/ThreadSuggestion.tsx
525
+ import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
526
+ import {
527
+ Primitive as Primitive4
528
+ } from "@radix-ui/react-primitive";
529
+ import { forwardRef as forwardRef4 } from "react";
530
+ import { jsx as jsx7 } from "react/jsx-runtime";
531
+ var ThreadSuggestion = forwardRef4(({ onClick, prompt, method, autoSend: send, ...rest }, ref) => {
532
+ const { useThread, useComposer } = useThreadContext();
533
+ const isDisabled = useThread((t) => t.isRunning);
534
+ const handleApplySuggestion = () => {
535
+ const thread = useThread.getState();
536
+ const composer = useComposer.getState();
537
+ composer.setValue(prompt);
538
+ if (send && !thread.isRunning) {
539
+ composer.send();
540
+ }
541
+ };
542
+ return /* @__PURE__ */ jsx7(
543
+ Primitive4.button,
544
+ {
545
+ ...rest,
546
+ disabled: isDisabled,
547
+ ref,
548
+ onClick: composeEventHandlers3(onClick, handleApplySuggestion)
549
+ }
550
+ );
551
+ });
552
+
553
+ // src/primitives/composer/index.ts
554
+ var composer_exports = {};
555
+ __export(composer_exports, {
556
+ Cancel: () => ComposerCancel,
557
+ If: () => ComposerIf,
558
+ Input: () => ComposerInput,
559
+ Root: () => ComposerRoot,
560
+ Send: () => ComposerSend
561
+ });
562
+
563
+ // src/primitives/composer/ComposerRoot.tsx
564
+ import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
565
+ import { useComposedRefs as useComposedRefs2 } from "@radix-ui/react-compose-refs";
566
+ import {
567
+ Primitive as Primitive5
568
+ } from "@radix-ui/react-primitive";
569
+ import { forwardRef as forwardRef5, useRef as useRef2 } from "react";
570
+ import { jsx as jsx8 } from "react/jsx-runtime";
571
+ var ComposerRoot = forwardRef5(
572
+ ({ onSubmit, ...rest }, forwardedRef) => {
573
+ const { useViewport } = useThreadContext();
574
+ const { useComposer } = useComposerContext();
575
+ const formRef = useRef2(null);
576
+ const ref = useComposedRefs2(forwardedRef, formRef);
577
+ const handleSubmit = (e) => {
578
+ const composerState = useComposer.getState();
579
+ if (!composerState.isEditing) return;
580
+ e.preventDefault();
581
+ composerState.send();
582
+ useViewport.getState().scrollToBottom();
583
+ };
584
+ return /* @__PURE__ */ jsx8(
585
+ Primitive5.form,
586
+ {
587
+ ...rest,
588
+ ref,
589
+ onSubmit: composeEventHandlers4(onSubmit, handleSubmit)
590
+ }
591
+ );
592
+ }
593
+ );
594
+
595
+ // src/primitives/composer/ComposerInput.tsx
596
+ import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
597
+ import { useComposedRefs as useComposedRefs3 } from "@radix-ui/react-compose-refs";
598
+ import { Slot } from "@radix-ui/react-slot";
599
+ import {
600
+ forwardRef as forwardRef6,
601
+ useCallback as useCallback6,
602
+ useEffect as useEffect4,
603
+ useRef as useRef3
604
+ } from "react";
605
+ import TextareaAutosize from "react-textarea-autosize";
606
+ import { jsx as jsx9 } from "react/jsx-runtime";
607
+ var ComposerInput = forwardRef6(
608
+ ({ autoFocus = false, asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
609
+ const { useThread } = useThreadContext();
610
+ const { useComposer, type } = useComposerContext();
611
+ const value = useComposer((c) => {
612
+ if (!c.isEditing) return "";
613
+ return c.value;
614
+ });
615
+ const Component = asChild ? Slot : TextareaAutosize;
616
+ const textareaRef = useRef3(null);
617
+ const ref = useComposedRefs3(forwardedRef, textareaRef);
618
+ const handleKeyPress = (e) => {
619
+ if (disabled) return;
620
+ if (e.key === "Escape") {
621
+ const composer = useComposer.getState();
622
+ if (composer.cancel()) {
623
+ e.preventDefault();
624
+ }
625
+ } else if (e.key === "Enter" && e.shiftKey === false) {
626
+ const isRunning = useThread.getState().isRunning;
627
+ if (!isRunning) {
628
+ e.preventDefault();
629
+ textareaRef.current?.closest("form")?.requestSubmit();
630
+ }
631
+ }
632
+ };
633
+ const autoFocusEnabled = autoFocus && !disabled;
634
+ const focus = useCallback6(() => {
635
+ const textarea = textareaRef.current;
636
+ if (!textarea || !autoFocusEnabled) return;
637
+ textarea.focus();
638
+ textarea.setSelectionRange(
639
+ textareaRef.current.value.length,
640
+ textareaRef.current.value.length
641
+ );
642
+ }, [autoFocusEnabled]);
643
+ useEffect4(() => focus(), [focus]);
644
+ useOnScrollToBottom(() => {
645
+ if (type === "new") {
646
+ focus();
647
+ }
648
+ });
649
+ return /* @__PURE__ */ jsx9(
650
+ Component,
651
+ {
652
+ value,
653
+ ...rest,
654
+ ref,
655
+ disabled,
656
+ onChange: composeEventHandlers5(onChange, (e) => {
657
+ const composerState = useComposer.getState();
658
+ if (!composerState.isEditing) return;
659
+ return composerState.setValue(e.target.value);
660
+ }),
661
+ onKeyDown: composeEventHandlers5(onKeyDown, handleKeyPress)
662
+ }
663
+ );
664
+ }
665
+ );
666
+
667
+ // src/primitives/composer/ComposerSend.tsx
668
+ import {
669
+ Primitive as Primitive6
670
+ } from "@radix-ui/react-primitive";
671
+ import { forwardRef as forwardRef7 } from "react";
672
+ import { jsx as jsx10 } from "react/jsx-runtime";
673
+ var ComposerSend = forwardRef7(
674
+ ({ disabled, ...rest }, ref) => {
675
+ const { useComposer } = useComposerContext();
676
+ const hasValue = useComposer((c) => c.isEditing && c.value.length > 0);
677
+ return /* @__PURE__ */ jsx10(
678
+ Primitive6.button,
679
+ {
680
+ type: "submit",
681
+ ...rest,
682
+ ref,
683
+ disabled: disabled || !hasValue
684
+ }
685
+ );
686
+ }
687
+ );
688
+
689
+ // src/primitives/composer/ComposerCancel.tsx
690
+ import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
691
+ import {
692
+ Primitive as Primitive7
693
+ } from "@radix-ui/react-primitive";
694
+ import { forwardRef as forwardRef8 } from "react";
695
+ import { jsx as jsx11 } from "react/jsx-runtime";
696
+ var ComposerCancel = forwardRef8(({ onClick, ...rest }, ref) => {
697
+ const { useComposer } = useComposerContext();
698
+ const handleCancel = () => {
699
+ useComposer.getState().cancel();
700
+ };
701
+ return /* @__PURE__ */ jsx11(
702
+ Primitive7.button,
703
+ {
704
+ type: "button",
705
+ ...rest,
706
+ ref,
707
+ onClick: composeEventHandlers6(onClick, handleCancel)
708
+ }
709
+ );
710
+ });
711
+
712
+ // src/primitives/message/index.ts
713
+ var message_exports = {};
714
+ __export(message_exports, {
715
+ Content: () => MessageContent,
716
+ If: () => MessageIf,
717
+ InProgress: () => MessageInProgress,
718
+ Root: () => MessageRoot
719
+ });
720
+
721
+ // src/primitives/message/MessageRoot.tsx
722
+ import { composeEventHandlers as composeEventHandlers7 } from "@radix-ui/primitive";
723
+ import {
724
+ Primitive as Primitive8
725
+ } from "@radix-ui/react-primitive";
726
+ import { forwardRef as forwardRef9 } from "react";
727
+ import { jsx as jsx12 } from "react/jsx-runtime";
728
+ var MessageRoot = forwardRef9(
729
+ ({ onMouseEnter, onMouseLeave, ...rest }, ref) => {
730
+ const { useMessage } = useMessageContext();
731
+ const setIsHovering = useMessage((s) => s.setIsHovering);
732
+ const handleMouseEnter = () => {
733
+ setIsHovering(true);
734
+ };
735
+ const handleMouseLeave = () => {
736
+ setIsHovering(false);
737
+ };
738
+ return /* @__PURE__ */ jsx12(
739
+ Primitive8.div,
740
+ {
741
+ ...rest,
742
+ ref,
743
+ onMouseEnter: composeEventHandlers7(onMouseEnter, handleMouseEnter),
744
+ onMouseLeave: composeEventHandlers7(onMouseLeave, handleMouseLeave)
745
+ }
746
+ );
747
+ }
748
+ );
749
+
750
+ // src/primitives/message/MessageContent.tsx
751
+ import { memo as memo2 } from "react";
752
+
753
+ // src/context/providers/ContentPartProvider.tsx
754
+ import { useEffect as useEffect5, useState as useState2 } from "react";
755
+ import { create as create3 } from "zustand";
756
+ import { jsx as jsx13 } from "react/jsx-runtime";
757
+ var syncContentPart = ({ message }, useContentPart, partIndex) => {
758
+ const part = message.content[partIndex];
759
+ if (!part) return;
760
+ const messageStatus = message.role === "assistant" ? message.status : "done";
761
+ const status = partIndex === message.content.length - 1 ? messageStatus : "done";
762
+ const currentState = useContentPart.getState();
763
+ if (currentState.part === part && currentState.status === status) return;
764
+ useContentPart.setState({ part, status });
765
+ };
766
+ var useContentPartContext2 = (partIndex) => {
767
+ const { useMessage } = useMessageContext();
768
+ const [context] = useState2(() => {
769
+ const useContentPart = create3(() => ({
770
+ part: { type: "text", text: "" },
771
+ status: "done"
772
+ }));
773
+ syncContentPart(useMessage.getState(), useContentPart, partIndex);
774
+ return { useContentPart };
775
+ });
776
+ useEffect5(() => {
777
+ return useMessage.subscribe((message) => {
778
+ syncContentPart(message, context.useContentPart, partIndex);
779
+ });
780
+ }, [context, useMessage, partIndex]);
781
+ return context;
782
+ };
783
+ var ContentPartProvider = ({
784
+ partIndex,
785
+ children
786
+ }) => {
787
+ const context = useContentPartContext2(partIndex);
788
+ return /* @__PURE__ */ jsx13(ContentPartContext.Provider, { value: context, children });
789
+ };
790
+
791
+ // src/primitives/contentPart/ContentPartDisplay.tsx
792
+ var ContentPartDisplay = () => {
793
+ const { useContentPart } = useContentPartContext();
794
+ const display = useContentPart((c) => {
795
+ if (c.part.type !== "ui")
796
+ throw new Error(
797
+ "ContentPartDisplay can only be used inside ui content parts."
798
+ );
799
+ return c.part.display;
800
+ });
801
+ return display ?? null;
802
+ };
803
+
804
+ // src/primitives/contentPart/ContentPartInProgressIndicator.tsx
805
+ var ContentPartInProgressIndicator = () => {
806
+ const { useMessage } = useMessageContext();
807
+ const { useContentPart } = useContentPartContext();
808
+ const indicator = useCombinedStore(
809
+ [useMessage, useContentPart],
810
+ (m, c) => c.status === "in_progress" ? m.inProgressIndicator : null
811
+ );
812
+ return indicator;
813
+ };
814
+
815
+ // src/primitives/contentPart/ContentPartText.tsx
816
+ import {
817
+ Primitive as Primitive9
818
+ } from "@radix-ui/react-primitive";
819
+ import { forwardRef as forwardRef10 } from "react";
820
+ import { jsx as jsx14 } from "react/jsx-runtime";
821
+ var ContentPartText = forwardRef10((props, forwardedRef) => {
822
+ const { useContentPart } = useContentPartContext();
823
+ const text = useContentPart((c) => {
824
+ if (c.part.type !== "text")
825
+ throw new Error(
826
+ "ContentPartText can only be used inside text content parts."
827
+ );
828
+ return c.part.text;
829
+ });
830
+ return /* @__PURE__ */ jsx14(Primitive9.span, { ...props, ref: forwardedRef, children: text });
831
+ });
832
+
833
+ // src/primitives/message/MessageContent.tsx
834
+ import { Fragment, jsx as jsx15, jsxs as jsxs3 } from "react/jsx-runtime";
835
+ var defaultComponents = {
836
+ Text: () => /* @__PURE__ */ jsxs3(Fragment, { children: [
837
+ /* @__PURE__ */ jsx15(ContentPartText, {}),
838
+ /* @__PURE__ */ jsx15(ContentPartInProgressIndicator, {})
839
+ ] }),
840
+ Image: () => null,
841
+ UI: () => /* @__PURE__ */ jsx15(ContentPartDisplay, {}),
842
+ tools: {
843
+ Fallback: (props) => {
844
+ const { useToolRenderers } = useAssistantContext();
845
+ const Render = useToolRenderers(
846
+ (s) => s.getToolRenderer(props.part.toolName)
847
+ );
848
+ if (!Render) return null;
849
+ return /* @__PURE__ */ jsx15(Render, { ...props });
850
+ }
851
+ }
852
+ };
853
+ var MessageContentPartComponent = ({
854
+ components: {
855
+ Text = defaultComponents.Text,
856
+ Image = defaultComponents.Image,
857
+ UI = defaultComponents.UI,
858
+ tools: { by_name = {}, Fallback = defaultComponents.tools.Fallback } = {}
859
+ } = {}
860
+ }) => {
861
+ const { useContentPart } = useContentPartContext();
862
+ const { part, status } = useContentPart();
863
+ const type = part.type;
864
+ switch (type) {
865
+ case "text":
866
+ return /* @__PURE__ */ jsx15(Text, { part, status });
867
+ case "image":
868
+ return /* @__PURE__ */ jsx15(Image, { part, status });
869
+ case "ui":
870
+ return /* @__PURE__ */ jsx15(UI, { part, status });
871
+ case "tool-call": {
872
+ const Tool = by_name[part.toolName] || Fallback;
873
+ return /* @__PURE__ */ jsx15(Tool, { part, status });
874
+ }
875
+ default:
876
+ throw new Error(`Unknown content part type: ${type}`);
877
+ }
878
+ };
879
+ var MessageContentPartImpl = ({
880
+ partIndex,
881
+ components
882
+ }) => {
883
+ return /* @__PURE__ */ jsx15(ContentPartProvider, { partIndex, children: /* @__PURE__ */ jsx15(MessageContentPartComponent, { components }) });
884
+ };
885
+ var MessageContentPart = memo2(
886
+ MessageContentPartImpl,
887
+ (prev, next) => prev.partIndex === next.partIndex && prev.components?.Text === next.components?.Text && prev.components?.Image === next.components?.Image && prev.components?.UI === next.components?.UI && prev.components?.tools === next.components?.tools
888
+ );
889
+ var MessageContent = ({ components }) => {
890
+ const { useMessage } = useMessageContext();
891
+ const contentLength = useMessage((s) => s.message.content.length);
892
+ return new Array(contentLength).fill(null).map((_, idx) => {
893
+ const partIndex = idx;
894
+ return /* @__PURE__ */ jsx15(
895
+ MessageContentPart,
896
+ {
897
+ partIndex,
898
+ components
899
+ },
900
+ partIndex
901
+ );
902
+ });
903
+ };
904
+
905
+ // src/primitives/message/MessageInProgress.tsx
906
+ import {
907
+ Primitive as Primitive10
908
+ } from "@radix-ui/react-primitive";
909
+ import { forwardRef as forwardRef11, useMemo as useMemo2 } from "react";
910
+ import { jsx as jsx16 } from "react/jsx-runtime";
911
+ var MessageInProgress = forwardRef11((props, ref) => {
912
+ const { useMessage } = useMessageContext();
913
+ useMemo2(() => {
914
+ useMessage.getState().setInProgressIndicator(/* @__PURE__ */ jsx16(Primitive10.span, { ...props, ref }));
915
+ }, [useMessage, props, ref]);
916
+ return null;
917
+ });
918
+
919
+ // src/primitives/branchPicker/index.ts
920
+ var branchPicker_exports = {};
921
+ __export(branchPicker_exports, {
922
+ Count: () => BranchPickerCount,
923
+ Next: () => BranchPickerNext,
924
+ Number: () => BranchPickerNumber,
925
+ Previous: () => BranchPickerPrevious,
926
+ Root: () => BranchPickerRoot
927
+ });
928
+
929
+ // src/utils/createActionButton.tsx
930
+ import { composeEventHandlers as composeEventHandlers8 } from "@radix-ui/primitive";
931
+ import {
932
+ Primitive as Primitive11
933
+ } from "@radix-ui/react-primitive";
934
+ import { forwardRef as forwardRef12 } from "react";
935
+ import { jsx as jsx17 } from "react/jsx-runtime";
936
+ var createActionButton = (useActionButton) => {
937
+ return forwardRef12(
938
+ (props, forwardedRef) => {
939
+ const onClick = useActionButton(props);
940
+ return /* @__PURE__ */ jsx17(
941
+ Primitive11.button,
942
+ {
943
+ type: "button",
944
+ disabled: !onClick,
945
+ ...props,
946
+ ref: forwardedRef,
947
+ onClick: composeEventHandlers8(props.onClick, onClick ?? void 0)
948
+ }
949
+ );
950
+ }
951
+ );
952
+ };
953
+
954
+ // src/primitives/branchPicker/BranchPickerNext.tsx
955
+ var BranchPickerNext = createActionButton(useGoToNextBranch);
956
+
957
+ // src/primitives/branchPicker/BranchPickerPrevious.tsx
958
+ var BranchPickerPrevious = createActionButton(useGoToPreviousBranch);
959
+
960
+ // src/primitives/branchPicker/BranchPickerCount.tsx
961
+ import { Fragment as Fragment2, jsx as jsx18 } from "react/jsx-runtime";
962
+ var BranchPickerCount = () => {
963
+ const { useMessage } = useMessageContext();
964
+ const branchCount = useMessage((s) => s.branches.length);
965
+ return /* @__PURE__ */ jsx18(Fragment2, { children: branchCount });
966
+ };
967
+
968
+ // src/primitives/branchPicker/BranchPickerNumber.tsx
969
+ import { Fragment as Fragment3, jsx as jsx19 } from "react/jsx-runtime";
970
+ var BranchPickerNumber = () => {
971
+ const { useMessage } = useMessageContext();
972
+ const branchIdx = useMessage((s) => s.branches.indexOf(s.message.id));
973
+ return /* @__PURE__ */ jsx19(Fragment3, { children: branchIdx + 1 });
974
+ };
975
+
976
+ // src/primitives/branchPicker/BranchPickerRoot.tsx
977
+ import {
978
+ Primitive as Primitive12
979
+ } from "@radix-ui/react-primitive";
980
+ import { forwardRef as forwardRef13 } from "react";
981
+ import { jsx as jsx20 } from "react/jsx-runtime";
982
+ var BranchPickerRoot = forwardRef13(({ hideWhenSingleBranch, ...rest }, ref) => {
983
+ return /* @__PURE__ */ jsx20(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0, children: /* @__PURE__ */ jsx20(Primitive12.div, { ...rest, ref }) });
984
+ });
985
+
986
+ // src/primitives/actionBar/index.ts
987
+ var actionBar_exports = {};
988
+ __export(actionBar_exports, {
989
+ Copy: () => ActionBarCopy,
990
+ Edit: () => ActionBarEdit,
991
+ Reload: () => ActionBarReload,
992
+ Root: () => ActionBarRoot
993
+ });
994
+
995
+ // src/primitives/actionBar/ActionBarRoot.tsx
996
+ import {
997
+ Primitive as Primitive13
998
+ } from "@radix-ui/react-primitive";
999
+ import { forwardRef as forwardRef14 } from "react";
1000
+ import { jsx as jsx21 } from "react/jsx-runtime";
1001
+ var ActionBarRoot = forwardRef14(({ hideWhenRunning, autohide, autohideFloat, ...rest }, ref) => {
1002
+ const { useThread } = useThreadContext();
1003
+ const { useMessage } = useMessageContext();
1004
+ const hideAndfloatStatus = useCombinedStore(
1005
+ [useThread, useMessage],
1006
+ (t, m) => {
1007
+ if (hideWhenRunning && t.isRunning) return "hidden" /* Hidden */;
1008
+ const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
1009
+ if (!autohideEnabled) return "normal" /* Normal */;
1010
+ if (!m.isHovering) return "hidden" /* Hidden */;
1011
+ if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branches.length <= 1)
1012
+ return "floating" /* Floating */;
1013
+ return "normal" /* Normal */;
1014
+ }
1015
+ );
1016
+ if (hideAndfloatStatus === "hidden" /* Hidden */) return null;
1017
+ return /* @__PURE__ */ jsx21(
1018
+ Primitive13.div,
1019
+ {
1020
+ ...hideAndfloatStatus === "floating" /* Floating */ ? { "data-floating": "true" } : null,
1021
+ ...rest,
1022
+ ref
1023
+ }
1024
+ );
1025
+ });
1026
+
1027
+ // src/primitives/actionBar/ActionBarCopy.tsx
1028
+ var ActionBarCopy = createActionButton(useCopyMessage);
1029
+
1030
+ // src/primitives/actionBar/ActionBarReload.tsx
1031
+ var ActionBarReload = createActionButton(useReloadMessage);
1032
+
1033
+ // src/primitives/actionBar/ActionBarEdit.tsx
1034
+ var ActionBarEdit = createActionButton(useBeginMessageEdit);
1035
+
1036
+ // src/primitives/contentPart/index.ts
1037
+ var contentPart_exports = {};
1038
+ __export(contentPart_exports, {
1039
+ Display: () => ContentPartDisplay,
1040
+ Image: () => ContentPartImage,
1041
+ InProgressIndicator: () => ContentPartInProgressIndicator,
1042
+ Text: () => ContentPartText
1043
+ });
1044
+
1045
+ // src/primitives/contentPart/ContentPartImage.tsx
1046
+ import {
1047
+ Primitive as Primitive14
1048
+ } from "@radix-ui/react-primitive";
1049
+ import { forwardRef as forwardRef15 } from "react";
1050
+ import { jsx as jsx22 } from "react/jsx-runtime";
1051
+ var ContentPartImage = forwardRef15((props, forwardedRef) => {
1052
+ const { useContentPart } = useContentPartContext();
1053
+ const image = useContentPart((c) => {
1054
+ if (c.part.type !== "image")
1055
+ throw new Error(
1056
+ "ContentPartImage can only be used inside image content parts."
1057
+ );
1058
+ return c.part.image;
1059
+ });
1060
+ return /* @__PURE__ */ jsx22(Primitive14.img, { src: image, ...props, ref: forwardedRef });
1061
+ });
1062
+
1063
+ // src/runtime/local/useLocalRuntime.tsx
1064
+ import { useInsertionEffect, useState as useState3 } from "react";
1065
+
1066
+ // src/utils/ModelConfigTypes.ts
1067
+ var mergeModelConfigs = (configSet) => {
1068
+ const configs = Array.from(configSet).map((c) => c()).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
1069
+ return configs.reduce((acc, config) => {
1070
+ if (config.system) {
1071
+ if (acc.system) {
1072
+ acc.system += `
1073
+
1074
+ ${config.system}`;
1075
+ } else {
1076
+ acc.system = config.system;
1077
+ }
1078
+ }
1079
+ if (config.tools) {
1080
+ for (const [name, tool] of Object.entries(config.tools)) {
1081
+ if (acc.tools?.[name]) {
1082
+ throw new Error(
1083
+ `You tried to define a tool with the name ${name}, but it already exists.`
1084
+ );
1085
+ }
1086
+ if (!acc.tools) acc.tools = {};
1087
+ acc.tools[name] = tool;
1088
+ }
1089
+ }
1090
+ return acc;
1091
+ }, {});
1092
+ };
1093
+
1094
+ // src/runtime/utils/idUtils.tsx
1095
+ import { customAlphabet } from "nanoid/non-secure";
1096
+ var generateId = customAlphabet(
1097
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
1098
+ 7
1099
+ );
1100
+ var optimisticPrefix = "__optimistic__";
1101
+ var generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
1102
+
1103
+ // src/runtime/utils/MessageRepository.tsx
1104
+ var findHead = (message) => {
1105
+ if (message.next) return findHead(message.next);
1106
+ return message;
1107
+ };
1108
+ var MessageRepository = class {
1109
+ messages = /* @__PURE__ */ new Map();
1110
+ // message_id -> item
1111
+ head = null;
1112
+ root = {
1113
+ children: []
1114
+ };
1115
+ performOp(newParent, child, operation) {
1116
+ const parentOrRoot = child.prev ?? this.root;
1117
+ const newParentOrRoot = newParent ?? this.root;
1118
+ if (operation === "relink" && parentOrRoot === newParentOrRoot) return;
1119
+ if (operation !== "link") {
1120
+ parentOrRoot.children = parentOrRoot.children.filter(
1121
+ (m) => m !== child.current.id
1122
+ );
1123
+ if (child.prev?.next === child) {
1124
+ const fallbackId = child.prev.children.at(-1);
1125
+ const fallback = fallbackId ? this.messages.get(fallbackId) : null;
1126
+ if (fallback === void 0) {
1127
+ throw new Error(
1128
+ "MessageRepository(performOp/cut): Fallback sibling message not found. This is likely an internal bug in assistant-ui."
1129
+ );
1130
+ }
1131
+ child.prev.next = fallback;
1132
+ }
1133
+ }
1134
+ if (operation !== "cut") {
1135
+ newParentOrRoot.children = [
1136
+ ...newParentOrRoot.children,
1137
+ child.current.id
1138
+ ];
1139
+ if (newParent && (findHead(child) === this.head || newParent.next === null)) {
1140
+ newParent.next = child;
1141
+ }
1142
+ child.prev = newParent;
1143
+ }
1144
+ }
1145
+ getMessages() {
1146
+ const messages = new Array(this.head?.level ?? 0);
1147
+ for (let current = this.head; current; current = current.prev) {
1148
+ messages[current.level] = current.current;
1149
+ }
1150
+ return messages;
1151
+ }
1152
+ addOrUpdateMessage(parentId, message) {
1153
+ const existingItem = this.messages.get(message.id);
1154
+ const prev = parentId ? this.messages.get(parentId) : null;
1155
+ if (prev === void 0)
1156
+ throw new Error(
1157
+ "MessageRepository(addOrUpdateMessage): Parent message not found. This is likely an internal bug in assistant-ui."
1158
+ );
1159
+ if (existingItem) {
1160
+ existingItem.current = message;
1161
+ this.performOp(prev, existingItem, "relink");
1162
+ return;
1163
+ }
1164
+ const newItem = {
1165
+ prev,
1166
+ current: message,
1167
+ next: null,
1168
+ children: [],
1169
+ level: prev ? prev.level + 1 : 0
1170
+ };
1171
+ this.messages.set(message.id, newItem);
1172
+ this.performOp(prev, newItem, "link");
1173
+ if (this.head === prev) {
1174
+ this.head = newItem;
1175
+ }
1176
+ }
1177
+ appendOptimisticMessage(parentId, message) {
1178
+ let optimisticId;
1179
+ do {
1180
+ optimisticId = generateOptimisticId();
1181
+ } while (this.messages.has(optimisticId));
1182
+ this.addOrUpdateMessage(parentId, {
1183
+ ...message,
1184
+ id: optimisticId,
1185
+ createdAt: /* @__PURE__ */ new Date(),
1186
+ ...message.role === "assistant" ? { status: "in_progress" } : void 0
1187
+ });
1188
+ return optimisticId;
1189
+ }
1190
+ deleteMessage(messageId, replacementId) {
1191
+ const message = this.messages.get(messageId);
1192
+ if (!message)
1193
+ throw new Error(
1194
+ "MessageRepository(deleteMessage): Optimistic message not found. This is likely an internal bug in assistant-ui."
1195
+ );
1196
+ const replacement = replacementId === void 0 ? message.prev : replacementId === null ? null : this.messages.get(replacementId);
1197
+ if (replacement === void 0)
1198
+ throw new Error(
1199
+ "MessageRepository(deleteMessage): Replacement not found. This is likely an internal bug in assistant-ui."
1200
+ );
1201
+ for (const child of message.children) {
1202
+ const childMessage = this.messages.get(child);
1203
+ if (!childMessage)
1204
+ throw new Error(
1205
+ "MessageRepository(deleteMessage): Child message not found. This is likely an internal bug in assistant-ui."
1206
+ );
1207
+ this.performOp(replacement, childMessage, "relink");
1208
+ }
1209
+ this.performOp(null, message, "cut");
1210
+ this.messages.delete(messageId);
1211
+ if (this.head === message) {
1212
+ this.head = replacement ? findHead(replacement) : null;
1213
+ }
1214
+ }
1215
+ getBranches(messageId) {
1216
+ const message = this.messages.get(messageId);
1217
+ if (!message)
1218
+ throw new Error(
1219
+ "MessageRepository(getBranches): Message not found. This is likely an internal bug in assistant-ui."
1220
+ );
1221
+ const { children } = message.prev ?? this.root;
1222
+ return children;
1223
+ }
1224
+ switchToBranch(messageId) {
1225
+ const message = this.messages.get(messageId);
1226
+ if (!message)
1227
+ throw new Error(
1228
+ "MessageRepository(switchToBranch): Branch not found. This is likely an internal bug in assistant-ui."
1229
+ );
1230
+ if (message.prev) {
1231
+ message.prev.next = message;
1232
+ }
1233
+ this.head = findHead(message);
1234
+ }
1235
+ resetHead(messageId) {
1236
+ if (messageId === null) {
1237
+ this.head = null;
1238
+ return;
1239
+ }
1240
+ const message = this.messages.get(messageId);
1241
+ if (!message)
1242
+ throw new Error(
1243
+ "MessageRepository(resetHead): Branch not found. This is likely an internal bug in assistant-ui."
1244
+ );
1245
+ this.head = message;
1246
+ for (let current = message; current; current = current.prev) {
1247
+ if (current.prev) {
1248
+ current.prev.next = current;
1249
+ }
1250
+ }
1251
+ }
1252
+ };
1253
+
1254
+ // src/runtime/local/LocalRuntime.tsx
1255
+ var LocalRuntime = class {
1256
+ constructor(adapter) {
1257
+ this.adapter = adapter;
1258
+ }
1259
+ _subscriptions = /* @__PURE__ */ new Set();
1260
+ _configProviders = /* @__PURE__ */ new Set();
1261
+ abortController = null;
1262
+ repository = new MessageRepository();
1263
+ get messages() {
1264
+ return this.repository.getMessages();
1265
+ }
1266
+ get isRunning() {
1267
+ return this.abortController != null;
1268
+ }
1269
+ getBranches(messageId) {
1270
+ return this.repository.getBranches(messageId);
1271
+ }
1272
+ switchToBranch(branchId) {
1273
+ this.repository.switchToBranch(branchId);
1274
+ this.notifySubscribers();
1275
+ }
1276
+ async append(message) {
1277
+ const userMessageId = generateId();
1278
+ const userMessage = {
1279
+ id: userMessageId,
1280
+ role: "user",
1281
+ content: message.content,
1282
+ createdAt: /* @__PURE__ */ new Date()
1283
+ };
1284
+ this.repository.addOrUpdateMessage(message.parentId, userMessage);
1285
+ await this.startRun(userMessageId);
1286
+ }
1287
+ async startRun(parentId) {
1288
+ const id = generateId();
1289
+ this.repository.resetHead(parentId);
1290
+ const messages = this.repository.getMessages();
1291
+ const message = {
1292
+ id,
1293
+ role: "assistant",
1294
+ status: "in_progress",
1295
+ content: [{ type: "text", text: "" }],
1296
+ createdAt: /* @__PURE__ */ new Date()
1297
+ };
1298
+ this.repository.addOrUpdateMessage(parentId, { ...message });
1299
+ this.abortController?.abort();
1300
+ this.abortController = new AbortController();
1301
+ this.notifySubscribers();
1302
+ try {
1303
+ const updateHandler = ({ content }) => {
1304
+ message.content = content;
1305
+ this.repository.addOrUpdateMessage(parentId, { ...message });
1306
+ this.notifySubscribers();
1307
+ };
1308
+ const result = await this.adapter.run({
1309
+ messages,
1310
+ abortSignal: this.abortController.signal,
1311
+ config: mergeModelConfigs(this._configProviders),
1312
+ onUpdate: updateHandler
1313
+ });
1314
+ updateHandler(result);
1315
+ message.status = "done";
1316
+ this.repository.addOrUpdateMessage(parentId, { ...message });
1317
+ } catch (e) {
1318
+ message.status = "error";
1319
+ this.repository.addOrUpdateMessage(parentId, { ...message });
1320
+ console.error(e);
1321
+ } finally {
1322
+ this.abortController = null;
1323
+ this.notifySubscribers();
1324
+ }
1325
+ }
1326
+ cancelRun() {
1327
+ if (!this.abortController) return;
1328
+ this.abortController.abort();
1329
+ this.abortController = null;
1330
+ this.notifySubscribers();
1331
+ }
1332
+ notifySubscribers() {
1333
+ for (const callback of this._subscriptions) callback();
1334
+ }
1335
+ subscribe(callback) {
1336
+ this._subscriptions.add(callback);
1337
+ return () => this._subscriptions.delete(callback);
1338
+ }
1339
+ registerModelConfigProvider(provider) {
1340
+ this._configProviders.add(provider);
1341
+ return () => this._configProviders.delete(provider);
1342
+ }
1343
+ };
1344
+
1345
+ // src/runtime/local/useLocalRuntime.tsx
1346
+ var useLocalRuntime = (adapter) => {
1347
+ const [runtime] = useState3(() => new LocalRuntime(adapter));
1348
+ useInsertionEffect(() => {
1349
+ runtime.adapter = adapter;
1350
+ });
1351
+ return runtime;
1352
+ };
1353
+
1354
+ // src/context/providers/AssistantRuntimeProvider.tsx
1355
+ import { memo as memo3 } from "react";
1356
+
1357
+ // src/context/providers/AssistantProvider.tsx
1358
+ import { useEffect as useEffect7, useInsertionEffect as useInsertionEffect3, useRef as useRef5, useState as useState5 } from "react";
1359
+
1360
+ // src/context/stores/AssistantModelConfig.ts
1361
+ import { create as create4 } from "zustand";
1362
+
1363
+ // src/utils/ProxyConfigProvider.ts
1364
+ var ProxyConfigProvider = class {
1365
+ _providers = /* @__PURE__ */ new Set();
1366
+ getModelConfig() {
1367
+ return mergeModelConfigs(this._providers);
1368
+ }
1369
+ registerModelConfigProvider(provider) {
1370
+ this._providers.add(provider);
1371
+ return () => {
1372
+ this._providers.delete(provider);
1373
+ };
1374
+ }
1375
+ };
1376
+
1377
+ // src/context/stores/AssistantModelConfig.ts
1378
+ var makeAssistantModelConfigStore = () => create4(() => {
1379
+ const proxy = new ProxyConfigProvider();
1380
+ return {
1381
+ getModelConfig: () => {
1382
+ return proxy.getModelConfig();
1383
+ },
1384
+ registerModelConfigProvider: (provider) => {
1385
+ return proxy.registerModelConfigProvider(provider);
1386
+ }
1387
+ };
1388
+ });
1389
+
1390
+ // src/context/stores/AssistantToolRenderers.ts
1391
+ import { create as create5 } from "zustand";
1392
+ var makeAssistantToolRenderersStore = () => create5((set) => {
1393
+ const renderers = /* @__PURE__ */ new Map();
1394
+ return {
1395
+ getToolRenderer: (name) => {
1396
+ const arr = renderers.get(name);
1397
+ const last = arr?.at(-1);
1398
+ if (last) return last;
1399
+ return null;
1400
+ },
1401
+ setToolRenderer: (name, render) => {
1402
+ let arr = renderers.get(name);
1403
+ if (!arr) {
1404
+ arr = [];
1405
+ renderers.set(name, arr);
1406
+ }
1407
+ arr.push(render);
1408
+ set({});
1409
+ return () => {
1410
+ const index = arr.indexOf(render);
1411
+ if (index !== -1) {
1412
+ arr.splice(index, 1);
1413
+ }
1414
+ set({});
1415
+ };
1416
+ }
1417
+ };
1418
+ });
1419
+
1420
+ // src/context/providers/ThreadProvider.tsx
1421
+ import { useEffect as useEffect6, useInsertionEffect as useInsertionEffect2, useRef as useRef4, useState as useState4 } from "react";
1422
+
1423
+ // src/context/stores/Composer.ts
1424
+ import { create as create6 } from "zustand";
1425
+ var makeComposerStore = (useThread) => create6()((set, get, store) => {
1426
+ return {
1427
+ ...makeBaseComposer(set, get, store),
1428
+ isEditing: true,
1429
+ send: () => {
1430
+ const { setValue, value } = get();
1431
+ setValue("");
1432
+ useThread.getState().append({
1433
+ parentId: useThread.getState().messages.at(-1)?.id ?? null,
1434
+ content: [{ type: "text", text: value }]
1435
+ });
1436
+ },
1437
+ cancel: () => {
1438
+ const thread = useThread.getState();
1439
+ if (!thread.isRunning) return false;
1440
+ useThread.getState().cancelRun();
1441
+ return true;
1442
+ }
1443
+ };
1444
+ });
1445
+
1446
+ // src/context/stores/Thread.ts
1447
+ import { create as create7 } from "zustand";
1448
+ var makeThreadStore = (runtimeRef) => {
1449
+ const useThread = create7(() => ({
1450
+ messages: runtimeRef.current.messages,
1451
+ isRunning: runtimeRef.current.isRunning,
1452
+ getBranches: (messageId) => runtimeRef.current.getBranches(messageId),
1453
+ switchToBranch: (branchId) => runtimeRef.current.switchToBranch(branchId),
1454
+ startRun: (parentId) => runtimeRef.current.startRun(parentId),
1455
+ append: (message) => runtimeRef.current.append(message),
1456
+ cancelRun: () => runtimeRef.current.cancelRun()
1457
+ }));
1458
+ const onRuntimeUpdate = () => {
1459
+ useThread.setState({
1460
+ messages: runtimeRef.current.messages,
1461
+ isRunning: runtimeRef.current.isRunning
1462
+ });
1463
+ };
1464
+ return {
1465
+ useThread,
1466
+ onRuntimeUpdate
1467
+ };
1468
+ };
1469
+
1470
+ // src/context/stores/ThreadViewport.tsx
1471
+ import { create as create8 } from "zustand";
1472
+ var makeThreadViewportStore = () => {
1473
+ const scrollToBottomListeners = /* @__PURE__ */ new Set();
1474
+ return create8(() => ({
1475
+ isAtBottom: true,
1476
+ scrollToBottom: () => {
1477
+ for (const listener of scrollToBottomListeners) {
1478
+ listener();
1479
+ }
1480
+ },
1481
+ onScrollToBottom: (callback) => {
1482
+ scrollToBottomListeners.add(callback);
1483
+ return () => {
1484
+ scrollToBottomListeners.delete(callback);
1485
+ };
1486
+ }
1487
+ }));
1488
+ };
1489
+
1490
+ // src/context/providers/ThreadProvider.tsx
1491
+ import { jsx as jsx23, jsxs as jsxs4 } from "react/jsx-runtime";
1492
+ var ThreadProvider = ({
1493
+ children,
1494
+ runtime
1495
+ }) => {
1496
+ const runtimeRef = useRef4(runtime);
1497
+ useInsertionEffect2(() => {
1498
+ runtimeRef.current = runtime;
1499
+ });
1500
+ const [{ context, onRuntimeUpdate }] = useState4(() => {
1501
+ const { useThread, onRuntimeUpdate: onRuntimeUpdate2 } = makeThreadStore(runtimeRef);
1502
+ const useViewport = makeThreadViewportStore();
1503
+ const useComposer = makeComposerStore(useThread);
1504
+ return {
1505
+ context: {
1506
+ useViewport,
1507
+ useThread,
1508
+ useComposer
1509
+ },
1510
+ onRuntimeUpdate: onRuntimeUpdate2
1511
+ };
1512
+ });
1513
+ useEffect6(() => {
1514
+ onRuntimeUpdate();
1515
+ return runtime.subscribe(onRuntimeUpdate);
1516
+ }, [onRuntimeUpdate, runtime]);
1517
+ const RuntimeSynchronizer = runtime.unstable_synchronizer;
1518
+ return /* @__PURE__ */ jsxs4(ThreadContext.Provider, { value: context, children: [
1519
+ RuntimeSynchronizer && /* @__PURE__ */ jsx23(RuntimeSynchronizer, {}),
1520
+ children
1521
+ ] });
1522
+ };
1523
+
1524
+ // src/context/providers/AssistantProvider.tsx
1525
+ import { jsx as jsx24 } from "react/jsx-runtime";
1526
+ var AssistantProvider = ({ children, runtime }) => {
1527
+ const runtimeRef = useRef5(runtime);
1528
+ useInsertionEffect3(() => {
1529
+ runtimeRef.current = runtime;
1530
+ });
1531
+ const [context] = useState5(() => {
1532
+ const useModelConfig = makeAssistantModelConfigStore();
1533
+ const useToolRenderers = makeAssistantToolRenderersStore();
1534
+ return { useModelConfig, useToolRenderers };
1535
+ });
1536
+ const getModelCOnfig = context.useModelConfig((c) => c.getModelConfig);
1537
+ useEffect7(() => {
1538
+ return runtime.registerModelConfigProvider(getModelCOnfig);
1539
+ }, [runtime, getModelCOnfig]);
1540
+ return /* @__PURE__ */ jsx24(AssistantContext.Provider, { value: context, children: /* @__PURE__ */ jsx24(ThreadProvider, { runtime, children }) });
1541
+ };
1542
+
1543
+ // src/context/providers/AssistantRuntimeProvider.tsx
1544
+ import { jsx as jsx25 } from "react/jsx-runtime";
1545
+ var AssistantRuntimeProviderImpl = ({ children, runtime }) => {
1546
+ return /* @__PURE__ */ jsx25(AssistantProvider, { runtime, children });
1547
+ };
1548
+ var AssistantRuntimeProvider = memo3(AssistantRuntimeProviderImpl);
1549
+
1550
+ // src/internal.ts
1551
+ var internal_exports = {};
1552
+ __export(internal_exports, {
1553
+ MessageRepository: () => MessageRepository,
1554
+ ProxyConfigProvider: () => ProxyConfigProvider
1555
+ });
1556
+ export {
1557
+ actionBar_exports as ActionBarPrimitive,
1558
+ AssistantRuntimeProvider,
1559
+ branchPicker_exports as BranchPickerPrimitive,
1560
+ composer_exports as ComposerPrimitive,
1561
+ contentPart_exports as ContentPartPrimitive,
1562
+ internal_exports as INTERNAL,
1563
+ message_exports as MessagePrimitive,
1564
+ thread_exports as ThreadPrimitive,
1565
+ useBeginMessageEdit,
1566
+ useCopyMessage,
1567
+ useGoToNextBranch,
1568
+ useGoToPreviousBranch,
1569
+ useLocalRuntime,
1570
+ useReloadMessage
1571
+ };
1572
+ //# sourceMappingURL=index.mjs.map