@assistant-ui/react 0.0.8 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
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,18 +166,20 @@ 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
+ const lastScrollTop = (0, import_react5.useRef)(0);
171
172
  const scrollToBottom = () => {
172
173
  const div = messagesEndRef.current;
173
174
  if (!div || !autoScroll)
174
175
  return;
175
176
  const behavior = firstRenderRef.current ? "instant" : "auto";
176
177
  firstRenderRef.current = false;
178
+ useViewport.setState({ isAtBottom: true });
177
179
  div.scrollIntoView({ behavior });
178
180
  };
179
181
  useOnResizeContent(divRef, () => {
180
- if (!useThread.getState().isAtBottom)
182
+ if (!useViewport.getState().isAtBottom)
181
183
  return;
182
184
  scrollToBottom();
183
185
  });
@@ -188,11 +190,13 @@ var ThreadViewport = (0, import_react5.forwardRef)(({ autoScroll = true, onScrol
188
190
  const div = divRef.current;
189
191
  if (!div)
190
192
  return;
191
- const isAtBottom = useThread.getState().isAtBottom;
192
- const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight + 50;
193
- if (newIsAtBottom !== isAtBottom) {
194
- useThread.setState({ isAtBottom: newIsAtBottom });
193
+ const isAtBottom = useViewport.getState().isAtBottom;
194
+ const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
195
+ if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
196
+ } else if (newIsAtBottom !== isAtBottom) {
197
+ useViewport.setState({ isAtBottom: newIsAtBottom });
195
198
  }
199
+ lastScrollTop.current = div.scrollTop;
196
200
  };
197
201
  return /* @__PURE__ */ React.createElement(
198
202
  import_react_primitive2.Primitive.div,
@@ -206,15 +210,19 @@ var ThreadViewport = (0, import_react5.forwardRef)(({ autoScroll = true, onScrol
206
210
  );
207
211
  });
208
212
 
209
- // src/vercel/useVercelAIBranches.tsx
213
+ // src/adapters/vercel/useVercelAIBranches.tsx
210
214
  var import_react6 = require("react");
211
- var ROOT_ID = "__ROOT_ID__";
215
+
216
+ // src/utils/context/stores/AssistantTypes.ts
217
+ var ROOT_PARENT_ID = "__ROOT_ID__";
218
+
219
+ // src/adapters/vercel/useVercelAIBranches.tsx
212
220
  var UPCOMING_MESSAGE_ID = "__UPCOMING_MESSAGE_ID__";
213
221
  var updateBranchData = (data, messages) => {
214
222
  for (let i = 0; i < messages.length; i++) {
215
223
  const child = messages[i];
216
224
  const childId = child.id;
217
- const parentId = messages[i - 1]?.id ?? ROOT_ID;
225
+ const parentId = messages[i - 1]?.id ?? ROOT_PARENT_ID;
218
226
  data.parentMap.set(childId, parentId);
219
227
  const parentArray = data.branchMap.get(parentId);
220
228
  if (!parentArray) {
@@ -225,32 +233,32 @@ var updateBranchData = (data, messages) => {
225
233
  data.snapshots.set(childId, messages);
226
234
  }
227
235
  };
228
- var getParentId = (data, messages, message) => {
229
- if (message.id === UPCOMING_MESSAGE_ID) {
236
+ var getParentId = (data, messages, messageId) => {
237
+ if (messageId === UPCOMING_MESSAGE_ID) {
230
238
  const parent = messages.at(-1);
231
239
  if (!parent)
232
- return ROOT_ID;
240
+ return ROOT_PARENT_ID;
233
241
  return parent.id;
234
242
  }
235
- const parentId = data.parentMap.get(message.id);
243
+ const parentId = data.parentMap.get(messageId);
236
244
  if (!parentId)
237
245
  throw new Error("Unexpected: Message parent not found");
238
246
  return parentId;
239
247
  };
240
- var getBranchStateImpl = (data, messages, message) => {
241
- const parentId = getParentId(data, messages, message);
248
+ var getBranchStateImpl = (data, messages, messageId) => {
249
+ const parentId = getParentId(data, messages, messageId);
242
250
  const branches = data.branchMap.get(parentId) ?? [];
243
- const branchId = message.id === UPCOMING_MESSAGE_ID ? branches.length : branches.indexOf(message.id);
251
+ const branchId = messageId === UPCOMING_MESSAGE_ID ? branches.length : branches.indexOf(messageId);
244
252
  if (branchId === -1)
245
253
  throw new Error("Unexpected: Message not found in parent children");
246
- const upcomingOffset = message.id === UPCOMING_MESSAGE_ID ? 1 : 0;
254
+ const upcomingOffset = messageId === UPCOMING_MESSAGE_ID ? 1 : 0;
247
255
  return {
248
256
  branchId,
249
257
  branchCount: branches.length + upcomingOffset
250
258
  };
251
259
  };
252
- var switchToBranchImpl = (data, messages, message, branchId) => {
253
- const parentId = getParentId(data, messages, message);
260
+ var switchToBranchImpl = (data, messages, messageId, branchId) => {
261
+ const parentId = getParentId(data, messages, messageId);
254
262
  const branches = data.branchMap.get(parentId);
255
263
  if (!branches)
256
264
  throw new Error("Unexpected: Parent children not found");
@@ -259,20 +267,22 @@ var switchToBranchImpl = (data, messages, message, branchId) => {
259
267
  throw new Error("Unexpected: Requested branch not found");
260
268
  if (branchId < 0 || branchId >= branches.length)
261
269
  throw new Error("Switch to branch called with a branch index out of range");
262
- if (newMessageId === message.id)
270
+ if (newMessageId === messageId)
263
271
  return messages;
264
272
  const snapshot = data.snapshots.get(newMessageId);
265
273
  if (!snapshot)
266
274
  throw new Error("Unexpected: Branch snapshot not found");
267
275
  return snapshot;
268
276
  };
269
- var sliceMessagesUntil = (messages, message) => {
270
- if (message.id === UPCOMING_MESSAGE_ID)
277
+ var sliceMessagesUntil = (messages, messageId) => {
278
+ if (messageId === ROOT_PARENT_ID)
279
+ return [];
280
+ if (messageId === UPCOMING_MESSAGE_ID)
271
281
  return messages;
272
- const messageIdx = messages.findIndex((m) => m.id === message.id);
282
+ const messageIdx = messages.findIndex((m) => m.id === messageId);
273
283
  if (messageIdx === -1)
274
284
  throw new Error("Unexpected: Message not found");
275
- return messages.slice(0, messageIdx);
285
+ return messages.slice(0, messageIdx + 1);
276
286
  };
277
287
  var useVercelAIBranches = (chat, context) => {
278
288
  const data = (0, import_react6.useRef)({
@@ -282,17 +292,17 @@ var useVercelAIBranches = (chat, context) => {
282
292
  }).current;
283
293
  updateBranchData(data, chat.messages);
284
294
  const getBranchState = (0, import_react6.useCallback)(
285
- (message) => {
286
- return getBranchStateImpl(data, chat.messages, message);
295
+ (messageId) => {
296
+ return getBranchStateImpl(data, chat.messages, messageId);
287
297
  },
288
298
  [data, chat.messages]
289
299
  );
290
300
  const switchToBranch = (0, import_react6.useCallback)(
291
- (message, branchId) => {
301
+ (messageId, branchId) => {
292
302
  const newMessages = switchToBranchImpl(
293
303
  data,
294
304
  chat.messages,
295
- message,
305
+ messageId,
296
306
  branchId
297
307
  );
298
308
  chat.setMessages(newMessages);
@@ -300,27 +310,27 @@ var useVercelAIBranches = (chat, context) => {
300
310
  [data, chat.messages, chat.setMessages]
301
311
  );
302
312
  const reloadMaybe = "reload" in chat ? chat.reload : void 0;
303
- const reloadAt = (0, import_react6.useCallback)(
304
- async (message) => {
313
+ const startRun = (0, import_react6.useCallback)(
314
+ async (parentId) => {
305
315
  if (!reloadMaybe)
306
316
  throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
307
- const newMessages = sliceMessagesUntil(chat.messages, message);
317
+ const newMessages = sliceMessagesUntil(chat.messages, parentId);
308
318
  chat.setMessages(newMessages);
309
- context.useThread.getState().scrollToBottom();
319
+ context.useViewport.getState().scrollToBottom();
310
320
  await reloadMaybe();
311
321
  },
312
322
  [context, chat.messages, chat.setMessages, reloadMaybe]
313
323
  );
314
- const editAt = (0, import_react6.useCallback)(
315
- async (message, newMessage) => {
316
- const newMessages = sliceMessagesUntil(chat.messages, message);
324
+ const append = (0, import_react6.useCallback)(
325
+ async (message) => {
326
+ const newMessages = sliceMessagesUntil(chat.messages, message.parentId);
317
327
  chat.setMessages(newMessages);
318
- if (newMessage.content[0]?.type !== "text")
328
+ if (message.content.length !== 1 || message.content[0]?.type !== "text")
319
329
  throw new Error("Only text content is currently supported");
320
- context.useThread.getState().scrollToBottom();
330
+ context.useViewport.getState().scrollToBottom();
321
331
  await chat.append({
322
332
  role: "user",
323
- content: newMessage.content[0].text
333
+ content: message.content[0].text
324
334
  });
325
335
  },
326
336
  [context, chat.messages, chat.setMessages, chat.append]
@@ -329,14 +339,14 @@ var useVercelAIBranches = (chat, context) => {
329
339
  () => ({
330
340
  getBranchState,
331
341
  switchToBranch,
332
- editAt,
333
- reloadAt
342
+ append,
343
+ startRun
334
344
  }),
335
- [getBranchState, switchToBranch, editAt, reloadAt]
345
+ [getBranchState, switchToBranch, append, startRun]
336
346
  );
337
347
  };
338
348
  var hasUpcomingMessage = (thread) => {
339
- return thread.isLoading && thread.messages[thread.messages.length - 1]?.role !== "assistant";
349
+ return thread.isRunning && thread.messages[thread.messages.length - 1]?.role !== "assistant";
340
350
  };
341
351
 
342
352
  // src/utils/context/useComposerContext.ts
@@ -389,16 +399,62 @@ __export(message_exports, {
389
399
 
390
400
  // src/primitives/message/MessageProvider.tsx
391
401
  var import_react9 = require("react");
402
+ var import_zustand2 = require("zustand");
403
+
404
+ // src/utils/context/stores/ComposerStore.ts
392
405
  var import_zustand = require("zustand");
393
- var import_shallow = require("zustand/react/shallow");
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
394
450
  var getIsLast = (thread, message) => {
395
451
  const hasUpcoming = hasUpcomingMessage(thread);
396
452
  return hasUpcoming ? message.id === UPCOMING_MESSAGE_ID : thread.messages[thread.messages.length - 1]?.id === message.id;
397
453
  };
398
454
  var useMessageContext2 = () => {
399
- const { useBranchObserver } = useAssistantContext();
400
455
  const [context] = (0, import_react9.useState)(() => {
401
- const useMessage = (0, import_zustand.create)(() => ({
456
+ const { useThread } = useAssistantContext();
457
+ const useMessage = (0, import_zustand2.create)(() => ({
402
458
  message: null,
403
459
  isLast: false,
404
460
  isCopied: false,
@@ -406,40 +462,25 @@ var useMessageContext2 = () => {
406
462
  setIsCopied: () => {
407
463
  },
408
464
  setIsHovering: () => {
409
- },
410
- branchState: {
411
- branchId: 0,
412
- branchCount: 0
413
465
  }
414
466
  }));
415
- const useComposer = (0, import_zustand.create)((set, get) => ({
416
- isEditing: false,
417
- canCancel: true,
418
- edit: () => {
467
+ const useComposer = makeMessageComposerStore({
468
+ onEdit: () => {
419
469
  const message = useMessage.getState().message;
420
470
  if (message.role !== "user")
421
471
  throw new Error("Editing is only supported for user messages");
422
472
  if (message.content[0]?.type !== "text")
423
473
  throw new Error("Editing is only supported for text-only messages");
424
- return set({
425
- isEditing: true,
426
- value: message.content[0].text
427
- });
474
+ return message.content[0].text;
428
475
  },
429
- cancel: () => set({ isEditing: false }),
430
- send: () => {
476
+ onSend: (text) => {
431
477
  const message = useMessage.getState().message;
432
- if (message.role !== "user")
433
- throw new Error("Editing is only supported for user messages");
434
- useBranchObserver.getState().editAt(message, {
435
- role: "user",
436
- content: [{ type: "text", text: get().value }]
478
+ return useThread.getState().append({
479
+ parentId: message.parentId,
480
+ content: [{ type: "text", text }]
437
481
  });
438
- set({ isEditing: false });
439
- },
440
- value: "",
441
- setValue: (value) => set({ value })
442
- }));
482
+ }
483
+ });
443
484
  return { useMessage, useComposer };
444
485
  });
445
486
  return context;
@@ -448,11 +489,8 @@ var MessageProvider = ({
448
489
  message,
449
490
  children
450
491
  }) => {
451
- const { useThread, useBranchObserver } = useAssistantContext();
492
+ const { useThread } = useAssistantContext();
452
493
  const context = useMessageContext2();
453
- const branchState = useBranchObserver(
454
- (0, import_shallow.useShallow)((b) => b.getBranchState(message))
455
- );
456
494
  const isLast = useThread((thread) => getIsLast(thread, message));
457
495
  const [isCopied, setIsCopied] = (0, import_react9.useState)(false);
458
496
  const [isHovering, setIsHovering] = (0, import_react9.useState)(false);
@@ -464,12 +502,11 @@ var MessageProvider = ({
464
502
  isCopied,
465
503
  isHovering,
466
504
  setIsCopied,
467
- setIsHovering,
468
- branchState
505
+ setIsHovering
469
506
  },
470
507
  true
471
508
  );
472
- }, [context, message, isLast, isCopied, isHovering, branchState]);
509
+ }, [context, message, isLast, isCopied, isHovering]);
473
510
  return /* @__PURE__ */ React.createElement(MessageContext.Provider, { value: context }, children);
474
511
  };
475
512
 
@@ -502,29 +539,21 @@ var MessageRoot = (0, import_react10.forwardRef)(
502
539
  // src/primitives/message/MessageIf.tsx
503
540
  var useMessageIf = (props) => {
504
541
  const { useMessage } = useMessageContext();
505
- return useMessage(
506
- ({
507
- message,
508
- isLast,
509
- isCopied,
510
- isHovering,
511
- branchState: { branchCount }
512
- }) => {
513
- if (props.hasBranches === true && branchCount < 2)
514
- return false;
515
- if (props.user && message.role !== "user")
516
- return false;
517
- if (props.assistant && message.role !== "assistant")
518
- return false;
519
- if (props.lastOrHover === true && !isHovering && !isLast)
520
- return false;
521
- if (props.copied === true && !isCopied)
522
- return false;
523
- if (props.copied === false && isCopied)
524
- return false;
525
- return true;
526
- }
527
- );
542
+ return useMessage(({ message, isLast, isCopied, isHovering }) => {
543
+ if (props.hasBranches === true && message.branchCount < 2)
544
+ return false;
545
+ if (props.user && message.role !== "user")
546
+ return false;
547
+ if (props.assistant && message.role !== "assistant")
548
+ return false;
549
+ if (props.lastOrHover === true && !isHovering && !isLast)
550
+ return false;
551
+ if (props.copied === true && !isCopied)
552
+ return false;
553
+ if (props.copied === false && isCopied)
554
+ return false;
555
+ return true;
556
+ });
528
557
  };
529
558
  var MessageIf = ({ children, ...query }) => {
530
559
  const result = useMessageIf(query);
@@ -595,7 +624,12 @@ var ThreadMessages = ({ components }) => {
595
624
  message: {
596
625
  id: UPCOMING_MESSAGE_ID,
597
626
  role: "assistant",
598
- content: [{ type: "text", text: "..." }]
627
+ content: [{ type: "text", text: "..." }],
628
+ parentId: messages.at(-1)?.id ?? ROOT_PARENT_ID,
629
+ // TODO fix these (move upcoming message to AssistantContext)
630
+ branchId: 0,
631
+ branchCount: 1,
632
+ createdAt: /* @__PURE__ */ new Date()
599
633
  }
600
634
  },
601
635
  /* @__PURE__ */ React.createElement(AssistantMessage, null)
@@ -607,18 +641,16 @@ var import_primitive3 = require("@radix-ui/primitive");
607
641
  var import_react_primitive4 = require("@radix-ui/react-primitive");
608
642
  var import_react11 = require("react");
609
643
  var ThreadScrollToBottom = (0, import_react11.forwardRef)(({ onClick, ...rest }, ref) => {
610
- const { useThread } = useAssistantContext();
611
- const isAtBottom = useThread((s) => s.isAtBottom);
644
+ const { useViewport } = useAssistantContext();
645
+ const isAtBottom = useViewport((s) => s.isAtBottom);
612
646
  const handleScrollToBottom = () => {
613
- const thread = useThread.getState();
614
- thread.scrollToBottom();
647
+ useViewport.getState().scrollToBottom();
615
648
  };
616
- if (isAtBottom)
617
- return null;
618
649
  return /* @__PURE__ */ React.createElement(
619
650
  import_react_primitive4.Primitive.button,
620
651
  {
621
652
  ...rest,
653
+ disabled: isAtBottom,
622
654
  ref,
623
655
  onClick: (0, import_primitive3.composeEventHandlers)(onClick, handleScrollToBottom)
624
656
  }
@@ -688,8 +720,8 @@ var ComposerInput = (0, import_react13.forwardRef)(
688
720
  useComposer.getState().cancel();
689
721
  }
690
722
  if (e.key === "Enter" && e.shiftKey === false) {
691
- const isLoading = useThread.getState().isLoading;
692
- if (!isLoading) {
723
+ const isRunning = useThread.getState().isRunning;
724
+ if (!isRunning) {
693
725
  e.preventDefault();
694
726
  composer.send();
695
727
  }
@@ -812,20 +844,17 @@ var useCombinedStore = (stores, selector) => {
812
844
 
813
845
  // src/actions/useGoToNextBranch.tsx
814
846
  var useGoToNextBranch = () => {
815
- const { useThread, useBranchObserver } = useAssistantContext();
847
+ const { useThread } = useAssistantContext();
816
848
  const { useComposer, useMessage } = useMessageContext();
817
849
  const disabled = useCombinedStore(
818
850
  [useThread, useComposer, useMessage],
819
- (t, c, m) => t.isLoading || c.isEditing || m.branchState.branchId + 1 >= m.branchState.branchCount
851
+ (t, c, m) => t.isRunning || c.isEditing || m.message.branchId + 1 >= m.message.branchCount
820
852
  );
821
853
  if (disabled)
822
854
  return null;
823
855
  return () => {
824
- const {
825
- message,
826
- branchState: { branchId }
827
- } = useMessage.getState();
828
- useBranchObserver.getState().switchToBranch(message, branchId + 1);
856
+ const { message } = useMessage.getState();
857
+ useThread.getState().switchToBranch(message.id, message.branchId + 1);
829
858
  };
830
859
  };
831
860
 
@@ -856,20 +885,17 @@ var BranchPickerNext = createActionButton(useGoToNextBranch);
856
885
 
857
886
  // src/actions/useGoToPreviousBranch.tsx
858
887
  var useGoToPreviousBranch = () => {
859
- const { useThread, useBranchObserver } = useAssistantContext();
888
+ const { useThread } = useAssistantContext();
860
889
  const { useComposer, useMessage } = useMessageContext();
861
890
  const disabled = useCombinedStore(
862
891
  [useThread, useComposer, useMessage],
863
- (t, c, m) => t.isLoading || c.isEditing || m.branchState.branchId <= 0
892
+ (t, c, m) => t.isRunning || c.isEditing || m.message.branchId <= 0
864
893
  );
865
894
  if (disabled)
866
895
  return null;
867
896
  return () => {
868
- const {
869
- message,
870
- branchState: { branchId }
871
- } = useMessage.getState();
872
- useBranchObserver.getState().switchToBranch(message, branchId - 1);
897
+ const { message } = useMessage.getState();
898
+ useThread.getState().switchToBranch(message.id, message.branchId - 1);
873
899
  };
874
900
  };
875
901
 
@@ -879,14 +905,14 @@ var BranchPickerPrevious = createActionButton(useGoToPreviousBranch);
879
905
  // src/primitives/branchPicker/BranchPickerCount.tsx
880
906
  var BranchPickerCount = () => {
881
907
  const { useMessage } = useMessageContext();
882
- const branchCount = useMessage((s) => s.branchState.branchCount);
908
+ const branchCount = useMessage((s) => s.message.branchCount);
883
909
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchCount);
884
910
  };
885
911
 
886
912
  // src/primitives/branchPicker/BranchPickerNumber.tsx
887
913
  var BranchPickerNumber = () => {
888
914
  const { useMessage } = useMessageContext();
889
- const branchId = useMessage((s) => s.branchState.branchId);
915
+ const branchId = useMessage((s) => s.message.branchId);
890
916
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchId + 1);
891
917
  };
892
918
 
@@ -909,20 +935,20 @@ __export(actionBar_exports, {
909
935
  // src/primitives/actionBar/ActionBarRoot.tsx
910
936
  var import_react_primitive10 = require("@radix-ui/react-primitive");
911
937
  var import_react20 = require("react");
912
- var ActionBarRoot = (0, import_react20.forwardRef)(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
938
+ var ActionBarRoot = (0, import_react20.forwardRef)(({ hideWhenRunning, autohide, autohideFloat, ...rest }, ref) => {
913
939
  const { useThread } = useAssistantContext();
914
940
  const { useMessage } = useMessageContext();
915
941
  const hideAndfloatStatus = useCombinedStore(
916
942
  [useThread, useMessage],
917
943
  (t, m) => {
918
- if (hideWhenBusy && t.isLoading)
944
+ if (hideWhenRunning && t.isRunning)
919
945
  return "hidden" /* Hidden */;
920
946
  const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
921
947
  if (!autohideEnabled)
922
948
  return "normal" /* Normal */;
923
949
  if (!m.isHovering)
924
950
  return "hidden" /* Hidden */;
925
- if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branchState.branchCount <= 1)
951
+ if (autohideFloat === "always" || autohideFloat === "single-branch" && m.message.branchCount <= 1)
926
952
  return "floating" /* Floating */;
927
953
  return "normal" /* Normal */;
928
954
  }
@@ -960,11 +986,11 @@ var ActionBarCopy = createActionButton(useCopyMessage);
960
986
 
961
987
  // src/actions/useReloadMessage.tsx
962
988
  var useReloadMessage = () => {
963
- const { useThread, useBranchObserver } = useAssistantContext();
989
+ const { useThread } = useAssistantContext();
964
990
  const { useMessage } = useMessageContext();
965
991
  const disabled = useCombinedStore(
966
992
  [useThread, useMessage],
967
- (t, m) => t.isLoading || m.message.role !== "assistant"
993
+ (t, m) => t.isRunning || m.message.role !== "assistant"
968
994
  );
969
995
  if (disabled)
970
996
  return null;
@@ -972,7 +998,7 @@ var useReloadMessage = () => {
972
998
  const message = useMessage.getState().message;
973
999
  if (message.role !== "assistant")
974
1000
  throw new Error("Reloading is only supported on assistant messages");
975
- useBranchObserver.getState().reloadAt(message);
1001
+ useThread.getState().startRun(message.parentId);
976
1002
  };
977
1003
  };
978
1004
 
@@ -997,95 +1023,98 @@ var useBeginMessageEdit = () => {
997
1023
  // src/primitives/actionBar/ActionBarEdit.tsx
998
1024
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
999
1025
 
1000
- // src/vercel/VercelAIAssistantProvider.tsx
1026
+ // src/adapters/vercel/VercelAIAssistantProvider.tsx
1001
1027
  var import_react22 = require("react");
1002
1028
 
1003
- // src/vercel/useDummyAIAssistantContext.tsx
1029
+ // src/adapters/vercel/useDummyAIAssistantContext.tsx
1004
1030
  var import_react21 = require("react");
1005
- 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
1006
1054
  var useDummyAIAssistantContext = () => {
1007
1055
  const [context] = (0, import_react21.useState)(() => {
1008
- const scrollToBottomListeners = /* @__PURE__ */ new Set();
1009
- const useThread = (0, import_zustand2.create)()(() => ({
1056
+ const useThread = (0, import_zustand4.create)()(() => ({
1057
+ id: "",
1010
1058
  messages: [],
1011
- isLoading: false,
1059
+ isRunning: false,
1012
1060
  append: async () => {
1013
1061
  throw new Error("Not implemented");
1014
1062
  },
1015
- stop: () => {
1016
- throw new Error("Not implemented");
1017
- },
1018
- isAtBottom: true,
1019
- scrollToBottom: () => {
1020
- for (const listener of scrollToBottomListeners) {
1021
- listener();
1022
- }
1023
- },
1024
- onScrollToBottom: (callback) => {
1025
- scrollToBottomListeners.add(callback);
1026
- return () => {
1027
- scrollToBottomListeners.delete(callback);
1028
- };
1029
- }
1030
- }));
1031
- const useComposer = (0, import_zustand2.create)()(() => ({
1032
- isEditing: true,
1033
- canCancel: false,
1034
- value: "",
1035
- setValue: (value) => {
1036
- useComposer.setState({ value });
1037
- },
1038
- edit: () => {
1063
+ cancelRun: () => {
1039
1064
  throw new Error("Not implemented");
1040
1065
  },
1041
- send: () => {
1042
- useThread.getState().append({
1043
- role: "user",
1044
- content: [{ type: "text", text: useComposer.getState().value }]
1045
- });
1046
- useComposer.getState().setValue("");
1047
- },
1048
- cancel: () => {
1049
- useThread.getState().stop();
1050
- }
1051
- }));
1052
- const useBranchObserver = (0, import_zustand2.create)()(() => ({
1053
- getBranchState: () => ({
1054
- branchId: 0,
1055
- branchCount: 1
1056
- }),
1057
1066
  switchToBranch: () => {
1058
1067
  throw new Error("Not implemented");
1059
1068
  },
1060
- editAt: async () => {
1061
- throw new Error("Not implemented");
1062
- },
1063
- reloadAt: async () => {
1069
+ startRun: async () => {
1064
1070
  throw new Error("Not implemented");
1065
1071
  }
1066
1072
  }));
1067
- return { useThread, useComposer, useBranchObserver };
1073
+ const useViewport = makeViewportStore();
1074
+ const useComposer = makeThreadComposerStore({
1075
+ onSend: async (text) => {
1076
+ await useThread.getState().append({
1077
+ parentId: useThread.getState().messages.at(-1)?.id ?? ROOT_PARENT_ID,
1078
+ content: [{ type: "text", text }]
1079
+ });
1080
+ },
1081
+ onCancel: () => {
1082
+ useThread.getState().cancelRun();
1083
+ }
1084
+ });
1085
+ return { useThread, useViewport, useComposer };
1068
1086
  });
1069
1087
  return context;
1070
1088
  };
1071
1089
 
1072
- // src/vercel/VercelAIAssistantProvider.tsx
1090
+ // src/adapters/vercel/VercelAIAssistantProvider.tsx
1073
1091
  var ThreadMessageCache = /* @__PURE__ */ new WeakMap();
1074
- var vercelToThreadMessage = (message) => {
1092
+ var vercelToThreadMessage = (message, parentId, branchId, branchCount) => {
1075
1093
  if (message.role !== "user" && message.role !== "assistant")
1076
1094
  throw new Error("Unsupported role");
1077
1095
  return {
1096
+ parentId,
1078
1097
  id: message.id,
1079
1098
  role: message.role,
1080
- content: [{ type: "text", text: message.content }]
1099
+ content: [{ type: "text", text: message.content }],
1100
+ branchId,
1101
+ branchCount,
1102
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date()
1081
1103
  };
1082
1104
  };
1083
- var vercelToCachedThreadMessages = (messages) => {
1084
- return messages.map((m) => {
1105
+ var vercelToCachedThreadMessages = (messages, getBranchState) => {
1106
+ return messages.map((m, idx) => {
1085
1107
  const cached = ThreadMessageCache.get(m);
1086
- if (cached)
1108
+ const parentId = messages[idx - 1]?.id ?? ROOT_PARENT_ID;
1109
+ const { branchId, branchCount } = getBranchState(m.id);
1110
+ if (cached && cached.parentId === parentId && cached.branchId === branchId && cached.branchCount === branchCount)
1087
1111
  return cached;
1088
- const newMessage = vercelToThreadMessage(m);
1112
+ const newMessage = vercelToThreadMessage(
1113
+ m,
1114
+ parentId,
1115
+ branchId,
1116
+ branchCount
1117
+ );
1089
1118
  ThreadMessageCache.set(m, newMessage);
1090
1119
  return newMessage;
1091
1120
  });
@@ -1096,70 +1125,64 @@ var VercelAIAssistantProvider = ({
1096
1125
  }) => {
1097
1126
  const context = useDummyAIAssistantContext();
1098
1127
  const vercel = "chat" in rest ? rest.chat : rest.assistant;
1128
+ const branches = useVercelAIBranches(vercel, context);
1099
1129
  const messages = (0, import_react22.useMemo)(() => {
1100
- return vercelToCachedThreadMessages(vercel.messages);
1101
- }, [vercel.messages]);
1102
- const append = (0, import_react22.useCallback)(
1103
- async (message) => {
1104
- if (message.content[0]?.type !== "text") {
1105
- throw new Error("Only text content is currently supported");
1106
- }
1107
- context.useThread.getState().scrollToBottom();
1108
- await vercel.append({
1109
- role: message.role,
1110
- content: message.content[0].text
1111
- });
1112
- },
1113
- [context, vercel.append]
1114
- );
1115
- const stop = (0, import_react22.useCallback)(() => {
1130
+ return vercelToCachedThreadMessages(
1131
+ vercel.messages,
1132
+ branches.getBranchState
1133
+ );
1134
+ }, [vercel.messages, branches.getBranchState]);
1135
+ const cancelRun = (0, import_react22.useCallback)(() => {
1116
1136
  const lastMessage = vercel.messages.at(-1);
1117
1137
  vercel.stop();
1118
1138
  if (lastMessage?.role === "user") {
1119
1139
  vercel.setInput(lastMessage.content);
1120
1140
  }
1121
1141
  }, [vercel.messages, vercel.stop, vercel.setInput]);
1122
- const isLoading = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1142
+ const isRunning = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1123
1143
  (0, import_react22.useMemo)(() => {
1124
1144
  context.useThread.setState({
1125
1145
  messages,
1126
- isLoading,
1127
- append,
1128
- stop
1146
+ isRunning,
1147
+ cancelRun,
1148
+ switchToBranch: branches.switchToBranch,
1149
+ append: branches.append,
1150
+ startRun: branches.startRun
1129
1151
  });
1130
- }, [context, messages, append, stop, isLoading]);
1152
+ }, [context, messages, isRunning, cancelRun, branches]);
1131
1153
  (0, import_react22.useMemo)(() => {
1132
1154
  context.useComposer.setState({
1133
- canCancel: isLoading,
1155
+ canCancel: isRunning,
1134
1156
  value: vercel.input,
1135
1157
  setValue: vercel.setInput
1136
1158
  });
1137
- }, [context, isLoading, vercel.input, vercel.setInput]);
1138
- const branches = useVercelAIBranches(vercel, context);
1139
- (0, import_react22.useMemo)(() => {
1140
- context.useBranchObserver.setState(branches, true);
1141
- }, [context, branches]);
1159
+ }, [context, isRunning, vercel.input, vercel.setInput]);
1142
1160
  return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
1143
1161
  };
1144
1162
 
1145
- // src/vercel/VercelRSCAssistantProvider.tsx
1163
+ // src/adapters/vercel/VercelRSCAssistantProvider.tsx
1146
1164
  var import_react23 = require("react");
1147
1165
  var ThreadMessageCache2 = /* @__PURE__ */ new WeakMap();
1148
- var vercelToThreadMessage2 = (message) => {
1166
+ var vercelToThreadMessage2 = (parentId, message) => {
1149
1167
  if (message.role !== "user" && message.role !== "assistant")
1150
1168
  throw new Error("Unsupported role");
1151
1169
  return {
1170
+ parentId,
1152
1171
  id: message.id,
1153
1172
  role: message.role,
1154
- content: [{ type: "ui", display: message.display }]
1173
+ content: [{ type: "ui", display: message.display }],
1174
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1175
+ branchId: 0,
1176
+ branchCount: 1
1155
1177
  };
1156
1178
  };
1157
1179
  var vercelToCachedThreadMessages2 = (messages) => {
1158
- return messages.map((m) => {
1180
+ return messages.map((m, idx) => {
1159
1181
  const cached = ThreadMessageCache2.get(m);
1160
- if (cached)
1182
+ const parentId = messages[idx - 1]?.id ?? ROOT_PARENT_ID;
1183
+ if (cached && cached.parentId === parentId)
1161
1184
  return cached;
1162
- const newMessage = vercelToThreadMessage2(m);
1185
+ const newMessage = vercelToThreadMessage2(parentId, m);
1163
1186
  ThreadMessageCache2.set(m, newMessage);
1164
1187
  return newMessage;
1165
1188
  });
@@ -1175,14 +1198,13 @@ var VercelRSCAssistantProvider = ({
1175
1198
  }, [vercelMessages]);
1176
1199
  const append = (0, import_react23.useCallback)(
1177
1200
  async (message) => {
1201
+ if (message.parentId !== (context.useThread.getState().messages.at(-1)?.id ?? ROOT_PARENT_ID))
1202
+ throw new Error("Unexpected: Message editing is not supported");
1178
1203
  if (message.content[0]?.type !== "text") {
1179
1204
  throw new Error("Only text content is currently supported");
1180
1205
  }
1181
- context.useThread.getState().scrollToBottom();
1182
- await vercelAppend({
1183
- role: message.role,
1184
- content: [{ type: "text", text: message.content[0].text }]
1185
- });
1206
+ context.useViewport.getState().scrollToBottom();
1207
+ await vercelAppend(message);
1186
1208
  },
1187
1209
  [context, vercelAppend]
1188
1210
  );