@assistant-ui/react 0.0.8 → 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.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,18 +128,20 @@ 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
+ const lastScrollTop = useRef3(0);
133
134
  const scrollToBottom = () => {
134
135
  const div = messagesEndRef.current;
135
136
  if (!div || !autoScroll)
136
137
  return;
137
138
  const behavior = firstRenderRef.current ? "instant" : "auto";
138
139
  firstRenderRef.current = false;
140
+ useViewport.setState({ isAtBottom: true });
139
141
  div.scrollIntoView({ behavior });
140
142
  };
141
143
  useOnResizeContent(divRef, () => {
142
- if (!useThread.getState().isAtBottom)
144
+ if (!useViewport.getState().isAtBottom)
143
145
  return;
144
146
  scrollToBottom();
145
147
  });
@@ -150,11 +152,13 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
150
152
  const div = divRef.current;
151
153
  if (!div)
152
154
  return;
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 });
155
+ const isAtBottom = useViewport.getState().isAtBottom;
156
+ const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
157
+ if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
158
+ } else if (newIsAtBottom !== isAtBottom) {
159
+ useViewport.setState({ isAtBottom: newIsAtBottom });
157
160
  }
161
+ lastScrollTop.current = div.scrollTop;
158
162
  };
159
163
  return /* @__PURE__ */ React.createElement(
160
164
  Primitive2.div,
@@ -168,15 +172,19 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
168
172
  );
169
173
  });
170
174
 
171
- // src/vercel/useVercelAIBranches.tsx
175
+ // src/adapters/vercel/useVercelAIBranches.tsx
172
176
  import { useCallback, useMemo, useRef as useRef4 } from "react";
173
- var ROOT_ID = "__ROOT_ID__";
177
+
178
+ // src/utils/context/stores/AssistantTypes.ts
179
+ var ROOT_PARENT_ID = "__ROOT_ID__";
180
+
181
+ // src/adapters/vercel/useVercelAIBranches.tsx
174
182
  var UPCOMING_MESSAGE_ID = "__UPCOMING_MESSAGE_ID__";
175
183
  var updateBranchData = (data, messages) => {
176
184
  for (let i = 0; i < messages.length; i++) {
177
185
  const child = messages[i];
178
186
  const childId = child.id;
179
- const parentId = messages[i - 1]?.id ?? ROOT_ID;
187
+ const parentId = messages[i - 1]?.id ?? ROOT_PARENT_ID;
180
188
  data.parentMap.set(childId, parentId);
181
189
  const parentArray = data.branchMap.get(parentId);
182
190
  if (!parentArray) {
@@ -187,32 +195,32 @@ var updateBranchData = (data, messages) => {
187
195
  data.snapshots.set(childId, messages);
188
196
  }
189
197
  };
190
- var getParentId = (data, messages, message) => {
191
- if (message.id === UPCOMING_MESSAGE_ID) {
198
+ var getParentId = (data, messages, messageId) => {
199
+ if (messageId === UPCOMING_MESSAGE_ID) {
192
200
  const parent = messages.at(-1);
193
201
  if (!parent)
194
- return ROOT_ID;
202
+ return ROOT_PARENT_ID;
195
203
  return parent.id;
196
204
  }
197
- const parentId = data.parentMap.get(message.id);
205
+ const parentId = data.parentMap.get(messageId);
198
206
  if (!parentId)
199
207
  throw new Error("Unexpected: Message parent not found");
200
208
  return parentId;
201
209
  };
202
- var getBranchStateImpl = (data, messages, message) => {
203
- const parentId = getParentId(data, messages, message);
210
+ var getBranchStateImpl = (data, messages, messageId) => {
211
+ const parentId = getParentId(data, messages, messageId);
204
212
  const branches = data.branchMap.get(parentId) ?? [];
205
- const branchId = message.id === UPCOMING_MESSAGE_ID ? branches.length : branches.indexOf(message.id);
213
+ const branchId = messageId === UPCOMING_MESSAGE_ID ? branches.length : branches.indexOf(messageId);
206
214
  if (branchId === -1)
207
215
  throw new Error("Unexpected: Message not found in parent children");
208
- const upcomingOffset = message.id === UPCOMING_MESSAGE_ID ? 1 : 0;
216
+ const upcomingOffset = messageId === UPCOMING_MESSAGE_ID ? 1 : 0;
209
217
  return {
210
218
  branchId,
211
219
  branchCount: branches.length + upcomingOffset
212
220
  };
213
221
  };
214
- var switchToBranchImpl = (data, messages, message, branchId) => {
215
- const parentId = getParentId(data, messages, message);
222
+ var switchToBranchImpl = (data, messages, messageId, branchId) => {
223
+ const parentId = getParentId(data, messages, messageId);
216
224
  const branches = data.branchMap.get(parentId);
217
225
  if (!branches)
218
226
  throw new Error("Unexpected: Parent children not found");
@@ -221,20 +229,22 @@ var switchToBranchImpl = (data, messages, message, branchId) => {
221
229
  throw new Error("Unexpected: Requested branch not found");
222
230
  if (branchId < 0 || branchId >= branches.length)
223
231
  throw new Error("Switch to branch called with a branch index out of range");
224
- if (newMessageId === message.id)
232
+ if (newMessageId === messageId)
225
233
  return messages;
226
234
  const snapshot = data.snapshots.get(newMessageId);
227
235
  if (!snapshot)
228
236
  throw new Error("Unexpected: Branch snapshot not found");
229
237
  return snapshot;
230
238
  };
231
- var sliceMessagesUntil = (messages, message) => {
232
- if (message.id === UPCOMING_MESSAGE_ID)
239
+ var sliceMessagesUntil = (messages, messageId) => {
240
+ if (messageId === ROOT_PARENT_ID)
241
+ return [];
242
+ if (messageId === UPCOMING_MESSAGE_ID)
233
243
  return messages;
234
- const messageIdx = messages.findIndex((m) => m.id === message.id);
244
+ const messageIdx = messages.findIndex((m) => m.id === messageId);
235
245
  if (messageIdx === -1)
236
246
  throw new Error("Unexpected: Message not found");
237
- return messages.slice(0, messageIdx);
247
+ return messages.slice(0, messageIdx + 1);
238
248
  };
239
249
  var useVercelAIBranches = (chat, context) => {
240
250
  const data = useRef4({
@@ -244,17 +254,17 @@ var useVercelAIBranches = (chat, context) => {
244
254
  }).current;
245
255
  updateBranchData(data, chat.messages);
246
256
  const getBranchState = useCallback(
247
- (message) => {
248
- return getBranchStateImpl(data, chat.messages, message);
257
+ (messageId) => {
258
+ return getBranchStateImpl(data, chat.messages, messageId);
249
259
  },
250
260
  [data, chat.messages]
251
261
  );
252
262
  const switchToBranch = useCallback(
253
- (message, branchId) => {
263
+ (messageId, branchId) => {
254
264
  const newMessages = switchToBranchImpl(
255
265
  data,
256
266
  chat.messages,
257
- message,
267
+ messageId,
258
268
  branchId
259
269
  );
260
270
  chat.setMessages(newMessages);
@@ -262,27 +272,27 @@ var useVercelAIBranches = (chat, context) => {
262
272
  [data, chat.messages, chat.setMessages]
263
273
  );
264
274
  const reloadMaybe = "reload" in chat ? chat.reload : void 0;
265
- const reloadAt = useCallback(
266
- async (message) => {
275
+ const startRun = useCallback(
276
+ async (parentId) => {
267
277
  if (!reloadMaybe)
268
278
  throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
269
- const newMessages = sliceMessagesUntil(chat.messages, message);
279
+ const newMessages = sliceMessagesUntil(chat.messages, parentId);
270
280
  chat.setMessages(newMessages);
271
- context.useThread.getState().scrollToBottom();
281
+ context.useViewport.getState().scrollToBottom();
272
282
  await reloadMaybe();
273
283
  },
274
284
  [context, chat.messages, chat.setMessages, reloadMaybe]
275
285
  );
276
- const editAt = useCallback(
277
- async (message, newMessage) => {
278
- const newMessages = sliceMessagesUntil(chat.messages, message);
286
+ const append = useCallback(
287
+ async (message) => {
288
+ const newMessages = sliceMessagesUntil(chat.messages, message.parentId);
279
289
  chat.setMessages(newMessages);
280
- if (newMessage.content[0]?.type !== "text")
290
+ if (message.content.length !== 1 || message.content[0]?.type !== "text")
281
291
  throw new Error("Only text content is currently supported");
282
- context.useThread.getState().scrollToBottom();
292
+ context.useViewport.getState().scrollToBottom();
283
293
  await chat.append({
284
294
  role: "user",
285
- content: newMessage.content[0].text
295
+ content: message.content[0].text
286
296
  });
287
297
  },
288
298
  [context, chat.messages, chat.setMessages, chat.append]
@@ -291,14 +301,14 @@ var useVercelAIBranches = (chat, context) => {
291
301
  () => ({
292
302
  getBranchState,
293
303
  switchToBranch,
294
- editAt,
295
- reloadAt
304
+ append,
305
+ startRun
296
306
  }),
297
- [getBranchState, switchToBranch, editAt, reloadAt]
307
+ [getBranchState, switchToBranch, append, startRun]
298
308
  );
299
309
  };
300
310
  var hasUpcomingMessage = (thread) => {
301
- return thread.isLoading && thread.messages[thread.messages.length - 1]?.role !== "assistant";
311
+ return thread.isRunning && thread.messages[thread.messages.length - 1]?.role !== "assistant";
302
312
  };
303
313
 
304
314
  // src/utils/context/useComposerContext.ts
@@ -351,16 +361,64 @@ __export(message_exports, {
351
361
 
352
362
  // src/primitives/message/MessageProvider.tsx
353
363
  import { useMemo as useMemo2, useState } from "react";
354
- import { create } from "zustand";
355
- import { useShallow } from "zustand/react/shallow";
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
356
414
  var getIsLast = (thread, message) => {
357
415
  const hasUpcoming = hasUpcomingMessage(thread);
358
416
  return hasUpcoming ? message.id === UPCOMING_MESSAGE_ID : thread.messages[thread.messages.length - 1]?.id === message.id;
359
417
  };
360
418
  var useMessageContext2 = () => {
361
- const { useBranchObserver } = useAssistantContext();
362
419
  const [context] = useState(() => {
363
- const useMessage = create(() => ({
420
+ const { useThread } = useAssistantContext();
421
+ const useMessage = create2(() => ({
364
422
  message: null,
365
423
  isLast: false,
366
424
  isCopied: false,
@@ -368,40 +426,25 @@ var useMessageContext2 = () => {
368
426
  setIsCopied: () => {
369
427
  },
370
428
  setIsHovering: () => {
371
- },
372
- branchState: {
373
- branchId: 0,
374
- branchCount: 0
375
429
  }
376
430
  }));
377
- const useComposer = create((set, get) => ({
378
- isEditing: false,
379
- canCancel: true,
380
- edit: () => {
431
+ const useComposer = makeMessageComposerStore({
432
+ onEdit: () => {
381
433
  const message = useMessage.getState().message;
382
434
  if (message.role !== "user")
383
435
  throw new Error("Editing is only supported for user messages");
384
436
  if (message.content[0]?.type !== "text")
385
437
  throw new Error("Editing is only supported for text-only messages");
386
- return set({
387
- isEditing: true,
388
- value: message.content[0].text
389
- });
438
+ return message.content[0].text;
390
439
  },
391
- cancel: () => set({ isEditing: false }),
392
- send: () => {
440
+ onSend: (text) => {
393
441
  const message = useMessage.getState().message;
394
- if (message.role !== "user")
395
- throw new Error("Editing is only supported for user messages");
396
- useBranchObserver.getState().editAt(message, {
397
- role: "user",
398
- content: [{ type: "text", text: get().value }]
442
+ return useThread.getState().append({
443
+ parentId: message.parentId,
444
+ content: [{ type: "text", text }]
399
445
  });
400
- set({ isEditing: false });
401
- },
402
- value: "",
403
- setValue: (value) => set({ value })
404
- }));
446
+ }
447
+ });
405
448
  return { useMessage, useComposer };
406
449
  });
407
450
  return context;
@@ -410,11 +453,8 @@ var MessageProvider = ({
410
453
  message,
411
454
  children
412
455
  }) => {
413
- const { useThread, useBranchObserver } = useAssistantContext();
456
+ const { useThread } = useAssistantContext();
414
457
  const context = useMessageContext2();
415
- const branchState = useBranchObserver(
416
- useShallow((b) => b.getBranchState(message))
417
- );
418
458
  const isLast = useThread((thread) => getIsLast(thread, message));
419
459
  const [isCopied, setIsCopied] = useState(false);
420
460
  const [isHovering, setIsHovering] = useState(false);
@@ -426,12 +466,11 @@ var MessageProvider = ({
426
466
  isCopied,
427
467
  isHovering,
428
468
  setIsCopied,
429
- setIsHovering,
430
- branchState
469
+ setIsHovering
431
470
  },
432
471
  true
433
472
  );
434
- }, [context, message, isLast, isCopied, isHovering, branchState]);
473
+ }, [context, message, isLast, isCopied, isHovering]);
435
474
  return /* @__PURE__ */ React.createElement(MessageContext.Provider, { value: context }, children);
436
475
  };
437
476
 
@@ -466,29 +505,21 @@ var MessageRoot = forwardRef3(
466
505
  // src/primitives/message/MessageIf.tsx
467
506
  var useMessageIf = (props) => {
468
507
  const { useMessage } = useMessageContext();
469
- return useMessage(
470
- ({
471
- message,
472
- isLast,
473
- isCopied,
474
- isHovering,
475
- branchState: { branchCount }
476
- }) => {
477
- if (props.hasBranches === true && branchCount < 2)
478
- return false;
479
- if (props.user && message.role !== "user")
480
- return false;
481
- if (props.assistant && message.role !== "assistant")
482
- return false;
483
- if (props.lastOrHover === true && !isHovering && !isLast)
484
- return false;
485
- if (props.copied === true && !isCopied)
486
- return false;
487
- if (props.copied === false && isCopied)
488
- return false;
489
- return true;
490
- }
491
- );
508
+ return useMessage(({ message, isLast, isCopied, isHovering }) => {
509
+ if (props.hasBranches === true && message.branchCount < 2)
510
+ return false;
511
+ if (props.user && message.role !== "user")
512
+ return false;
513
+ if (props.assistant && message.role !== "assistant")
514
+ return false;
515
+ if (props.lastOrHover === true && !isHovering && !isLast)
516
+ return false;
517
+ if (props.copied === true && !isCopied)
518
+ return false;
519
+ if (props.copied === false && isCopied)
520
+ return false;
521
+ return true;
522
+ });
492
523
  };
493
524
  var MessageIf = ({ children, ...query }) => {
494
525
  const result = useMessageIf(query);
@@ -559,7 +590,12 @@ var ThreadMessages = ({ components }) => {
559
590
  message: {
560
591
  id: UPCOMING_MESSAGE_ID,
561
592
  role: "assistant",
562
- content: [{ type: "text", text: "..." }]
593
+ content: [{ type: "text", text: "..." }],
594
+ parentId: messages.at(-1)?.id ?? ROOT_PARENT_ID,
595
+ // TODO fix these (move upcoming message to AssistantContext)
596
+ branchId: 0,
597
+ branchCount: 1,
598
+ createdAt: /* @__PURE__ */ new Date()
563
599
  }
564
600
  },
565
601
  /* @__PURE__ */ React.createElement(AssistantMessage, null)
@@ -573,18 +609,16 @@ import {
573
609
  } from "@radix-ui/react-primitive";
574
610
  import { forwardRef as forwardRef4 } from "react";
575
611
  var ThreadScrollToBottom = forwardRef4(({ onClick, ...rest }, ref) => {
576
- const { useThread } = useAssistantContext();
577
- const isAtBottom = useThread((s) => s.isAtBottom);
612
+ const { useViewport } = useAssistantContext();
613
+ const isAtBottom = useViewport((s) => s.isAtBottom);
578
614
  const handleScrollToBottom = () => {
579
- const thread = useThread.getState();
580
- thread.scrollToBottom();
615
+ useViewport.getState().scrollToBottom();
581
616
  };
582
- if (isAtBottom)
583
- return null;
584
617
  return /* @__PURE__ */ React.createElement(
585
618
  Primitive4.button,
586
619
  {
587
620
  ...rest,
621
+ disabled: isAtBottom,
588
622
  ref,
589
623
  onClick: composeEventHandlers3(onClick, handleScrollToBottom)
590
624
  }
@@ -661,8 +695,8 @@ var ComposerInput = forwardRef6(
661
695
  useComposer.getState().cancel();
662
696
  }
663
697
  if (e.key === "Enter" && e.shiftKey === false) {
664
- const isLoading = useThread.getState().isLoading;
665
- if (!isLoading) {
698
+ const isRunning = useThread.getState().isRunning;
699
+ if (!isRunning) {
666
700
  e.preventDefault();
667
701
  composer.send();
668
702
  }
@@ -789,20 +823,17 @@ var useCombinedStore = (stores, selector) => {
789
823
 
790
824
  // src/actions/useGoToNextBranch.tsx
791
825
  var useGoToNextBranch = () => {
792
- const { useThread, useBranchObserver } = useAssistantContext();
826
+ const { useThread } = useAssistantContext();
793
827
  const { useComposer, useMessage } = useMessageContext();
794
828
  const disabled = useCombinedStore(
795
829
  [useThread, useComposer, useMessage],
796
- (t, c, m) => t.isLoading || c.isEditing || m.branchState.branchId + 1 >= m.branchState.branchCount
830
+ (t, c, m) => t.isRunning || c.isEditing || m.message.branchId + 1 >= m.message.branchCount
797
831
  );
798
832
  if (disabled)
799
833
  return null;
800
834
  return () => {
801
- const {
802
- message,
803
- branchState: { branchId }
804
- } = useMessage.getState();
805
- useBranchObserver.getState().switchToBranch(message, branchId + 1);
835
+ const { message } = useMessage.getState();
836
+ useThread.getState().switchToBranch(message.id, message.branchId + 1);
806
837
  };
807
838
  };
808
839
 
@@ -835,20 +866,17 @@ var BranchPickerNext = createActionButton(useGoToNextBranch);
835
866
 
836
867
  // src/actions/useGoToPreviousBranch.tsx
837
868
  var useGoToPreviousBranch = () => {
838
- const { useThread, useBranchObserver } = useAssistantContext();
869
+ const { useThread } = useAssistantContext();
839
870
  const { useComposer, useMessage } = useMessageContext();
840
871
  const disabled = useCombinedStore(
841
872
  [useThread, useComposer, useMessage],
842
- (t, c, m) => t.isLoading || c.isEditing || m.branchState.branchId <= 0
873
+ (t, c, m) => t.isRunning || c.isEditing || m.message.branchId <= 0
843
874
  );
844
875
  if (disabled)
845
876
  return null;
846
877
  return () => {
847
- const {
848
- message,
849
- branchState: { branchId }
850
- } = useMessage.getState();
851
- useBranchObserver.getState().switchToBranch(message, branchId - 1);
878
+ const { message } = useMessage.getState();
879
+ useThread.getState().switchToBranch(message.id, message.branchId - 1);
852
880
  };
853
881
  };
854
882
 
@@ -858,14 +886,14 @@ var BranchPickerPrevious = createActionButton(useGoToPreviousBranch);
858
886
  // src/primitives/branchPicker/BranchPickerCount.tsx
859
887
  var BranchPickerCount = () => {
860
888
  const { useMessage } = useMessageContext();
861
- const branchCount = useMessage((s) => s.branchState.branchCount);
889
+ const branchCount = useMessage((s) => s.message.branchCount);
862
890
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchCount);
863
891
  };
864
892
 
865
893
  // src/primitives/branchPicker/BranchPickerNumber.tsx
866
894
  var BranchPickerNumber = () => {
867
895
  const { useMessage } = useMessageContext();
868
- const branchId = useMessage((s) => s.branchState.branchId);
896
+ const branchId = useMessage((s) => s.message.branchId);
869
897
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchId + 1);
870
898
  };
871
899
 
@@ -892,20 +920,20 @@ import {
892
920
  Primitive as Primitive10
893
921
  } from "@radix-ui/react-primitive";
894
922
  import { forwardRef as forwardRef11 } from "react";
895
- var ActionBarRoot = forwardRef11(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
923
+ var ActionBarRoot = forwardRef11(({ hideWhenRunning, autohide, autohideFloat, ...rest }, ref) => {
896
924
  const { useThread } = useAssistantContext();
897
925
  const { useMessage } = useMessageContext();
898
926
  const hideAndfloatStatus = useCombinedStore(
899
927
  [useThread, useMessage],
900
928
  (t, m) => {
901
- if (hideWhenBusy && t.isLoading)
929
+ if (hideWhenRunning && t.isRunning)
902
930
  return "hidden" /* Hidden */;
903
931
  const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
904
932
  if (!autohideEnabled)
905
933
  return "normal" /* Normal */;
906
934
  if (!m.isHovering)
907
935
  return "hidden" /* Hidden */;
908
- if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branchState.branchCount <= 1)
936
+ if (autohideFloat === "always" || autohideFloat === "single-branch" && m.message.branchCount <= 1)
909
937
  return "floating" /* Floating */;
910
938
  return "normal" /* Normal */;
911
939
  }
@@ -943,11 +971,11 @@ var ActionBarCopy = createActionButton(useCopyMessage);
943
971
 
944
972
  // src/actions/useReloadMessage.tsx
945
973
  var useReloadMessage = () => {
946
- const { useThread, useBranchObserver } = useAssistantContext();
974
+ const { useThread } = useAssistantContext();
947
975
  const { useMessage } = useMessageContext();
948
976
  const disabled = useCombinedStore(
949
977
  [useThread, useMessage],
950
- (t, m) => t.isLoading || m.message.role !== "assistant"
978
+ (t, m) => t.isRunning || m.message.role !== "assistant"
951
979
  );
952
980
  if (disabled)
953
981
  return null;
@@ -955,7 +983,7 @@ var useReloadMessage = () => {
955
983
  const message = useMessage.getState().message;
956
984
  if (message.role !== "assistant")
957
985
  throw new Error("Reloading is only supported on assistant messages");
958
- useBranchObserver.getState().reloadAt(message);
986
+ useThread.getState().startRun(message.parentId);
959
987
  };
960
988
  };
961
989
 
@@ -980,95 +1008,98 @@ var useBeginMessageEdit = () => {
980
1008
  // src/primitives/actionBar/ActionBarEdit.tsx
981
1009
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
982
1010
 
983
- // src/vercel/VercelAIAssistantProvider.tsx
1011
+ // src/adapters/vercel/VercelAIAssistantProvider.tsx
984
1012
  import { useCallback as useCallback3, useMemo as useMemo4 } from "react";
985
1013
 
986
- // src/vercel/useDummyAIAssistantContext.tsx
1014
+ // src/adapters/vercel/useDummyAIAssistantContext.tsx
987
1015
  import { useState as useState2 } from "react";
988
- 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
989
1039
  var useDummyAIAssistantContext = () => {
990
1040
  const [context] = useState2(() => {
991
- const scrollToBottomListeners = /* @__PURE__ */ new Set();
992
- const useThread = create2()(() => ({
1041
+ const useThread = create4()(() => ({
1042
+ id: "",
993
1043
  messages: [],
994
- isLoading: false,
1044
+ isRunning: false,
995
1045
  append: async () => {
996
1046
  throw new Error("Not implemented");
997
1047
  },
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
- };
1012
- }
1013
- }));
1014
- const useComposer = create2()(() => ({
1015
- isEditing: true,
1016
- canCancel: false,
1017
- value: "",
1018
- setValue: (value) => {
1019
- useComposer.setState({ value });
1020
- },
1021
- edit: () => {
1048
+ cancelRun: () => {
1022
1049
  throw new Error("Not implemented");
1023
1050
  },
1024
- send: () => {
1025
- useThread.getState().append({
1026
- role: "user",
1027
- content: [{ type: "text", text: useComposer.getState().value }]
1028
- });
1029
- useComposer.getState().setValue("");
1030
- },
1031
- cancel: () => {
1032
- useThread.getState().stop();
1033
- }
1034
- }));
1035
- const useBranchObserver = create2()(() => ({
1036
- getBranchState: () => ({
1037
- branchId: 0,
1038
- branchCount: 1
1039
- }),
1040
1051
  switchToBranch: () => {
1041
1052
  throw new Error("Not implemented");
1042
1053
  },
1043
- editAt: async () => {
1044
- throw new Error("Not implemented");
1045
- },
1046
- reloadAt: async () => {
1054
+ startRun: async () => {
1047
1055
  throw new Error("Not implemented");
1048
1056
  }
1049
1057
  }));
1050
- return { useThread, useComposer, useBranchObserver };
1058
+ const useViewport = makeViewportStore();
1059
+ const useComposer = makeThreadComposerStore({
1060
+ onSend: async (text) => {
1061
+ await useThread.getState().append({
1062
+ parentId: useThread.getState().messages.at(-1)?.id ?? ROOT_PARENT_ID,
1063
+ content: [{ type: "text", text }]
1064
+ });
1065
+ },
1066
+ onCancel: () => {
1067
+ useThread.getState().cancelRun();
1068
+ }
1069
+ });
1070
+ return { useThread, useViewport, useComposer };
1051
1071
  });
1052
1072
  return context;
1053
1073
  };
1054
1074
 
1055
- // src/vercel/VercelAIAssistantProvider.tsx
1075
+ // src/adapters/vercel/VercelAIAssistantProvider.tsx
1056
1076
  var ThreadMessageCache = /* @__PURE__ */ new WeakMap();
1057
- var vercelToThreadMessage = (message) => {
1077
+ var vercelToThreadMessage = (message, parentId, branchId, branchCount) => {
1058
1078
  if (message.role !== "user" && message.role !== "assistant")
1059
1079
  throw new Error("Unsupported role");
1060
1080
  return {
1081
+ parentId,
1061
1082
  id: message.id,
1062
1083
  role: message.role,
1063
- content: [{ type: "text", text: message.content }]
1084
+ content: [{ type: "text", text: message.content }],
1085
+ branchId,
1086
+ branchCount,
1087
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date()
1064
1088
  };
1065
1089
  };
1066
- var vercelToCachedThreadMessages = (messages) => {
1067
- return messages.map((m) => {
1090
+ var vercelToCachedThreadMessages = (messages, getBranchState) => {
1091
+ return messages.map((m, idx) => {
1068
1092
  const cached = ThreadMessageCache.get(m);
1069
- if (cached)
1093
+ const parentId = messages[idx - 1]?.id ?? ROOT_PARENT_ID;
1094
+ const { branchId, branchCount } = getBranchState(m.id);
1095
+ if (cached && cached.parentId === parentId && cached.branchId === branchId && cached.branchCount === branchCount)
1070
1096
  return cached;
1071
- const newMessage = vercelToThreadMessage(m);
1097
+ const newMessage = vercelToThreadMessage(
1098
+ m,
1099
+ parentId,
1100
+ branchId,
1101
+ branchCount
1102
+ );
1072
1103
  ThreadMessageCache.set(m, newMessage);
1073
1104
  return newMessage;
1074
1105
  });
@@ -1079,73 +1110,67 @@ var VercelAIAssistantProvider = ({
1079
1110
  }) => {
1080
1111
  const context = useDummyAIAssistantContext();
1081
1112
  const vercel = "chat" in rest ? rest.chat : rest.assistant;
1113
+ const branches = useVercelAIBranches(vercel, context);
1082
1114
  const messages = useMemo4(() => {
1083
- return vercelToCachedThreadMessages(vercel.messages);
1084
- }, [vercel.messages]);
1085
- const append = useCallback3(
1086
- async (message) => {
1087
- if (message.content[0]?.type !== "text") {
1088
- throw new Error("Only text content is currently supported");
1089
- }
1090
- context.useThread.getState().scrollToBottom();
1091
- await vercel.append({
1092
- role: message.role,
1093
- content: message.content[0].text
1094
- });
1095
- },
1096
- [context, vercel.append]
1097
- );
1098
- const stop = useCallback3(() => {
1115
+ return vercelToCachedThreadMessages(
1116
+ vercel.messages,
1117
+ branches.getBranchState
1118
+ );
1119
+ }, [vercel.messages, branches.getBranchState]);
1120
+ const cancelRun = useCallback3(() => {
1099
1121
  const lastMessage = vercel.messages.at(-1);
1100
1122
  vercel.stop();
1101
1123
  if (lastMessage?.role === "user") {
1102
1124
  vercel.setInput(lastMessage.content);
1103
1125
  }
1104
1126
  }, [vercel.messages, vercel.stop, vercel.setInput]);
1105
- const isLoading = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1127
+ const isRunning = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1106
1128
  useMemo4(() => {
1107
1129
  context.useThread.setState({
1108
1130
  messages,
1109
- isLoading,
1110
- append,
1111
- stop
1131
+ isRunning,
1132
+ cancelRun,
1133
+ switchToBranch: branches.switchToBranch,
1134
+ append: branches.append,
1135
+ startRun: branches.startRun
1112
1136
  });
1113
- }, [context, messages, append, stop, isLoading]);
1137
+ }, [context, messages, isRunning, cancelRun, branches]);
1114
1138
  useMemo4(() => {
1115
1139
  context.useComposer.setState({
1116
- canCancel: isLoading,
1140
+ canCancel: isRunning,
1117
1141
  value: vercel.input,
1118
1142
  setValue: vercel.setInput
1119
1143
  });
1120
- }, [context, isLoading, vercel.input, vercel.setInput]);
1121
- const branches = useVercelAIBranches(vercel, context);
1122
- useMemo4(() => {
1123
- context.useBranchObserver.setState(branches, true);
1124
- }, [context, branches]);
1144
+ }, [context, isRunning, vercel.input, vercel.setInput]);
1125
1145
  return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
1126
1146
  };
1127
1147
 
1128
- // src/vercel/VercelRSCAssistantProvider.tsx
1148
+ // src/adapters/vercel/VercelRSCAssistantProvider.tsx
1129
1149
  import {
1130
1150
  useCallback as useCallback4,
1131
1151
  useMemo as useMemo5
1132
1152
  } from "react";
1133
1153
  var ThreadMessageCache2 = /* @__PURE__ */ new WeakMap();
1134
- var vercelToThreadMessage2 = (message) => {
1154
+ var vercelToThreadMessage2 = (parentId, message) => {
1135
1155
  if (message.role !== "user" && message.role !== "assistant")
1136
1156
  throw new Error("Unsupported role");
1137
1157
  return {
1158
+ parentId,
1138
1159
  id: message.id,
1139
1160
  role: message.role,
1140
- content: [{ type: "ui", display: message.display }]
1161
+ content: [{ type: "ui", display: message.display }],
1162
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1163
+ branchId: 0,
1164
+ branchCount: 1
1141
1165
  };
1142
1166
  };
1143
1167
  var vercelToCachedThreadMessages2 = (messages) => {
1144
- return messages.map((m) => {
1168
+ return messages.map((m, idx) => {
1145
1169
  const cached = ThreadMessageCache2.get(m);
1146
- if (cached)
1170
+ const parentId = messages[idx - 1]?.id ?? ROOT_PARENT_ID;
1171
+ if (cached && cached.parentId === parentId)
1147
1172
  return cached;
1148
- const newMessage = vercelToThreadMessage2(m);
1173
+ const newMessage = vercelToThreadMessage2(parentId, m);
1149
1174
  ThreadMessageCache2.set(m, newMessage);
1150
1175
  return newMessage;
1151
1176
  });
@@ -1161,14 +1186,13 @@ var VercelRSCAssistantProvider = ({
1161
1186
  }, [vercelMessages]);
1162
1187
  const append = useCallback4(
1163
1188
  async (message) => {
1189
+ if (message.parentId !== (context.useThread.getState().messages.at(-1)?.id ?? ROOT_PARENT_ID))
1190
+ throw new Error("Unexpected: Message editing is not supported");
1164
1191
  if (message.content[0]?.type !== "text") {
1165
1192
  throw new Error("Only text content is currently supported");
1166
1193
  }
1167
- context.useThread.getState().scrollToBottom();
1168
- await vercelAppend({
1169
- role: message.role,
1170
- content: [{ type: "text", text: message.content[0].text }]
1171
- });
1194
+ context.useViewport.getState().scrollToBottom();
1195
+ await vercelAppend(message);
1172
1196
  },
1173
1197
  [context, vercelAppend]
1174
1198
  );