@assistant-ui/react 0.0.7 → 0.0.8

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
@@ -11,6 +11,7 @@ __export(thread_exports, {
11
11
  If: () => ThreadIf,
12
12
  Messages: () => ThreadMessages,
13
13
  Root: () => ThreadRoot,
14
+ ScrollToBottom: () => ThreadScrollToBottom,
14
15
  Viewport: () => ThreadViewport
15
16
  });
16
17
 
@@ -68,7 +69,7 @@ import { useComposedRefs } from "@radix-ui/react-compose-refs";
68
69
  import {
69
70
  Primitive as Primitive2
70
71
  } from "@radix-ui/react-primitive";
71
- import { forwardRef as forwardRef2, useRef as useRef2, useState } from "react";
72
+ import { forwardRef as forwardRef2, useRef as useRef3 } from "react";
72
73
 
73
74
  // src/utils/hooks/useOnResizeContent.tsx
74
75
  import { useLayoutEffect, useRef } from "react";
@@ -109,22 +110,51 @@ var useOnResizeContent = (ref, callback) => {
109
110
  }, [ref.current]);
110
111
  };
111
112
 
113
+ // src/utils/hooks/useOnScrollToBottom.tsx
114
+ import { useEffect, useRef as useRef2 } from "react";
115
+ var useOnScrollToBottom = (callback) => {
116
+ const callbackRef = useRef2(callback);
117
+ callbackRef.current = callback;
118
+ const { useThread } = useAssistantContext();
119
+ useEffect(() => {
120
+ return useThread.getState().onScrollToBottom(() => {
121
+ callbackRef.current();
122
+ });
123
+ }, [useThread]);
124
+ };
125
+
112
126
  // src/primitives/thread/ThreadViewport.tsx
113
- var ThreadViewport = forwardRef2(({ onScroll, children, ...rest }, forwardedRef) => {
114
- const divRef = useRef2(null);
127
+ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...rest }, forwardedRef) => {
128
+ const messagesEndRef = useRef3(null);
129
+ const divRef = useRef3(null);
115
130
  const ref = useComposedRefs(forwardedRef, divRef);
116
- const [isAtBottom, setIsAtBottom] = useState(true);
131
+ const { useThread } = useAssistantContext();
132
+ const firstRenderRef = useRef3(true);
133
+ const scrollToBottom = () => {
134
+ const div = messagesEndRef.current;
135
+ if (!div || !autoScroll)
136
+ return;
137
+ const behavior = firstRenderRef.current ? "instant" : "auto";
138
+ firstRenderRef.current = false;
139
+ div.scrollIntoView({ behavior });
140
+ };
117
141
  useOnResizeContent(divRef, () => {
118
- const div = divRef.current;
119
- if (!div || !isAtBottom)
142
+ if (!useThread.getState().isAtBottom)
120
143
  return;
121
- div.scrollTop = div.scrollHeight;
144
+ scrollToBottom();
145
+ });
146
+ useOnScrollToBottom(() => {
147
+ scrollToBottom();
122
148
  });
123
149
  const handleScroll = () => {
124
150
  const div = divRef.current;
125
151
  if (!div)
126
152
  return;
127
- setIsAtBottom(div.scrollHeight - div.scrollTop <= div.clientHeight + 50);
153
+ const isAtBottom = useThread.getState().isAtBottom;
154
+ const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight + 50;
155
+ if (newIsAtBottom !== isAtBottom) {
156
+ useThread.setState({ isAtBottom: newIsAtBottom });
157
+ }
128
158
  };
129
159
  return /* @__PURE__ */ React.createElement(
130
160
  Primitive2.div,
@@ -133,12 +163,13 @@ var ThreadViewport = forwardRef2(({ onScroll, children, ...rest }, forwardedRef)
133
163
  onScroll: composeEventHandlers(onScroll, handleScroll),
134
164
  ref
135
165
  },
136
- children
166
+ children,
167
+ /* @__PURE__ */ React.createElement("div", { ref: messagesEndRef })
137
168
  );
138
169
  });
139
170
 
140
171
  // src/vercel/useVercelAIBranches.tsx
141
- import { useCallback, useMemo, useRef as useRef3 } from "react";
172
+ import { useCallback, useMemo, useRef as useRef4 } from "react";
142
173
  var ROOT_ID = "__ROOT_ID__";
143
174
  var UPCOMING_MESSAGE_ID = "__UPCOMING_MESSAGE_ID__";
144
175
  var updateBranchData = (data, messages) => {
@@ -205,8 +236,8 @@ var sliceMessagesUntil = (messages, message) => {
205
236
  throw new Error("Unexpected: Message not found");
206
237
  return messages.slice(0, messageIdx);
207
238
  };
208
- var useVercelAIBranches = (chat) => {
209
- const data = useRef3({
239
+ var useVercelAIBranches = (chat, context) => {
240
+ const data = useRef4({
210
241
  parentMap: /* @__PURE__ */ new Map(),
211
242
  branchMap: /* @__PURE__ */ new Map(),
212
243
  snapshots: /* @__PURE__ */ new Map()
@@ -237,9 +268,10 @@ var useVercelAIBranches = (chat) => {
237
268
  throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
238
269
  const newMessages = sliceMessagesUntil(chat.messages, message);
239
270
  chat.setMessages(newMessages);
271
+ context.useThread.getState().scrollToBottom();
240
272
  await reloadMaybe();
241
273
  },
242
- [chat.messages, chat.setMessages, reloadMaybe]
274
+ [context, chat.messages, chat.setMessages, reloadMaybe]
243
275
  );
244
276
  const editAt = useCallback(
245
277
  async (message, newMessage) => {
@@ -247,12 +279,13 @@ var useVercelAIBranches = (chat) => {
247
279
  chat.setMessages(newMessages);
248
280
  if (newMessage.content[0]?.type !== "text")
249
281
  throw new Error("Only text content is currently supported");
282
+ context.useThread.getState().scrollToBottom();
250
283
  await chat.append({
251
284
  role: "user",
252
285
  content: newMessage.content[0].text
253
286
  });
254
287
  },
255
- [chat.messages, chat.setMessages, chat.append]
288
+ [context, chat.messages, chat.setMessages, chat.append]
256
289
  );
257
290
  return useMemo(
258
291
  () => ({
@@ -268,10 +301,10 @@ var hasUpcomingMessage = (thread) => {
268
301
  return thread.isLoading && thread.messages[thread.messages.length - 1]?.role !== "assistant";
269
302
  };
270
303
 
271
- // src/utils/context/ComposerState.ts
304
+ // src/utils/context/useComposerContext.ts
272
305
  import { useContext as useContext3 } from "react";
273
306
 
274
- // src/utils/context/MessageContext.ts
307
+ // src/utils/context/useMessageContext.ts
275
308
  import { createContext as createContext2, useContext as useContext2 } from "react";
276
309
  var MessageContext = createContext2(null);
277
310
  var useMessageContext = () => {
@@ -281,11 +314,14 @@ var useMessageContext = () => {
281
314
  return context;
282
315
  };
283
316
 
284
- // src/utils/context/ComposerState.ts
317
+ // src/utils/context/useComposerContext.ts
285
318
  var useComposerContext = () => {
286
319
  const { useComposer: useAssisstantComposer } = useAssistantContext();
287
320
  const { useComposer: useMessageComposer } = useContext3(MessageContext) ?? {};
288
- return { useComposer: useMessageComposer ?? useAssisstantComposer };
321
+ return {
322
+ useComposer: useMessageComposer ?? useAssisstantComposer,
323
+ type: useMessageComposer ? "message" : "assistant"
324
+ };
289
325
  };
290
326
 
291
327
  // src/primitives/composer/ComposerIf.tsx
@@ -314,7 +350,7 @@ __export(message_exports, {
314
350
  });
315
351
 
316
352
  // src/primitives/message/MessageProvider.tsx
317
- import { useMemo as useMemo2, useState as useState2 } from "react";
353
+ import { useMemo as useMemo2, useState } from "react";
318
354
  import { create } from "zustand";
319
355
  import { useShallow } from "zustand/react/shallow";
320
356
  var getIsLast = (thread, message) => {
@@ -323,7 +359,7 @@ var getIsLast = (thread, message) => {
323
359
  };
324
360
  var useMessageContext2 = () => {
325
361
  const { useBranchObserver } = useAssistantContext();
326
- const [context] = useState2(() => {
362
+ const [context] = useState(() => {
327
363
  const useMessage = create(() => ({
328
364
  message: null,
329
365
  isLast: false,
@@ -380,8 +416,8 @@ var MessageProvider = ({
380
416
  useShallow((b) => b.getBranchState(message))
381
417
  );
382
418
  const isLast = useThread((thread) => getIsLast(thread, message));
383
- const [isCopied, setIsCopied] = useState2(false);
384
- const [isHovering, setIsHovering] = useState2(false);
419
+ const [isCopied, setIsCopied] = useState(false);
420
+ const [isHovering, setIsHovering] = useState(false);
385
421
  useMemo2(() => {
386
422
  context.useMessage.setState(
387
423
  {
@@ -530,6 +566,31 @@ var ThreadMessages = ({ components }) => {
530
566
  ));
531
567
  };
532
568
 
569
+ // src/primitives/thread/ThreadScrollToBottom.tsx
570
+ import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
571
+ import {
572
+ Primitive as Primitive4
573
+ } from "@radix-ui/react-primitive";
574
+ import { forwardRef as forwardRef4 } from "react";
575
+ var ThreadScrollToBottom = forwardRef4(({ onClick, ...rest }, ref) => {
576
+ const { useThread } = useAssistantContext();
577
+ const isAtBottom = useThread((s) => s.isAtBottom);
578
+ const handleScrollToBottom = () => {
579
+ const thread = useThread.getState();
580
+ thread.scrollToBottom();
581
+ };
582
+ if (isAtBottom)
583
+ return null;
584
+ return /* @__PURE__ */ React.createElement(
585
+ Primitive4.button,
586
+ {
587
+ ...rest,
588
+ ref,
589
+ onClick: composeEventHandlers3(onClick, handleScrollToBottom)
590
+ }
591
+ );
592
+ });
593
+
533
594
  // src/primitives/composer/index.ts
534
595
  var composer_exports = {};
535
596
  __export(composer_exports, {
@@ -541,41 +602,17 @@ __export(composer_exports, {
541
602
  });
542
603
 
543
604
  // src/primitives/composer/ComposerRoot.tsx
544
- import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
605
+ import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
545
606
  import { useComposedRefs as useComposedRefs2 } from "@radix-ui/react-compose-refs";
546
607
  import {
547
- Primitive as Primitive4
608
+ Primitive as Primitive5
548
609
  } from "@radix-ui/react-primitive";
549
- import {
550
- createContext as createContext3,
551
- forwardRef as forwardRef4,
552
- useContext as useContext4,
553
- useMemo as useMemo3,
554
- useRef as useRef4
555
- } from "react";
556
- var ComposerFormContext = createContext3(null);
557
- var useComposerFormContext = () => {
558
- const context = useContext4(ComposerFormContext);
559
- if (!context) {
560
- throw new Error(
561
- "Composer compound components cannot be rendered outside the Composer component"
562
- );
563
- }
564
- return context;
565
- };
566
- var ComposerRoot = forwardRef4(
610
+ import { forwardRef as forwardRef5, useRef as useRef5 } from "react";
611
+ var ComposerRoot = forwardRef5(
567
612
  ({ onSubmit, ...rest }, forwardedRef) => {
568
613
  const { useComposer } = useComposerContext();
569
- const formRef = useRef4(null);
614
+ const formRef = useRef5(null);
570
615
  const ref = useComposedRefs2(forwardedRef, formRef);
571
- const composerContextValue = useMemo3(
572
- () => ({
573
- submit: () => formRef.current?.dispatchEvent(
574
- new Event("submit", { cancelable: true, bubbles: true })
575
- )
576
- }),
577
- []
578
- );
579
616
  const handleSubmit = (e) => {
580
617
  const composerState = useComposer.getState();
581
618
  if (!composerState.isEditing)
@@ -583,75 +620,103 @@ var ComposerRoot = forwardRef4(
583
620
  e.preventDefault();
584
621
  composerState.send();
585
622
  };
586
- return /* @__PURE__ */ React.createElement(ComposerFormContext.Provider, { value: composerContextValue }, /* @__PURE__ */ React.createElement(
587
- Primitive4.form,
623
+ return /* @__PURE__ */ React.createElement(
624
+ Primitive5.form,
588
625
  {
589
626
  ...rest,
590
627
  ref,
591
- onSubmit: composeEventHandlers3(onSubmit, handleSubmit)
628
+ onSubmit: composeEventHandlers4(onSubmit, handleSubmit)
592
629
  }
593
- ));
630
+ );
594
631
  }
595
632
  );
596
633
 
597
634
  // src/primitives/composer/ComposerInput.tsx
598
- import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
635
+ import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
636
+ import { useComposedRefs as useComposedRefs3 } from "@radix-ui/react-compose-refs";
599
637
  import { Slot } from "@radix-ui/react-slot";
600
- import { forwardRef as forwardRef5 } from "react";
638
+ import {
639
+ forwardRef as forwardRef6,
640
+ useCallback as useCallback2,
641
+ useEffect as useEffect2,
642
+ useRef as useRef6
643
+ } from "react";
601
644
  import TextareaAutosize from "react-textarea-autosize";
602
- var ComposerInput = forwardRef5(({ asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
603
- const { useThread } = useAssistantContext();
604
- const isLoading = useThread((t) => t.isLoading);
605
- const { useComposer } = useComposerContext();
606
- const value = useComposer((c) => {
607
- if (!c.isEditing)
608
- return "";
609
- return c.value;
610
- });
611
- const Component = asChild ? Slot : TextareaAutosize;
612
- const composerForm = useComposerFormContext();
613
- const handleKeyPress = (e) => {
614
- if (disabled)
615
- return;
616
- if (e.key === "Escape") {
617
- useComposer.getState().cancel();
618
- }
619
- if (isLoading)
620
- return;
621
- if (e.key === "Enter" && e.shiftKey === false) {
622
- e.preventDefault();
623
- composerForm.submit();
624
- }
625
- };
626
- return /* @__PURE__ */ React.createElement(
627
- Component,
628
- {
629
- value,
630
- ...rest,
631
- ref: forwardedRef,
632
- disabled,
633
- onChange: composeEventHandlers4(onChange, (e) => {
634
- const composerState = useComposer.getState();
635
- if (!composerState.isEditing)
636
- return;
637
- return composerState.setValue(e.target.value);
638
- }),
639
- onKeyDown: composeEventHandlers4(onKeyDown, handleKeyPress)
640
- }
641
- );
642
- });
645
+ var ComposerInput = forwardRef6(
646
+ ({ autoFocus, asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
647
+ const { useThread } = useAssistantContext();
648
+ const { useComposer, type } = useComposerContext();
649
+ const value = useComposer((c) => {
650
+ if (!c.isEditing)
651
+ return "";
652
+ return c.value;
653
+ });
654
+ const Component = asChild ? Slot : TextareaAutosize;
655
+ const handleKeyPress = (e) => {
656
+ if (disabled)
657
+ return;
658
+ const composer = useComposer.getState();
659
+ if (e.key === "Escape" && composer.canCancel) {
660
+ e.preventDefault();
661
+ useComposer.getState().cancel();
662
+ }
663
+ if (e.key === "Enter" && e.shiftKey === false) {
664
+ const isLoading = useThread.getState().isLoading;
665
+ if (!isLoading) {
666
+ e.preventDefault();
667
+ composer.send();
668
+ }
669
+ }
670
+ };
671
+ const textareaRef = useRef6(null);
672
+ const ref = useComposedRefs3(forwardedRef, textareaRef);
673
+ const autoFocusEnabled = autoFocus !== false && !disabled;
674
+ const focus = useCallback2(() => {
675
+ const textarea = textareaRef.current;
676
+ if (!textarea || !autoFocusEnabled)
677
+ return;
678
+ textarea.focus();
679
+ textarea.setSelectionRange(
680
+ textareaRef.current.value.length,
681
+ textareaRef.current.value.length
682
+ );
683
+ }, [autoFocusEnabled]);
684
+ useEffect2(() => focus(), [focus]);
685
+ useOnScrollToBottom(() => {
686
+ if (type === "assistant") {
687
+ focus();
688
+ }
689
+ });
690
+ return /* @__PURE__ */ React.createElement(
691
+ Component,
692
+ {
693
+ value,
694
+ ...rest,
695
+ ref,
696
+ disabled,
697
+ onChange: composeEventHandlers5(onChange, (e) => {
698
+ const composerState = useComposer.getState();
699
+ if (!composerState.isEditing)
700
+ return;
701
+ return composerState.setValue(e.target.value);
702
+ }),
703
+ onKeyDown: composeEventHandlers5(onKeyDown, handleKeyPress)
704
+ }
705
+ );
706
+ }
707
+ );
643
708
 
644
709
  // src/primitives/composer/ComposerSend.tsx
645
710
  import {
646
- Primitive as Primitive5
711
+ Primitive as Primitive6
647
712
  } from "@radix-ui/react-primitive";
648
- import { forwardRef as forwardRef6 } from "react";
649
- var ComposerSend = forwardRef6(
713
+ import { forwardRef as forwardRef7 } from "react";
714
+ var ComposerSend = forwardRef7(
650
715
  ({ disabled, ...rest }, ref) => {
651
716
  const { useComposer } = useComposerContext();
652
717
  const hasValue = useComposer((c) => c.isEditing && c.value.length > 0);
653
718
  return /* @__PURE__ */ React.createElement(
654
- Primitive5.button,
719
+ Primitive6.button,
655
720
  {
656
721
  type: "submit",
657
722
  ...rest,
@@ -663,24 +728,24 @@ var ComposerSend = forwardRef6(
663
728
  );
664
729
 
665
730
  // src/primitives/composer/ComposerCancel.tsx
666
- import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
731
+ import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
667
732
  import {
668
- Primitive as Primitive6
733
+ Primitive as Primitive7
669
734
  } from "@radix-ui/react-primitive";
670
- import { forwardRef as forwardRef7 } from "react";
671
- var ComposerCancel = forwardRef7(({ disabled, onClick, ...rest }, ref) => {
735
+ import { forwardRef as forwardRef8 } from "react";
736
+ var ComposerCancel = forwardRef8(({ disabled, onClick, ...rest }, ref) => {
672
737
  const { useComposer } = useComposerContext();
673
738
  const hasValue = useComposer((c) => c.canCancel);
674
739
  const handleClose = () => {
675
740
  useComposer.getState().cancel();
676
741
  };
677
742
  return /* @__PURE__ */ React.createElement(
678
- Primitive6.button,
743
+ Primitive7.button,
679
744
  {
680
745
  type: "button",
681
746
  ...rest,
682
747
  ref,
683
- onClick: composeEventHandlers5(onClick, handleClose),
748
+ onClick: composeEventHandlers6(onClick, handleClose),
684
749
  disabled: disabled || !hasValue
685
750
  }
686
751
  );
@@ -696,16 +761,41 @@ __export(branchPicker_exports, {
696
761
  Root: () => BranchPickerRoot
697
762
  });
698
763
 
764
+ // src/utils/context/combined/useCombinedStore.ts
765
+ import { useMemo as useMemo3 } from "react";
766
+
767
+ // src/utils/context/combined/createCombinedStore.ts
768
+ import { useSyncExternalStore } from "react";
769
+ var createCombinedStore = (stores) => {
770
+ const subscribe = (callback) => {
771
+ const unsubscribes = stores.map((store) => store.subscribe(callback));
772
+ return () => {
773
+ for (const unsub of unsubscribes) {
774
+ unsub();
775
+ }
776
+ };
777
+ };
778
+ return (selector) => {
779
+ const getSnapshot = () => selector(...stores.map((store) => store.getState()));
780
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
781
+ };
782
+ };
783
+
784
+ // src/utils/context/combined/useCombinedStore.ts
785
+ var useCombinedStore = (stores, selector) => {
786
+ const useCombined = useMemo3(() => createCombinedStore(stores), stores);
787
+ return useCombined(selector);
788
+ };
789
+
699
790
  // src/actions/useGoToNextBranch.tsx
700
791
  var useGoToNextBranch = () => {
701
792
  const { useThread, useBranchObserver } = useAssistantContext();
702
793
  const { useComposer, useMessage } = useMessageContext();
703
- const isLoading = useThread((s) => s.isLoading);
704
- const isEditing = useComposer((s) => s.isEditing);
705
- const hasNext = useMessage(
706
- ({ branchState: { branchId, branchCount } }) => branchId + 1 < branchCount
794
+ const disabled = useCombinedStore(
795
+ [useThread, useComposer, useMessage],
796
+ (t, c, m) => t.isLoading || c.isEditing || m.branchState.branchId + 1 >= m.branchState.branchCount
707
797
  );
708
- if (isLoading || isEditing || !hasNext)
798
+ if (disabled)
709
799
  return null;
710
800
  return () => {
711
801
  const {
@@ -717,23 +807,23 @@ var useGoToNextBranch = () => {
717
807
  };
718
808
 
719
809
  // src/utils/createActionButton.tsx
720
- import { forwardRef as forwardRef8 } from "react";
810
+ import { forwardRef as forwardRef9 } from "react";
721
811
  import {
722
- Primitive as Primitive7
812
+ Primitive as Primitive8
723
813
  } from "@radix-ui/react-primitive";
724
- import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
814
+ import { composeEventHandlers as composeEventHandlers7 } from "@radix-ui/primitive";
725
815
  var createActionButton = (useActionButton) => {
726
- return forwardRef8(
816
+ return forwardRef9(
727
817
  (props, forwardedRef) => {
728
818
  const onClick = useActionButton(props);
729
819
  return /* @__PURE__ */ React.createElement(
730
- Primitive7.button,
820
+ Primitive8.button,
731
821
  {
732
822
  type: "button",
733
823
  disabled: !onClick,
734
824
  ...props,
735
825
  ref: forwardedRef,
736
- onClick: composeEventHandlers6(props.onClick, onClick ?? void 0)
826
+ onClick: composeEventHandlers7(props.onClick, onClick ?? void 0)
737
827
  }
738
828
  );
739
829
  }
@@ -747,10 +837,11 @@ var BranchPickerNext = createActionButton(useGoToNextBranch);
747
837
  var useGoToPreviousBranch = () => {
748
838
  const { useThread, useBranchObserver } = useAssistantContext();
749
839
  const { useComposer, useMessage } = useMessageContext();
750
- const isLoading = useThread((s) => s.isLoading);
751
- const isEditing = useComposer((s) => s.isEditing);
752
- const hasNext = useMessage(({ branchState: { branchId } }) => branchId > 0);
753
- if (isLoading || isEditing || !hasNext)
840
+ const disabled = useCombinedStore(
841
+ [useThread, useComposer, useMessage],
842
+ (t, c, m) => t.isLoading || c.isEditing || m.branchState.branchId <= 0
843
+ );
844
+ if (disabled)
754
845
  return null;
755
846
  return () => {
756
847
  const {
@@ -780,11 +871,11 @@ var BranchPickerNumber = () => {
780
871
 
781
872
  // src/primitives/branchPicker/BranchPickerRoot.tsx
782
873
  import {
783
- Primitive as Primitive8
874
+ Primitive as Primitive9
784
875
  } from "@radix-ui/react-primitive";
785
- import { forwardRef as forwardRef9 } from "react";
786
- var BranchPickerRoot = forwardRef9(({ hideWhenSingleBranch, ...rest }, ref) => {
787
- return /* @__PURE__ */ React.createElement(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0 }, /* @__PURE__ */ React.createElement(Primitive8.div, { ...rest, ref }));
876
+ import { forwardRef as forwardRef10 } from "react";
877
+ var BranchPickerRoot = forwardRef10(({ hideWhenSingleBranch, ...rest }, ref) => {
878
+ return /* @__PURE__ */ React.createElement(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0 }, /* @__PURE__ */ React.createElement(Primitive9.div, { ...rest, ref }));
788
879
  });
789
880
 
790
881
  // src/primitives/actionBar/index.ts
@@ -798,29 +889,31 @@ __export(actionBar_exports, {
798
889
 
799
890
  // src/primitives/actionBar/ActionBarRoot.tsx
800
891
  import {
801
- Primitive as Primitive9
892
+ Primitive as Primitive10
802
893
  } from "@radix-ui/react-primitive";
803
- import { forwardRef as forwardRef10 } from "react";
804
- var ActionBarRoot = forwardRef10(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
894
+ import { forwardRef as forwardRef11 } from "react";
895
+ var ActionBarRoot = forwardRef11(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
805
896
  const { useThread } = useAssistantContext();
806
897
  const { useMessage } = useMessageContext();
807
- const hideAndfloatStatus = useMessage((m) => {
808
- const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
809
- if (!autohideEnabled)
898
+ const hideAndfloatStatus = useCombinedStore(
899
+ [useThread, useMessage],
900
+ (t, m) => {
901
+ if (hideWhenBusy && t.isLoading)
902
+ return "hidden" /* Hidden */;
903
+ const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
904
+ if (!autohideEnabled)
905
+ return "normal" /* Normal */;
906
+ if (!m.isHovering)
907
+ return "hidden" /* Hidden */;
908
+ if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branchState.branchCount <= 1)
909
+ return "floating" /* Floating */;
810
910
  return "normal" /* Normal */;
811
- if (!m.isHovering)
812
- return "hidden" /* Hidden */;
813
- if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branchState.branchCount <= 1)
814
- return "floating" /* Floating */;
815
- return "normal" /* Normal */;
816
- });
817
- const busy = useThread((t) => t.isLoading);
818
- if (hideWhenBusy && busy)
819
- return null;
911
+ }
912
+ );
820
913
  if (hideAndfloatStatus === "hidden" /* Hidden */)
821
914
  return null;
822
915
  return /* @__PURE__ */ React.createElement(
823
- Primitive9.div,
916
+ Primitive10.div,
824
917
  {
825
918
  "data-floating": hideAndfloatStatus === "floating" /* Floating */,
826
919
  ...rest,
@@ -852,9 +945,11 @@ var ActionBarCopy = createActionButton(useCopyMessage);
852
945
  var useReloadMessage = () => {
853
946
  const { useThread, useBranchObserver } = useAssistantContext();
854
947
  const { useMessage } = useMessageContext();
855
- const isLoading = useThread((s) => s.isLoading);
856
- const isAssistant = useMessage((s) => s.message.role === "assistant");
857
- if (isLoading || !isAssistant)
948
+ const disabled = useCombinedStore(
949
+ [useThread, useMessage],
950
+ (t, m) => t.isLoading || m.message.role !== "assistant"
951
+ );
952
+ if (disabled)
858
953
  return null;
859
954
  return () => {
860
955
  const message = useMessage.getState().message;
@@ -870,9 +965,11 @@ var ActionBarReload = createActionButton(useReloadMessage);
870
965
  // src/actions/useBeginMessageEdit.tsx
871
966
  var useBeginMessageEdit = () => {
872
967
  const { useMessage, useComposer } = useMessageContext();
873
- const isUser = useMessage((s) => s.message.role === "user");
874
- const isEditing = useComposer((s) => s.isEditing);
875
- if (!isUser || isEditing)
968
+ const disabled = useCombinedStore(
969
+ [useMessage, useComposer],
970
+ (m, c) => m.message.role !== "user" || c.isEditing
971
+ );
972
+ if (disabled)
876
973
  return null;
877
974
  return () => {
878
975
  const { edit } = useComposer.getState();
@@ -884,24 +981,34 @@ var useBeginMessageEdit = () => {
884
981
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
885
982
 
886
983
  // src/vercel/VercelAIAssistantProvider.tsx
887
- import { useCallback as useCallback2, useMemo as useMemo4 } from "react";
984
+ import { useCallback as useCallback3, useMemo as useMemo4 } from "react";
888
985
 
889
986
  // src/vercel/useDummyAIAssistantContext.tsx
890
- import { useState as useState3 } from "react";
987
+ import { useState as useState2 } from "react";
891
988
  import { create as create2 } from "zustand";
892
989
  var useDummyAIAssistantContext = () => {
893
- const [context] = useState3(() => {
990
+ const [context] = useState2(() => {
991
+ const scrollToBottomListeners = /* @__PURE__ */ new Set();
894
992
  const useThread = create2()(() => ({
895
993
  messages: [],
896
994
  isLoading: false,
897
- reload: async () => {
898
- throw new Error("Not implemented");
899
- },
900
995
  append: async () => {
901
996
  throw new Error("Not implemented");
902
997
  },
903
998
  stop: () => {
904
999
  throw new Error("Not implemented");
1000
+ },
1001
+ isAtBottom: true,
1002
+ scrollToBottom: () => {
1003
+ for (const listener of scrollToBottomListeners) {
1004
+ listener();
1005
+ }
1006
+ },
1007
+ onScrollToBottom: (callback) => {
1008
+ scrollToBottomListeners.add(callback);
1009
+ return () => {
1010
+ scrollToBottomListeners.delete(callback);
1011
+ };
905
1012
  }
906
1013
  }));
907
1014
  const useComposer = create2()(() => ({
@@ -975,25 +1082,20 @@ var VercelAIAssistantProvider = ({
975
1082
  const messages = useMemo4(() => {
976
1083
  return vercelToCachedThreadMessages(vercel.messages);
977
1084
  }, [vercel.messages]);
978
- const maybeReload = "reload" in vercel ? vercel.reload : null;
979
- const reload = useCallback2(async () => {
980
- if (!maybeReload)
981
- throw new Error("Reload not supported");
982
- await maybeReload();
983
- }, [maybeReload]);
984
- const append = useCallback2(
1085
+ const append = useCallback3(
985
1086
  async (message) => {
986
1087
  if (message.content[0]?.type !== "text") {
987
1088
  throw new Error("Only text content is currently supported");
988
1089
  }
1090
+ context.useThread.getState().scrollToBottom();
989
1091
  await vercel.append({
990
1092
  role: message.role,
991
1093
  content: message.content[0].text
992
1094
  });
993
1095
  },
994
- [vercel.append]
1096
+ [context, vercel.append]
995
1097
  );
996
- const stop = useCallback2(() => {
1098
+ const stop = useCallback3(() => {
997
1099
  const lastMessage = vercel.messages.at(-1);
998
1100
  vercel.stop();
999
1101
  if (lastMessage?.role === "user") {
@@ -1002,17 +1104,13 @@ var VercelAIAssistantProvider = ({
1002
1104
  }, [vercel.messages, vercel.stop, vercel.setInput]);
1003
1105
  const isLoading = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1004
1106
  useMemo4(() => {
1005
- context.useThread.setState(
1006
- {
1007
- messages,
1008
- isLoading,
1009
- reload,
1010
- append,
1011
- stop
1012
- },
1013
- true
1014
- );
1015
- }, [context, messages, reload, append, stop, isLoading]);
1107
+ context.useThread.setState({
1108
+ messages,
1109
+ isLoading,
1110
+ append,
1111
+ stop
1112
+ });
1113
+ }, [context, messages, append, stop, isLoading]);
1016
1114
  useMemo4(() => {
1017
1115
  context.useComposer.setState({
1018
1116
  canCancel: isLoading,
@@ -1020,7 +1118,7 @@ var VercelAIAssistantProvider = ({
1020
1118
  setValue: vercel.setInput
1021
1119
  });
1022
1120
  }, [context, isLoading, vercel.input, vercel.setInput]);
1023
- const branches = useVercelAIBranches(vercel);
1121
+ const branches = useVercelAIBranches(vercel, context);
1024
1122
  useMemo4(() => {
1025
1123
  context.useBranchObserver.setState(branches, true);
1026
1124
  }, [context, branches]);
@@ -1029,7 +1127,7 @@ var VercelAIAssistantProvider = ({
1029
1127
 
1030
1128
  // src/vercel/VercelRSCAssistantProvider.tsx
1031
1129
  import {
1032
- useCallback as useCallback3,
1130
+ useCallback as useCallback4,
1033
1131
  useMemo as useMemo5
1034
1132
  } from "react";
1035
1133
  var ThreadMessageCache2 = /* @__PURE__ */ new WeakMap();
@@ -1061,17 +1159,18 @@ var VercelRSCAssistantProvider = ({
1061
1159
  const messages = useMemo5(() => {
1062
1160
  return vercelToCachedThreadMessages2(vercelMessages);
1063
1161
  }, [vercelMessages]);
1064
- const append = useCallback3(
1162
+ const append = useCallback4(
1065
1163
  async (message) => {
1066
1164
  if (message.content[0]?.type !== "text") {
1067
1165
  throw new Error("Only text content is currently supported");
1068
1166
  }
1167
+ context.useThread.getState().scrollToBottom();
1069
1168
  await vercelAppend({
1070
1169
  role: message.role,
1071
1170
  content: [{ type: "text", text: message.content[0].text }]
1072
1171
  });
1073
1172
  },
1074
- [vercelAppend]
1173
+ [context, vercelAppend]
1075
1174
  );
1076
1175
  useMemo5(() => {
1077
1176
  context.useThread.setState({