@assistant-ui/react 0.0.9 → 0.0.10

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.d.mts CHANGED
@@ -21,7 +21,7 @@ type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyo
21
21
 
22
22
  type ThreadIfFilters = {
23
23
  empty: boolean | undefined;
24
- busy: boolean | undefined;
24
+ running: boolean | undefined;
25
25
  };
26
26
  type ThreadIfProps = PropsWithChildren<RequireAtLeastOne<ThreadIfFilters>>;
27
27
  declare const ThreadIf: FC<ThreadIfProps>;
@@ -91,17 +91,16 @@ declare namespace index$3 {
91
91
  export { ComposerCancel as Cancel, ComposerIf as If, ComposerInput as Input, ComposerRoot as Root, ComposerSend as Send };
92
92
  }
93
93
 
94
- type ComposerState = {
94
+ type BaseComposerState = {
95
+ value: string;
96
+ setValue: (value: string) => void;
97
+ };
98
+ type MessageComposerState = BaseComposerState & {
95
99
  isEditing: boolean;
96
- canCancel: boolean;
100
+ canCancel: true;
97
101
  edit: () => void;
98
102
  send: () => void;
99
103
  cancel: () => void;
100
- value: string;
101
- setValue: (value: string) => void;
102
- };
103
- type ComposerStore = {
104
- useComposer: UseBoundStore<StoreApi<ComposerState>>;
105
104
  };
106
105
 
107
106
  type ThreadMessageTextPart = {
@@ -226,7 +225,7 @@ declare const ActionBarRoot: react.ForwardRefExoticComponent<Pick<Omit<react.Det
226
225
  } & {
227
226
  asChild?: boolean;
228
227
  }, "key" | keyof react.HTMLAttributes<HTMLDivElement> | "asChild"> & {
229
- hideWhenBusy?: boolean;
228
+ hideWhenRunning?: boolean;
230
229
  autohide?: "always" | "not-last" | "never";
231
230
  autohideFloat?: "always" | "single-branch" | "never";
232
231
  } & react.RefAttributes<HTMLDivElement>>;
@@ -283,8 +282,9 @@ type MessageState = {
283
282
  isHovering: boolean;
284
283
  setIsHovering: (value: boolean) => void;
285
284
  };
286
- type MessageStore = ComposerStore & {
285
+ type MessageStore = {
287
286
  useMessage: UseBoundStore<StoreApi<MessageState>>;
287
+ useComposer: UseBoundStore<StoreApi<MessageComposerState>>;
288
288
  };
289
289
 
290
290
  declare const useMessageContext: () => MessageStore;
package/dist/index.d.ts CHANGED
@@ -21,7 +21,7 @@ type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyo
21
21
 
22
22
  type ThreadIfFilters = {
23
23
  empty: boolean | undefined;
24
- busy: boolean | undefined;
24
+ running: boolean | undefined;
25
25
  };
26
26
  type ThreadIfProps = PropsWithChildren<RequireAtLeastOne<ThreadIfFilters>>;
27
27
  declare const ThreadIf: FC<ThreadIfProps>;
@@ -91,17 +91,16 @@ declare namespace index$3 {
91
91
  export { ComposerCancel as Cancel, ComposerIf as If, ComposerInput as Input, ComposerRoot as Root, ComposerSend as Send };
92
92
  }
93
93
 
94
- type ComposerState = {
94
+ type BaseComposerState = {
95
+ value: string;
96
+ setValue: (value: string) => void;
97
+ };
98
+ type MessageComposerState = BaseComposerState & {
95
99
  isEditing: boolean;
96
- canCancel: boolean;
100
+ canCancel: true;
97
101
  edit: () => void;
98
102
  send: () => void;
99
103
  cancel: () => void;
100
- value: string;
101
- setValue: (value: string) => void;
102
- };
103
- type ComposerStore = {
104
- useComposer: UseBoundStore<StoreApi<ComposerState>>;
105
104
  };
106
105
 
107
106
  type ThreadMessageTextPart = {
@@ -226,7 +225,7 @@ declare const ActionBarRoot: react.ForwardRefExoticComponent<Pick<Omit<react.Det
226
225
  } & {
227
226
  asChild?: boolean;
228
227
  }, "key" | keyof react.HTMLAttributes<HTMLDivElement> | "asChild"> & {
229
- hideWhenBusy?: boolean;
228
+ hideWhenRunning?: boolean;
230
229
  autohide?: "always" | "not-last" | "never";
231
230
  autohideFloat?: "always" | "single-branch" | "never";
232
231
  } & react.RefAttributes<HTMLDivElement>>;
@@ -283,8 +282,9 @@ type MessageState = {
283
282
  isHovering: boolean;
284
283
  setIsHovering: (value: boolean) => void;
285
284
  };
286
- type MessageStore = ComposerStore & {
285
+ type MessageStore = {
287
286
  useMessage: UseBoundStore<StoreApi<MessageState>>;
287
+ useComposer: UseBoundStore<StoreApi<MessageComposerState>>;
288
288
  };
289
289
 
290
290
  declare const useMessageContext: () => MessageStore;
package/dist/index.js CHANGED
@@ -86,9 +86,9 @@ var useThreadIf = (props) => {
86
86
  return false;
87
87
  if (props.empty === false && thread.messages.length === 0)
88
88
  return false;
89
- if (props.busy === true && !thread.isLoading)
89
+ if (props.running === true && !thread.isRunning)
90
90
  return false;
91
- if (props.busy === false && thread.isLoading)
91
+ if (props.running === false && thread.isRunning)
92
92
  return false;
93
93
  return true;
94
94
  });
@@ -153,12 +153,12 @@ var import_react4 = require("react");
153
153
  var useOnScrollToBottom = (callback) => {
154
154
  const callbackRef = (0, import_react4.useRef)(callback);
155
155
  callbackRef.current = callback;
156
- const { useThread } = useAssistantContext();
156
+ const { useViewport } = useAssistantContext();
157
157
  (0, import_react4.useEffect)(() => {
158
- return useThread.getState().onScrollToBottom(() => {
158
+ return useViewport.getState().onScrollToBottom(() => {
159
159
  callbackRef.current();
160
160
  });
161
- }, [useThread]);
161
+ }, [useViewport]);
162
162
  };
163
163
 
164
164
  // src/primitives/thread/ThreadViewport.tsx
@@ -166,7 +166,7 @@ var ThreadViewport = (0, import_react5.forwardRef)(({ autoScroll = true, onScrol
166
166
  const messagesEndRef = (0, import_react5.useRef)(null);
167
167
  const divRef = (0, import_react5.useRef)(null);
168
168
  const ref = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, divRef);
169
- const { useThread } = useAssistantContext();
169
+ const { useViewport } = useAssistantContext();
170
170
  const firstRenderRef = (0, import_react5.useRef)(true);
171
171
  const lastScrollTop = (0, import_react5.useRef)(0);
172
172
  const scrollToBottom = () => {
@@ -175,11 +175,11 @@ var ThreadViewport = (0, import_react5.forwardRef)(({ autoScroll = true, onScrol
175
175
  return;
176
176
  const behavior = firstRenderRef.current ? "instant" : "auto";
177
177
  firstRenderRef.current = false;
178
- useThread.setState({ isAtBottom: true });
178
+ useViewport.setState({ isAtBottom: true });
179
179
  div.scrollIntoView({ behavior });
180
180
  };
181
181
  useOnResizeContent(divRef, () => {
182
- if (!useThread.getState().isAtBottom)
182
+ if (!useViewport.getState().isAtBottom)
183
183
  return;
184
184
  scrollToBottom();
185
185
  });
@@ -190,11 +190,11 @@ var ThreadViewport = (0, import_react5.forwardRef)(({ autoScroll = true, onScrol
190
190
  const div = divRef.current;
191
191
  if (!div)
192
192
  return;
193
- const isAtBottom = useThread.getState().isAtBottom;
193
+ const isAtBottom = useViewport.getState().isAtBottom;
194
194
  const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
195
195
  if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
196
196
  } else if (newIsAtBottom !== isAtBottom) {
197
- useThread.setState({ isAtBottom: newIsAtBottom });
197
+ useViewport.setState({ isAtBottom: newIsAtBottom });
198
198
  }
199
199
  lastScrollTop.current = div.scrollTop;
200
200
  };
@@ -310,13 +310,13 @@ var useVercelAIBranches = (chat, context) => {
310
310
  [data, chat.messages, chat.setMessages]
311
311
  );
312
312
  const reloadMaybe = "reload" in chat ? chat.reload : void 0;
313
- const reload = (0, import_react6.useCallback)(
314
- async (messageId) => {
313
+ const startRun = (0, import_react6.useCallback)(
314
+ async (parentId) => {
315
315
  if (!reloadMaybe)
316
316
  throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
317
- const newMessages = sliceMessagesUntil(chat.messages, messageId);
317
+ const newMessages = sliceMessagesUntil(chat.messages, parentId);
318
318
  chat.setMessages(newMessages);
319
- context.useThread.getState().scrollToBottom();
319
+ context.useViewport.getState().scrollToBottom();
320
320
  await reloadMaybe();
321
321
  },
322
322
  [context, chat.messages, chat.setMessages, reloadMaybe]
@@ -327,7 +327,7 @@ var useVercelAIBranches = (chat, context) => {
327
327
  chat.setMessages(newMessages);
328
328
  if (message.content.length !== 1 || message.content[0]?.type !== "text")
329
329
  throw new Error("Only text content is currently supported");
330
- context.useThread.getState().scrollToBottom();
330
+ context.useViewport.getState().scrollToBottom();
331
331
  await chat.append({
332
332
  role: "user",
333
333
  content: message.content[0].text
@@ -340,13 +340,13 @@ var useVercelAIBranches = (chat, context) => {
340
340
  getBranchState,
341
341
  switchToBranch,
342
342
  append,
343
- reload
343
+ startRun
344
344
  }),
345
- [getBranchState, switchToBranch, append, reload]
345
+ [getBranchState, switchToBranch, append, startRun]
346
346
  );
347
347
  };
348
348
  var hasUpcomingMessage = (thread) => {
349
- return thread.isLoading && thread.messages[thread.messages.length - 1]?.role !== "assistant";
349
+ return thread.isRunning && thread.messages[thread.messages.length - 1]?.role !== "assistant";
350
350
  };
351
351
 
352
352
  // src/utils/context/useComposerContext.ts
@@ -399,7 +399,54 @@ __export(message_exports, {
399
399
 
400
400
  // src/primitives/message/MessageProvider.tsx
401
401
  var import_react9 = require("react");
402
+ var import_zustand2 = require("zustand");
403
+
404
+ // src/utils/context/stores/ComposerStore.ts
402
405
  var import_zustand = require("zustand");
406
+ var makeBaseComposer = (set) => ({
407
+ value: "",
408
+ setValue: (value) => {
409
+ set({ value });
410
+ }
411
+ });
412
+ var makeMessageComposerStore = ({
413
+ onEdit,
414
+ onSend
415
+ }) => (0, import_zustand.create)()((set, get, store) => ({
416
+ ...makeBaseComposer(set, get, store),
417
+ canCancel: true,
418
+ isEditing: false,
419
+ edit: () => {
420
+ const value = onEdit();
421
+ set({ isEditing: true, value });
422
+ },
423
+ send: () => {
424
+ const value = get().value;
425
+ set({ isEditing: false });
426
+ return onSend(value);
427
+ },
428
+ cancel: () => {
429
+ set({ isEditing: false });
430
+ }
431
+ }));
432
+ var makeThreadComposerStore = ({
433
+ onSend,
434
+ onCancel
435
+ }) => (0, import_zustand.create)()((set, get, store) => ({
436
+ ...makeBaseComposer(set, get, store),
437
+ isEditing: true,
438
+ canCancel: false,
439
+ send: () => {
440
+ const value = get().value;
441
+ set({ value: "", canCancel: true });
442
+ onSend(value).then(() => {
443
+ set({ canCancel: false });
444
+ });
445
+ },
446
+ cancel: onCancel
447
+ }));
448
+
449
+ // src/primitives/message/MessageProvider.tsx
403
450
  var getIsLast = (thread, message) => {
404
451
  const hasUpcoming = hasUpcomingMessage(thread);
405
452
  return hasUpcoming ? message.id === UPCOMING_MESSAGE_ID : thread.messages[thread.messages.length - 1]?.id === message.id;
@@ -407,7 +454,7 @@ var getIsLast = (thread, message) => {
407
454
  var useMessageContext2 = () => {
408
455
  const [context] = (0, import_react9.useState)(() => {
409
456
  const { useThread } = useAssistantContext();
410
- const useMessage = (0, import_zustand.create)(() => ({
457
+ const useMessage = (0, import_zustand2.create)(() => ({
411
458
  message: null,
412
459
  isLast: false,
413
460
  isCopied: false,
@@ -417,34 +464,23 @@ var useMessageContext2 = () => {
417
464
  setIsHovering: () => {
418
465
  }
419
466
  }));
420
- const useComposer = (0, import_zustand.create)((set, get) => ({
421
- isEditing: false,
422
- canCancel: true,
423
- edit: () => {
467
+ const useComposer = makeMessageComposerStore({
468
+ onEdit: () => {
424
469
  const message = useMessage.getState().message;
425
470
  if (message.role !== "user")
426
471
  throw new Error("Editing is only supported for user messages");
427
472
  if (message.content[0]?.type !== "text")
428
473
  throw new Error("Editing is only supported for text-only messages");
429
- return set({
430
- isEditing: true,
431
- value: message.content[0].text
432
- });
474
+ return message.content[0].text;
433
475
  },
434
- cancel: () => set({ isEditing: false }),
435
- send: () => {
476
+ onSend: (text) => {
436
477
  const message = useMessage.getState().message;
437
- if (message.role !== "user")
438
- throw new Error("Editing is only supported for user messages");
439
- useThread.getState().append({
478
+ return useThread.getState().append({
440
479
  parentId: message.parentId,
441
- content: [{ type: "text", text: get().value }]
480
+ content: [{ type: "text", text }]
442
481
  });
443
- set({ isEditing: false });
444
- },
445
- value: "",
446
- setValue: (value) => set({ value })
447
- }));
482
+ }
483
+ });
448
484
  return { useMessage, useComposer };
449
485
  });
450
486
  return context;
@@ -605,11 +641,10 @@ var import_primitive3 = require("@radix-ui/primitive");
605
641
  var import_react_primitive4 = require("@radix-ui/react-primitive");
606
642
  var import_react11 = require("react");
607
643
  var ThreadScrollToBottom = (0, import_react11.forwardRef)(({ onClick, ...rest }, ref) => {
608
- const { useThread } = useAssistantContext();
609
- const isAtBottom = useThread((s) => s.isAtBottom);
644
+ const { useViewport } = useAssistantContext();
645
+ const isAtBottom = useViewport((s) => s.isAtBottom);
610
646
  const handleScrollToBottom = () => {
611
- const thread = useThread.getState();
612
- thread.scrollToBottom();
647
+ useViewport.getState().scrollToBottom();
613
648
  };
614
649
  return /* @__PURE__ */ React.createElement(
615
650
  import_react_primitive4.Primitive.button,
@@ -685,8 +720,8 @@ var ComposerInput = (0, import_react13.forwardRef)(
685
720
  useComposer.getState().cancel();
686
721
  }
687
722
  if (e.key === "Enter" && e.shiftKey === false) {
688
- const isLoading = useThread.getState().isLoading;
689
- if (!isLoading) {
723
+ const isRunning = useThread.getState().isRunning;
724
+ if (!isRunning) {
690
725
  e.preventDefault();
691
726
  composer.send();
692
727
  }
@@ -813,7 +848,7 @@ var useGoToNextBranch = () => {
813
848
  const { useComposer, useMessage } = useMessageContext();
814
849
  const disabled = useCombinedStore(
815
850
  [useThread, useComposer, useMessage],
816
- (t, c, m) => t.isLoading || c.isEditing || m.message.branchId + 1 >= m.message.branchCount
851
+ (t, c, m) => t.isRunning || c.isEditing || m.message.branchId + 1 >= m.message.branchCount
817
852
  );
818
853
  if (disabled)
819
854
  return null;
@@ -854,7 +889,7 @@ var useGoToPreviousBranch = () => {
854
889
  const { useComposer, useMessage } = useMessageContext();
855
890
  const disabled = useCombinedStore(
856
891
  [useThread, useComposer, useMessage],
857
- (t, c, m) => t.isLoading || c.isEditing || m.message.branchId <= 0
892
+ (t, c, m) => t.isRunning || c.isEditing || m.message.branchId <= 0
858
893
  );
859
894
  if (disabled)
860
895
  return null;
@@ -900,13 +935,13 @@ __export(actionBar_exports, {
900
935
  // src/primitives/actionBar/ActionBarRoot.tsx
901
936
  var import_react_primitive10 = require("@radix-ui/react-primitive");
902
937
  var import_react20 = require("react");
903
- var ActionBarRoot = (0, import_react20.forwardRef)(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
938
+ var ActionBarRoot = (0, import_react20.forwardRef)(({ hideWhenRunning, autohide, autohideFloat, ...rest }, ref) => {
904
939
  const { useThread } = useAssistantContext();
905
940
  const { useMessage } = useMessageContext();
906
941
  const hideAndfloatStatus = useCombinedStore(
907
942
  [useThread, useMessage],
908
943
  (t, m) => {
909
- if (hideWhenBusy && t.isLoading)
944
+ if (hideWhenRunning && t.isRunning)
910
945
  return "hidden" /* Hidden */;
911
946
  const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
912
947
  if (!autohideEnabled)
@@ -955,7 +990,7 @@ var useReloadMessage = () => {
955
990
  const { useMessage } = useMessageContext();
956
991
  const disabled = useCombinedStore(
957
992
  [useThread, useMessage],
958
- (t, m) => t.isLoading || m.message.role !== "assistant"
993
+ (t, m) => t.isRunning || m.message.role !== "assistant"
959
994
  );
960
995
  if (disabled)
961
996
  return null;
@@ -963,7 +998,7 @@ var useReloadMessage = () => {
963
998
  const message = useMessage.getState().message;
964
999
  if (message.role !== "assistant")
965
1000
  throw new Error("Reloading is only supported on assistant messages");
966
- useThread.getState().reload(message.id);
1001
+ useThread.getState().startRun(message.parentId);
967
1002
  };
968
1003
  };
969
1004
 
@@ -993,60 +1028,61 @@ var import_react22 = require("react");
993
1028
 
994
1029
  // src/adapters/vercel/useDummyAIAssistantContext.tsx
995
1030
  var import_react21 = require("react");
996
- var import_zustand2 = require("zustand");
1031
+ var import_zustand4 = require("zustand");
1032
+
1033
+ // src/utils/context/stores/ViewportStore.tsx
1034
+ var import_zustand3 = require("zustand");
1035
+ var makeViewportStore = () => {
1036
+ const scrollToBottomListeners = /* @__PURE__ */ new Set();
1037
+ return (0, import_zustand3.create)(() => ({
1038
+ isAtBottom: true,
1039
+ scrollToBottom: () => {
1040
+ for (const listener of scrollToBottomListeners) {
1041
+ listener();
1042
+ }
1043
+ },
1044
+ onScrollToBottom: (callback) => {
1045
+ scrollToBottomListeners.add(callback);
1046
+ return () => {
1047
+ scrollToBottomListeners.delete(callback);
1048
+ };
1049
+ }
1050
+ }));
1051
+ };
1052
+
1053
+ // src/adapters/vercel/useDummyAIAssistantContext.tsx
997
1054
  var useDummyAIAssistantContext = () => {
998
1055
  const [context] = (0, import_react21.useState)(() => {
999
- const scrollToBottomListeners = /* @__PURE__ */ new Set();
1000
- const useThread = (0, import_zustand2.create)()(() => ({
1056
+ const useThread = (0, import_zustand4.create)()(() => ({
1057
+ id: "",
1001
1058
  messages: [],
1002
- isLoading: false,
1059
+ isRunning: false,
1003
1060
  append: async () => {
1004
1061
  throw new Error("Not implemented");
1005
1062
  },
1006
- stop: () => {
1063
+ cancelRun: () => {
1007
1064
  throw new Error("Not implemented");
1008
1065
  },
1009
1066
  switchToBranch: () => {
1010
1067
  throw new Error("Not implemented");
1011
1068
  },
1012
- reload: async () => {
1069
+ startRun: async () => {
1013
1070
  throw new Error("Not implemented");
1014
- },
1015
- isAtBottom: true,
1016
- scrollToBottom: () => {
1017
- for (const listener of scrollToBottomListeners) {
1018
- listener();
1019
- }
1020
- },
1021
- onScrollToBottom: (callback) => {
1022
- scrollToBottomListeners.add(callback);
1023
- return () => {
1024
- scrollToBottomListeners.delete(callback);
1025
- };
1026
1071
  }
1027
1072
  }));
1028
- const useComposer = (0, import_zustand2.create)()(() => ({
1029
- isEditing: true,
1030
- canCancel: false,
1031
- value: "",
1032
- setValue: (value) => {
1033
- useComposer.setState({ value });
1034
- },
1035
- edit: () => {
1036
- throw new Error("Not implemented");
1037
- },
1038
- send: () => {
1039
- useThread.getState().append({
1073
+ const useViewport = makeViewportStore();
1074
+ const useComposer = makeThreadComposerStore({
1075
+ onSend: async (text) => {
1076
+ await useThread.getState().append({
1040
1077
  parentId: useThread.getState().messages.at(-1)?.id ?? ROOT_PARENT_ID,
1041
- content: [{ type: "text", text: useComposer.getState().value }]
1078
+ content: [{ type: "text", text }]
1042
1079
  });
1043
- useComposer.getState().setValue("");
1044
1080
  },
1045
- cancel: () => {
1046
- useThread.getState().stop();
1081
+ onCancel: () => {
1082
+ useThread.getState().cancelRun();
1047
1083
  }
1048
- }));
1049
- return { useThread, useComposer };
1084
+ });
1085
+ return { useThread, useViewport, useComposer };
1050
1086
  });
1051
1087
  return context;
1052
1088
  };
@@ -1096,31 +1132,31 @@ var VercelAIAssistantProvider = ({
1096
1132
  branches.getBranchState
1097
1133
  );
1098
1134
  }, [vercel.messages, branches.getBranchState]);
1099
- const stop = (0, import_react22.useCallback)(() => {
1135
+ const cancelRun = (0, import_react22.useCallback)(() => {
1100
1136
  const lastMessage = vercel.messages.at(-1);
1101
1137
  vercel.stop();
1102
1138
  if (lastMessage?.role === "user") {
1103
1139
  vercel.setInput(lastMessage.content);
1104
1140
  }
1105
1141
  }, [vercel.messages, vercel.stop, vercel.setInput]);
1106
- const isLoading = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1142
+ const isRunning = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1107
1143
  (0, import_react22.useMemo)(() => {
1108
1144
  context.useThread.setState({
1109
1145
  messages,
1110
- isLoading,
1111
- stop,
1146
+ isRunning,
1147
+ cancelRun,
1112
1148
  switchToBranch: branches.switchToBranch,
1113
1149
  append: branches.append,
1114
- reload: branches.reload
1150
+ startRun: branches.startRun
1115
1151
  });
1116
- }, [context, messages, isLoading, stop, branches]);
1152
+ }, [context, messages, isRunning, cancelRun, branches]);
1117
1153
  (0, import_react22.useMemo)(() => {
1118
1154
  context.useComposer.setState({
1119
- canCancel: isLoading,
1155
+ canCancel: isRunning,
1120
1156
  value: vercel.input,
1121
1157
  setValue: vercel.setInput
1122
1158
  });
1123
- }, [context, isLoading, vercel.input, vercel.setInput]);
1159
+ }, [context, isRunning, vercel.input, vercel.setInput]);
1124
1160
  return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
1125
1161
  };
1126
1162
 
@@ -1167,7 +1203,7 @@ var VercelRSCAssistantProvider = ({
1167
1203
  if (message.content[0]?.type !== "text") {
1168
1204
  throw new Error("Only text content is currently supported");
1169
1205
  }
1170
- context.useThread.getState().scrollToBottom();
1206
+ context.useViewport.getState().scrollToBottom();
1171
1207
  await vercelAppend(message);
1172
1208
  },
1173
1209
  [context, vercelAppend]
package/dist/index.mjs CHANGED
@@ -46,9 +46,9 @@ var useThreadIf = (props) => {
46
46
  return false;
47
47
  if (props.empty === false && thread.messages.length === 0)
48
48
  return false;
49
- if (props.busy === true && !thread.isLoading)
49
+ if (props.running === true && !thread.isRunning)
50
50
  return false;
51
- if (props.busy === false && thread.isLoading)
51
+ if (props.running === false && thread.isRunning)
52
52
  return false;
53
53
  return true;
54
54
  });
@@ -115,12 +115,12 @@ import { useEffect, useRef as useRef2 } from "react";
115
115
  var useOnScrollToBottom = (callback) => {
116
116
  const callbackRef = useRef2(callback);
117
117
  callbackRef.current = callback;
118
- const { useThread } = useAssistantContext();
118
+ const { useViewport } = useAssistantContext();
119
119
  useEffect(() => {
120
- return useThread.getState().onScrollToBottom(() => {
120
+ return useViewport.getState().onScrollToBottom(() => {
121
121
  callbackRef.current();
122
122
  });
123
- }, [useThread]);
123
+ }, [useViewport]);
124
124
  };
125
125
 
126
126
  // src/primitives/thread/ThreadViewport.tsx
@@ -128,7 +128,7 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
128
128
  const messagesEndRef = useRef3(null);
129
129
  const divRef = useRef3(null);
130
130
  const ref = useComposedRefs(forwardedRef, divRef);
131
- const { useThread } = useAssistantContext();
131
+ const { useViewport } = useAssistantContext();
132
132
  const firstRenderRef = useRef3(true);
133
133
  const lastScrollTop = useRef3(0);
134
134
  const scrollToBottom = () => {
@@ -137,11 +137,11 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
137
137
  return;
138
138
  const behavior = firstRenderRef.current ? "instant" : "auto";
139
139
  firstRenderRef.current = false;
140
- useThread.setState({ isAtBottom: true });
140
+ useViewport.setState({ isAtBottom: true });
141
141
  div.scrollIntoView({ behavior });
142
142
  };
143
143
  useOnResizeContent(divRef, () => {
144
- if (!useThread.getState().isAtBottom)
144
+ if (!useViewport.getState().isAtBottom)
145
145
  return;
146
146
  scrollToBottom();
147
147
  });
@@ -152,11 +152,11 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
152
152
  const div = divRef.current;
153
153
  if (!div)
154
154
  return;
155
- const isAtBottom = useThread.getState().isAtBottom;
155
+ const isAtBottom = useViewport.getState().isAtBottom;
156
156
  const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
157
157
  if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
158
158
  } else if (newIsAtBottom !== isAtBottom) {
159
- useThread.setState({ isAtBottom: newIsAtBottom });
159
+ useViewport.setState({ isAtBottom: newIsAtBottom });
160
160
  }
161
161
  lastScrollTop.current = div.scrollTop;
162
162
  };
@@ -272,13 +272,13 @@ var useVercelAIBranches = (chat, context) => {
272
272
  [data, chat.messages, chat.setMessages]
273
273
  );
274
274
  const reloadMaybe = "reload" in chat ? chat.reload : void 0;
275
- const reload = useCallback(
276
- async (messageId) => {
275
+ const startRun = useCallback(
276
+ async (parentId) => {
277
277
  if (!reloadMaybe)
278
278
  throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
279
- const newMessages = sliceMessagesUntil(chat.messages, messageId);
279
+ const newMessages = sliceMessagesUntil(chat.messages, parentId);
280
280
  chat.setMessages(newMessages);
281
- context.useThread.getState().scrollToBottom();
281
+ context.useViewport.getState().scrollToBottom();
282
282
  await reloadMaybe();
283
283
  },
284
284
  [context, chat.messages, chat.setMessages, reloadMaybe]
@@ -289,7 +289,7 @@ var useVercelAIBranches = (chat, context) => {
289
289
  chat.setMessages(newMessages);
290
290
  if (message.content.length !== 1 || message.content[0]?.type !== "text")
291
291
  throw new Error("Only text content is currently supported");
292
- context.useThread.getState().scrollToBottom();
292
+ context.useViewport.getState().scrollToBottom();
293
293
  await chat.append({
294
294
  role: "user",
295
295
  content: message.content[0].text
@@ -302,13 +302,13 @@ var useVercelAIBranches = (chat, context) => {
302
302
  getBranchState,
303
303
  switchToBranch,
304
304
  append,
305
- reload
305
+ startRun
306
306
  }),
307
- [getBranchState, switchToBranch, append, reload]
307
+ [getBranchState, switchToBranch, append, startRun]
308
308
  );
309
309
  };
310
310
  var hasUpcomingMessage = (thread) => {
311
- return thread.isLoading && thread.messages[thread.messages.length - 1]?.role !== "assistant";
311
+ return thread.isRunning && thread.messages[thread.messages.length - 1]?.role !== "assistant";
312
312
  };
313
313
 
314
314
  // src/utils/context/useComposerContext.ts
@@ -361,7 +361,56 @@ __export(message_exports, {
361
361
 
362
362
  // src/primitives/message/MessageProvider.tsx
363
363
  import { useMemo as useMemo2, useState } from "react";
364
- import { create } from "zustand";
364
+ import { create as create2 } from "zustand";
365
+
366
+ // src/utils/context/stores/ComposerStore.ts
367
+ import {
368
+ create
369
+ } from "zustand";
370
+ var makeBaseComposer = (set) => ({
371
+ value: "",
372
+ setValue: (value) => {
373
+ set({ value });
374
+ }
375
+ });
376
+ var makeMessageComposerStore = ({
377
+ onEdit,
378
+ onSend
379
+ }) => create()((set, get, store) => ({
380
+ ...makeBaseComposer(set, get, store),
381
+ canCancel: true,
382
+ isEditing: false,
383
+ edit: () => {
384
+ const value = onEdit();
385
+ set({ isEditing: true, value });
386
+ },
387
+ send: () => {
388
+ const value = get().value;
389
+ set({ isEditing: false });
390
+ return onSend(value);
391
+ },
392
+ cancel: () => {
393
+ set({ isEditing: false });
394
+ }
395
+ }));
396
+ var makeThreadComposerStore = ({
397
+ onSend,
398
+ onCancel
399
+ }) => create()((set, get, store) => ({
400
+ ...makeBaseComposer(set, get, store),
401
+ isEditing: true,
402
+ canCancel: false,
403
+ send: () => {
404
+ const value = get().value;
405
+ set({ value: "", canCancel: true });
406
+ onSend(value).then(() => {
407
+ set({ canCancel: false });
408
+ });
409
+ },
410
+ cancel: onCancel
411
+ }));
412
+
413
+ // src/primitives/message/MessageProvider.tsx
365
414
  var getIsLast = (thread, message) => {
366
415
  const hasUpcoming = hasUpcomingMessage(thread);
367
416
  return hasUpcoming ? message.id === UPCOMING_MESSAGE_ID : thread.messages[thread.messages.length - 1]?.id === message.id;
@@ -369,7 +418,7 @@ var getIsLast = (thread, message) => {
369
418
  var useMessageContext2 = () => {
370
419
  const [context] = useState(() => {
371
420
  const { useThread } = useAssistantContext();
372
- const useMessage = create(() => ({
421
+ const useMessage = create2(() => ({
373
422
  message: null,
374
423
  isLast: false,
375
424
  isCopied: false,
@@ -379,34 +428,23 @@ var useMessageContext2 = () => {
379
428
  setIsHovering: () => {
380
429
  }
381
430
  }));
382
- const useComposer = create((set, get) => ({
383
- isEditing: false,
384
- canCancel: true,
385
- edit: () => {
431
+ const useComposer = makeMessageComposerStore({
432
+ onEdit: () => {
386
433
  const message = useMessage.getState().message;
387
434
  if (message.role !== "user")
388
435
  throw new Error("Editing is only supported for user messages");
389
436
  if (message.content[0]?.type !== "text")
390
437
  throw new Error("Editing is only supported for text-only messages");
391
- return set({
392
- isEditing: true,
393
- value: message.content[0].text
394
- });
438
+ return message.content[0].text;
395
439
  },
396
- cancel: () => set({ isEditing: false }),
397
- send: () => {
440
+ onSend: (text) => {
398
441
  const message = useMessage.getState().message;
399
- if (message.role !== "user")
400
- throw new Error("Editing is only supported for user messages");
401
- useThread.getState().append({
442
+ return useThread.getState().append({
402
443
  parentId: message.parentId,
403
- content: [{ type: "text", text: get().value }]
444
+ content: [{ type: "text", text }]
404
445
  });
405
- set({ isEditing: false });
406
- },
407
- value: "",
408
- setValue: (value) => set({ value })
409
- }));
446
+ }
447
+ });
410
448
  return { useMessage, useComposer };
411
449
  });
412
450
  return context;
@@ -571,11 +609,10 @@ import {
571
609
  } from "@radix-ui/react-primitive";
572
610
  import { forwardRef as forwardRef4 } from "react";
573
611
  var ThreadScrollToBottom = forwardRef4(({ onClick, ...rest }, ref) => {
574
- const { useThread } = useAssistantContext();
575
- const isAtBottom = useThread((s) => s.isAtBottom);
612
+ const { useViewport } = useAssistantContext();
613
+ const isAtBottom = useViewport((s) => s.isAtBottom);
576
614
  const handleScrollToBottom = () => {
577
- const thread = useThread.getState();
578
- thread.scrollToBottom();
615
+ useViewport.getState().scrollToBottom();
579
616
  };
580
617
  return /* @__PURE__ */ React.createElement(
581
618
  Primitive4.button,
@@ -658,8 +695,8 @@ var ComposerInput = forwardRef6(
658
695
  useComposer.getState().cancel();
659
696
  }
660
697
  if (e.key === "Enter" && e.shiftKey === false) {
661
- const isLoading = useThread.getState().isLoading;
662
- if (!isLoading) {
698
+ const isRunning = useThread.getState().isRunning;
699
+ if (!isRunning) {
663
700
  e.preventDefault();
664
701
  composer.send();
665
702
  }
@@ -790,7 +827,7 @@ var useGoToNextBranch = () => {
790
827
  const { useComposer, useMessage } = useMessageContext();
791
828
  const disabled = useCombinedStore(
792
829
  [useThread, useComposer, useMessage],
793
- (t, c, m) => t.isLoading || c.isEditing || m.message.branchId + 1 >= m.message.branchCount
830
+ (t, c, m) => t.isRunning || c.isEditing || m.message.branchId + 1 >= m.message.branchCount
794
831
  );
795
832
  if (disabled)
796
833
  return null;
@@ -833,7 +870,7 @@ var useGoToPreviousBranch = () => {
833
870
  const { useComposer, useMessage } = useMessageContext();
834
871
  const disabled = useCombinedStore(
835
872
  [useThread, useComposer, useMessage],
836
- (t, c, m) => t.isLoading || c.isEditing || m.message.branchId <= 0
873
+ (t, c, m) => t.isRunning || c.isEditing || m.message.branchId <= 0
837
874
  );
838
875
  if (disabled)
839
876
  return null;
@@ -883,13 +920,13 @@ import {
883
920
  Primitive as Primitive10
884
921
  } from "@radix-ui/react-primitive";
885
922
  import { forwardRef as forwardRef11 } from "react";
886
- var ActionBarRoot = forwardRef11(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
923
+ var ActionBarRoot = forwardRef11(({ hideWhenRunning, autohide, autohideFloat, ...rest }, ref) => {
887
924
  const { useThread } = useAssistantContext();
888
925
  const { useMessage } = useMessageContext();
889
926
  const hideAndfloatStatus = useCombinedStore(
890
927
  [useThread, useMessage],
891
928
  (t, m) => {
892
- if (hideWhenBusy && t.isLoading)
929
+ if (hideWhenRunning && t.isRunning)
893
930
  return "hidden" /* Hidden */;
894
931
  const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
895
932
  if (!autohideEnabled)
@@ -938,7 +975,7 @@ var useReloadMessage = () => {
938
975
  const { useMessage } = useMessageContext();
939
976
  const disabled = useCombinedStore(
940
977
  [useThread, useMessage],
941
- (t, m) => t.isLoading || m.message.role !== "assistant"
978
+ (t, m) => t.isRunning || m.message.role !== "assistant"
942
979
  );
943
980
  if (disabled)
944
981
  return null;
@@ -946,7 +983,7 @@ var useReloadMessage = () => {
946
983
  const message = useMessage.getState().message;
947
984
  if (message.role !== "assistant")
948
985
  throw new Error("Reloading is only supported on assistant messages");
949
- useThread.getState().reload(message.id);
986
+ useThread.getState().startRun(message.parentId);
950
987
  };
951
988
  };
952
989
 
@@ -976,60 +1013,61 @@ import { useCallback as useCallback3, useMemo as useMemo4 } from "react";
976
1013
 
977
1014
  // src/adapters/vercel/useDummyAIAssistantContext.tsx
978
1015
  import { useState as useState2 } from "react";
979
- import { create as create2 } from "zustand";
1016
+ import { create as create4 } from "zustand";
1017
+
1018
+ // src/utils/context/stores/ViewportStore.tsx
1019
+ import { create as create3 } from "zustand";
1020
+ var makeViewportStore = () => {
1021
+ const scrollToBottomListeners = /* @__PURE__ */ new Set();
1022
+ return create3(() => ({
1023
+ isAtBottom: true,
1024
+ scrollToBottom: () => {
1025
+ for (const listener of scrollToBottomListeners) {
1026
+ listener();
1027
+ }
1028
+ },
1029
+ onScrollToBottom: (callback) => {
1030
+ scrollToBottomListeners.add(callback);
1031
+ return () => {
1032
+ scrollToBottomListeners.delete(callback);
1033
+ };
1034
+ }
1035
+ }));
1036
+ };
1037
+
1038
+ // src/adapters/vercel/useDummyAIAssistantContext.tsx
980
1039
  var useDummyAIAssistantContext = () => {
981
1040
  const [context] = useState2(() => {
982
- const scrollToBottomListeners = /* @__PURE__ */ new Set();
983
- const useThread = create2()(() => ({
1041
+ const useThread = create4()(() => ({
1042
+ id: "",
984
1043
  messages: [],
985
- isLoading: false,
1044
+ isRunning: false,
986
1045
  append: async () => {
987
1046
  throw new Error("Not implemented");
988
1047
  },
989
- stop: () => {
1048
+ cancelRun: () => {
990
1049
  throw new Error("Not implemented");
991
1050
  },
992
1051
  switchToBranch: () => {
993
1052
  throw new Error("Not implemented");
994
1053
  },
995
- reload: async () => {
1054
+ startRun: async () => {
996
1055
  throw new Error("Not implemented");
997
- },
998
- isAtBottom: true,
999
- scrollToBottom: () => {
1000
- for (const listener of scrollToBottomListeners) {
1001
- listener();
1002
- }
1003
- },
1004
- onScrollToBottom: (callback) => {
1005
- scrollToBottomListeners.add(callback);
1006
- return () => {
1007
- scrollToBottomListeners.delete(callback);
1008
- };
1009
1056
  }
1010
1057
  }));
1011
- const useComposer = create2()(() => ({
1012
- isEditing: true,
1013
- canCancel: false,
1014
- value: "",
1015
- setValue: (value) => {
1016
- useComposer.setState({ value });
1017
- },
1018
- edit: () => {
1019
- throw new Error("Not implemented");
1020
- },
1021
- send: () => {
1022
- useThread.getState().append({
1058
+ const useViewport = makeViewportStore();
1059
+ const useComposer = makeThreadComposerStore({
1060
+ onSend: async (text) => {
1061
+ await useThread.getState().append({
1023
1062
  parentId: useThread.getState().messages.at(-1)?.id ?? ROOT_PARENT_ID,
1024
- content: [{ type: "text", text: useComposer.getState().value }]
1063
+ content: [{ type: "text", text }]
1025
1064
  });
1026
- useComposer.getState().setValue("");
1027
1065
  },
1028
- cancel: () => {
1029
- useThread.getState().stop();
1066
+ onCancel: () => {
1067
+ useThread.getState().cancelRun();
1030
1068
  }
1031
- }));
1032
- return { useThread, useComposer };
1069
+ });
1070
+ return { useThread, useViewport, useComposer };
1033
1071
  });
1034
1072
  return context;
1035
1073
  };
@@ -1079,31 +1117,31 @@ var VercelAIAssistantProvider = ({
1079
1117
  branches.getBranchState
1080
1118
  );
1081
1119
  }, [vercel.messages, branches.getBranchState]);
1082
- const stop = useCallback3(() => {
1120
+ const cancelRun = useCallback3(() => {
1083
1121
  const lastMessage = vercel.messages.at(-1);
1084
1122
  vercel.stop();
1085
1123
  if (lastMessage?.role === "user") {
1086
1124
  vercel.setInput(lastMessage.content);
1087
1125
  }
1088
1126
  }, [vercel.messages, vercel.stop, vercel.setInput]);
1089
- const isLoading = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1127
+ const isRunning = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1090
1128
  useMemo4(() => {
1091
1129
  context.useThread.setState({
1092
1130
  messages,
1093
- isLoading,
1094
- stop,
1131
+ isRunning,
1132
+ cancelRun,
1095
1133
  switchToBranch: branches.switchToBranch,
1096
1134
  append: branches.append,
1097
- reload: branches.reload
1135
+ startRun: branches.startRun
1098
1136
  });
1099
- }, [context, messages, isLoading, stop, branches]);
1137
+ }, [context, messages, isRunning, cancelRun, branches]);
1100
1138
  useMemo4(() => {
1101
1139
  context.useComposer.setState({
1102
- canCancel: isLoading,
1140
+ canCancel: isRunning,
1103
1141
  value: vercel.input,
1104
1142
  setValue: vercel.setInput
1105
1143
  });
1106
- }, [context, isLoading, vercel.input, vercel.setInput]);
1144
+ }, [context, isRunning, vercel.input, vercel.setInput]);
1107
1145
  return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
1108
1146
  };
1109
1147
 
@@ -1153,7 +1191,7 @@ var VercelRSCAssistantProvider = ({
1153
1191
  if (message.content[0]?.type !== "text") {
1154
1192
  throw new Error("Only text content is currently supported");
1155
1193
  }
1156
- context.useThread.getState().scrollToBottom();
1194
+ context.useViewport.getState().scrollToBottom();
1157
1195
  await vercelAppend(message);
1158
1196
  },
1159
1197
  [context, vercelAppend]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": {