@assistant-ui/react 0.0.7 → 0.0.8

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