@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.js CHANGED
@@ -53,6 +53,7 @@ __export(thread_exports, {
53
53
  If: () => ThreadIf,
54
54
  Messages: () => ThreadMessages,
55
55
  Root: () => ThreadRoot,
56
+ ScrollToBottom: () => ThreadScrollToBottom,
56
57
  Viewport: () => ThreadViewport
57
58
  });
58
59
 
@@ -106,7 +107,7 @@ var ThreadEmpty = ({ children }) => {
106
107
  var import_primitive = require("@radix-ui/primitive");
107
108
  var import_react_compose_refs = require("@radix-ui/react-compose-refs");
108
109
  var import_react_primitive2 = require("@radix-ui/react-primitive");
109
- var import_react4 = require("react");
110
+ var import_react5 = require("react");
110
111
 
111
112
  // src/utils/hooks/useOnResizeContent.tsx
112
113
  var import_react3 = require("react");
@@ -147,22 +148,51 @@ var useOnResizeContent = (ref, callback) => {
147
148
  }, [ref.current]);
148
149
  };
149
150
 
151
+ // src/utils/hooks/useOnScrollToBottom.tsx
152
+ var import_react4 = require("react");
153
+ var useOnScrollToBottom = (callback) => {
154
+ const callbackRef = (0, import_react4.useRef)(callback);
155
+ callbackRef.current = callback;
156
+ const { useThread } = useAssistantContext();
157
+ (0, import_react4.useEffect)(() => {
158
+ return useThread.getState().onScrollToBottom(() => {
159
+ callbackRef.current();
160
+ });
161
+ }, [useThread]);
162
+ };
163
+
150
164
  // src/primitives/thread/ThreadViewport.tsx
151
- var ThreadViewport = (0, import_react4.forwardRef)(({ onScroll, children, ...rest }, forwardedRef) => {
152
- const divRef = (0, import_react4.useRef)(null);
165
+ var ThreadViewport = (0, import_react5.forwardRef)(({ autoScroll = true, onScroll, children, ...rest }, forwardedRef) => {
166
+ const messagesEndRef = (0, import_react5.useRef)(null);
167
+ const divRef = (0, import_react5.useRef)(null);
153
168
  const ref = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, divRef);
154
- const [isAtBottom, setIsAtBottom] = (0, import_react4.useState)(true);
169
+ const { useThread } = useAssistantContext();
170
+ const firstRenderRef = (0, import_react5.useRef)(true);
171
+ const scrollToBottom = () => {
172
+ const div = messagesEndRef.current;
173
+ if (!div || !autoScroll)
174
+ return;
175
+ const behavior = firstRenderRef.current ? "instant" : "auto";
176
+ firstRenderRef.current = false;
177
+ div.scrollIntoView({ behavior });
178
+ };
155
179
  useOnResizeContent(divRef, () => {
156
- const div = divRef.current;
157
- if (!div || !isAtBottom)
180
+ if (!useThread.getState().isAtBottom)
158
181
  return;
159
- div.scrollTop = div.scrollHeight;
182
+ scrollToBottom();
183
+ });
184
+ useOnScrollToBottom(() => {
185
+ scrollToBottom();
160
186
  });
161
187
  const handleScroll = () => {
162
188
  const div = divRef.current;
163
189
  if (!div)
164
190
  return;
165
- setIsAtBottom(div.scrollHeight - div.scrollTop <= div.clientHeight + 50);
191
+ const isAtBottom = useThread.getState().isAtBottom;
192
+ const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight + 50;
193
+ if (newIsAtBottom !== isAtBottom) {
194
+ useThread.setState({ isAtBottom: newIsAtBottom });
195
+ }
166
196
  };
167
197
  return /* @__PURE__ */ React.createElement(
168
198
  import_react_primitive2.Primitive.div,
@@ -171,12 +201,13 @@ var ThreadViewport = (0, import_react4.forwardRef)(({ onScroll, children, ...res
171
201
  onScroll: (0, import_primitive.composeEventHandlers)(onScroll, handleScroll),
172
202
  ref
173
203
  },
174
- children
204
+ children,
205
+ /* @__PURE__ */ React.createElement("div", { ref: messagesEndRef })
175
206
  );
176
207
  });
177
208
 
178
209
  // src/vercel/useVercelAIBranches.tsx
179
- var import_react5 = require("react");
210
+ var import_react6 = require("react");
180
211
  var ROOT_ID = "__ROOT_ID__";
181
212
  var UPCOMING_MESSAGE_ID = "__UPCOMING_MESSAGE_ID__";
182
213
  var updateBranchData = (data, messages) => {
@@ -243,20 +274,20 @@ var sliceMessagesUntil = (messages, message) => {
243
274
  throw new Error("Unexpected: Message not found");
244
275
  return messages.slice(0, messageIdx);
245
276
  };
246
- var useVercelAIBranches = (chat) => {
247
- const data = (0, import_react5.useRef)({
277
+ var useVercelAIBranches = (chat, context) => {
278
+ const data = (0, import_react6.useRef)({
248
279
  parentMap: /* @__PURE__ */ new Map(),
249
280
  branchMap: /* @__PURE__ */ new Map(),
250
281
  snapshots: /* @__PURE__ */ new Map()
251
282
  }).current;
252
283
  updateBranchData(data, chat.messages);
253
- const getBranchState = (0, import_react5.useCallback)(
284
+ const getBranchState = (0, import_react6.useCallback)(
254
285
  (message) => {
255
286
  return getBranchStateImpl(data, chat.messages, message);
256
287
  },
257
288
  [data, chat.messages]
258
289
  );
259
- const switchToBranch = (0, import_react5.useCallback)(
290
+ const switchToBranch = (0, import_react6.useCallback)(
260
291
  (message, branchId) => {
261
292
  const newMessages = switchToBranchImpl(
262
293
  data,
@@ -269,30 +300,32 @@ var useVercelAIBranches = (chat) => {
269
300
  [data, chat.messages, chat.setMessages]
270
301
  );
271
302
  const reloadMaybe = "reload" in chat ? chat.reload : void 0;
272
- const reloadAt = (0, import_react5.useCallback)(
303
+ const reloadAt = (0, import_react6.useCallback)(
273
304
  async (message) => {
274
305
  if (!reloadMaybe)
275
306
  throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
276
307
  const newMessages = sliceMessagesUntil(chat.messages, message);
277
308
  chat.setMessages(newMessages);
309
+ context.useThread.getState().scrollToBottom();
278
310
  await reloadMaybe();
279
311
  },
280
- [chat.messages, chat.setMessages, reloadMaybe]
312
+ [context, chat.messages, chat.setMessages, reloadMaybe]
281
313
  );
282
- const editAt = (0, import_react5.useCallback)(
314
+ const editAt = (0, import_react6.useCallback)(
283
315
  async (message, newMessage) => {
284
316
  const newMessages = sliceMessagesUntil(chat.messages, message);
285
317
  chat.setMessages(newMessages);
286
318
  if (newMessage.content[0]?.type !== "text")
287
319
  throw new Error("Only text content is currently supported");
320
+ context.useThread.getState().scrollToBottom();
288
321
  await chat.append({
289
322
  role: "user",
290
323
  content: newMessage.content[0].text
291
324
  });
292
325
  },
293
- [chat.messages, chat.setMessages, chat.append]
326
+ [context, chat.messages, chat.setMessages, chat.append]
294
327
  );
295
- return (0, import_react5.useMemo)(
328
+ return (0, import_react6.useMemo)(
296
329
  () => ({
297
330
  getBranchState,
298
331
  switchToBranch,
@@ -306,24 +339,27 @@ var hasUpcomingMessage = (thread) => {
306
339
  return thread.isLoading && thread.messages[thread.messages.length - 1]?.role !== "assistant";
307
340
  };
308
341
 
309
- // src/utils/context/ComposerState.ts
310
- var import_react7 = require("react");
342
+ // src/utils/context/useComposerContext.ts
343
+ var import_react8 = require("react");
311
344
 
312
- // src/utils/context/MessageContext.ts
313
- var import_react6 = require("react");
314
- var MessageContext = (0, import_react6.createContext)(null);
345
+ // src/utils/context/useMessageContext.ts
346
+ var import_react7 = require("react");
347
+ var MessageContext = (0, import_react7.createContext)(null);
315
348
  var useMessageContext = () => {
316
- const context = (0, import_react6.useContext)(MessageContext);
349
+ const context = (0, import_react7.useContext)(MessageContext);
317
350
  if (!context)
318
351
  throw new Error("useMessageContext must be used within a MessageProvider");
319
352
  return context;
320
353
  };
321
354
 
322
- // src/utils/context/ComposerState.ts
355
+ // src/utils/context/useComposerContext.ts
323
356
  var useComposerContext = () => {
324
357
  const { useComposer: useAssisstantComposer } = useAssistantContext();
325
- const { useComposer: useMessageComposer } = (0, import_react7.useContext)(MessageContext) ?? {};
326
- return { useComposer: useMessageComposer ?? useAssisstantComposer };
358
+ const { useComposer: useMessageComposer } = (0, import_react8.useContext)(MessageContext) ?? {};
359
+ return {
360
+ useComposer: useMessageComposer ?? useAssisstantComposer,
361
+ type: useMessageComposer ? "message" : "assistant"
362
+ };
327
363
  };
328
364
 
329
365
  // src/primitives/composer/ComposerIf.tsx
@@ -352,7 +388,7 @@ __export(message_exports, {
352
388
  });
353
389
 
354
390
  // src/primitives/message/MessageProvider.tsx
355
- var import_react8 = require("react");
391
+ var import_react9 = require("react");
356
392
  var import_zustand = require("zustand");
357
393
  var import_shallow = require("zustand/react/shallow");
358
394
  var getIsLast = (thread, message) => {
@@ -361,7 +397,7 @@ var getIsLast = (thread, message) => {
361
397
  };
362
398
  var useMessageContext2 = () => {
363
399
  const { useBranchObserver } = useAssistantContext();
364
- const [context] = (0, import_react8.useState)(() => {
400
+ const [context] = (0, import_react9.useState)(() => {
365
401
  const useMessage = (0, import_zustand.create)(() => ({
366
402
  message: null,
367
403
  isLast: false,
@@ -418,9 +454,9 @@ var MessageProvider = ({
418
454
  (0, import_shallow.useShallow)((b) => b.getBranchState(message))
419
455
  );
420
456
  const isLast = useThread((thread) => getIsLast(thread, message));
421
- const [isCopied, setIsCopied] = (0, import_react8.useState)(false);
422
- const [isHovering, setIsHovering] = (0, import_react8.useState)(false);
423
- (0, import_react8.useMemo)(() => {
457
+ const [isCopied, setIsCopied] = (0, import_react9.useState)(false);
458
+ const [isHovering, setIsHovering] = (0, import_react9.useState)(false);
459
+ (0, import_react9.useMemo)(() => {
424
460
  context.useMessage.setState(
425
461
  {
426
462
  message,
@@ -440,8 +476,8 @@ var MessageProvider = ({
440
476
  // src/primitives/message/MessageRoot.tsx
441
477
  var import_primitive2 = require("@radix-ui/primitive");
442
478
  var import_react_primitive3 = require("@radix-ui/react-primitive");
443
- var import_react9 = require("react");
444
- var MessageRoot = (0, import_react9.forwardRef)(
479
+ var import_react10 = require("react");
480
+ var MessageRoot = (0, import_react10.forwardRef)(
445
481
  ({ onMouseEnter, onMouseLeave, ...rest }, ref) => {
446
482
  const { useMessage } = useMessageContext();
447
483
  const setIsHovering = useMessage((s) => s.setIsHovering);
@@ -566,6 +602,29 @@ var ThreadMessages = ({ components }) => {
566
602
  ));
567
603
  };
568
604
 
605
+ // src/primitives/thread/ThreadScrollToBottom.tsx
606
+ var import_primitive3 = require("@radix-ui/primitive");
607
+ var import_react_primitive4 = require("@radix-ui/react-primitive");
608
+ var import_react11 = require("react");
609
+ var ThreadScrollToBottom = (0, import_react11.forwardRef)(({ onClick, ...rest }, ref) => {
610
+ const { useThread } = useAssistantContext();
611
+ const isAtBottom = useThread((s) => s.isAtBottom);
612
+ const handleScrollToBottom = () => {
613
+ const thread = useThread.getState();
614
+ thread.scrollToBottom();
615
+ };
616
+ if (isAtBottom)
617
+ return null;
618
+ return /* @__PURE__ */ React.createElement(
619
+ import_react_primitive4.Primitive.button,
620
+ {
621
+ ...rest,
622
+ ref,
623
+ onClick: (0, import_primitive3.composeEventHandlers)(onClick, handleScrollToBottom)
624
+ }
625
+ );
626
+ });
627
+
569
628
  // src/primitives/composer/index.ts
570
629
  var composer_exports = {};
571
630
  __export(composer_exports, {
@@ -577,33 +636,15 @@ __export(composer_exports, {
577
636
  });
578
637
 
579
638
  // src/primitives/composer/ComposerRoot.tsx
580
- var import_primitive3 = require("@radix-ui/primitive");
639
+ var import_primitive4 = require("@radix-ui/primitive");
581
640
  var import_react_compose_refs2 = require("@radix-ui/react-compose-refs");
582
- var import_react_primitive4 = require("@radix-ui/react-primitive");
583
- var import_react10 = require("react");
584
- var ComposerFormContext = (0, import_react10.createContext)(null);
585
- var useComposerFormContext = () => {
586
- const context = (0, import_react10.useContext)(ComposerFormContext);
587
- if (!context) {
588
- throw new Error(
589
- "Composer compound components cannot be rendered outside the Composer component"
590
- );
591
- }
592
- return context;
593
- };
594
- var ComposerRoot = (0, import_react10.forwardRef)(
641
+ var import_react_primitive5 = require("@radix-ui/react-primitive");
642
+ var import_react12 = require("react");
643
+ var ComposerRoot = (0, import_react12.forwardRef)(
595
644
  ({ onSubmit, ...rest }, forwardedRef) => {
596
645
  const { useComposer } = useComposerContext();
597
- const formRef = (0, import_react10.useRef)(null);
646
+ const formRef = (0, import_react12.useRef)(null);
598
647
  const ref = (0, import_react_compose_refs2.useComposedRefs)(forwardedRef, formRef);
599
- const composerContextValue = (0, import_react10.useMemo)(
600
- () => ({
601
- submit: () => formRef.current?.dispatchEvent(
602
- new Event("submit", { cancelable: true, bubbles: true })
603
- )
604
- }),
605
- []
606
- );
607
648
  const handleSubmit = (e) => {
608
649
  const composerState = useComposer.getState();
609
650
  if (!composerState.isEditing)
@@ -611,73 +652,96 @@ var ComposerRoot = (0, import_react10.forwardRef)(
611
652
  e.preventDefault();
612
653
  composerState.send();
613
654
  };
614
- return /* @__PURE__ */ React.createElement(ComposerFormContext.Provider, { value: composerContextValue }, /* @__PURE__ */ React.createElement(
615
- import_react_primitive4.Primitive.form,
655
+ return /* @__PURE__ */ React.createElement(
656
+ import_react_primitive5.Primitive.form,
616
657
  {
617
658
  ...rest,
618
659
  ref,
619
- onSubmit: (0, import_primitive3.composeEventHandlers)(onSubmit, handleSubmit)
660
+ onSubmit: (0, import_primitive4.composeEventHandlers)(onSubmit, handleSubmit)
620
661
  }
621
- ));
662
+ );
622
663
  }
623
664
  );
624
665
 
625
666
  // src/primitives/composer/ComposerInput.tsx
626
- var import_primitive4 = require("@radix-ui/primitive");
667
+ var import_primitive5 = require("@radix-ui/primitive");
668
+ var import_react_compose_refs3 = require("@radix-ui/react-compose-refs");
627
669
  var import_react_slot = require("@radix-ui/react-slot");
628
- var import_react11 = require("react");
670
+ var import_react13 = require("react");
629
671
  var import_react_textarea_autosize = __toESM(require("react-textarea-autosize"));
630
- var ComposerInput = (0, import_react11.forwardRef)(({ asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
631
- const { useThread } = useAssistantContext();
632
- const isLoading = useThread((t) => t.isLoading);
633
- const { useComposer } = useComposerContext();
634
- const value = useComposer((c) => {
635
- if (!c.isEditing)
636
- return "";
637
- return c.value;
638
- });
639
- const Component = asChild ? import_react_slot.Slot : import_react_textarea_autosize.default;
640
- const composerForm = useComposerFormContext();
641
- const handleKeyPress = (e) => {
642
- if (disabled)
643
- return;
644
- if (e.key === "Escape") {
645
- useComposer.getState().cancel();
646
- }
647
- if (isLoading)
648
- return;
649
- if (e.key === "Enter" && e.shiftKey === false) {
650
- e.preventDefault();
651
- composerForm.submit();
652
- }
653
- };
654
- return /* @__PURE__ */ React.createElement(
655
- Component,
656
- {
657
- value,
658
- ...rest,
659
- ref: forwardedRef,
660
- disabled,
661
- onChange: (0, import_primitive4.composeEventHandlers)(onChange, (e) => {
662
- const composerState = useComposer.getState();
663
- if (!composerState.isEditing)
664
- return;
665
- return composerState.setValue(e.target.value);
666
- }),
667
- onKeyDown: (0, import_primitive4.composeEventHandlers)(onKeyDown, handleKeyPress)
668
- }
669
- );
670
- });
672
+ var ComposerInput = (0, import_react13.forwardRef)(
673
+ ({ autoFocus, asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
674
+ const { useThread } = useAssistantContext();
675
+ const { useComposer, type } = useComposerContext();
676
+ const value = useComposer((c) => {
677
+ if (!c.isEditing)
678
+ return "";
679
+ return c.value;
680
+ });
681
+ const Component = asChild ? import_react_slot.Slot : import_react_textarea_autosize.default;
682
+ const handleKeyPress = (e) => {
683
+ if (disabled)
684
+ return;
685
+ const composer = useComposer.getState();
686
+ if (e.key === "Escape" && composer.canCancel) {
687
+ e.preventDefault();
688
+ useComposer.getState().cancel();
689
+ }
690
+ if (e.key === "Enter" && e.shiftKey === false) {
691
+ const isLoading = useThread.getState().isLoading;
692
+ if (!isLoading) {
693
+ e.preventDefault();
694
+ composer.send();
695
+ }
696
+ }
697
+ };
698
+ const textareaRef = (0, import_react13.useRef)(null);
699
+ const ref = (0, import_react_compose_refs3.useComposedRefs)(forwardedRef, textareaRef);
700
+ const autoFocusEnabled = autoFocus !== false && !disabled;
701
+ const focus = (0, import_react13.useCallback)(() => {
702
+ const textarea = textareaRef.current;
703
+ if (!textarea || !autoFocusEnabled)
704
+ return;
705
+ textarea.focus();
706
+ textarea.setSelectionRange(
707
+ textareaRef.current.value.length,
708
+ textareaRef.current.value.length
709
+ );
710
+ }, [autoFocusEnabled]);
711
+ (0, import_react13.useEffect)(() => focus(), [focus]);
712
+ useOnScrollToBottom(() => {
713
+ if (type === "assistant") {
714
+ focus();
715
+ }
716
+ });
717
+ return /* @__PURE__ */ React.createElement(
718
+ Component,
719
+ {
720
+ value,
721
+ ...rest,
722
+ ref,
723
+ disabled,
724
+ onChange: (0, import_primitive5.composeEventHandlers)(onChange, (e) => {
725
+ const composerState = useComposer.getState();
726
+ if (!composerState.isEditing)
727
+ return;
728
+ return composerState.setValue(e.target.value);
729
+ }),
730
+ onKeyDown: (0, import_primitive5.composeEventHandlers)(onKeyDown, handleKeyPress)
731
+ }
732
+ );
733
+ }
734
+ );
671
735
 
672
736
  // src/primitives/composer/ComposerSend.tsx
673
- var import_react_primitive5 = require("@radix-ui/react-primitive");
674
- var import_react12 = require("react");
675
- var ComposerSend = (0, import_react12.forwardRef)(
737
+ var import_react_primitive6 = require("@radix-ui/react-primitive");
738
+ var import_react14 = require("react");
739
+ var ComposerSend = (0, import_react14.forwardRef)(
676
740
  ({ disabled, ...rest }, ref) => {
677
741
  const { useComposer } = useComposerContext();
678
742
  const hasValue = useComposer((c) => c.isEditing && c.value.length > 0);
679
743
  return /* @__PURE__ */ React.createElement(
680
- import_react_primitive5.Primitive.button,
744
+ import_react_primitive6.Primitive.button,
681
745
  {
682
746
  type: "submit",
683
747
  ...rest,
@@ -689,22 +753,22 @@ var ComposerSend = (0, import_react12.forwardRef)(
689
753
  );
690
754
 
691
755
  // src/primitives/composer/ComposerCancel.tsx
692
- var import_primitive5 = require("@radix-ui/primitive");
693
- var import_react_primitive6 = require("@radix-ui/react-primitive");
694
- var import_react13 = require("react");
695
- var ComposerCancel = (0, import_react13.forwardRef)(({ disabled, onClick, ...rest }, ref) => {
756
+ var import_primitive6 = require("@radix-ui/primitive");
757
+ var import_react_primitive7 = require("@radix-ui/react-primitive");
758
+ var import_react15 = require("react");
759
+ var ComposerCancel = (0, import_react15.forwardRef)(({ disabled, onClick, ...rest }, ref) => {
696
760
  const { useComposer } = useComposerContext();
697
761
  const hasValue = useComposer((c) => c.canCancel);
698
762
  const handleClose = () => {
699
763
  useComposer.getState().cancel();
700
764
  };
701
765
  return /* @__PURE__ */ React.createElement(
702
- import_react_primitive6.Primitive.button,
766
+ import_react_primitive7.Primitive.button,
703
767
  {
704
768
  type: "button",
705
769
  ...rest,
706
770
  ref,
707
- onClick: (0, import_primitive5.composeEventHandlers)(onClick, handleClose),
771
+ onClick: (0, import_primitive6.composeEventHandlers)(onClick, handleClose),
708
772
  disabled: disabled || !hasValue
709
773
  }
710
774
  );
@@ -720,16 +784,41 @@ __export(branchPicker_exports, {
720
784
  Root: () => BranchPickerRoot
721
785
  });
722
786
 
787
+ // src/utils/context/combined/useCombinedStore.ts
788
+ var import_react17 = require("react");
789
+
790
+ // src/utils/context/combined/createCombinedStore.ts
791
+ var import_react16 = require("react");
792
+ var createCombinedStore = (stores) => {
793
+ const subscribe = (callback) => {
794
+ const unsubscribes = stores.map((store) => store.subscribe(callback));
795
+ return () => {
796
+ for (const unsub of unsubscribes) {
797
+ unsub();
798
+ }
799
+ };
800
+ };
801
+ return (selector) => {
802
+ const getSnapshot = () => selector(...stores.map((store) => store.getState()));
803
+ return (0, import_react16.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
804
+ };
805
+ };
806
+
807
+ // src/utils/context/combined/useCombinedStore.ts
808
+ var useCombinedStore = (stores, selector) => {
809
+ const useCombined = (0, import_react17.useMemo)(() => createCombinedStore(stores), stores);
810
+ return useCombined(selector);
811
+ };
812
+
723
813
  // src/actions/useGoToNextBranch.tsx
724
814
  var useGoToNextBranch = () => {
725
815
  const { useThread, useBranchObserver } = useAssistantContext();
726
816
  const { useComposer, useMessage } = useMessageContext();
727
- const isLoading = useThread((s) => s.isLoading);
728
- const isEditing = useComposer((s) => s.isEditing);
729
- const hasNext = useMessage(
730
- ({ branchState: { branchId, branchCount } }) => branchId + 1 < branchCount
817
+ const disabled = useCombinedStore(
818
+ [useThread, useComposer, useMessage],
819
+ (t, c, m) => t.isLoading || c.isEditing || m.branchState.branchId + 1 >= m.branchState.branchCount
731
820
  );
732
- if (isLoading || isEditing || !hasNext)
821
+ if (disabled)
733
822
  return null;
734
823
  return () => {
735
824
  const {
@@ -741,21 +830,21 @@ var useGoToNextBranch = () => {
741
830
  };
742
831
 
743
832
  // src/utils/createActionButton.tsx
744
- var import_react14 = require("react");
745
- var import_react_primitive7 = require("@radix-ui/react-primitive");
746
- var import_primitive6 = require("@radix-ui/primitive");
833
+ var import_react18 = require("react");
834
+ var import_react_primitive8 = require("@radix-ui/react-primitive");
835
+ var import_primitive7 = require("@radix-ui/primitive");
747
836
  var createActionButton = (useActionButton) => {
748
- return (0, import_react14.forwardRef)(
837
+ return (0, import_react18.forwardRef)(
749
838
  (props, forwardedRef) => {
750
839
  const onClick = useActionButton(props);
751
840
  return /* @__PURE__ */ React.createElement(
752
- import_react_primitive7.Primitive.button,
841
+ import_react_primitive8.Primitive.button,
753
842
  {
754
843
  type: "button",
755
844
  disabled: !onClick,
756
845
  ...props,
757
846
  ref: forwardedRef,
758
- onClick: (0, import_primitive6.composeEventHandlers)(props.onClick, onClick ?? void 0)
847
+ onClick: (0, import_primitive7.composeEventHandlers)(props.onClick, onClick ?? void 0)
759
848
  }
760
849
  );
761
850
  }
@@ -769,10 +858,11 @@ var BranchPickerNext = createActionButton(useGoToNextBranch);
769
858
  var useGoToPreviousBranch = () => {
770
859
  const { useThread, useBranchObserver } = useAssistantContext();
771
860
  const { useComposer, useMessage } = useMessageContext();
772
- const isLoading = useThread((s) => s.isLoading);
773
- const isEditing = useComposer((s) => s.isEditing);
774
- const hasNext = useMessage(({ branchState: { branchId } }) => branchId > 0);
775
- if (isLoading || isEditing || !hasNext)
861
+ const disabled = useCombinedStore(
862
+ [useThread, useComposer, useMessage],
863
+ (t, c, m) => t.isLoading || c.isEditing || m.branchState.branchId <= 0
864
+ );
865
+ if (disabled)
776
866
  return null;
777
867
  return () => {
778
868
  const {
@@ -801,10 +891,10 @@ var BranchPickerNumber = () => {
801
891
  };
802
892
 
803
893
  // src/primitives/branchPicker/BranchPickerRoot.tsx
804
- var import_react_primitive8 = require("@radix-ui/react-primitive");
805
- var import_react15 = require("react");
806
- var BranchPickerRoot = (0, import_react15.forwardRef)(({ hideWhenSingleBranch, ...rest }, ref) => {
807
- return /* @__PURE__ */ React.createElement(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0 }, /* @__PURE__ */ React.createElement(import_react_primitive8.Primitive.div, { ...rest, ref }));
894
+ var import_react_primitive9 = require("@radix-ui/react-primitive");
895
+ var import_react19 = require("react");
896
+ var BranchPickerRoot = (0, import_react19.forwardRef)(({ hideWhenSingleBranch, ...rest }, ref) => {
897
+ return /* @__PURE__ */ React.createElement(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0 }, /* @__PURE__ */ React.createElement(import_react_primitive9.Primitive.div, { ...rest, ref }));
808
898
  });
809
899
 
810
900
  // src/primitives/actionBar/index.ts
@@ -817,28 +907,30 @@ __export(actionBar_exports, {
817
907
  });
818
908
 
819
909
  // src/primitives/actionBar/ActionBarRoot.tsx
820
- var import_react_primitive9 = require("@radix-ui/react-primitive");
821
- var import_react16 = require("react");
822
- var ActionBarRoot = (0, import_react16.forwardRef)(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
910
+ var import_react_primitive10 = require("@radix-ui/react-primitive");
911
+ var import_react20 = require("react");
912
+ var ActionBarRoot = (0, import_react20.forwardRef)(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
823
913
  const { useThread } = useAssistantContext();
824
914
  const { useMessage } = useMessageContext();
825
- const hideAndfloatStatus = useMessage((m) => {
826
- const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
827
- if (!autohideEnabled)
915
+ const hideAndfloatStatus = useCombinedStore(
916
+ [useThread, useMessage],
917
+ (t, m) => {
918
+ if (hideWhenBusy && t.isLoading)
919
+ return "hidden" /* Hidden */;
920
+ const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
921
+ if (!autohideEnabled)
922
+ return "normal" /* Normal */;
923
+ if (!m.isHovering)
924
+ return "hidden" /* Hidden */;
925
+ if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branchState.branchCount <= 1)
926
+ return "floating" /* Floating */;
828
927
  return "normal" /* Normal */;
829
- if (!m.isHovering)
830
- return "hidden" /* Hidden */;
831
- if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branchState.branchCount <= 1)
832
- return "floating" /* Floating */;
833
- return "normal" /* Normal */;
834
- });
835
- const busy = useThread((t) => t.isLoading);
836
- if (hideWhenBusy && busy)
837
- return null;
928
+ }
929
+ );
838
930
  if (hideAndfloatStatus === "hidden" /* Hidden */)
839
931
  return null;
840
932
  return /* @__PURE__ */ React.createElement(
841
- import_react_primitive9.Primitive.div,
933
+ import_react_primitive10.Primitive.div,
842
934
  {
843
935
  "data-floating": hideAndfloatStatus === "floating" /* Floating */,
844
936
  ...rest,
@@ -870,9 +962,11 @@ var ActionBarCopy = createActionButton(useCopyMessage);
870
962
  var useReloadMessage = () => {
871
963
  const { useThread, useBranchObserver } = useAssistantContext();
872
964
  const { useMessage } = useMessageContext();
873
- const isLoading = useThread((s) => s.isLoading);
874
- const isAssistant = useMessage((s) => s.message.role === "assistant");
875
- if (isLoading || !isAssistant)
965
+ const disabled = useCombinedStore(
966
+ [useThread, useMessage],
967
+ (t, m) => t.isLoading || m.message.role !== "assistant"
968
+ );
969
+ if (disabled)
876
970
  return null;
877
971
  return () => {
878
972
  const message = useMessage.getState().message;
@@ -888,9 +982,11 @@ var ActionBarReload = createActionButton(useReloadMessage);
888
982
  // src/actions/useBeginMessageEdit.tsx
889
983
  var useBeginMessageEdit = () => {
890
984
  const { useMessage, useComposer } = useMessageContext();
891
- const isUser = useMessage((s) => s.message.role === "user");
892
- const isEditing = useComposer((s) => s.isEditing);
893
- if (!isUser || isEditing)
985
+ const disabled = useCombinedStore(
986
+ [useMessage, useComposer],
987
+ (m, c) => m.message.role !== "user" || c.isEditing
988
+ );
989
+ if (disabled)
894
990
  return null;
895
991
  return () => {
896
992
  const { edit } = useComposer.getState();
@@ -902,24 +998,34 @@ var useBeginMessageEdit = () => {
902
998
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
903
999
 
904
1000
  // src/vercel/VercelAIAssistantProvider.tsx
905
- var import_react18 = require("react");
1001
+ var import_react22 = require("react");
906
1002
 
907
1003
  // src/vercel/useDummyAIAssistantContext.tsx
908
- var import_react17 = require("react");
1004
+ var import_react21 = require("react");
909
1005
  var import_zustand2 = require("zustand");
910
1006
  var useDummyAIAssistantContext = () => {
911
- const [context] = (0, import_react17.useState)(() => {
1007
+ const [context] = (0, import_react21.useState)(() => {
1008
+ const scrollToBottomListeners = /* @__PURE__ */ new Set();
912
1009
  const useThread = (0, import_zustand2.create)()(() => ({
913
1010
  messages: [],
914
1011
  isLoading: false,
915
- reload: async () => {
916
- throw new Error("Not implemented");
917
- },
918
1012
  append: async () => {
919
1013
  throw new Error("Not implemented");
920
1014
  },
921
1015
  stop: () => {
922
1016
  throw new Error("Not implemented");
1017
+ },
1018
+ isAtBottom: true,
1019
+ scrollToBottom: () => {
1020
+ for (const listener of scrollToBottomListeners) {
1021
+ listener();
1022
+ }
1023
+ },
1024
+ onScrollToBottom: (callback) => {
1025
+ scrollToBottomListeners.add(callback);
1026
+ return () => {
1027
+ scrollToBottomListeners.delete(callback);
1028
+ };
923
1029
  }
924
1030
  }));
925
1031
  const useComposer = (0, import_zustand2.create)()(() => ({
@@ -990,28 +1096,23 @@ var VercelAIAssistantProvider = ({
990
1096
  }) => {
991
1097
  const context = useDummyAIAssistantContext();
992
1098
  const vercel = "chat" in rest ? rest.chat : rest.assistant;
993
- const messages = (0, import_react18.useMemo)(() => {
1099
+ const messages = (0, import_react22.useMemo)(() => {
994
1100
  return vercelToCachedThreadMessages(vercel.messages);
995
1101
  }, [vercel.messages]);
996
- const maybeReload = "reload" in vercel ? vercel.reload : null;
997
- const reload = (0, import_react18.useCallback)(async () => {
998
- if (!maybeReload)
999
- throw new Error("Reload not supported");
1000
- await maybeReload();
1001
- }, [maybeReload]);
1002
- const append = (0, import_react18.useCallback)(
1102
+ const append = (0, import_react22.useCallback)(
1003
1103
  async (message) => {
1004
1104
  if (message.content[0]?.type !== "text") {
1005
1105
  throw new Error("Only text content is currently supported");
1006
1106
  }
1107
+ context.useThread.getState().scrollToBottom();
1007
1108
  await vercel.append({
1008
1109
  role: message.role,
1009
1110
  content: message.content[0].text
1010
1111
  });
1011
1112
  },
1012
- [vercel.append]
1113
+ [context, vercel.append]
1013
1114
  );
1014
- const stop = (0, import_react18.useCallback)(() => {
1115
+ const stop = (0, import_react22.useCallback)(() => {
1015
1116
  const lastMessage = vercel.messages.at(-1);
1016
1117
  vercel.stop();
1017
1118
  if (lastMessage?.role === "user") {
@@ -1019,34 +1120,30 @@ var VercelAIAssistantProvider = ({
1019
1120
  }
1020
1121
  }, [vercel.messages, vercel.stop, vercel.setInput]);
1021
1122
  const isLoading = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1022
- (0, import_react18.useMemo)(() => {
1023
- context.useThread.setState(
1024
- {
1025
- messages,
1026
- isLoading,
1027
- reload,
1028
- append,
1029
- stop
1030
- },
1031
- true
1032
- );
1033
- }, [context, messages, reload, append, stop, isLoading]);
1034
- (0, import_react18.useMemo)(() => {
1123
+ (0, import_react22.useMemo)(() => {
1124
+ context.useThread.setState({
1125
+ messages,
1126
+ isLoading,
1127
+ append,
1128
+ stop
1129
+ });
1130
+ }, [context, messages, append, stop, isLoading]);
1131
+ (0, import_react22.useMemo)(() => {
1035
1132
  context.useComposer.setState({
1036
1133
  canCancel: isLoading,
1037
1134
  value: vercel.input,
1038
1135
  setValue: vercel.setInput
1039
1136
  });
1040
1137
  }, [context, isLoading, vercel.input, vercel.setInput]);
1041
- const branches = useVercelAIBranches(vercel);
1042
- (0, import_react18.useMemo)(() => {
1138
+ const branches = useVercelAIBranches(vercel, context);
1139
+ (0, import_react22.useMemo)(() => {
1043
1140
  context.useBranchObserver.setState(branches, true);
1044
1141
  }, [context, branches]);
1045
1142
  return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
1046
1143
  };
1047
1144
 
1048
1145
  // src/vercel/VercelRSCAssistantProvider.tsx
1049
- var import_react19 = require("react");
1146
+ var import_react23 = require("react");
1050
1147
  var ThreadMessageCache2 = /* @__PURE__ */ new WeakMap();
1051
1148
  var vercelToThreadMessage2 = (message) => {
1052
1149
  if (message.role !== "user" && message.role !== "assistant")
@@ -1073,22 +1170,23 @@ var VercelRSCAssistantProvider = ({
1073
1170
  append: vercelAppend
1074
1171
  }) => {
1075
1172
  const context = useDummyAIAssistantContext();
1076
- const messages = (0, import_react19.useMemo)(() => {
1173
+ const messages = (0, import_react23.useMemo)(() => {
1077
1174
  return vercelToCachedThreadMessages2(vercelMessages);
1078
1175
  }, [vercelMessages]);
1079
- const append = (0, import_react19.useCallback)(
1176
+ const append = (0, import_react23.useCallback)(
1080
1177
  async (message) => {
1081
1178
  if (message.content[0]?.type !== "text") {
1082
1179
  throw new Error("Only text content is currently supported");
1083
1180
  }
1181
+ context.useThread.getState().scrollToBottom();
1084
1182
  await vercelAppend({
1085
1183
  role: message.role,
1086
1184
  content: [{ type: "text", text: message.content[0].text }]
1087
1185
  });
1088
1186
  },
1089
- [vercelAppend]
1187
+ [context, vercelAppend]
1090
1188
  );
1091
- (0, import_react19.useMemo)(() => {
1189
+ (0, import_react23.useMemo)(() => {
1092
1190
  context.useThread.setState({
1093
1191
  messages,
1094
1192
  append