@assistant-ui/react 0.1.4 → 0.1.6

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,1574 @@
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
+ console.log("focus");
638
+ textarea.focus();
639
+ textarea.setSelectionRange(
640
+ textareaRef.current.value.length,
641
+ textareaRef.current.value.length
642
+ );
643
+ }, [autoFocusEnabled]);
644
+ useEffect4(() => focus(), [focus]);
645
+ useOnScrollToBottom(() => {
646
+ if (type === "new") {
647
+ focus();
648
+ }
649
+ });
650
+ return /* @__PURE__ */ jsx9(
651
+ Component,
652
+ {
653
+ value,
654
+ ...rest,
655
+ ref,
656
+ autoFocus,
657
+ disabled,
658
+ onChange: composeEventHandlers5(onChange, (e) => {
659
+ const composerState = useComposer.getState();
660
+ if (!composerState.isEditing) return;
661
+ return composerState.setValue(e.target.value);
662
+ }),
663
+ onKeyDown: composeEventHandlers5(onKeyDown, handleKeyPress)
664
+ }
665
+ );
666
+ }
667
+ );
668
+
669
+ // src/primitives/composer/ComposerSend.tsx
670
+ import {
671
+ Primitive as Primitive6
672
+ } from "@radix-ui/react-primitive";
673
+ import { forwardRef as forwardRef7 } from "react";
674
+ import { jsx as jsx10 } from "react/jsx-runtime";
675
+ var ComposerSend = forwardRef7(
676
+ ({ disabled, ...rest }, ref) => {
677
+ const { useComposer } = useComposerContext();
678
+ const hasValue = useComposer((c) => c.isEditing && c.value.length > 0);
679
+ return /* @__PURE__ */ jsx10(
680
+ Primitive6.button,
681
+ {
682
+ type: "submit",
683
+ ...rest,
684
+ ref,
685
+ disabled: disabled || !hasValue
686
+ }
687
+ );
688
+ }
689
+ );
690
+
691
+ // src/primitives/composer/ComposerCancel.tsx
692
+ import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
693
+ import {
694
+ Primitive as Primitive7
695
+ } from "@radix-ui/react-primitive";
696
+ import { forwardRef as forwardRef8 } from "react";
697
+ import { jsx as jsx11 } from "react/jsx-runtime";
698
+ var ComposerCancel = forwardRef8(({ onClick, ...rest }, ref) => {
699
+ const { useComposer } = useComposerContext();
700
+ const handleCancel = () => {
701
+ useComposer.getState().cancel();
702
+ };
703
+ return /* @__PURE__ */ jsx11(
704
+ Primitive7.button,
705
+ {
706
+ type: "button",
707
+ ...rest,
708
+ ref,
709
+ onClick: composeEventHandlers6(onClick, handleCancel)
710
+ }
711
+ );
712
+ });
713
+
714
+ // src/primitives/message/index.ts
715
+ var message_exports = {};
716
+ __export(message_exports, {
717
+ Content: () => MessageContent,
718
+ If: () => MessageIf,
719
+ InProgress: () => MessageInProgress,
720
+ Root: () => MessageRoot
721
+ });
722
+
723
+ // src/primitives/message/MessageRoot.tsx
724
+ import { composeEventHandlers as composeEventHandlers7 } from "@radix-ui/primitive";
725
+ import {
726
+ Primitive as Primitive8
727
+ } from "@radix-ui/react-primitive";
728
+ import { forwardRef as forwardRef9 } from "react";
729
+ import { jsx as jsx12 } from "react/jsx-runtime";
730
+ var MessageRoot = forwardRef9(
731
+ ({ onMouseEnter, onMouseLeave, ...rest }, ref) => {
732
+ const { useMessage } = useMessageContext();
733
+ const setIsHovering = useMessage((s) => s.setIsHovering);
734
+ const handleMouseEnter = () => {
735
+ setIsHovering(true);
736
+ };
737
+ const handleMouseLeave = () => {
738
+ setIsHovering(false);
739
+ };
740
+ return /* @__PURE__ */ jsx12(
741
+ Primitive8.div,
742
+ {
743
+ ...rest,
744
+ ref,
745
+ onMouseEnter: composeEventHandlers7(onMouseEnter, handleMouseEnter),
746
+ onMouseLeave: composeEventHandlers7(onMouseLeave, handleMouseLeave)
747
+ }
748
+ );
749
+ }
750
+ );
751
+
752
+ // src/primitives/message/MessageContent.tsx
753
+ import { memo as memo2 } from "react";
754
+
755
+ // src/context/providers/ContentPartProvider.tsx
756
+ import { useEffect as useEffect5, useState as useState2 } from "react";
757
+ import { create as create3 } from "zustand";
758
+ import { jsx as jsx13 } from "react/jsx-runtime";
759
+ var syncContentPart = ({ message }, useContentPart, partIndex) => {
760
+ const part = message.content[partIndex];
761
+ if (!part) return;
762
+ const messageStatus = message.role === "assistant" ? message.status : "done";
763
+ const status = partIndex === message.content.length - 1 ? messageStatus : "done";
764
+ const currentState = useContentPart.getState();
765
+ if (currentState.part === part && currentState.status === status) return;
766
+ useContentPart.setState({ part, status });
767
+ };
768
+ var useContentPartContext2 = (partIndex) => {
769
+ const { useMessage } = useMessageContext();
770
+ const [context] = useState2(() => {
771
+ const useContentPart = create3(() => ({
772
+ part: { type: "text", text: "" },
773
+ status: "done"
774
+ }));
775
+ syncContentPart(useMessage.getState(), useContentPart, partIndex);
776
+ return { useContentPart };
777
+ });
778
+ useEffect5(() => {
779
+ return useMessage.subscribe((message) => {
780
+ syncContentPart(message, context.useContentPart, partIndex);
781
+ });
782
+ }, [context, useMessage, partIndex]);
783
+ return context;
784
+ };
785
+ var ContentPartProvider = ({
786
+ partIndex,
787
+ children
788
+ }) => {
789
+ const context = useContentPartContext2(partIndex);
790
+ return /* @__PURE__ */ jsx13(ContentPartContext.Provider, { value: context, children });
791
+ };
792
+
793
+ // src/primitives/contentPart/ContentPartDisplay.tsx
794
+ var ContentPartDisplay = () => {
795
+ const { useContentPart } = useContentPartContext();
796
+ const display = useContentPart((c) => {
797
+ if (c.part.type !== "ui")
798
+ throw new Error(
799
+ "ContentPartDisplay can only be used inside ui content parts."
800
+ );
801
+ return c.part.display;
802
+ });
803
+ return display ?? null;
804
+ };
805
+
806
+ // src/primitives/contentPart/ContentPartInProgressIndicator.tsx
807
+ var ContentPartInProgressIndicator = () => {
808
+ const { useMessage } = useMessageContext();
809
+ const { useContentPart } = useContentPartContext();
810
+ const indicator = useCombinedStore(
811
+ [useMessage, useContentPart],
812
+ (m, c) => c.status === "in_progress" ? m.inProgressIndicator : null
813
+ );
814
+ return indicator;
815
+ };
816
+
817
+ // src/primitives/contentPart/ContentPartText.tsx
818
+ import {
819
+ Primitive as Primitive9
820
+ } from "@radix-ui/react-primitive";
821
+ import { forwardRef as forwardRef10 } from "react";
822
+ import { jsx as jsx14 } from "react/jsx-runtime";
823
+ var ContentPartText = forwardRef10((props, forwardedRef) => {
824
+ const { useContentPart } = useContentPartContext();
825
+ const text = useContentPart((c) => {
826
+ if (c.part.type !== "text")
827
+ throw new Error(
828
+ "ContentPartText can only be used inside text content parts."
829
+ );
830
+ return c.part.text;
831
+ });
832
+ return /* @__PURE__ */ jsx14(Primitive9.span, { ...props, ref: forwardedRef, children: text });
833
+ });
834
+
835
+ // src/primitives/message/MessageContent.tsx
836
+ import { Fragment, jsx as jsx15, jsxs as jsxs3 } from "react/jsx-runtime";
837
+ var defaultComponents = {
838
+ Text: () => /* @__PURE__ */ jsxs3(Fragment, { children: [
839
+ /* @__PURE__ */ jsx15(ContentPartText, {}),
840
+ /* @__PURE__ */ jsx15(ContentPartInProgressIndicator, {})
841
+ ] }),
842
+ Image: () => null,
843
+ UI: () => /* @__PURE__ */ jsx15(ContentPartDisplay, {}),
844
+ tools: {
845
+ Fallback: (props) => {
846
+ const { useToolRenderers } = useAssistantContext();
847
+ const Render = useToolRenderers(
848
+ (s) => s.getToolRenderer(props.part.toolName)
849
+ );
850
+ if (!Render) return null;
851
+ return /* @__PURE__ */ jsx15(Render, { ...props });
852
+ }
853
+ }
854
+ };
855
+ var MessageContentPartComponent = ({
856
+ components: {
857
+ Text = defaultComponents.Text,
858
+ Image = defaultComponents.Image,
859
+ UI = defaultComponents.UI,
860
+ tools: { by_name = {}, Fallback = defaultComponents.tools.Fallback } = {}
861
+ } = {}
862
+ }) => {
863
+ const { useContentPart } = useContentPartContext();
864
+ const { part, status } = useContentPart();
865
+ const type = part.type;
866
+ switch (type) {
867
+ case "text":
868
+ return /* @__PURE__ */ jsx15(Text, { part, status });
869
+ case "image":
870
+ return /* @__PURE__ */ jsx15(Image, { part, status });
871
+ case "ui":
872
+ return /* @__PURE__ */ jsx15(UI, { part, status });
873
+ case "tool-call": {
874
+ const Tool = by_name[part.toolName] || Fallback;
875
+ return /* @__PURE__ */ jsx15(Tool, { part, status });
876
+ }
877
+ default:
878
+ throw new Error(`Unknown content part type: ${type}`);
879
+ }
880
+ };
881
+ var MessageContentPartImpl = ({
882
+ partIndex,
883
+ components
884
+ }) => {
885
+ return /* @__PURE__ */ jsx15(ContentPartProvider, { partIndex, children: /* @__PURE__ */ jsx15(MessageContentPartComponent, { components }) });
886
+ };
887
+ var MessageContentPart = memo2(
888
+ MessageContentPartImpl,
889
+ (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
890
+ );
891
+ var MessageContent = ({ components }) => {
892
+ const { useMessage } = useMessageContext();
893
+ const contentLength = useMessage((s) => s.message.content.length);
894
+ return new Array(contentLength).fill(null).map((_, idx) => {
895
+ const partIndex = idx;
896
+ return /* @__PURE__ */ jsx15(
897
+ MessageContentPart,
898
+ {
899
+ partIndex,
900
+ components
901
+ },
902
+ partIndex
903
+ );
904
+ });
905
+ };
906
+
907
+ // src/primitives/message/MessageInProgress.tsx
908
+ import {
909
+ Primitive as Primitive10
910
+ } from "@radix-ui/react-primitive";
911
+ import { forwardRef as forwardRef11, useMemo as useMemo2 } from "react";
912
+ import { jsx as jsx16 } from "react/jsx-runtime";
913
+ var MessageInProgress = forwardRef11((props, ref) => {
914
+ const { useMessage } = useMessageContext();
915
+ useMemo2(() => {
916
+ useMessage.getState().setInProgressIndicator(/* @__PURE__ */ jsx16(Primitive10.span, { ...props, ref }));
917
+ }, [useMessage, props, ref]);
918
+ return null;
919
+ });
920
+
921
+ // src/primitives/branchPicker/index.ts
922
+ var branchPicker_exports = {};
923
+ __export(branchPicker_exports, {
924
+ Count: () => BranchPickerCount,
925
+ Next: () => BranchPickerNext,
926
+ Number: () => BranchPickerNumber,
927
+ Previous: () => BranchPickerPrevious,
928
+ Root: () => BranchPickerRoot
929
+ });
930
+
931
+ // src/utils/createActionButton.tsx
932
+ import { composeEventHandlers as composeEventHandlers8 } from "@radix-ui/primitive";
933
+ import {
934
+ Primitive as Primitive11
935
+ } from "@radix-ui/react-primitive";
936
+ import { forwardRef as forwardRef12 } from "react";
937
+ import { jsx as jsx17 } from "react/jsx-runtime";
938
+ var createActionButton = (useActionButton) => {
939
+ return forwardRef12(
940
+ (props, forwardedRef) => {
941
+ const onClick = useActionButton(props);
942
+ return /* @__PURE__ */ jsx17(
943
+ Primitive11.button,
944
+ {
945
+ type: "button",
946
+ disabled: !onClick,
947
+ ...props,
948
+ ref: forwardedRef,
949
+ onClick: composeEventHandlers8(props.onClick, onClick ?? void 0)
950
+ }
951
+ );
952
+ }
953
+ );
954
+ };
955
+
956
+ // src/primitives/branchPicker/BranchPickerNext.tsx
957
+ var BranchPickerNext = createActionButton(useGoToNextBranch);
958
+
959
+ // src/primitives/branchPicker/BranchPickerPrevious.tsx
960
+ var BranchPickerPrevious = createActionButton(useGoToPreviousBranch);
961
+
962
+ // src/primitives/branchPicker/BranchPickerCount.tsx
963
+ import { Fragment as Fragment2, jsx as jsx18 } from "react/jsx-runtime";
964
+ var BranchPickerCount = () => {
965
+ const { useMessage } = useMessageContext();
966
+ const branchCount = useMessage((s) => s.branches.length);
967
+ return /* @__PURE__ */ jsx18(Fragment2, { children: branchCount });
968
+ };
969
+
970
+ // src/primitives/branchPicker/BranchPickerNumber.tsx
971
+ import { Fragment as Fragment3, jsx as jsx19 } from "react/jsx-runtime";
972
+ var BranchPickerNumber = () => {
973
+ const { useMessage } = useMessageContext();
974
+ const branchIdx = useMessage((s) => s.branches.indexOf(s.message.id));
975
+ return /* @__PURE__ */ jsx19(Fragment3, { children: branchIdx + 1 });
976
+ };
977
+
978
+ // src/primitives/branchPicker/BranchPickerRoot.tsx
979
+ import {
980
+ Primitive as Primitive12
981
+ } from "@radix-ui/react-primitive";
982
+ import { forwardRef as forwardRef13 } from "react";
983
+ import { jsx as jsx20 } from "react/jsx-runtime";
984
+ var BranchPickerRoot = forwardRef13(({ hideWhenSingleBranch, ...rest }, ref) => {
985
+ return /* @__PURE__ */ jsx20(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0, children: /* @__PURE__ */ jsx20(Primitive12.div, { ...rest, ref }) });
986
+ });
987
+
988
+ // src/primitives/actionBar/index.ts
989
+ var actionBar_exports = {};
990
+ __export(actionBar_exports, {
991
+ Copy: () => ActionBarCopy,
992
+ Edit: () => ActionBarEdit,
993
+ Reload: () => ActionBarReload,
994
+ Root: () => ActionBarRoot
995
+ });
996
+
997
+ // src/primitives/actionBar/ActionBarRoot.tsx
998
+ import {
999
+ Primitive as Primitive13
1000
+ } from "@radix-ui/react-primitive";
1001
+ import { forwardRef as forwardRef14 } from "react";
1002
+ import { jsx as jsx21 } from "react/jsx-runtime";
1003
+ var ActionBarRoot = forwardRef14(({ hideWhenRunning, autohide, autohideFloat, ...rest }, ref) => {
1004
+ const { useThread } = useThreadContext();
1005
+ const { useMessage } = useMessageContext();
1006
+ const hideAndfloatStatus = useCombinedStore(
1007
+ [useThread, useMessage],
1008
+ (t, m) => {
1009
+ if (hideWhenRunning && t.isRunning) return "hidden" /* Hidden */;
1010
+ const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
1011
+ if (!autohideEnabled) return "normal" /* Normal */;
1012
+ if (!m.isHovering) return "hidden" /* Hidden */;
1013
+ if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branches.length <= 1)
1014
+ return "floating" /* Floating */;
1015
+ return "normal" /* Normal */;
1016
+ }
1017
+ );
1018
+ if (hideAndfloatStatus === "hidden" /* Hidden */) return null;
1019
+ return /* @__PURE__ */ jsx21(
1020
+ Primitive13.div,
1021
+ {
1022
+ ...hideAndfloatStatus === "floating" /* Floating */ ? { "data-floating": "true" } : null,
1023
+ ...rest,
1024
+ ref
1025
+ }
1026
+ );
1027
+ });
1028
+
1029
+ // src/primitives/actionBar/ActionBarCopy.tsx
1030
+ var ActionBarCopy = createActionButton(useCopyMessage);
1031
+
1032
+ // src/primitives/actionBar/ActionBarReload.tsx
1033
+ var ActionBarReload = createActionButton(useReloadMessage);
1034
+
1035
+ // src/primitives/actionBar/ActionBarEdit.tsx
1036
+ var ActionBarEdit = createActionButton(useBeginMessageEdit);
1037
+
1038
+ // src/primitives/contentPart/index.ts
1039
+ var contentPart_exports = {};
1040
+ __export(contentPart_exports, {
1041
+ Display: () => ContentPartDisplay,
1042
+ Image: () => ContentPartImage,
1043
+ InProgressIndicator: () => ContentPartInProgressIndicator,
1044
+ Text: () => ContentPartText
1045
+ });
1046
+
1047
+ // src/primitives/contentPart/ContentPartImage.tsx
1048
+ import {
1049
+ Primitive as Primitive14
1050
+ } from "@radix-ui/react-primitive";
1051
+ import { forwardRef as forwardRef15 } from "react";
1052
+ import { jsx as jsx22 } from "react/jsx-runtime";
1053
+ var ContentPartImage = forwardRef15((props, forwardedRef) => {
1054
+ const { useContentPart } = useContentPartContext();
1055
+ const image = useContentPart((c) => {
1056
+ if (c.part.type !== "image")
1057
+ throw new Error(
1058
+ "ContentPartImage can only be used inside image content parts."
1059
+ );
1060
+ return c.part.image;
1061
+ });
1062
+ return /* @__PURE__ */ jsx22(Primitive14.img, { src: image, ...props, ref: forwardedRef });
1063
+ });
1064
+
1065
+ // src/runtime/local/useLocalRuntime.tsx
1066
+ import { useInsertionEffect, useState as useState3 } from "react";
1067
+
1068
+ // src/utils/ModelConfigTypes.ts
1069
+ var mergeModelConfigs = (configSet) => {
1070
+ const configs = Array.from(configSet).map((c) => c()).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
1071
+ return configs.reduce((acc, config) => {
1072
+ if (config.system) {
1073
+ if (acc.system) {
1074
+ acc.system += `
1075
+
1076
+ ${config.system}`;
1077
+ } else {
1078
+ acc.system = config.system;
1079
+ }
1080
+ }
1081
+ if (config.tools) {
1082
+ for (const [name, tool] of Object.entries(config.tools)) {
1083
+ if (acc.tools?.[name]) {
1084
+ throw new Error(
1085
+ `You tried to define a tool with the name ${name}, but it already exists.`
1086
+ );
1087
+ }
1088
+ if (!acc.tools) acc.tools = {};
1089
+ acc.tools[name] = tool;
1090
+ }
1091
+ }
1092
+ return acc;
1093
+ }, {});
1094
+ };
1095
+
1096
+ // src/runtime/utils/idUtils.tsx
1097
+ import { customAlphabet } from "nanoid/non-secure";
1098
+ var generateId = customAlphabet(
1099
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
1100
+ 7
1101
+ );
1102
+ var optimisticPrefix = "__optimistic__";
1103
+ var generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
1104
+
1105
+ // src/runtime/utils/MessageRepository.tsx
1106
+ var findHead = (message) => {
1107
+ if (message.next) return findHead(message.next);
1108
+ return message;
1109
+ };
1110
+ var MessageRepository = class {
1111
+ messages = /* @__PURE__ */ new Map();
1112
+ // message_id -> item
1113
+ head = null;
1114
+ root = {
1115
+ children: []
1116
+ };
1117
+ performOp(newParent, child, operation) {
1118
+ const parentOrRoot = child.prev ?? this.root;
1119
+ const newParentOrRoot = newParent ?? this.root;
1120
+ if (operation === "relink" && parentOrRoot === newParentOrRoot) return;
1121
+ if (operation !== "link") {
1122
+ parentOrRoot.children = parentOrRoot.children.filter(
1123
+ (m) => m !== child.current.id
1124
+ );
1125
+ if (child.prev?.next === child) {
1126
+ const fallbackId = child.prev.children.at(-1);
1127
+ const fallback = fallbackId ? this.messages.get(fallbackId) : null;
1128
+ if (fallback === void 0) {
1129
+ throw new Error(
1130
+ "MessageRepository(performOp/cut): Fallback sibling message not found. This is likely an internal bug in assistant-ui."
1131
+ );
1132
+ }
1133
+ child.prev.next = fallback;
1134
+ }
1135
+ }
1136
+ if (operation !== "cut") {
1137
+ newParentOrRoot.children = [
1138
+ ...newParentOrRoot.children,
1139
+ child.current.id
1140
+ ];
1141
+ if (newParent && (findHead(child) === this.head || newParent.next === null)) {
1142
+ newParent.next = child;
1143
+ }
1144
+ child.prev = newParent;
1145
+ }
1146
+ }
1147
+ getMessages() {
1148
+ const messages = new Array(this.head?.level ?? 0);
1149
+ for (let current = this.head; current; current = current.prev) {
1150
+ messages[current.level] = current.current;
1151
+ }
1152
+ return messages;
1153
+ }
1154
+ addOrUpdateMessage(parentId, message) {
1155
+ const existingItem = this.messages.get(message.id);
1156
+ const prev = parentId ? this.messages.get(parentId) : null;
1157
+ if (prev === void 0)
1158
+ throw new Error(
1159
+ "MessageRepository(addOrUpdateMessage): Parent message not found. This is likely an internal bug in assistant-ui."
1160
+ );
1161
+ if (existingItem) {
1162
+ existingItem.current = message;
1163
+ this.performOp(prev, existingItem, "relink");
1164
+ return;
1165
+ }
1166
+ const newItem = {
1167
+ prev,
1168
+ current: message,
1169
+ next: null,
1170
+ children: [],
1171
+ level: prev ? prev.level + 1 : 0
1172
+ };
1173
+ this.messages.set(message.id, newItem);
1174
+ this.performOp(prev, newItem, "link");
1175
+ if (this.head === prev) {
1176
+ this.head = newItem;
1177
+ }
1178
+ }
1179
+ appendOptimisticMessage(parentId, message) {
1180
+ let optimisticId;
1181
+ do {
1182
+ optimisticId = generateOptimisticId();
1183
+ } while (this.messages.has(optimisticId));
1184
+ this.addOrUpdateMessage(parentId, {
1185
+ ...message,
1186
+ id: optimisticId,
1187
+ createdAt: /* @__PURE__ */ new Date(),
1188
+ ...message.role === "assistant" ? { status: "in_progress" } : void 0
1189
+ });
1190
+ return optimisticId;
1191
+ }
1192
+ deleteMessage(messageId, replacementId) {
1193
+ const message = this.messages.get(messageId);
1194
+ if (!message)
1195
+ throw new Error(
1196
+ "MessageRepository(deleteMessage): Optimistic message not found. This is likely an internal bug in assistant-ui."
1197
+ );
1198
+ const replacement = replacementId === void 0 ? message.prev : replacementId === null ? null : this.messages.get(replacementId);
1199
+ if (replacement === void 0)
1200
+ throw new Error(
1201
+ "MessageRepository(deleteMessage): Replacement not found. This is likely an internal bug in assistant-ui."
1202
+ );
1203
+ for (const child of message.children) {
1204
+ const childMessage = this.messages.get(child);
1205
+ if (!childMessage)
1206
+ throw new Error(
1207
+ "MessageRepository(deleteMessage): Child message not found. This is likely an internal bug in assistant-ui."
1208
+ );
1209
+ this.performOp(replacement, childMessage, "relink");
1210
+ }
1211
+ this.performOp(null, message, "cut");
1212
+ this.messages.delete(messageId);
1213
+ if (this.head === message) {
1214
+ this.head = replacement ? findHead(replacement) : null;
1215
+ }
1216
+ }
1217
+ getBranches(messageId) {
1218
+ const message = this.messages.get(messageId);
1219
+ if (!message)
1220
+ throw new Error(
1221
+ "MessageRepository(getBranches): Message not found. This is likely an internal bug in assistant-ui."
1222
+ );
1223
+ const { children } = message.prev ?? this.root;
1224
+ return children;
1225
+ }
1226
+ switchToBranch(messageId) {
1227
+ const message = this.messages.get(messageId);
1228
+ if (!message)
1229
+ throw new Error(
1230
+ "MessageRepository(switchToBranch): Branch not found. This is likely an internal bug in assistant-ui."
1231
+ );
1232
+ if (message.prev) {
1233
+ message.prev.next = message;
1234
+ }
1235
+ this.head = findHead(message);
1236
+ }
1237
+ resetHead(messageId) {
1238
+ if (messageId === null) {
1239
+ this.head = null;
1240
+ return;
1241
+ }
1242
+ const message = this.messages.get(messageId);
1243
+ if (!message)
1244
+ throw new Error(
1245
+ "MessageRepository(resetHead): Branch not found. This is likely an internal bug in assistant-ui."
1246
+ );
1247
+ this.head = message;
1248
+ for (let current = message; current; current = current.prev) {
1249
+ if (current.prev) {
1250
+ current.prev.next = current;
1251
+ }
1252
+ }
1253
+ }
1254
+ };
1255
+
1256
+ // src/runtime/local/LocalRuntime.tsx
1257
+ var LocalRuntime = class {
1258
+ constructor(adapter) {
1259
+ this.adapter = adapter;
1260
+ }
1261
+ _subscriptions = /* @__PURE__ */ new Set();
1262
+ _configProviders = /* @__PURE__ */ new Set();
1263
+ abortController = null;
1264
+ repository = new MessageRepository();
1265
+ get messages() {
1266
+ return this.repository.getMessages();
1267
+ }
1268
+ get isRunning() {
1269
+ return this.abortController != null;
1270
+ }
1271
+ getBranches(messageId) {
1272
+ return this.repository.getBranches(messageId);
1273
+ }
1274
+ switchToBranch(branchId) {
1275
+ this.repository.switchToBranch(branchId);
1276
+ this.notifySubscribers();
1277
+ }
1278
+ async append(message) {
1279
+ const userMessageId = generateId();
1280
+ const userMessage = {
1281
+ id: userMessageId,
1282
+ role: "user",
1283
+ content: message.content,
1284
+ createdAt: /* @__PURE__ */ new Date()
1285
+ };
1286
+ this.repository.addOrUpdateMessage(message.parentId, userMessage);
1287
+ await this.startRun(userMessageId);
1288
+ }
1289
+ async startRun(parentId) {
1290
+ const id = generateId();
1291
+ this.repository.resetHead(parentId);
1292
+ const messages = this.repository.getMessages();
1293
+ const message = {
1294
+ id,
1295
+ role: "assistant",
1296
+ status: "in_progress",
1297
+ content: [{ type: "text", text: "" }],
1298
+ createdAt: /* @__PURE__ */ new Date()
1299
+ };
1300
+ this.repository.addOrUpdateMessage(parentId, { ...message });
1301
+ this.abortController?.abort();
1302
+ this.abortController = new AbortController();
1303
+ this.notifySubscribers();
1304
+ try {
1305
+ const updateHandler = ({ content }) => {
1306
+ message.content = content;
1307
+ this.repository.addOrUpdateMessage(parentId, { ...message });
1308
+ this.notifySubscribers();
1309
+ };
1310
+ const result = await this.adapter.run({
1311
+ messages,
1312
+ abortSignal: this.abortController.signal,
1313
+ config: mergeModelConfigs(this._configProviders),
1314
+ onUpdate: updateHandler
1315
+ });
1316
+ updateHandler(result);
1317
+ message.status = "done";
1318
+ this.repository.addOrUpdateMessage(parentId, { ...message });
1319
+ } catch (e) {
1320
+ message.status = "error";
1321
+ this.repository.addOrUpdateMessage(parentId, { ...message });
1322
+ console.error(e);
1323
+ } finally {
1324
+ this.abortController = null;
1325
+ this.notifySubscribers();
1326
+ }
1327
+ }
1328
+ cancelRun() {
1329
+ if (!this.abortController) return;
1330
+ this.abortController.abort();
1331
+ this.abortController = null;
1332
+ this.notifySubscribers();
1333
+ }
1334
+ notifySubscribers() {
1335
+ for (const callback of this._subscriptions) callback();
1336
+ }
1337
+ subscribe(callback) {
1338
+ this._subscriptions.add(callback);
1339
+ return () => this._subscriptions.delete(callback);
1340
+ }
1341
+ registerModelConfigProvider(provider) {
1342
+ this._configProviders.add(provider);
1343
+ return () => this._configProviders.delete(provider);
1344
+ }
1345
+ };
1346
+
1347
+ // src/runtime/local/useLocalRuntime.tsx
1348
+ var useLocalRuntime = (adapter) => {
1349
+ const [runtime] = useState3(() => new LocalRuntime(adapter));
1350
+ useInsertionEffect(() => {
1351
+ runtime.adapter = adapter;
1352
+ });
1353
+ return runtime;
1354
+ };
1355
+
1356
+ // src/context/providers/AssistantRuntimeProvider.tsx
1357
+ import { memo as memo3 } from "react";
1358
+
1359
+ // src/context/providers/AssistantProvider.tsx
1360
+ import { useEffect as useEffect7, useInsertionEffect as useInsertionEffect3, useRef as useRef5, useState as useState5 } from "react";
1361
+
1362
+ // src/context/stores/AssistantModelConfig.ts
1363
+ import { create as create4 } from "zustand";
1364
+
1365
+ // src/utils/ProxyConfigProvider.ts
1366
+ var ProxyConfigProvider = class {
1367
+ _providers = /* @__PURE__ */ new Set();
1368
+ getModelConfig() {
1369
+ return mergeModelConfigs(this._providers);
1370
+ }
1371
+ registerModelConfigProvider(provider) {
1372
+ this._providers.add(provider);
1373
+ return () => {
1374
+ this._providers.delete(provider);
1375
+ };
1376
+ }
1377
+ };
1378
+
1379
+ // src/context/stores/AssistantModelConfig.ts
1380
+ var makeAssistantModelConfigStore = () => create4(() => {
1381
+ const proxy = new ProxyConfigProvider();
1382
+ return {
1383
+ getModelConfig: () => {
1384
+ return proxy.getModelConfig();
1385
+ },
1386
+ registerModelConfigProvider: (provider) => {
1387
+ return proxy.registerModelConfigProvider(provider);
1388
+ }
1389
+ };
1390
+ });
1391
+
1392
+ // src/context/stores/AssistantToolRenderers.ts
1393
+ import { create as create5 } from "zustand";
1394
+ var makeAssistantToolRenderersStore = () => create5((set) => {
1395
+ const renderers = /* @__PURE__ */ new Map();
1396
+ return {
1397
+ getToolRenderer: (name) => {
1398
+ const arr = renderers.get(name);
1399
+ const last = arr?.at(-1);
1400
+ if (last) return last;
1401
+ return null;
1402
+ },
1403
+ setToolRenderer: (name, render) => {
1404
+ let arr = renderers.get(name);
1405
+ if (!arr) {
1406
+ arr = [];
1407
+ renderers.set(name, arr);
1408
+ }
1409
+ arr.push(render);
1410
+ set({});
1411
+ return () => {
1412
+ const index = arr.indexOf(render);
1413
+ if (index !== -1) {
1414
+ arr.splice(index, 1);
1415
+ }
1416
+ set({});
1417
+ };
1418
+ }
1419
+ };
1420
+ });
1421
+
1422
+ // src/context/providers/ThreadProvider.tsx
1423
+ import { useEffect as useEffect6, useInsertionEffect as useInsertionEffect2, useRef as useRef4, useState as useState4 } from "react";
1424
+
1425
+ // src/context/stores/Composer.ts
1426
+ import { create as create6 } from "zustand";
1427
+ var makeComposerStore = (useThread) => create6()((set, get, store) => {
1428
+ return {
1429
+ ...makeBaseComposer(set, get, store),
1430
+ isEditing: true,
1431
+ send: () => {
1432
+ const { setValue, value } = get();
1433
+ setValue("");
1434
+ useThread.getState().append({
1435
+ parentId: useThread.getState().messages.at(-1)?.id ?? null,
1436
+ content: [{ type: "text", text: value }]
1437
+ });
1438
+ },
1439
+ cancel: () => {
1440
+ const thread = useThread.getState();
1441
+ if (!thread.isRunning) return false;
1442
+ useThread.getState().cancelRun();
1443
+ return true;
1444
+ }
1445
+ };
1446
+ });
1447
+
1448
+ // src/context/stores/Thread.ts
1449
+ import { create as create7 } from "zustand";
1450
+ var makeThreadStore = (runtimeRef) => {
1451
+ const useThread = create7(() => ({
1452
+ messages: runtimeRef.current.messages,
1453
+ isRunning: runtimeRef.current.isRunning,
1454
+ getBranches: (messageId) => runtimeRef.current.getBranches(messageId),
1455
+ switchToBranch: (branchId) => runtimeRef.current.switchToBranch(branchId),
1456
+ startRun: (parentId) => runtimeRef.current.startRun(parentId),
1457
+ append: (message) => runtimeRef.current.append(message),
1458
+ cancelRun: () => runtimeRef.current.cancelRun()
1459
+ }));
1460
+ const onRuntimeUpdate = () => {
1461
+ useThread.setState({
1462
+ messages: runtimeRef.current.messages,
1463
+ isRunning: runtimeRef.current.isRunning
1464
+ });
1465
+ };
1466
+ return {
1467
+ useThread,
1468
+ onRuntimeUpdate
1469
+ };
1470
+ };
1471
+
1472
+ // src/context/stores/ThreadViewport.tsx
1473
+ import { create as create8 } from "zustand";
1474
+ var makeThreadViewportStore = () => {
1475
+ const scrollToBottomListeners = /* @__PURE__ */ new Set();
1476
+ return create8(() => ({
1477
+ isAtBottom: true,
1478
+ scrollToBottom: () => {
1479
+ for (const listener of scrollToBottomListeners) {
1480
+ listener();
1481
+ }
1482
+ },
1483
+ onScrollToBottom: (callback) => {
1484
+ scrollToBottomListeners.add(callback);
1485
+ return () => {
1486
+ scrollToBottomListeners.delete(callback);
1487
+ };
1488
+ }
1489
+ }));
1490
+ };
1491
+
1492
+ // src/context/providers/ThreadProvider.tsx
1493
+ import { jsx as jsx23, jsxs as jsxs4 } from "react/jsx-runtime";
1494
+ var ThreadProvider = ({
1495
+ children,
1496
+ runtime
1497
+ }) => {
1498
+ const runtimeRef = useRef4(runtime);
1499
+ useInsertionEffect2(() => {
1500
+ runtimeRef.current = runtime;
1501
+ });
1502
+ const [{ context, onRuntimeUpdate }] = useState4(() => {
1503
+ const { useThread, onRuntimeUpdate: onRuntimeUpdate2 } = makeThreadStore(runtimeRef);
1504
+ const useViewport = makeThreadViewportStore();
1505
+ const useComposer = makeComposerStore(useThread);
1506
+ return {
1507
+ context: {
1508
+ useViewport,
1509
+ useThread,
1510
+ useComposer
1511
+ },
1512
+ onRuntimeUpdate: onRuntimeUpdate2
1513
+ };
1514
+ });
1515
+ useEffect6(() => {
1516
+ onRuntimeUpdate();
1517
+ return runtime.subscribe(onRuntimeUpdate);
1518
+ }, [onRuntimeUpdate, runtime]);
1519
+ const RuntimeSynchronizer = runtime.unstable_synchronizer;
1520
+ return /* @__PURE__ */ jsxs4(ThreadContext.Provider, { value: context, children: [
1521
+ RuntimeSynchronizer && /* @__PURE__ */ jsx23(RuntimeSynchronizer, {}),
1522
+ children
1523
+ ] });
1524
+ };
1525
+
1526
+ // src/context/providers/AssistantProvider.tsx
1527
+ import { jsx as jsx24 } from "react/jsx-runtime";
1528
+ var AssistantProvider = ({ children, runtime }) => {
1529
+ const runtimeRef = useRef5(runtime);
1530
+ useInsertionEffect3(() => {
1531
+ runtimeRef.current = runtime;
1532
+ });
1533
+ const [context] = useState5(() => {
1534
+ const useModelConfig = makeAssistantModelConfigStore();
1535
+ const useToolRenderers = makeAssistantToolRenderersStore();
1536
+ return { useModelConfig, useToolRenderers };
1537
+ });
1538
+ const getModelCOnfig = context.useModelConfig((c) => c.getModelConfig);
1539
+ useEffect7(() => {
1540
+ return runtime.registerModelConfigProvider(getModelCOnfig);
1541
+ }, [runtime, getModelCOnfig]);
1542
+ return /* @__PURE__ */ jsx24(AssistantContext.Provider, { value: context, children: /* @__PURE__ */ jsx24(ThreadProvider, { runtime, children }) });
1543
+ };
1544
+
1545
+ // src/context/providers/AssistantRuntimeProvider.tsx
1546
+ import { jsx as jsx25 } from "react/jsx-runtime";
1547
+ var AssistantRuntimeProviderImpl = ({ children, runtime }) => {
1548
+ return /* @__PURE__ */ jsx25(AssistantProvider, { runtime, children });
1549
+ };
1550
+ var AssistantRuntimeProvider = memo3(AssistantRuntimeProviderImpl);
1551
+
1552
+ // src/internal.ts
1553
+ var internal_exports = {};
1554
+ __export(internal_exports, {
1555
+ MessageRepository: () => MessageRepository,
1556
+ ProxyConfigProvider: () => ProxyConfigProvider
1557
+ });
1558
+ export {
1559
+ actionBar_exports as ActionBarPrimitive,
1560
+ AssistantRuntimeProvider,
1561
+ branchPicker_exports as BranchPickerPrimitive,
1562
+ composer_exports as ComposerPrimitive,
1563
+ contentPart_exports as ContentPartPrimitive,
1564
+ internal_exports as INTERNAL,
1565
+ message_exports as MessagePrimitive,
1566
+ thread_exports as ThreadPrimitive,
1567
+ useBeginMessageEdit,
1568
+ useCopyMessage,
1569
+ useGoToNextBranch,
1570
+ useGoToPreviousBranch,
1571
+ useLocalRuntime,
1572
+ useReloadMessage
1573
+ };
1574
+ //# sourceMappingURL=index.mjs.map