@assistant-ui/react 0.0.6 → 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
 
@@ -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,