@assistant-ui/react 0.0.9 → 0.0.10

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