@assistant-ui/react 0.0.6 → 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
 
@@ -63,12 +64,12 @@ var ThreadEmpty = ({ children }) => {
63
64
  };
64
65
 
65
66
  // src/primitives/thread/ThreadViewport.tsx
66
- import { forwardRef as forwardRef2, useRef as useRef2, useState } from "react";
67
+ import { composeEventHandlers } from "@radix-ui/primitive";
68
+ import { useComposedRefs } from "@radix-ui/react-compose-refs";
67
69
  import {
68
70
  Primitive as Primitive2
69
71
  } from "@radix-ui/react-primitive";
70
- import { useComposedRefs } from "@radix-ui/react-compose-refs";
71
- import { composeEventHandlers } from "@radix-ui/primitive";
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";
@@ -101,34 +102,60 @@ var useOnResizeContent = (ref, callback) => {
101
102
  mutationObserver.observe(el, { childList: true });
102
103
  for (const child of el.children) {
103
104
  resizeObserver.observe(child);
104
- console.log("observing child", child);
105
105
  }
106
106
  return () => {
107
- console.log("disconnecting");
108
107
  resizeObserver.disconnect();
109
108
  mutationObserver.disconnect();
110
109
  };
111
110
  }, [ref.current]);
112
111
  };
113
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
+
114
126
  // src/primitives/thread/ThreadViewport.tsx
115
- var ThreadViewport = forwardRef2(({ onScroll, children, ...rest }, forwardedRef) => {
116
- const divRef = useRef2(null);
127
+ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...rest }, forwardedRef) => {
128
+ const messagesEndRef = useRef3(null);
129
+ const divRef = useRef3(null);
117
130
  const ref = useComposedRefs(forwardedRef, divRef);
118
- 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
+ };
119
141
  useOnResizeContent(divRef, () => {
120
- const div = divRef.current;
121
- if (!div || !isAtBottom)
142
+ if (!useThread.getState().isAtBottom)
122
143
  return;
123
- div.scrollTop = div.scrollHeight;
144
+ scrollToBottom();
145
+ });
146
+ useOnScrollToBottom(() => {
147
+ scrollToBottom();
124
148
  });
125
149
  const handleScroll = () => {
126
150
  const div = divRef.current;
127
151
  if (!div)
128
152
  return;
129
- 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
+ }
130
158
  };
131
- console.log(isAtBottom);
132
159
  return /* @__PURE__ */ React.createElement(
133
160
  Primitive2.div,
134
161
  {
@@ -136,12 +163,13 @@ var ThreadViewport = forwardRef2(({ onScroll, children, ...rest }, forwardedRef)
136
163
  onScroll: composeEventHandlers(onScroll, handleScroll),
137
164
  ref
138
165
  },
139
- children
166
+ children,
167
+ /* @__PURE__ */ React.createElement("div", { ref: messagesEndRef })
140
168
  );
141
169
  });
142
170
 
143
171
  // src/vercel/useVercelAIBranches.tsx
144
- import { useCallback, useMemo, useRef as useRef3 } from "react";
172
+ import { useCallback, useMemo, useRef as useRef4 } from "react";
145
173
  var ROOT_ID = "__ROOT_ID__";
146
174
  var UPCOMING_MESSAGE_ID = "__UPCOMING_MESSAGE_ID__";
147
175
  var updateBranchData = (data, messages) => {
@@ -208,8 +236,8 @@ var sliceMessagesUntil = (messages, message) => {
208
236
  throw new Error("Unexpected: Message not found");
209
237
  return messages.slice(0, messageIdx);
210
238
  };
211
- var useVercelAIBranches = (chat) => {
212
- const data = useRef3({
239
+ var useVercelAIBranches = (chat, context) => {
240
+ const data = useRef4({
213
241
  parentMap: /* @__PURE__ */ new Map(),
214
242
  branchMap: /* @__PURE__ */ new Map(),
215
243
  snapshots: /* @__PURE__ */ new Map()
@@ -233,13 +261,17 @@ var useVercelAIBranches = (chat) => {
233
261
  },
234
262
  [data, chat.messages, chat.setMessages]
235
263
  );
264
+ const reloadMaybe = "reload" in chat ? chat.reload : void 0;
236
265
  const reloadAt = useCallback(
237
266
  async (message) => {
267
+ if (!reloadMaybe)
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);
240
- await chat.reload();
271
+ context.useThread.getState().scrollToBottom();
272
+ await reloadMaybe();
241
273
  },
242
- [chat.messages, chat.setMessages, chat.reload]
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
  {
@@ -460,12 +496,41 @@ var MessageIf = ({ children, ...query }) => {
460
496
  };
461
497
 
462
498
  // src/primitives/message/MessageContent.tsx
463
- var MessageContent = () => {
499
+ var defaultComponents = {
500
+ Text: ({ part }) => /* @__PURE__ */ React.createElement(React.Fragment, null, part.text),
501
+ Image: () => null,
502
+ UI: ({ part }) => part.display,
503
+ tools: {
504
+ Fallback: () => null
505
+ }
506
+ };
507
+ var MessageContent = ({
508
+ components: {
509
+ Text = defaultComponents.Text,
510
+ Image = defaultComponents.Image,
511
+ UI = defaultComponents.UI,
512
+ tools: { by_name = {}, Fallback = defaultComponents.tools.Fallback } = {}
513
+ } = {}
514
+ }) => {
464
515
  const { useMessage } = useMessageContext();
465
516
  const content = useMessage((s) => s.message.content);
466
- if (content[0]?.type !== "text")
467
- throw new Error("Unsupported message content type");
468
- return /* @__PURE__ */ React.createElement(React.Fragment, null, content[0].text);
517
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, content.map((part, i) => {
518
+ const key = i;
519
+ switch (part.type) {
520
+ case "text":
521
+ return /* @__PURE__ */ React.createElement(Text, { key, part });
522
+ case "image":
523
+ return /* @__PURE__ */ React.createElement(Image, { key, part });
524
+ case "ui":
525
+ return /* @__PURE__ */ React.createElement(UI, { key, part });
526
+ case "tool-call": {
527
+ const Tool = by_name[part.name] || Fallback;
528
+ return /* @__PURE__ */ React.createElement(Tool, { key, part });
529
+ }
530
+ default:
531
+ return null;
532
+ }
533
+ }));
469
534
  };
470
535
 
471
536
  // src/primitives/thread/ThreadMessages.tsx
@@ -501,6 +566,31 @@ var ThreadMessages = ({ components }) => {
501
566
  ));
502
567
  };
503
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
+
504
594
  // src/primitives/composer/index.ts
505
595
  var composer_exports = {};
506
596
  __export(composer_exports, {
@@ -512,41 +602,17 @@ __export(composer_exports, {
512
602
  });
513
603
 
514
604
  // src/primitives/composer/ComposerRoot.tsx
515
- import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
605
+ import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
516
606
  import { useComposedRefs as useComposedRefs2 } from "@radix-ui/react-compose-refs";
517
607
  import {
518
- Primitive as Primitive4
608
+ Primitive as Primitive5
519
609
  } from "@radix-ui/react-primitive";
520
- import {
521
- createContext as createContext3,
522
- forwardRef as forwardRef4,
523
- useContext as useContext4,
524
- useMemo as useMemo3,
525
- useRef as useRef4
526
- } from "react";
527
- var ComposerFormContext = createContext3(null);
528
- var useComposerFormContext = () => {
529
- const context = useContext4(ComposerFormContext);
530
- if (!context) {
531
- throw new Error(
532
- "Composer compound components cannot be rendered outside the Composer component"
533
- );
534
- }
535
- return context;
536
- };
537
- var ComposerRoot = forwardRef4(
610
+ import { forwardRef as forwardRef5, useRef as useRef5 } from "react";
611
+ var ComposerRoot = forwardRef5(
538
612
  ({ onSubmit, ...rest }, forwardedRef) => {
539
613
  const { useComposer } = useComposerContext();
540
- const formRef = useRef4(null);
614
+ const formRef = useRef5(null);
541
615
  const ref = useComposedRefs2(forwardedRef, formRef);
542
- const composerContextValue = useMemo3(
543
- () => ({
544
- submit: () => formRef.current?.dispatchEvent(
545
- new Event("submit", { cancelable: true, bubbles: true })
546
- )
547
- }),
548
- []
549
- );
550
616
  const handleSubmit = (e) => {
551
617
  const composerState = useComposer.getState();
552
618
  if (!composerState.isEditing)
@@ -554,75 +620,103 @@ var ComposerRoot = forwardRef4(
554
620
  e.preventDefault();
555
621
  composerState.send();
556
622
  };
557
- return /* @__PURE__ */ React.createElement(ComposerFormContext.Provider, { value: composerContextValue }, /* @__PURE__ */ React.createElement(
558
- Primitive4.form,
623
+ return /* @__PURE__ */ React.createElement(
624
+ Primitive5.form,
559
625
  {
560
626
  ...rest,
561
627
  ref,
562
- onSubmit: composeEventHandlers3(onSubmit, handleSubmit)
628
+ onSubmit: composeEventHandlers4(onSubmit, handleSubmit)
563
629
  }
564
- ));
630
+ );
565
631
  }
566
632
  );
567
633
 
568
634
  // src/primitives/composer/ComposerInput.tsx
569
- 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";
570
637
  import { Slot } from "@radix-ui/react-slot";
571
- 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";
572
644
  import TextareaAutosize from "react-textarea-autosize";
573
- var ComposerInput = forwardRef5(({ asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
574
- const { useThread } = useAssistantContext();
575
- const isLoading = useThread((t) => t.isLoading);
576
- const { useComposer } = useComposerContext();
577
- const value = useComposer((c) => {
578
- if (!c.isEditing)
579
- return "";
580
- return c.value;
581
- });
582
- const Component = asChild ? Slot : TextareaAutosize;
583
- const composerForm = useComposerFormContext();
584
- const handleKeyPress = (e) => {
585
- if (disabled)
586
- return;
587
- if (e.key === "Escape") {
588
- useComposer.getState().cancel();
589
- }
590
- if (isLoading)
591
- return;
592
- if (e.key === "Enter" && e.shiftKey === false) {
593
- e.preventDefault();
594
- composerForm.submit();
595
- }
596
- };
597
- return /* @__PURE__ */ React.createElement(
598
- Component,
599
- {
600
- value,
601
- ...rest,
602
- ref: forwardedRef,
603
- disabled,
604
- onChange: composeEventHandlers4(onChange, (e) => {
605
- const composerState = useComposer.getState();
606
- if (!composerState.isEditing)
607
- return;
608
- return composerState.setValue(e.target.value);
609
- }),
610
- onKeyDown: composeEventHandlers4(onKeyDown, handleKeyPress)
611
- }
612
- );
613
- });
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
+ );
614
708
 
615
709
  // src/primitives/composer/ComposerSend.tsx
616
710
  import {
617
- Primitive as Primitive5
711
+ Primitive as Primitive6
618
712
  } from "@radix-ui/react-primitive";
619
- import { forwardRef as forwardRef6 } from "react";
620
- var ComposerSend = forwardRef6(
713
+ import { forwardRef as forwardRef7 } from "react";
714
+ var ComposerSend = forwardRef7(
621
715
  ({ disabled, ...rest }, ref) => {
622
716
  const { useComposer } = useComposerContext();
623
717
  const hasValue = useComposer((c) => c.isEditing && c.value.length > 0);
624
718
  return /* @__PURE__ */ React.createElement(
625
- Primitive5.button,
719
+ Primitive6.button,
626
720
  {
627
721
  type: "submit",
628
722
  ...rest,
@@ -634,24 +728,24 @@ var ComposerSend = forwardRef6(
634
728
  );
635
729
 
636
730
  // src/primitives/composer/ComposerCancel.tsx
637
- import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
731
+ import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
638
732
  import {
639
- Primitive as Primitive6
733
+ Primitive as Primitive7
640
734
  } from "@radix-ui/react-primitive";
641
- import { forwardRef as forwardRef7 } from "react";
642
- var ComposerCancel = forwardRef7(({ disabled, onClick, ...rest }, ref) => {
735
+ import { forwardRef as forwardRef8 } from "react";
736
+ var ComposerCancel = forwardRef8(({ disabled, onClick, ...rest }, ref) => {
643
737
  const { useComposer } = useComposerContext();
644
738
  const hasValue = useComposer((c) => c.canCancel);
645
739
  const handleClose = () => {
646
740
  useComposer.getState().cancel();
647
741
  };
648
742
  return /* @__PURE__ */ React.createElement(
649
- Primitive6.button,
743
+ Primitive7.button,
650
744
  {
651
745
  type: "button",
652
746
  ...rest,
653
747
  ref,
654
- onClick: composeEventHandlers5(onClick, handleClose),
748
+ onClick: composeEventHandlers6(onClick, handleClose),
655
749
  disabled: disabled || !hasValue
656
750
  }
657
751
  );
@@ -667,16 +761,41 @@ __export(branchPicker_exports, {
667
761
  Root: () => BranchPickerRoot
668
762
  });
669
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
+
670
790
  // src/actions/useGoToNextBranch.tsx
671
791
  var useGoToNextBranch = () => {
672
792
  const { useThread, useBranchObserver } = useAssistantContext();
673
793
  const { useComposer, useMessage } = useMessageContext();
674
- const isLoading = useThread((s) => s.isLoading);
675
- const isEditing = useComposer((s) => s.isEditing);
676
- const hasNext = useMessage(
677
- ({ 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
678
797
  );
679
- if (isLoading || isEditing || !hasNext)
798
+ if (disabled)
680
799
  return null;
681
800
  return () => {
682
801
  const {
@@ -688,23 +807,23 @@ var useGoToNextBranch = () => {
688
807
  };
689
808
 
690
809
  // src/utils/createActionButton.tsx
691
- import { forwardRef as forwardRef8 } from "react";
810
+ import { forwardRef as forwardRef9 } from "react";
692
811
  import {
693
- Primitive as Primitive7
812
+ Primitive as Primitive8
694
813
  } from "@radix-ui/react-primitive";
695
- import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
814
+ import { composeEventHandlers as composeEventHandlers7 } from "@radix-ui/primitive";
696
815
  var createActionButton = (useActionButton) => {
697
- return forwardRef8(
816
+ return forwardRef9(
698
817
  (props, forwardedRef) => {
699
818
  const onClick = useActionButton(props);
700
819
  return /* @__PURE__ */ React.createElement(
701
- Primitive7.button,
820
+ Primitive8.button,
702
821
  {
703
822
  type: "button",
704
823
  disabled: !onClick,
705
824
  ...props,
706
825
  ref: forwardedRef,
707
- onClick: composeEventHandlers6(props.onClick, onClick ?? void 0)
826
+ onClick: composeEventHandlers7(props.onClick, onClick ?? void 0)
708
827
  }
709
828
  );
710
829
  }
@@ -718,10 +837,11 @@ var BranchPickerNext = createActionButton(useGoToNextBranch);
718
837
  var useGoToPreviousBranch = () => {
719
838
  const { useThread, useBranchObserver } = useAssistantContext();
720
839
  const { useComposer, useMessage } = useMessageContext();
721
- const isLoading = useThread((s) => s.isLoading);
722
- const isEditing = useComposer((s) => s.isEditing);
723
- const hasNext = useMessage(({ branchState: { branchId } }) => branchId > 0);
724
- 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)
725
845
  return null;
726
846
  return () => {
727
847
  const {
@@ -751,11 +871,11 @@ var BranchPickerNumber = () => {
751
871
 
752
872
  // src/primitives/branchPicker/BranchPickerRoot.tsx
753
873
  import {
754
- Primitive as Primitive8
874
+ Primitive as Primitive9
755
875
  } from "@radix-ui/react-primitive";
756
- import { forwardRef as forwardRef9 } from "react";
757
- var BranchPickerRoot = forwardRef9(({ hideWhenSingleBranch, ...rest }, ref) => {
758
- 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 }));
759
879
  });
760
880
 
761
881
  // src/primitives/actionBar/index.ts
@@ -769,29 +889,31 @@ __export(actionBar_exports, {
769
889
 
770
890
  // src/primitives/actionBar/ActionBarRoot.tsx
771
891
  import {
772
- Primitive as Primitive9
892
+ Primitive as Primitive10
773
893
  } from "@radix-ui/react-primitive";
774
- import { forwardRef as forwardRef10 } from "react";
775
- var ActionBarRoot = forwardRef10(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
894
+ import { forwardRef as forwardRef11 } from "react";
895
+ var ActionBarRoot = forwardRef11(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
776
896
  const { useThread } = useAssistantContext();
777
897
  const { useMessage } = useMessageContext();
778
- const hideAndfloatStatus = useMessage((m) => {
779
- const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
780
- 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 */;
781
910
  return "normal" /* Normal */;
782
- if (!m.isHovering)
783
- return "hidden" /* Hidden */;
784
- if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branchState.branchCount <= 1)
785
- return "floating" /* Floating */;
786
- return "normal" /* Normal */;
787
- });
788
- const busy = useThread((t) => t.isLoading);
789
- if (hideWhenBusy && busy)
790
- return null;
911
+ }
912
+ );
791
913
  if (hideAndfloatStatus === "hidden" /* Hidden */)
792
914
  return null;
793
915
  return /* @__PURE__ */ React.createElement(
794
- Primitive9.div,
916
+ Primitive10.div,
795
917
  {
796
918
  "data-floating": hideAndfloatStatus === "floating" /* Floating */,
797
919
  ...rest,
@@ -823,9 +945,11 @@ var ActionBarCopy = createActionButton(useCopyMessage);
823
945
  var useReloadMessage = () => {
824
946
  const { useThread, useBranchObserver } = useAssistantContext();
825
947
  const { useMessage } = useMessageContext();
826
- const isLoading = useThread((s) => s.isLoading);
827
- const isAssistant = useMessage((s) => s.message.role === "assistant");
828
- if (isLoading || !isAssistant)
948
+ const disabled = useCombinedStore(
949
+ [useThread, useMessage],
950
+ (t, m) => t.isLoading || m.message.role !== "assistant"
951
+ );
952
+ if (disabled)
829
953
  return null;
830
954
  return () => {
831
955
  const message = useMessage.getState().message;
@@ -841,9 +965,11 @@ var ActionBarReload = createActionButton(useReloadMessage);
841
965
  // src/actions/useBeginMessageEdit.tsx
842
966
  var useBeginMessageEdit = () => {
843
967
  const { useMessage, useComposer } = useMessageContext();
844
- const isUser = useMessage((s) => s.message.role === "user");
845
- const isEditing = useComposer((s) => s.isEditing);
846
- if (!isUser || isEditing)
968
+ const disabled = useCombinedStore(
969
+ [useMessage, useComposer],
970
+ (m, c) => m.message.role !== "user" || c.isEditing
971
+ );
972
+ if (disabled)
847
973
  return null;
848
974
  return () => {
849
975
  const { edit } = useComposer.getState();
@@ -855,25 +981,42 @@ var useBeginMessageEdit = () => {
855
981
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
856
982
 
857
983
  // src/vercel/VercelAIAssistantProvider.tsx
858
- import { useCallback as useCallback2, useMemo as useMemo4, useState as useState3 } from "react";
984
+ import { useCallback as useCallback3, useMemo as useMemo4 } from "react";
985
+
986
+ // src/vercel/useDummyAIAssistantContext.tsx
987
+ import { useState as useState2 } from "react";
859
988
  import { create as create2 } from "zustand";
860
- var useAIAssistantContext = () => {
861
- const [context] = useState3(() => {
989
+ var useDummyAIAssistantContext = () => {
990
+ const [context] = useState2(() => {
991
+ const scrollToBottomListeners = /* @__PURE__ */ new Set();
862
992
  const useThread = create2()(() => ({
863
993
  messages: [],
864
994
  isLoading: false,
865
- reload: async () => {
866
- },
867
995
  append: async () => {
996
+ throw new Error("Not implemented");
868
997
  },
869
998
  stop: () => {
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
+ };
870
1012
  }
871
1013
  }));
872
1014
  const useComposer = create2()(() => ({
873
1015
  isEditing: true,
874
1016
  canCancel: false,
875
1017
  value: "",
876
- setValue: () => {
1018
+ setValue: (value) => {
1019
+ useComposer.setState({ value });
877
1020
  },
878
1021
  edit: () => {
879
1022
  throw new Error("Not implemented");
@@ -892,19 +1035,24 @@ var useAIAssistantContext = () => {
892
1035
  const useBranchObserver = create2()(() => ({
893
1036
  getBranchState: () => ({
894
1037
  branchId: 0,
895
- branchCount: 0
1038
+ branchCount: 1
896
1039
  }),
897
1040
  switchToBranch: () => {
1041
+ throw new Error("Not implemented");
898
1042
  },
899
1043
  editAt: async () => {
1044
+ throw new Error("Not implemented");
900
1045
  },
901
1046
  reloadAt: async () => {
1047
+ throw new Error("Not implemented");
902
1048
  }
903
1049
  }));
904
1050
  return { useThread, useComposer, useBranchObserver };
905
1051
  });
906
1052
  return context;
907
1053
  };
1054
+
1055
+ // src/vercel/VercelAIAssistantProvider.tsx
908
1056
  var ThreadMessageCache = /* @__PURE__ */ new WeakMap();
909
1057
  var vercelToThreadMessage = (message) => {
910
1058
  if (message.role !== "user" && message.role !== "assistant")
@@ -925,65 +1073,121 @@ var vercelToCachedThreadMessages = (messages) => {
925
1073
  return newMessage;
926
1074
  });
927
1075
  };
928
- var VercelAIChatAssistantProvider = ({ chat, children }) => {
929
- const context = useAIAssistantContext();
1076
+ var VercelAIAssistantProvider = ({
1077
+ children,
1078
+ ...rest
1079
+ }) => {
1080
+ const context = useDummyAIAssistantContext();
1081
+ const vercel = "chat" in rest ? rest.chat : rest.assistant;
930
1082
  const messages = useMemo4(() => {
931
- return vercelToCachedThreadMessages(chat.messages);
932
- }, [chat.messages]);
933
- const reload = useCallback2(async () => {
934
- await chat.reload();
935
- }, [chat.reload]);
936
- const append = useCallback2(
1083
+ return vercelToCachedThreadMessages(vercel.messages);
1084
+ }, [vercel.messages]);
1085
+ const append = useCallback3(
937
1086
  async (message) => {
938
1087
  if (message.content[0]?.type !== "text") {
939
1088
  throw new Error("Only text content is currently supported");
940
1089
  }
941
- await chat.append({
1090
+ context.useThread.getState().scrollToBottom();
1091
+ await vercel.append({
942
1092
  role: message.role,
943
1093
  content: message.content[0].text
944
1094
  });
945
1095
  },
946
- [chat.append]
1096
+ [context, vercel.append]
947
1097
  );
948
- const stop = useCallback2(() => {
949
- const lastMessage = chat.messages.at(-1);
950
- chat.stop();
1098
+ const stop = useCallback3(() => {
1099
+ const lastMessage = vercel.messages.at(-1);
1100
+ vercel.stop();
951
1101
  if (lastMessage?.role === "user") {
952
- chat.setInput(lastMessage.content);
1102
+ vercel.setInput(lastMessage.content);
953
1103
  }
954
- }, [chat.messages, chat.stop, chat.setInput]);
1104
+ }, [vercel.messages, vercel.stop, vercel.setInput]);
1105
+ const isLoading = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
955
1106
  useMemo4(() => {
956
- context.useThread.setState(
957
- {
958
- messages,
959
- isLoading: chat.isLoading,
960
- reload,
961
- append,
962
- stop
963
- },
964
- true
965
- );
966
- }, [context, messages, reload, append, stop, chat.isLoading]);
1107
+ context.useThread.setState({
1108
+ messages,
1109
+ isLoading,
1110
+ append,
1111
+ stop
1112
+ });
1113
+ }, [context, messages, append, stop, isLoading]);
967
1114
  useMemo4(() => {
968
1115
  context.useComposer.setState({
969
- canCancel: chat.isLoading,
970
- value: chat.input,
971
- setValue: chat.setInput
1116
+ canCancel: isLoading,
1117
+ value: vercel.input,
1118
+ setValue: vercel.setInput
972
1119
  });
973
- }, [context, chat.isLoading, chat.input, chat.setInput]);
974
- const branches = useVercelAIBranches(chat);
1120
+ }, [context, isLoading, vercel.input, vercel.setInput]);
1121
+ const branches = useVercelAIBranches(vercel, context);
975
1122
  useMemo4(() => {
976
1123
  context.useBranchObserver.setState(branches, true);
977
1124
  }, [context, branches]);
978
1125
  return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
979
1126
  };
1127
+
1128
+ // src/vercel/VercelRSCAssistantProvider.tsx
1129
+ import {
1130
+ useCallback as useCallback4,
1131
+ useMemo as useMemo5
1132
+ } from "react";
1133
+ var ThreadMessageCache2 = /* @__PURE__ */ new WeakMap();
1134
+ var vercelToThreadMessage2 = (message) => {
1135
+ if (message.role !== "user" && message.role !== "assistant")
1136
+ throw new Error("Unsupported role");
1137
+ return {
1138
+ id: message.id,
1139
+ role: message.role,
1140
+ content: [{ type: "ui", display: message.display }]
1141
+ };
1142
+ };
1143
+ var vercelToCachedThreadMessages2 = (messages) => {
1144
+ return messages.map((m) => {
1145
+ const cached = ThreadMessageCache2.get(m);
1146
+ if (cached)
1147
+ return cached;
1148
+ const newMessage = vercelToThreadMessage2(m);
1149
+ ThreadMessageCache2.set(m, newMessage);
1150
+ return newMessage;
1151
+ });
1152
+ };
1153
+ var VercelRSCAssistantProvider = ({
1154
+ children,
1155
+ messages: vercelMessages,
1156
+ append: vercelAppend
1157
+ }) => {
1158
+ const context = useDummyAIAssistantContext();
1159
+ const messages = useMemo5(() => {
1160
+ return vercelToCachedThreadMessages2(vercelMessages);
1161
+ }, [vercelMessages]);
1162
+ const append = useCallback4(
1163
+ async (message) => {
1164
+ if (message.content[0]?.type !== "text") {
1165
+ throw new Error("Only text content is currently supported");
1166
+ }
1167
+ context.useThread.getState().scrollToBottom();
1168
+ await vercelAppend({
1169
+ role: message.role,
1170
+ content: [{ type: "text", text: message.content[0].text }]
1171
+ });
1172
+ },
1173
+ [context, vercelAppend]
1174
+ );
1175
+ useMemo5(() => {
1176
+ context.useThread.setState({
1177
+ messages,
1178
+ append
1179
+ });
1180
+ }, [context, messages, append]);
1181
+ return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
1182
+ };
980
1183
  export {
981
1184
  actionBar_exports as ActionBarPrimitive,
982
1185
  branchPicker_exports as BranchPickerPrimitive,
983
1186
  composer_exports as ComposerPrimitive,
984
1187
  message_exports as MessagePrimitive,
985
1188
  thread_exports as ThreadPrimitive,
986
- VercelAIChatAssistantProvider as VercelAIAssistantProvider,
1189
+ VercelAIAssistantProvider,
1190
+ VercelRSCAssistantProvider as unstable_VercelRSCAssistantProvider,
987
1191
  useMessageContext as unstable_useMessageContext,
988
1192
  useBeginMessageEdit,
989
1193
  useCopyMessage,