@assistant-ui/react 0.0.14 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -35,7 +35,7 @@ var useAssistantContext = () => {
35
35
  const context = useContext(AssistantContext);
36
36
  if (!context)
37
37
  throw new Error(
38
- "This component must be used within a AssistantProvider"
38
+ "This component must be used within a AssistantProvider."
39
39
  );
40
40
  return context;
41
41
  };
@@ -44,14 +44,10 @@ var useAssistantContext = () => {
44
44
  var useThreadIf = (props) => {
45
45
  const { useThread } = useAssistantContext();
46
46
  return useThread((thread) => {
47
- if (props.empty === true && thread.messages.length !== 0)
48
- return false;
49
- if (props.empty === false && thread.messages.length === 0)
50
- return false;
51
- if (props.running === true && !thread.isRunning)
52
- return false;
53
- if (props.running === false && thread.isRunning)
54
- return false;
47
+ if (props.empty === true && thread.messages.length !== 0) return false;
48
+ if (props.empty === false && thread.messages.length === 0) return false;
49
+ if (props.running === true && !thread.isRunning) return false;
50
+ if (props.running === false && thread.isRunning) return false;
55
51
  return true;
56
52
  });
57
53
  };
@@ -75,14 +71,13 @@ import {
75
71
  import { forwardRef as forwardRef2, useRef as useRef3 } from "react";
76
72
 
77
73
  // src/utils/hooks/useOnResizeContent.tsx
78
- import { useLayoutEffect, useRef } from "react";
74
+ import { useEffect, useRef } from "react";
79
75
  var useOnResizeContent = (ref, callback) => {
80
76
  const callbackRef = useRef(callback);
81
77
  callbackRef.current = callback;
82
- useLayoutEffect(() => {
78
+ useEffect(() => {
83
79
  const el = ref.current;
84
- if (!el)
85
- return;
80
+ if (!el) return;
86
81
  const resizeObserver = new ResizeObserver(() => {
87
82
  callbackRef.current();
88
83
  });
@@ -114,12 +109,12 @@ var useOnResizeContent = (ref, callback) => {
114
109
  };
115
110
 
116
111
  // src/utils/hooks/useOnScrollToBottom.tsx
117
- import { useEffect, useRef as useRef2 } from "react";
112
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
118
113
  var useOnScrollToBottom = (callback) => {
119
114
  const callbackRef = useRef2(callback);
120
115
  callbackRef.current = callback;
121
116
  const { useViewport } = useAssistantContext();
122
- useEffect(() => {
117
+ useEffect2(() => {
123
118
  return useViewport.getState().onScrollToBottom(() => {
124
119
  callbackRef.current();
125
120
  });
@@ -137,16 +132,14 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
137
132
  const lastScrollTop = useRef3(0);
138
133
  const scrollToBottom = () => {
139
134
  const div = messagesEndRef.current;
140
- if (!div || !autoScroll)
141
- return;
135
+ if (!div || !autoScroll) return;
142
136
  const behavior = firstRenderRef.current ? "instant" : "auto";
143
137
  firstRenderRef.current = false;
144
138
  useViewport.setState({ isAtBottom: true });
145
139
  div.scrollIntoView({ behavior });
146
140
  };
147
141
  useOnResizeContent(divRef, () => {
148
- if (!useViewport.getState().isAtBottom)
149
- return;
142
+ if (!useViewport.getState().isAtBottom) return;
150
143
  scrollToBottom();
151
144
  });
152
145
  useOnScrollToBottom(() => {
@@ -154,8 +147,7 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
154
147
  });
155
148
  const handleScroll = () => {
156
149
  const div = divRef.current;
157
- if (!div)
158
- return;
150
+ if (!div) return;
159
151
  const isAtBottom = useViewport.getState().isAtBottom;
160
152
  const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
161
153
  if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
@@ -187,7 +179,9 @@ var MessageContext = createContext2(null);
187
179
  var useMessageContext = () => {
188
180
  const context = useContext2(MessageContext);
189
181
  if (!context)
190
- throw new Error("This component must be used within a MessageProvider");
182
+ throw new Error(
183
+ "This component must be used within a MessagePrimitive.Provider."
184
+ );
191
185
  return context;
192
186
  };
193
187
 
@@ -205,10 +199,8 @@ var useComposerContext = () => {
205
199
  var useComposerIf = (props) => {
206
200
  const { useComposer } = useComposerContext();
207
201
  return useComposer((composer) => {
208
- if (props.editing === true && !composer.isEditing)
209
- return false;
210
- if (props.editing === false && composer.isEditing)
211
- return false;
202
+ if (props.editing === true && !composer.isEditing) return false;
203
+ if (props.editing === false && composer.isEditing) return false;
212
204
  return true;
213
205
  });
214
206
  };
@@ -222,6 +214,7 @@ var message_exports = {};
222
214
  __export(message_exports, {
223
215
  Content: () => MessageContent,
224
216
  If: () => MessageIf,
217
+ Loading: () => MessageLoading,
225
218
  Provider: () => MessageProvider,
226
219
  Root: () => MessageRoot
227
220
  });
@@ -230,6 +223,14 @@ __export(message_exports, {
230
223
  import { useMemo, useState } from "react";
231
224
  import { create as create2 } from "zustand";
232
225
 
226
+ // src/utils/context/getMessageText.tsx
227
+ var getMessageText = (message) => {
228
+ const textParts = message.content.filter(
229
+ (part) => part.type === "text"
230
+ );
231
+ return textParts.map((part) => part.text).join("\n\n");
232
+ };
233
+
233
234
  // src/utils/context/stores/ComposerStore.ts
234
235
  import {
235
236
  create
@@ -256,8 +257,7 @@ var makeMessageComposerStore = ({
256
257
  onSend(value);
257
258
  },
258
259
  cancel: () => {
259
- if (!get().isEditing)
260
- return false;
260
+ if (!get().isEditing) return false;
261
261
  set({ isEditing: false });
262
262
  return true;
263
263
  }
@@ -276,8 +276,7 @@ var makeThreadComposerStore = (useThread) => create()((set, get, store) => {
276
276
  },
277
277
  cancel: () => {
278
278
  const thread = useThread.getState();
279
- if (!thread.isRunning)
280
- return false;
279
+ if (!thread.isRunning) return false;
281
280
  useThread.getState().cancelRun();
282
281
  return true;
283
282
  }
@@ -292,30 +291,40 @@ var getIsLast = (thread, message) => {
292
291
  var useMessageContext2 = () => {
293
292
  const { useThread } = useAssistantContext();
294
293
  const [context] = useState(() => {
295
- const useMessage = create2(() => ({
294
+ const useMessage = create2((set) => ({
296
295
  message: null,
297
296
  parentId: null,
298
297
  branches: [],
299
298
  isLast: false,
299
+ loadingIndicator: null,
300
300
  isCopied: false,
301
301
  isHovering: false,
302
- setIsCopied: () => {
302
+ setLoadingIndicator: (value) => {
303
+ set({ loadingIndicator: value });
303
304
  },
304
- setIsHovering: () => {
305
+ setIsCopied: (value) => {
306
+ set({ isCopied: value });
307
+ },
308
+ setIsHovering: (value) => {
309
+ set({ isHovering: value });
305
310
  }
306
311
  }));
307
312
  const useComposer = makeMessageComposerStore({
308
313
  onEdit: () => {
309
314
  const message = useMessage.getState().message;
310
315
  if (message.role !== "user")
311
- throw new Error("Editing is only supported for user messages");
312
- const text = message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
316
+ throw new Error(
317
+ "Tried to edit a non-user message. Editing is only supported for user messages. This is likely an internal bug in assistant-ui."
318
+ );
319
+ const text = getMessageText(message);
313
320
  return text;
314
321
  },
315
322
  onSend: (text) => {
316
323
  const { message, parentId } = useMessage.getState();
317
324
  if (message.role !== "user")
318
- throw new Error("Editing is only supported for user messages");
325
+ throw new Error(
326
+ "Tried to edit a non-user message. Editing is only supported for user messages. This is likely an internal bug in assistant-ui."
327
+ );
319
328
  const nonTextParts = message.content.filter(
320
329
  (part) => part.type !== "text" && part.type !== "ui"
321
330
  );
@@ -338,23 +347,14 @@ var MessageProvider = ({
338
347
  const context = useMessageContext2();
339
348
  const isLast = useThread((thread) => getIsLast(thread, message));
340
349
  const branches = useThread((thread) => thread.getBranches(message.id));
341
- const [isCopied, setIsCopied] = useState(false);
342
- const [isHovering, setIsHovering] = useState(false);
343
350
  useMemo(() => {
344
- context.useMessage.setState(
345
- {
346
- message,
347
- parentId,
348
- branches,
349
- isLast,
350
- isCopied,
351
- isHovering,
352
- setIsCopied,
353
- setIsHovering
354
- },
355
- true
356
- );
357
- }, [context, message, parentId, branches, isLast, isCopied, isHovering]);
351
+ context.useMessage.setState({
352
+ message,
353
+ parentId,
354
+ branches,
355
+ isLast
356
+ });
357
+ }, [context, message, parentId, branches, isLast]);
358
358
  return /* @__PURE__ */ jsx4(MessageContext.Provider, { value: context, children });
359
359
  };
360
360
 
@@ -391,18 +391,12 @@ var MessageRoot = forwardRef3(
391
391
  var useMessageIf = (props) => {
392
392
  const { useMessage } = useMessageContext();
393
393
  return useMessage(({ message, branches, isLast, isCopied, isHovering }) => {
394
- if (props.hasBranches === true && branches.length < 2)
395
- return false;
396
- if (props.user && message.role !== "user")
397
- return false;
398
- if (props.assistant && message.role !== "assistant")
399
- return false;
400
- if (props.lastOrHover === true && !isHovering && !isLast)
401
- return false;
402
- if (props.copied === true && !isCopied)
403
- return false;
404
- if (props.copied === false && isCopied)
405
- return false;
394
+ if (props.hasBranches === true && branches.length < 2) return false;
395
+ if (props.user && message.role !== "user") return false;
396
+ if (props.assistant && message.role !== "assistant") return false;
397
+ if (props.lastOrHover === true && !isHovering && !isLast) return false;
398
+ if (props.copied === true && !isCopied) return false;
399
+ if (props.copied === false && isCopied) return false;
406
400
  return true;
407
401
  });
408
402
  };
@@ -411,10 +405,94 @@ var MessageIf = ({ children, ...query }) => {
411
405
  return result ? children : null;
412
406
  };
413
407
 
408
+ // src/utils/context/combined/useCombinedStore.ts
409
+ import { useMemo as useMemo2 } from "react";
410
+
411
+ // src/utils/context/combined/createCombinedStore.ts
412
+ import { useSyncExternalStore } from "react";
413
+ var createCombinedStore = (stores) => {
414
+ const subscribe = (callback) => {
415
+ const unsubscribes = stores.map((store) => store.subscribe(callback));
416
+ return () => {
417
+ for (const unsub of unsubscribes) {
418
+ unsub();
419
+ }
420
+ };
421
+ };
422
+ return (selector) => {
423
+ const getSnapshot = () => selector(...stores.map((store) => store.getState()));
424
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
425
+ };
426
+ };
427
+
428
+ // src/utils/context/combined/useCombinedStore.ts
429
+ var useCombinedStore = (stores, selector) => {
430
+ const useCombined = useMemo2(() => createCombinedStore(stores), stores);
431
+ return useCombined(selector);
432
+ };
433
+
434
+ // src/utils/context/useContentPartContext.ts
435
+ import { createContext as createContext3, useContext as useContext4 } from "react";
436
+ var ContentPartContext = createContext3(null);
437
+ var useContentPartContext = () => {
438
+ const context = useContext4(ContentPartContext);
439
+ if (!context)
440
+ throw new Error(
441
+ "This component must be used within a ContentPartPrimitive.Provider."
442
+ );
443
+ return context;
444
+ };
445
+
446
+ // src/primitives/contentPart/ContentPartLoadingIndicator.tsx
447
+ var ContentPartLoadingIndicator = () => {
448
+ const { useMessage } = useMessageContext();
449
+ const { useContentPart } = useContentPartContext();
450
+ const loadingIndicator = useCombinedStore(
451
+ [useMessage, useContentPart],
452
+ (m, c) => c.isLoading ? m.loadingIndicator : null
453
+ );
454
+ return loadingIndicator;
455
+ };
456
+
457
+ // src/primitives/contentPart/ContentPartProvider.tsx
458
+ import { useMemo as useMemo3, useState as useState2 } from "react";
459
+ import { create as create3 } from "zustand";
460
+ import { jsx as jsx6 } from "react/jsx-runtime";
461
+ var useContentPartContext2 = () => {
462
+ const [context] = useState2(() => {
463
+ const useContentPart = create3(() => ({
464
+ part: null,
465
+ isLoading: false
466
+ }));
467
+ return { useContentPart };
468
+ });
469
+ return context;
470
+ };
471
+ var ContentPartProvider = ({
472
+ part,
473
+ isLoading,
474
+ children
475
+ }) => {
476
+ const context = useContentPartContext2();
477
+ useMemo3(() => {
478
+ context.useContentPart.setState(
479
+ {
480
+ part,
481
+ isLoading
482
+ },
483
+ true
484
+ );
485
+ }, [context, part, isLoading]);
486
+ return /* @__PURE__ */ jsx6(ContentPartContext.Provider, { value: context, children });
487
+ };
488
+
414
489
  // src/primitives/message/MessageContent.tsx
415
- import { Fragment, jsx as jsx6 } from "react/jsx-runtime";
490
+ import { Fragment, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
416
491
  var defaultComponents = {
417
- Text: ({ part }) => /* @__PURE__ */ jsx6(Fragment, { children: part.text }),
492
+ Text: ({ part }) => /* @__PURE__ */ jsxs2(Fragment, { children: [
493
+ part.text,
494
+ /* @__PURE__ */ jsx7(ContentPartLoadingIndicator, {})
495
+ ] }),
418
496
  Image: () => null,
419
497
  UI: ({ part }) => part.display,
420
498
  tools: {
@@ -429,29 +507,63 @@ var MessageContent = ({
429
507
  tools: { by_name = {}, Fallback = defaultComponents.tools.Fallback } = {}
430
508
  } = {}
431
509
  }) => {
510
+ const { useThread } = useAssistantContext();
432
511
  const { useMessage } = useMessageContext();
433
512
  const content = useMessage((s) => s.message.content);
434
- return /* @__PURE__ */ jsx6(Fragment, { children: content.map((part, i) => {
513
+ const isLoading = useCombinedStore(
514
+ [useThread, useMessage],
515
+ (t, s) => s.isLast && t.isRunning
516
+ );
517
+ return /* @__PURE__ */ jsx7(Fragment, { children: content.map((part, i) => {
435
518
  const key = i;
436
- switch (part.type) {
519
+ const type = part.type;
520
+ let component = null;
521
+ switch (type) {
437
522
  case "text":
438
- return /* @__PURE__ */ jsx6(Text, { part }, key);
523
+ component = /* @__PURE__ */ jsx7(Text, { part });
524
+ break;
439
525
  case "image":
440
- return /* @__PURE__ */ jsx6(Image, { part }, key);
526
+ component = /* @__PURE__ */ jsx7(Image, { part });
527
+ break;
441
528
  case "ui":
442
- return /* @__PURE__ */ jsx6(UI, { part }, key);
529
+ component = /* @__PURE__ */ jsx7(UI, { part });
530
+ break;
443
531
  case "tool-call": {
444
532
  const Tool = by_name[part.name] || Fallback;
445
- return /* @__PURE__ */ jsx6(Tool, { part }, key);
533
+ component = /* @__PURE__ */ jsx7(Tool, { part });
534
+ break;
446
535
  }
447
536
  default:
448
- return null;
537
+ throw new Error(`Unknown content part type: ${type}`);
449
538
  }
539
+ return /* @__PURE__ */ jsx7(
540
+ ContentPartProvider,
541
+ {
542
+ part,
543
+ isLoading: i === content.length - 1 && isLoading,
544
+ children: component
545
+ },
546
+ key
547
+ );
450
548
  }) });
451
549
  };
452
550
 
551
+ // src/primitives/message/MessageLoading.tsx
552
+ import {
553
+ Primitive as Primitive4
554
+ } from "@radix-ui/react-primitive";
555
+ import { forwardRef as forwardRef4, useMemo as useMemo4 } from "react";
556
+ import { jsx as jsx8 } from "react/jsx-runtime";
557
+ var MessageLoading = forwardRef4((props, ref) => {
558
+ const { useMessage } = useMessageContext();
559
+ useMemo4(() => {
560
+ useMessage.getState().setLoadingIndicator(/* @__PURE__ */ jsx8(Primitive4.div, { ...props, ref }));
561
+ }, [useMessage, props, ref]);
562
+ return null;
563
+ });
564
+
453
565
  // src/primitives/thread/ThreadMessages.tsx
454
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
566
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
455
567
  var getComponents = (components) => {
456
568
  return {
457
569
  EditComposer: components.EditComposer ?? components.UserMessage ?? components.Message,
@@ -464,21 +576,20 @@ var ThreadMessages = ({ components }) => {
464
576
  const thread = useThread();
465
577
  const messages = thread.messages;
466
578
  const { UserMessage, EditComposer, AssistantMessage } = getComponents(components);
467
- if (messages.length === 0)
468
- return null;
469
- return /* @__PURE__ */ jsx7(Fragment2, { children: messages.map((message, idx) => {
579
+ if (messages.length === 0) return null;
580
+ return /* @__PURE__ */ jsx9(Fragment2, { children: messages.map((message, idx) => {
470
581
  const parentId = messages[idx - 1]?.id ?? null;
471
- return /* @__PURE__ */ jsxs2(
582
+ return /* @__PURE__ */ jsxs3(
472
583
  MessageProvider,
473
584
  {
474
585
  message,
475
586
  parentId,
476
587
  children: [
477
- /* @__PURE__ */ jsxs2(MessageIf, { user: true, children: [
478
- /* @__PURE__ */ jsx7(ComposerIf, { editing: false, children: /* @__PURE__ */ jsx7(UserMessage, {}) }),
479
- /* @__PURE__ */ jsx7(ComposerIf, { editing: true, children: /* @__PURE__ */ jsx7(EditComposer, {}) })
588
+ /* @__PURE__ */ jsxs3(MessageIf, { user: true, children: [
589
+ /* @__PURE__ */ jsx9(ComposerIf, { editing: false, children: /* @__PURE__ */ jsx9(UserMessage, {}) }),
590
+ /* @__PURE__ */ jsx9(ComposerIf, { editing: true, children: /* @__PURE__ */ jsx9(EditComposer, {}) })
480
591
  ] }),
481
- /* @__PURE__ */ jsx7(MessageIf, { assistant: true, children: /* @__PURE__ */ jsx7(AssistantMessage, {}) })
592
+ /* @__PURE__ */ jsx9(MessageIf, { assistant: true, children: /* @__PURE__ */ jsx9(AssistantMessage, {}) })
482
593
  ]
483
594
  },
484
595
  parentId ?? "__ROOT__"
@@ -489,18 +600,18 @@ var ThreadMessages = ({ components }) => {
489
600
  // src/primitives/thread/ThreadScrollToBottom.tsx
490
601
  import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
491
602
  import {
492
- Primitive as Primitive4
603
+ Primitive as Primitive5
493
604
  } from "@radix-ui/react-primitive";
494
- import { forwardRef as forwardRef4 } from "react";
495
- import { jsx as jsx8 } from "react/jsx-runtime";
496
- var ThreadScrollToBottom = forwardRef4(({ onClick, ...rest }, ref) => {
605
+ import { forwardRef as forwardRef5 } from "react";
606
+ import { jsx as jsx10 } from "react/jsx-runtime";
607
+ var ThreadScrollToBottom = forwardRef5(({ onClick, ...rest }, ref) => {
497
608
  const { useViewport } = useAssistantContext();
498
609
  const isAtBottom = useViewport((s) => s.isAtBottom);
499
610
  const handleScrollToBottom = () => {
500
611
  useViewport.getState().scrollToBottom();
501
612
  };
502
- return /* @__PURE__ */ jsx8(
503
- Primitive4.button,
613
+ return /* @__PURE__ */ jsx10(
614
+ Primitive5.button,
504
615
  {
505
616
  ...rest,
506
617
  disabled: isAtBottom,
@@ -513,11 +624,11 @@ var ThreadScrollToBottom = forwardRef4(({ onClick, ...rest }, ref) => {
513
624
  // src/primitives/thread/ThreadSuggestion.tsx
514
625
  import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
515
626
  import {
516
- Primitive as Primitive5
627
+ Primitive as Primitive6
517
628
  } from "@radix-ui/react-primitive";
518
- import { forwardRef as forwardRef5 } from "react";
519
- import { jsx as jsx9 } from "react/jsx-runtime";
520
- var ThreadSuggestion = forwardRef5(({ onClick, prompt, method, autoSend: send, ...rest }, ref) => {
629
+ import { forwardRef as forwardRef6 } from "react";
630
+ import { jsx as jsx11 } from "react/jsx-runtime";
631
+ var ThreadSuggestion = forwardRef6(({ onClick, prompt, method, autoSend: send, ...rest }, ref) => {
521
632
  const { useThread, useComposer } = useAssistantContext();
522
633
  const isDisabled = useThread((t) => t.isRunning);
523
634
  const handleApplySuggestion = () => {
@@ -528,8 +639,8 @@ var ThreadSuggestion = forwardRef5(({ onClick, prompt, method, autoSend: send, .
528
639
  composer.send();
529
640
  }
530
641
  };
531
- return /* @__PURE__ */ jsx9(
532
- Primitive5.button,
642
+ return /* @__PURE__ */ jsx11(
643
+ Primitive6.button,
533
644
  {
534
645
  ...rest,
535
646
  disabled: isDisabled,
@@ -553,11 +664,11 @@ __export(composer_exports, {
553
664
  import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
554
665
  import { useComposedRefs as useComposedRefs2 } from "@radix-ui/react-compose-refs";
555
666
  import {
556
- Primitive as Primitive6
667
+ Primitive as Primitive7
557
668
  } from "@radix-ui/react-primitive";
558
- import { forwardRef as forwardRef6, useRef as useRef4 } from "react";
559
- import { jsx as jsx10 } from "react/jsx-runtime";
560
- var ComposerRoot = forwardRef6(
669
+ import { forwardRef as forwardRef7, useRef as useRef4 } from "react";
670
+ import { jsx as jsx12 } from "react/jsx-runtime";
671
+ var ComposerRoot = forwardRef7(
561
672
  ({ onSubmit, ...rest }, forwardedRef) => {
562
673
  const { useViewport } = useAssistantContext();
563
674
  const { useComposer } = useComposerContext();
@@ -565,14 +676,13 @@ var ComposerRoot = forwardRef6(
565
676
  const ref = useComposedRefs2(forwardedRef, formRef);
566
677
  const handleSubmit = (e) => {
567
678
  const composerState = useComposer.getState();
568
- if (!composerState.isEditing)
569
- return;
679
+ if (!composerState.isEditing) return;
570
680
  e.preventDefault();
571
681
  composerState.send();
572
682
  useViewport.getState().scrollToBottom();
573
683
  };
574
- return /* @__PURE__ */ jsx10(
575
- Primitive6.form,
684
+ return /* @__PURE__ */ jsx12(
685
+ Primitive7.form,
576
686
  {
577
687
  ...rest,
578
688
  ref,
@@ -587,26 +697,24 @@ import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primiti
587
697
  import { useComposedRefs as useComposedRefs3 } from "@radix-ui/react-compose-refs";
588
698
  import { Slot } from "@radix-ui/react-slot";
589
699
  import {
590
- forwardRef as forwardRef7,
700
+ forwardRef as forwardRef8,
591
701
  useCallback,
592
- useEffect as useEffect2,
702
+ useEffect as useEffect3,
593
703
  useRef as useRef5
594
704
  } from "react";
595
705
  import TextareaAutosize from "react-textarea-autosize";
596
- import { jsx as jsx11 } from "react/jsx-runtime";
597
- var ComposerInput = forwardRef7(
706
+ import { jsx as jsx13 } from "react/jsx-runtime";
707
+ var ComposerInput = forwardRef8(
598
708
  ({ autoFocus = false, asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
599
709
  const { useThread, useViewport } = useAssistantContext();
600
710
  const { useComposer, type } = useComposerContext();
601
711
  const value = useComposer((c) => {
602
- if (!c.isEditing)
603
- return "";
712
+ if (!c.isEditing) return "";
604
713
  return c.value;
605
714
  });
606
715
  const Component = asChild ? Slot : TextareaAutosize;
607
716
  const handleKeyPress = (e) => {
608
- if (disabled)
609
- return;
717
+ if (disabled) return;
610
718
  const composer = useComposer.getState();
611
719
  if (e.key === "Escape") {
612
720
  if (useComposer.getState().cancel()) {
@@ -626,21 +734,20 @@ var ComposerInput = forwardRef7(
626
734
  const autoFocusEnabled = autoFocus && !disabled;
627
735
  const focus = useCallback(() => {
628
736
  const textarea = textareaRef.current;
629
- if (!textarea || !autoFocusEnabled)
630
- return;
737
+ if (!textarea || !autoFocusEnabled) return;
631
738
  textarea.focus();
632
739
  textarea.setSelectionRange(
633
740
  textareaRef.current.value.length,
634
741
  textareaRef.current.value.length
635
742
  );
636
743
  }, [autoFocusEnabled]);
637
- useEffect2(() => focus(), [focus]);
744
+ useEffect3(() => focus(), [focus]);
638
745
  useOnScrollToBottom(() => {
639
746
  if (type === "assistant") {
640
747
  focus();
641
748
  }
642
749
  });
643
- return /* @__PURE__ */ jsx11(
750
+ return /* @__PURE__ */ jsx13(
644
751
  Component,
645
752
  {
646
753
  value,
@@ -649,8 +756,7 @@ var ComposerInput = forwardRef7(
649
756
  disabled,
650
757
  onChange: composeEventHandlers6(onChange, (e) => {
651
758
  const composerState = useComposer.getState();
652
- if (!composerState.isEditing)
653
- return;
759
+ if (!composerState.isEditing) return;
654
760
  return composerState.setValue(e.target.value);
655
761
  }),
656
762
  onKeyDown: composeEventHandlers6(onKeyDown, handleKeyPress)
@@ -661,16 +767,16 @@ var ComposerInput = forwardRef7(
661
767
 
662
768
  // src/primitives/composer/ComposerSend.tsx
663
769
  import {
664
- Primitive as Primitive7
770
+ Primitive as Primitive8
665
771
  } from "@radix-ui/react-primitive";
666
- import { forwardRef as forwardRef8 } from "react";
667
- import { jsx as jsx12 } from "react/jsx-runtime";
668
- var ComposerSend = forwardRef8(
772
+ import { forwardRef as forwardRef9 } from "react";
773
+ import { jsx as jsx14 } from "react/jsx-runtime";
774
+ var ComposerSend = forwardRef9(
669
775
  ({ disabled, ...rest }, ref) => {
670
776
  const { useComposer } = useComposerContext();
671
777
  const hasValue = useComposer((c) => c.isEditing && c.value.length > 0);
672
- return /* @__PURE__ */ jsx12(
673
- Primitive7.button,
778
+ return /* @__PURE__ */ jsx14(
779
+ Primitive8.button,
674
780
  {
675
781
  type: "submit",
676
782
  ...rest,
@@ -684,17 +790,17 @@ var ComposerSend = forwardRef8(
684
790
  // src/primitives/composer/ComposerCancel.tsx
685
791
  import { composeEventHandlers as composeEventHandlers7 } from "@radix-ui/primitive";
686
792
  import {
687
- Primitive as Primitive8
793
+ Primitive as Primitive9
688
794
  } from "@radix-ui/react-primitive";
689
- import { forwardRef as forwardRef9 } from "react";
690
- import { jsx as jsx13 } from "react/jsx-runtime";
691
- var ComposerCancel = forwardRef9(({ onClick, ...rest }, ref) => {
795
+ import { forwardRef as forwardRef10 } from "react";
796
+ import { jsx as jsx15 } from "react/jsx-runtime";
797
+ var ComposerCancel = forwardRef10(({ onClick, ...rest }, ref) => {
692
798
  const { useComposer } = useComposerContext();
693
799
  const handleCancel = () => {
694
800
  useComposer.getState().cancel();
695
801
  };
696
- return /* @__PURE__ */ jsx13(
697
- Primitive8.button,
802
+ return /* @__PURE__ */ jsx15(
803
+ Primitive9.button,
698
804
  {
699
805
  type: "button",
700
806
  ...rest,
@@ -714,32 +820,6 @@ __export(branchPicker_exports, {
714
820
  Root: () => BranchPickerRoot
715
821
  });
716
822
 
717
- // src/utils/context/combined/useCombinedStore.ts
718
- import { useMemo as useMemo2 } from "react";
719
-
720
- // src/utils/context/combined/createCombinedStore.ts
721
- import { useSyncExternalStore } from "react";
722
- var createCombinedStore = (stores) => {
723
- const subscribe = (callback) => {
724
- const unsubscribes = stores.map((store) => store.subscribe(callback));
725
- return () => {
726
- for (const unsub of unsubscribes) {
727
- unsub();
728
- }
729
- };
730
- };
731
- return (selector) => {
732
- const getSnapshot = () => selector(...stores.map((store) => store.getState()));
733
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
734
- };
735
- };
736
-
737
- // src/utils/context/combined/useCombinedStore.ts
738
- var useCombinedStore = (stores, selector) => {
739
- const useCombined = useMemo2(() => createCombinedStore(stores), stores);
740
- return useCombined(selector);
741
- };
742
-
743
823
  // src/actions/useGoToNextBranch.tsx
744
824
  var useGoToNextBranch = () => {
745
825
  const { useThread } = useAssistantContext();
@@ -748,8 +828,7 @@ var useGoToNextBranch = () => {
748
828
  [useMessage, useComposer],
749
829
  (m, c) => c.isEditing || m.branches.indexOf(m.message.id) + 1 >= m.branches.length
750
830
  );
751
- if (disabled)
752
- return null;
831
+ if (disabled) return null;
753
832
  return () => {
754
833
  const { message, branches } = useMessage.getState();
755
834
  useThread.getState().switchToBranch(branches[branches.indexOf(message.id) + 1]);
@@ -759,16 +838,16 @@ var useGoToNextBranch = () => {
759
838
  // src/utils/createActionButton.tsx
760
839
  import { composeEventHandlers as composeEventHandlers8 } from "@radix-ui/primitive";
761
840
  import {
762
- Primitive as Primitive9
841
+ Primitive as Primitive10
763
842
  } from "@radix-ui/react-primitive";
764
- import { forwardRef as forwardRef10 } from "react";
765
- import { jsx as jsx14 } from "react/jsx-runtime";
843
+ import { forwardRef as forwardRef11 } from "react";
844
+ import { jsx as jsx16 } from "react/jsx-runtime";
766
845
  var createActionButton = (useActionButton) => {
767
- return forwardRef10(
846
+ return forwardRef11(
768
847
  (props, forwardedRef) => {
769
848
  const onClick = useActionButton(props);
770
- return /* @__PURE__ */ jsx14(
771
- Primitive9.button,
849
+ return /* @__PURE__ */ jsx16(
850
+ Primitive10.button,
772
851
  {
773
852
  type: "button",
774
853
  disabled: !onClick,
@@ -792,8 +871,7 @@ var useGoToPreviousBranch = () => {
792
871
  [useMessage, useComposer],
793
872
  (m, c) => c.isEditing || m.branches.indexOf(m.message.id) <= 0
794
873
  );
795
- if (disabled)
796
- return null;
874
+ if (disabled) return null;
797
875
  return () => {
798
876
  const { message, branches } = useMessage.getState();
799
877
  useThread.getState().switchToBranch(
@@ -807,29 +885,29 @@ var useGoToPreviousBranch = () => {
807
885
  var BranchPickerPrevious = createActionButton(useGoToPreviousBranch);
808
886
 
809
887
  // src/primitives/branchPicker/BranchPickerCount.tsx
810
- import { Fragment as Fragment3, jsx as jsx15 } from "react/jsx-runtime";
888
+ import { Fragment as Fragment3, jsx as jsx17 } from "react/jsx-runtime";
811
889
  var BranchPickerCount = () => {
812
890
  const { useMessage } = useMessageContext();
813
891
  const branchCount = useMessage((s) => s.branches.length);
814
- return /* @__PURE__ */ jsx15(Fragment3, { children: branchCount });
892
+ return /* @__PURE__ */ jsx17(Fragment3, { children: branchCount });
815
893
  };
816
894
 
817
895
  // src/primitives/branchPicker/BranchPickerNumber.tsx
818
- import { Fragment as Fragment4, jsx as jsx16 } from "react/jsx-runtime";
896
+ import { Fragment as Fragment4, jsx as jsx18 } from "react/jsx-runtime";
819
897
  var BranchPickerNumber = () => {
820
898
  const { useMessage } = useMessageContext();
821
899
  const branchIdx = useMessage((s) => s.branches.indexOf(s.message.id));
822
- return /* @__PURE__ */ jsx16(Fragment4, { children: branchIdx + 1 });
900
+ return /* @__PURE__ */ jsx18(Fragment4, { children: branchIdx + 1 });
823
901
  };
824
902
 
825
903
  // src/primitives/branchPicker/BranchPickerRoot.tsx
826
904
  import {
827
- Primitive as Primitive10
905
+ Primitive as Primitive11
828
906
  } from "@radix-ui/react-primitive";
829
- import { forwardRef as forwardRef11 } from "react";
830
- import { jsx as jsx17 } from "react/jsx-runtime";
831
- var BranchPickerRoot = forwardRef11(({ hideWhenSingleBranch, ...rest }, ref) => {
832
- return /* @__PURE__ */ jsx17(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0, children: /* @__PURE__ */ jsx17(Primitive10.div, { ...rest, ref }) });
907
+ import { forwardRef as forwardRef12 } from "react";
908
+ import { jsx as jsx19 } from "react/jsx-runtime";
909
+ var BranchPickerRoot = forwardRef12(({ hideWhenSingleBranch, ...rest }, ref) => {
910
+ return /* @__PURE__ */ jsx19(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0, children: /* @__PURE__ */ jsx19(Primitive11.div, { ...rest, ref }) });
833
911
  });
834
912
 
835
913
  // src/primitives/actionBar/index.ts
@@ -843,32 +921,28 @@ __export(actionBar_exports, {
843
921
 
844
922
  // src/primitives/actionBar/ActionBarRoot.tsx
845
923
  import {
846
- Primitive as Primitive11
924
+ Primitive as Primitive12
847
925
  } from "@radix-ui/react-primitive";
848
- import { forwardRef as forwardRef12 } from "react";
849
- import { jsx as jsx18 } from "react/jsx-runtime";
850
- var ActionBarRoot = forwardRef12(({ hideWhenRunning, autohide, autohideFloat, ...rest }, ref) => {
926
+ import { forwardRef as forwardRef13 } from "react";
927
+ import { jsx as jsx20 } from "react/jsx-runtime";
928
+ var ActionBarRoot = forwardRef13(({ hideWhenRunning, autohide, autohideFloat, ...rest }, ref) => {
851
929
  const { useThread } = useAssistantContext();
852
930
  const { useMessage } = useMessageContext();
853
931
  const hideAndfloatStatus = useCombinedStore(
854
932
  [useThread, useMessage],
855
933
  (t, m) => {
856
- if (hideWhenRunning && t.isRunning)
857
- return "hidden" /* Hidden */;
934
+ if (hideWhenRunning && t.isRunning) return "hidden" /* Hidden */;
858
935
  const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
859
- if (!autohideEnabled)
860
- return "normal" /* Normal */;
861
- if (!m.isHovering)
862
- return "hidden" /* Hidden */;
936
+ if (!autohideEnabled) return "normal" /* Normal */;
937
+ if (!m.isHovering) return "hidden" /* Hidden */;
863
938
  if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branches.length <= 1)
864
939
  return "floating" /* Floating */;
865
940
  return "normal" /* Normal */;
866
941
  }
867
942
  );
868
- if (hideAndfloatStatus === "hidden" /* Hidden */)
869
- return null;
870
- return /* @__PURE__ */ jsx18(
871
- Primitive11.div,
943
+ if (hideAndfloatStatus === "hidden" /* Hidden */) return null;
944
+ return /* @__PURE__ */ jsx20(
945
+ Primitive12.div,
872
946
  {
873
947
  "data-floating": hideAndfloatStatus === "floating" /* Floating */,
874
948
  ...rest,
@@ -880,14 +954,18 @@ var ActionBarRoot = forwardRef12(({ hideWhenRunning, autohide, autohideFloat, ..
880
954
  // src/actions/useCopyMessage.tsx
881
955
  var useCopyMessage = ({ copiedDuration = 3e3 }) => {
882
956
  const { useMessage, useComposer } = useMessageContext();
883
- const isEditing = useComposer((s) => s.isEditing);
884
- if (isEditing)
885
- return null;
957
+ const hasCopyableContent = useCombinedStore(
958
+ [useMessage, useComposer],
959
+ (m, c) => {
960
+ return c.isEditing || m.message.content.some((c2) => c2.type === "text");
961
+ }
962
+ );
963
+ if (!hasCopyableContent) return null;
886
964
  return () => {
965
+ const { isEditing, value: composerValue } = useComposer.getState();
887
966
  const { message, setIsCopied } = useMessage.getState();
888
- if (message.content[0]?.type !== "text")
889
- throw new Error("Copying is only supported for text-only messages");
890
- navigator.clipboard.writeText(message.content[0].text);
967
+ const valueToCopy = isEditing ? composerValue : getMessageText(message);
968
+ navigator.clipboard.writeText(valueToCopy);
891
969
  setIsCopied(true);
892
970
  setTimeout(() => setIsCopied(false), copiedDuration);
893
971
  };
@@ -904,12 +982,9 @@ var useReloadMessage = () => {
904
982
  [useThread, useMessage],
905
983
  (t, m) => t.isRunning || m.message.role !== "assistant"
906
984
  );
907
- if (disabled)
908
- return null;
985
+ if (disabled) return null;
909
986
  return () => {
910
- const { message, parentId } = useMessage.getState();
911
- if (message.role !== "assistant")
912
- throw new Error("Reloading is only supported on assistant messages");
987
+ const { parentId } = useMessage.getState();
913
988
  useThread.getState().startRun(parentId);
914
989
  useViewport.getState().scrollToBottom();
915
990
  };
@@ -925,8 +1000,7 @@ var useBeginMessageEdit = () => {
925
1000
  [useMessage, useComposer],
926
1001
  (m, c) => m.message.role !== "user" || c.isEditing
927
1002
  );
928
- if (disabled)
929
- return null;
1003
+ if (disabled) return null;
930
1004
  return () => {
931
1005
  const { edit } = useComposer.getState();
932
1006
  edit();
@@ -936,18 +1010,25 @@ var useBeginMessageEdit = () => {
936
1010
  // src/primitives/actionBar/ActionBarEdit.tsx
937
1011
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
938
1012
 
1013
+ // src/primitives/contentPart/index.ts
1014
+ var contentPart_exports = {};
1015
+ __export(contentPart_exports, {
1016
+ LoadingIndicator: () => ContentPartLoadingIndicator,
1017
+ Provider: () => ContentPartProvider
1018
+ });
1019
+
939
1020
  // src/adapters/vercel/VercelAIAssistantProvider.tsx
940
- import { useMemo as useMemo4 } from "react";
1021
+ import { useMemo as useMemo6 } from "react";
941
1022
 
942
1023
  // src/adapters/vercel/useDummyAIAssistantContext.tsx
943
- import { useState as useState2 } from "react";
944
- import { create as create4 } from "zustand";
1024
+ import { useState as useState3 } from "react";
1025
+ import { create as create5 } from "zustand";
945
1026
 
946
1027
  // src/utils/context/stores/ViewportStore.tsx
947
- import { create as create3 } from "zustand";
1028
+ import { create as create4 } from "zustand";
948
1029
  var makeViewportStore = () => {
949
1030
  const scrollToBottomListeners = /* @__PURE__ */ new Set();
950
- return create3(() => ({
1031
+ return create4(() => ({
951
1032
  isAtBottom: true,
952
1033
  scrollToBottom: () => {
953
1034
  for (const listener of scrollToBottomListeners) {
@@ -965,7 +1046,7 @@ var makeViewportStore = () => {
965
1046
 
966
1047
  // src/adapters/vercel/useDummyAIAssistantContext.tsx
967
1048
  var makeDummyThreadStore = () => {
968
- return create4(() => ({
1049
+ return create5(() => ({
969
1050
  messages: [],
970
1051
  isRunning: false,
971
1052
  getBranches: () => {
@@ -974,19 +1055,19 @@ var makeDummyThreadStore = () => {
974
1055
  switchToBranch: () => {
975
1056
  throw new Error("Not implemented");
976
1057
  },
977
- append: async () => {
1058
+ append: () => {
978
1059
  throw new Error("Not implemented");
979
1060
  },
980
- cancelRun: () => {
1061
+ startRun: () => {
981
1062
  throw new Error("Not implemented");
982
1063
  },
983
- startRun: async () => {
1064
+ cancelRun: () => {
984
1065
  throw new Error("Not implemented");
985
1066
  }
986
1067
  }));
987
1068
  };
988
1069
  var useDummyAIAssistantContext = () => {
989
- const [context] = useState2(() => {
1070
+ const [context] = useState3(() => {
990
1071
  const useThread = makeDummyThreadStore();
991
1072
  const useViewport = makeViewportStore();
992
1073
  const useComposer = makeThreadComposerStore(useThread);
@@ -996,7 +1077,7 @@ var useDummyAIAssistantContext = () => {
996
1077
  };
997
1078
 
998
1079
  // src/adapters/vercel/useVercelAIThreadState.tsx
999
- import { useCallback as useCallback2, useMemo as useMemo3, useRef as useRef6, useState as useState3 } from "react";
1080
+ import { useCallback as useCallback2, useMemo as useMemo5, useRef as useRef6, useState as useState4 } from "react";
1000
1081
 
1001
1082
  // src/adapters/MessageRepository.tsx
1002
1083
  import { customAlphabet } from "nanoid/non-secure";
@@ -1008,15 +1089,48 @@ var optimisticPrefix = "__optimistic__";
1008
1089
  var generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
1009
1090
  var isOptimisticId = (id) => id.startsWith(optimisticPrefix);
1010
1091
  var findHead = (message) => {
1011
- if (message.next)
1012
- return findHead(message.next);
1092
+ if (message.next) return findHead(message.next);
1013
1093
  return message;
1014
1094
  };
1015
1095
  var MessageRepository = class {
1016
1096
  messages = /* @__PURE__ */ new Map();
1017
1097
  // message_id -> item
1018
1098
  head = null;
1019
- rootChildren = [];
1099
+ root = {
1100
+ children: []
1101
+ };
1102
+ getFallbackChild(p) {
1103
+ const childId = p.children.at(-1);
1104
+ const child = childId ? this.messages.get(childId) : null;
1105
+ if (child === void 0)
1106
+ throw new Error(
1107
+ "MessageRepository(getFallbackChild): Child message not found. This is likely an internal bug in assistant-ui."
1108
+ );
1109
+ return child;
1110
+ }
1111
+ performOp(newParent, child, operation) {
1112
+ const parentOrRoot = child.prev ?? this.root;
1113
+ const newParentOrRoot = newParent ?? this.root;
1114
+ if (operation === "relink" && parentOrRoot === newParentOrRoot) return;
1115
+ if (operation !== "link") {
1116
+ parentOrRoot.children = parentOrRoot.children.filter(
1117
+ (m) => m !== child.current.id
1118
+ );
1119
+ if (child.prev?.next === child) {
1120
+ child.prev.next = this.getFallbackChild(child.prev);
1121
+ }
1122
+ }
1123
+ if (operation !== "cut") {
1124
+ newParentOrRoot.children = [
1125
+ ...newParentOrRoot.children,
1126
+ child.current.id
1127
+ ];
1128
+ if (newParent && (findHead(child) === this.head || newParent.next === null)) {
1129
+ newParent.next = child;
1130
+ }
1131
+ child.prev = newParent;
1132
+ }
1133
+ }
1020
1134
  getMessages() {
1021
1135
  const messages = new Array(this.head?.level ?? 0);
1022
1136
  for (let current = this.head; current; current = current.prev) {
@@ -1024,28 +1138,18 @@ var MessageRepository = class {
1024
1138
  }
1025
1139
  return messages;
1026
1140
  }
1027
- // TODO previousId is confusing
1028
- // TODO previousId does not fix children
1029
- // TODO cut / link operations
1030
- addOrUpdateMessage(parentId, message, previousId = message.id) {
1031
- const item = this.messages.get(message.id);
1032
- if (item) {
1033
- if (item.prev?.current.id !== parentId) {
1034
- if ((item.prev?.current.id ?? null) !== parentId) {
1035
- this.deleteMessage(message.id);
1036
- } else {
1037
- item.current = message;
1038
- if (previousId !== message.id) {
1039
- this.messages.delete(previousId);
1040
- this.messages.set(message.id, item);
1041
- }
1042
- return;
1043
- }
1044
- }
1045
- }
1141
+ addOrUpdateMessage(parentId, message) {
1142
+ const existingItem = this.messages.get(message.id);
1046
1143
  const prev = parentId ? this.messages.get(parentId) : null;
1047
1144
  if (prev === void 0)
1048
- throw new Error("Unexpected: Parent message not found");
1145
+ throw new Error(
1146
+ "MessageRepository(addOrUpdateMessage): Parent message not found. This is likely an internal bug in assistant-ui."
1147
+ );
1148
+ if (existingItem) {
1149
+ existingItem.current = message;
1150
+ this.performOp(prev, existingItem, "relink");
1151
+ return;
1152
+ }
1049
1153
  const newItem = {
1050
1154
  prev,
1051
1155
  current: message,
@@ -1054,108 +1158,83 @@ var MessageRepository = class {
1054
1158
  level: prev ? prev.level + 1 : 0
1055
1159
  };
1056
1160
  this.messages.set(message.id, newItem);
1057
- if (prev) {
1058
- prev.children = [...prev.children, message.id];
1059
- prev.next = newItem;
1060
- } else {
1061
- this.rootChildren = [...this.rootChildren, message.id];
1062
- }
1063
1161
  if (this.head === prev) {
1064
1162
  this.head = newItem;
1065
1163
  }
1164
+ this.performOp(prev, newItem, "link");
1066
1165
  }
1067
- deleteMessage(messageId) {
1068
- const message = this.messages.get(messageId);
1069
- if (!message)
1070
- throw new Error("Unexpected: Message not found");
1071
- if (message.children.length > 0) {
1072
- for (const child of message.children) {
1073
- this.deleteMessage(child);
1074
- }
1075
- }
1076
- this.messages.delete(messageId);
1077
- if (message.prev) {
1078
- message.prev.children = message.prev.children.filter(
1079
- (m) => m !== messageId
1080
- );
1081
- if (message.prev.next === message) {
1082
- const childId = message.prev.children.at(-1);
1083
- const child = childId ? this.messages.get(childId) : null;
1084
- if (child === void 0)
1085
- throw new Error("Unexpected: Child message not found");
1086
- message.prev.next = child;
1087
- }
1088
- } else {
1089
- this.rootChildren = this.rootChildren.filter((m) => m !== messageId);
1090
- }
1091
- if (this.head === message) {
1092
- this.head = message.prev ? findHead(message.prev) : null;
1093
- }
1094
- }
1095
- getOptimisticId() {
1166
+ appendOptimisticMessage(parentId, message) {
1096
1167
  let optimisticId;
1097
1168
  do {
1098
1169
  optimisticId = generateOptimisticId();
1099
1170
  } while (this.messages.has(optimisticId));
1100
- return optimisticId;
1101
- }
1102
- commitOptimisticAppend(message) {
1103
- const optimisticIdUser = this.getOptimisticId();
1104
- this.addOrUpdateMessage(message.parentId, {
1105
- id: optimisticIdUser,
1106
- role: "user",
1107
- content: message.content,
1108
- createdAt: /* @__PURE__ */ new Date()
1109
- });
1110
- const optimisticIdAssistant = this.commitOptimisticRun(optimisticIdUser);
1111
- return [optimisticIdUser, optimisticIdAssistant];
1112
- }
1113
- commitOptimisticRun(parentId) {
1114
- const optimisticId = this.getOptimisticId();
1115
1171
  this.addOrUpdateMessage(parentId, {
1172
+ ...message,
1116
1173
  id: optimisticId,
1117
- role: "assistant",
1118
- content: [
1119
- {
1120
- type: "text",
1121
- text: ""
1122
- }
1123
- ],
1124
1174
  createdAt: /* @__PURE__ */ new Date()
1125
1175
  });
1126
1176
  return optimisticId;
1127
1177
  }
1128
- getBranches(messageId) {
1178
+ deleteMessage(messageId, newParentId) {
1129
1179
  const message = this.messages.get(messageId);
1180
+ const newParent = newParentId ? this.messages.get(newParentId) : null;
1130
1181
  if (!message)
1131
- throw new Error("Unexpected: Message not found");
1132
- if (message.prev) {
1133
- return message.prev.children;
1182
+ throw new Error(
1183
+ "MessageRepository(deleteMessage): Optimistic message not found. This is likely an internal bug in assistant-ui."
1184
+ );
1185
+ if (newParent === void 0)
1186
+ throw new Error(
1187
+ "MessageRepository(deleteMessage): New message not found. This is likely an internal bug in assistant-ui."
1188
+ );
1189
+ for (const child of message.children) {
1190
+ const childMessage = this.messages.get(child);
1191
+ if (!childMessage)
1192
+ throw new Error(
1193
+ "MessageRepository(deleteMessage): Child message not found. This is likely an internal bug in assistant-ui."
1194
+ );
1195
+ this.performOp(newParent, childMessage, "relink");
1196
+ }
1197
+ this.messages.delete(messageId);
1198
+ if (this.head === message) {
1199
+ this.head = this.getFallbackChild(message.prev ?? this.root);
1134
1200
  }
1135
- return this.rootChildren;
1201
+ this.performOp(null, message, "cut");
1202
+ }
1203
+ getBranches(messageId) {
1204
+ const message = this.messages.get(messageId);
1205
+ if (!message)
1206
+ throw new Error(
1207
+ "MessageRepository(getBranches): Message not found. This is likely an internal bug in assistant-ui."
1208
+ );
1209
+ const { children } = message.prev ?? this.root;
1210
+ return children;
1136
1211
  }
1137
1212
  switchToBranch(messageId) {
1138
1213
  const message = this.messages.get(messageId);
1139
1214
  if (!message)
1140
- throw new Error("Unexpected: Branch not found");
1215
+ throw new Error(
1216
+ "MessageRepository(switchToBranch): Branch not found. This is likely an internal bug in assistant-ui."
1217
+ );
1141
1218
  if (message.prev) {
1142
1219
  message.prev.next = message;
1143
1220
  }
1144
1221
  this.head = findHead(message);
1145
1222
  }
1146
1223
  resetHead(messageId) {
1147
- if (messageId) {
1148
- const message = this.messages.get(messageId);
1149
- if (!message)
1150
- throw new Error("Unexpected: Branch not found");
1151
- this.head = message;
1152
- for (let current = message; current; current = current.prev) {
1153
- if (current.prev) {
1154
- current.prev.next = current;
1155
- }
1156
- }
1157
- } else {
1224
+ if (messageId === null) {
1158
1225
  this.head = null;
1226
+ return;
1227
+ }
1228
+ const message = this.messages.get(messageId);
1229
+ if (!message)
1230
+ throw new Error(
1231
+ "MessageRepository(resetHead): Branch not found. This is likely an internal bug in assistant-ui."
1232
+ );
1233
+ this.head = message;
1234
+ for (let current = message; current; current = current.prev) {
1235
+ if (current.prev) {
1236
+ current.prev.next = current;
1237
+ }
1159
1238
  }
1160
1239
  }
1161
1240
  };
@@ -1169,8 +1248,7 @@ var ThreadMessageConverter = class {
1169
1248
  convertMessages(messages) {
1170
1249
  return messages.map((m) => {
1171
1250
  const cached = this.cache.get(m);
1172
- if (cached)
1173
- return cached;
1251
+ if (cached) return cached;
1174
1252
  const newMessage = this.converter(m);
1175
1253
  this.cache.set(m, newMessage);
1176
1254
  return newMessage;
@@ -1181,41 +1259,51 @@ var ThreadMessageConverter = class {
1181
1259
  // src/adapters/vercel/useVercelAIThreadState.tsx
1182
1260
  var vercelToThreadMessage = (message) => {
1183
1261
  if (message.role !== "user" && message.role !== "assistant")
1184
- throw new Error("Unsupported role");
1262
+ throw new Error(
1263
+ `You have a message with an unsupported role. The role ${message.role} is not supported.`
1264
+ );
1185
1265
  return {
1186
1266
  id: message.id,
1187
1267
  role: message.role,
1188
- content: [{ type: "text", text: message.content }],
1268
+ content: [
1269
+ ...message.content ? [{ type: "text", text: message.content }] : [],
1270
+ ...message.toolInvocations?.map((t) => ({
1271
+ type: "tool-call",
1272
+ name: t.toolName,
1273
+ args: t.args,
1274
+ result: "result" in t ? t.result : void 0
1275
+ })) ?? []
1276
+ ],
1277
+ // ignore type mismatch for now
1189
1278
  createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1190
1279
  innerMessage: message
1191
1280
  };
1192
1281
  };
1193
1282
  var converter = new ThreadMessageConverter(vercelToThreadMessage);
1194
1283
  var sliceMessagesUntil = (messages, messageId) => {
1195
- if (messageId == null)
1196
- return [];
1197
- if (isOptimisticId(messageId))
1198
- return messages;
1284
+ if (messageId == null) return [];
1285
+ if (isOptimisticId(messageId)) return messages;
1199
1286
  const messageIdx = messages.findIndex((m) => m.id === messageId);
1200
1287
  if (messageIdx === -1)
1201
- throw new Error("Unexpected: Message not found");
1288
+ throw new Error(
1289
+ "useVercelAIThreadState: Message not found. This is liekly an internal bug in assistant-ui."
1290
+ );
1202
1291
  return messages.slice(0, messageIdx + 1);
1203
1292
  };
1204
1293
  var hasUpcomingMessage = (isRunning, messages) => {
1205
1294
  return isRunning && messages[messages.length - 1]?.role !== "assistant";
1206
1295
  };
1207
1296
  var getIsRunning = (vercel) => {
1208
- if ("isLoading" in vercel)
1209
- return vercel.isLoading;
1297
+ if ("isLoading" in vercel) return vercel.isLoading;
1210
1298
  return vercel.status === "in_progress";
1211
1299
  };
1212
1300
  var useVercelAIThreadState = (vercel) => {
1213
- const [data] = useState3(() => new MessageRepository());
1301
+ const [data] = useState4(() => new MessageRepository());
1214
1302
  const vercelRef = useRef6(vercel);
1215
1303
  vercelRef.current = vercel;
1216
1304
  const isRunning = getIsRunning(vercelRef.current);
1217
1305
  const assistantOptimisticIdRef = useRef6(null);
1218
- const messages = useMemo3(() => {
1306
+ const messages = useMemo5(() => {
1219
1307
  const vm = converter.convertMessages(vercel.messages);
1220
1308
  for (let i = 0; i < vm.length; i++) {
1221
1309
  const message = vm[i];
@@ -1223,24 +1311,28 @@ var useVercelAIThreadState = (vercel) => {
1223
1311
  data.addOrUpdateMessage(parent?.id ?? null, message);
1224
1312
  }
1225
1313
  if (assistantOptimisticIdRef.current) {
1226
- data.deleteMessage(assistantOptimisticIdRef.current);
1314
+ data.deleteMessage(assistantOptimisticIdRef.current, null);
1227
1315
  assistantOptimisticIdRef.current = null;
1228
1316
  }
1229
1317
  if (hasUpcomingMessage(isRunning, vm)) {
1230
- assistantOptimisticIdRef.current = data.commitOptimisticRun(
1231
- vm.at(-1)?.id ?? null
1318
+ assistantOptimisticIdRef.current = data.appendOptimisticMessage(
1319
+ vm.at(-1)?.id ?? null,
1320
+ {
1321
+ role: "assistant",
1322
+ content: [{ type: "text", text: "" }]
1323
+ }
1232
1324
  );
1233
1325
  }
1234
1326
  data.resetHead(assistantOptimisticIdRef.current ?? vm.at(-1)?.id ?? null);
1235
1327
  return data.getMessages();
1236
1328
  }, [data, isRunning, vercel.messages]);
1237
- const getBranches = useCallback2(
1329
+ const getBranches2 = useCallback2(
1238
1330
  (messageId) => {
1239
1331
  return data.getBranches(messageId);
1240
1332
  },
1241
1333
  [data]
1242
1334
  );
1243
- const switchToBranch = useCallback2(
1335
+ const switchToBranch2 = useCallback2(
1244
1336
  (messageId) => {
1245
1337
  data.switchToBranch(messageId);
1246
1338
  vercelRef.current.setMessages(
@@ -1252,7 +1344,9 @@ var useVercelAIThreadState = (vercel) => {
1252
1344
  const startRun = useCallback2(async (parentId) => {
1253
1345
  const reloadMaybe = "reload" in vercelRef.current ? vercelRef.current.reload : void 0;
1254
1346
  if (!reloadMaybe)
1255
- throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
1347
+ throw new Error(
1348
+ "Reload is not supported by Vercel AI SDK's useAssistant."
1349
+ );
1256
1350
  const newMessages = sliceMessagesUntil(
1257
1351
  vercelRef.current.messages,
1258
1352
  parentId
@@ -1262,7 +1356,7 @@ var useVercelAIThreadState = (vercel) => {
1262
1356
  }, []);
1263
1357
  const append = useCallback2(async (message) => {
1264
1358
  if (message.content.length !== 1 || message.content[0]?.type !== "text")
1265
- throw new Error("Only text content is supported by Vercel AI SDK");
1359
+ throw new Error("Only text content is supported by Vercel AI SDK.");
1266
1360
  const newMessages = sliceMessagesUntil(
1267
1361
  vercelRef.current.messages,
1268
1362
  message.parentId
@@ -1273,37 +1367,37 @@ var useVercelAIThreadState = (vercel) => {
1273
1367
  content: message.content[0].text
1274
1368
  });
1275
1369
  }, []);
1276
- const cancelRun = useCallback2(() => {
1370
+ const cancelRun2 = useCallback2(() => {
1277
1371
  const lastMessage = vercelRef.current.messages.at(-1);
1278
1372
  vercelRef.current.stop();
1279
1373
  if (lastMessage?.role === "user") {
1280
1374
  vercelRef.current.setInput(lastMessage.content);
1281
1375
  }
1282
1376
  }, []);
1283
- return useMemo3(
1377
+ return useMemo5(
1284
1378
  () => ({
1285
1379
  isRunning,
1286
1380
  messages,
1287
- getBranches,
1288
- switchToBranch,
1381
+ getBranches: getBranches2,
1382
+ switchToBranch: switchToBranch2,
1289
1383
  append,
1290
1384
  startRun,
1291
- cancelRun
1385
+ cancelRun: cancelRun2
1292
1386
  }),
1293
1387
  [
1294
1388
  isRunning,
1295
1389
  messages,
1296
- getBranches,
1297
- switchToBranch,
1390
+ getBranches2,
1391
+ switchToBranch2,
1298
1392
  append,
1299
1393
  startRun,
1300
- cancelRun
1394
+ cancelRun2
1301
1395
  ]
1302
1396
  );
1303
1397
  };
1304
1398
 
1305
1399
  // src/adapters/vercel/VercelAIAssistantProvider.tsx
1306
- import { jsx as jsx19 } from "react/jsx-runtime";
1400
+ import { jsx as jsx21 } from "react/jsx-runtime";
1307
1401
  var VercelAIAssistantProvider = ({
1308
1402
  children,
1309
1403
  ...rest
@@ -1311,27 +1405,26 @@ var VercelAIAssistantProvider = ({
1311
1405
  const context = useDummyAIAssistantContext();
1312
1406
  const vercel = "chat" in rest ? rest.chat : rest.assistant;
1313
1407
  const threadState = useVercelAIThreadState(vercel);
1314
- useMemo4(() => {
1408
+ useMemo6(() => {
1315
1409
  context.useThread.setState(threadState, true);
1316
1410
  }, [context, threadState]);
1317
- useMemo4(() => {
1411
+ useMemo6(() => {
1318
1412
  context.useComposer.setState({
1319
1413
  value: vercel.input,
1320
1414
  setValue: vercel.setInput
1321
1415
  });
1322
1416
  }, [context, vercel.input, vercel.setInput]);
1323
- return /* @__PURE__ */ jsx19(AssistantContext.Provider, { value: context, children });
1417
+ return /* @__PURE__ */ jsx21(AssistantContext.Provider, { value: context, children });
1324
1418
  };
1325
1419
 
1326
1420
  // src/adapters/vercel/VercelRSCAssistantProvider.tsx
1327
1421
  import {
1328
1422
  useCallback as useCallback3,
1329
- useMemo as useMemo5
1423
+ useMemo as useMemo7,
1424
+ useState as useState5
1330
1425
  } from "react";
1331
- import { jsx as jsx20 } from "react/jsx-runtime";
1426
+ import { jsx as jsx22 } from "react/jsx-runtime";
1332
1427
  var vercelToThreadMessage2 = (message) => {
1333
- if (message.role !== "user" && message.role !== "assistant")
1334
- throw new Error("Unsupported role");
1335
1428
  return {
1336
1429
  id: message.id,
1337
1430
  role: message.role,
@@ -1339,6 +1432,22 @@ var vercelToThreadMessage2 = (message) => {
1339
1432
  createdAt: message.createdAt ?? /* @__PURE__ */ new Date()
1340
1433
  };
1341
1434
  };
1435
+ var EMPTY_BRANCHES = [];
1436
+ var getBranches = () => {
1437
+ return EMPTY_BRANCHES;
1438
+ };
1439
+ var switchToBranch = () => {
1440
+ throw new Error(
1441
+ "Branch switching is not supported by VercelRSCAssistantProvider."
1442
+ );
1443
+ };
1444
+ var cancelRun = () => {
1445
+ if (process.env["NODE_ENV"] === "development") {
1446
+ console.warn(
1447
+ "Run cancellation is not supported by VercelRSCAssistantProvider."
1448
+ );
1449
+ }
1450
+ };
1342
1451
  var VercelRSCAssistantProvider = ({
1343
1452
  children,
1344
1453
  convertMessage,
@@ -1348,13 +1457,18 @@ var VercelRSCAssistantProvider = ({
1348
1457
  reload
1349
1458
  }) => {
1350
1459
  const context = useDummyAIAssistantContext();
1351
- const converter2 = useMemo5(() => {
1460
+ const [isRunning, setIsRunning] = useState5(false);
1461
+ const withRunning = useCallback3((callback) => {
1462
+ setIsRunning(true);
1463
+ return callback.finally(() => setIsRunning(false));
1464
+ }, []);
1465
+ const converter2 = useMemo7(() => {
1352
1466
  const rscConverter = convertMessage ?? ((m) => m);
1353
1467
  return new ThreadMessageConverter((m) => {
1354
1468
  return vercelToThreadMessage2(rscConverter(m));
1355
1469
  });
1356
1470
  }, [convertMessage]);
1357
- const messages = useMemo5(() => {
1471
+ const messages = useMemo7(() => {
1358
1472
  return converter2.convertMessages(vercelMessages);
1359
1473
  }, [converter2, vercelMessages]);
1360
1474
  const append = useCallback3(
@@ -1362,38 +1476,46 @@ var VercelRSCAssistantProvider = ({
1362
1476
  if (message.parentId !== (context.useThread.getState().messages.at(-1)?.id ?? null)) {
1363
1477
  if (!edit)
1364
1478
  throw new Error(
1365
- "Unexpected: Message editing is not supported, no edit callback was provided to VercelRSCAssistantProvider."
1479
+ "Message editing is not enabled, please provide an edit callback to VercelRSCAssistantProvider."
1366
1480
  );
1367
- await edit(message);
1481
+ await withRunning(edit(message));
1368
1482
  } else {
1369
- await appendCallback(message);
1483
+ await withRunning(appendCallback(message));
1370
1484
  }
1371
1485
  },
1372
- [context, appendCallback, edit]
1486
+ [context, withRunning, appendCallback, edit]
1373
1487
  );
1374
1488
  const startRun = useCallback3(
1375
1489
  async (parentId) => {
1376
1490
  if (!reload)
1377
1491
  throw new Error(
1378
- "Unexpected: Message reloading is not supported, no reload callback was provided to VercelRSCAssistantProvider."
1492
+ "Message reloading is not enabled, please provide a reload callback to VercelRSCAssistantProvider."
1379
1493
  );
1380
- await reload(parentId);
1494
+ await withRunning(reload(parentId));
1381
1495
  },
1382
- [reload]
1496
+ [withRunning, reload]
1383
1497
  );
1384
- useMemo5(() => {
1385
- context.useThread.setState({
1386
- messages,
1387
- append,
1388
- startRun
1389
- });
1390
- }, [context, messages, append, startRun]);
1391
- return /* @__PURE__ */ jsx20(AssistantContext.Provider, { value: context, children });
1498
+ useMemo7(() => {
1499
+ context.useThread.setState(
1500
+ {
1501
+ messages,
1502
+ isRunning,
1503
+ getBranches,
1504
+ switchToBranch,
1505
+ append,
1506
+ startRun,
1507
+ cancelRun
1508
+ },
1509
+ true
1510
+ );
1511
+ }, [context, messages, isRunning, append, startRun]);
1512
+ return /* @__PURE__ */ jsx22(AssistantContext.Provider, { value: context, children });
1392
1513
  };
1393
1514
  export {
1394
1515
  actionBar_exports as ActionBarPrimitive,
1395
1516
  branchPicker_exports as BranchPickerPrimitive,
1396
1517
  composer_exports as ComposerPrimitive,
1518
+ contentPart_exports as ContentPartPrimitive,
1397
1519
  message_exports as MessagePrimitive,
1398
1520
  thread_exports as ThreadPrimitive,
1399
1521
  VercelAIAssistantProvider,