@assistant-ui/react 0.0.14 → 0.0.16

Sign up to get free protection for your applications and to get access to all the features.
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,