@assistant-ui/react 0.0.7 → 0.0.9

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.js CHANGED
@@ -53,6 +53,7 @@ __export(thread_exports, {
53
53
  If: () => ThreadIf,
54
54
  Messages: () => ThreadMessages,
55
55
  Root: () => ThreadRoot,
56
+ ScrollToBottom: () => ThreadScrollToBottom,
56
57
  Viewport: () => ThreadViewport
57
58
  });
58
59
 
@@ -106,7 +107,7 @@ var ThreadEmpty = ({ children }) => {
106
107
  var import_primitive = require("@radix-ui/primitive");
107
108
  var import_react_compose_refs = require("@radix-ui/react-compose-refs");
108
109
  var import_react_primitive2 = require("@radix-ui/react-primitive");
109
- var import_react4 = require("react");
110
+ var import_react5 = require("react");
110
111
 
111
112
  // src/utils/hooks/useOnResizeContent.tsx
112
113
  var import_react3 = require("react");
@@ -147,22 +148,55 @@ var useOnResizeContent = (ref, callback) => {
147
148
  }, [ref.current]);
148
149
  };
149
150
 
151
+ // src/utils/hooks/useOnScrollToBottom.tsx
152
+ var import_react4 = require("react");
153
+ var useOnScrollToBottom = (callback) => {
154
+ const callbackRef = (0, import_react4.useRef)(callback);
155
+ callbackRef.current = callback;
156
+ const { useThread } = useAssistantContext();
157
+ (0, import_react4.useEffect)(() => {
158
+ return useThread.getState().onScrollToBottom(() => {
159
+ callbackRef.current();
160
+ });
161
+ }, [useThread]);
162
+ };
163
+
150
164
  // src/primitives/thread/ThreadViewport.tsx
151
- var ThreadViewport = (0, import_react4.forwardRef)(({ onScroll, children, ...rest }, forwardedRef) => {
152
- const divRef = (0, import_react4.useRef)(null);
165
+ var ThreadViewport = (0, import_react5.forwardRef)(({ autoScroll = true, onScroll, children, ...rest }, forwardedRef) => {
166
+ const messagesEndRef = (0, import_react5.useRef)(null);
167
+ const divRef = (0, import_react5.useRef)(null);
153
168
  const ref = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, divRef);
154
- const [isAtBottom, setIsAtBottom] = (0, import_react4.useState)(true);
169
+ const { useThread } = useAssistantContext();
170
+ const firstRenderRef = (0, import_react5.useRef)(true);
171
+ const lastScrollTop = (0, import_react5.useRef)(0);
172
+ const scrollToBottom = () => {
173
+ const div = messagesEndRef.current;
174
+ if (!div || !autoScroll)
175
+ return;
176
+ const behavior = firstRenderRef.current ? "instant" : "auto";
177
+ firstRenderRef.current = false;
178
+ useThread.setState({ isAtBottom: true });
179
+ div.scrollIntoView({ behavior });
180
+ };
155
181
  useOnResizeContent(divRef, () => {
156
- const div = divRef.current;
157
- if (!div || !isAtBottom)
182
+ if (!useThread.getState().isAtBottom)
158
183
  return;
159
- div.scrollTop = div.scrollHeight;
184
+ scrollToBottom();
185
+ });
186
+ useOnScrollToBottom(() => {
187
+ scrollToBottom();
160
188
  });
161
189
  const handleScroll = () => {
162
190
  const div = divRef.current;
163
191
  if (!div)
164
192
  return;
165
- setIsAtBottom(div.scrollHeight - div.scrollTop <= div.clientHeight + 50);
193
+ const isAtBottom = useThread.getState().isAtBottom;
194
+ const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
195
+ if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
196
+ } else if (newIsAtBottom !== isAtBottom) {
197
+ useThread.setState({ isAtBottom: newIsAtBottom });
198
+ }
199
+ lastScrollTop.current = div.scrollTop;
166
200
  };
167
201
  return /* @__PURE__ */ React.createElement(
168
202
  import_react_primitive2.Primitive.div,
@@ -171,19 +205,24 @@ var ThreadViewport = (0, import_react4.forwardRef)(({ onScroll, children, ...res
171
205
  onScroll: (0, import_primitive.composeEventHandlers)(onScroll, handleScroll),
172
206
  ref
173
207
  },
174
- children
208
+ children,
209
+ /* @__PURE__ */ React.createElement("div", { ref: messagesEndRef })
175
210
  );
176
211
  });
177
212
 
178
- // src/vercel/useVercelAIBranches.tsx
179
- var import_react5 = require("react");
180
- var ROOT_ID = "__ROOT_ID__";
213
+ // src/adapters/vercel/useVercelAIBranches.tsx
214
+ var import_react6 = require("react");
215
+
216
+ // src/utils/context/stores/AssistantTypes.ts
217
+ var ROOT_PARENT_ID = "__ROOT_ID__";
218
+
219
+ // src/adapters/vercel/useVercelAIBranches.tsx
181
220
  var UPCOMING_MESSAGE_ID = "__UPCOMING_MESSAGE_ID__";
182
221
  var updateBranchData = (data, messages) => {
183
222
  for (let i = 0; i < messages.length; i++) {
184
223
  const child = messages[i];
185
224
  const childId = child.id;
186
- const parentId = messages[i - 1]?.id ?? ROOT_ID;
225
+ const parentId = messages[i - 1]?.id ?? ROOT_PARENT_ID;
187
226
  data.parentMap.set(childId, parentId);
188
227
  const parentArray = data.branchMap.get(parentId);
189
228
  if (!parentArray) {
@@ -194,32 +233,32 @@ var updateBranchData = (data, messages) => {
194
233
  data.snapshots.set(childId, messages);
195
234
  }
196
235
  };
197
- var getParentId = (data, messages, message) => {
198
- if (message.id === UPCOMING_MESSAGE_ID) {
236
+ var getParentId = (data, messages, messageId) => {
237
+ if (messageId === UPCOMING_MESSAGE_ID) {
199
238
  const parent = messages.at(-1);
200
239
  if (!parent)
201
- return ROOT_ID;
240
+ return ROOT_PARENT_ID;
202
241
  return parent.id;
203
242
  }
204
- const parentId = data.parentMap.get(message.id);
243
+ const parentId = data.parentMap.get(messageId);
205
244
  if (!parentId)
206
245
  throw new Error("Unexpected: Message parent not found");
207
246
  return parentId;
208
247
  };
209
- var getBranchStateImpl = (data, messages, message) => {
210
- const parentId = getParentId(data, messages, message);
248
+ var getBranchStateImpl = (data, messages, messageId) => {
249
+ const parentId = getParentId(data, messages, messageId);
211
250
  const branches = data.branchMap.get(parentId) ?? [];
212
- 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);
213
252
  if (branchId === -1)
214
253
  throw new Error("Unexpected: Message not found in parent children");
215
- const upcomingOffset = message.id === UPCOMING_MESSAGE_ID ? 1 : 0;
254
+ const upcomingOffset = messageId === UPCOMING_MESSAGE_ID ? 1 : 0;
216
255
  return {
217
256
  branchId,
218
257
  branchCount: branches.length + upcomingOffset
219
258
  };
220
259
  };
221
- var switchToBranchImpl = (data, messages, message, branchId) => {
222
- const parentId = getParentId(data, messages, message);
260
+ var switchToBranchImpl = (data, messages, messageId, branchId) => {
261
+ const parentId = getParentId(data, messages, messageId);
223
262
  const branches = data.branchMap.get(parentId);
224
263
  if (!branches)
225
264
  throw new Error("Unexpected: Parent children not found");
@@ -228,40 +267,42 @@ var switchToBranchImpl = (data, messages, message, branchId) => {
228
267
  throw new Error("Unexpected: Requested branch not found");
229
268
  if (branchId < 0 || branchId >= branches.length)
230
269
  throw new Error("Switch to branch called with a branch index out of range");
231
- if (newMessageId === message.id)
270
+ if (newMessageId === messageId)
232
271
  return messages;
233
272
  const snapshot = data.snapshots.get(newMessageId);
234
273
  if (!snapshot)
235
274
  throw new Error("Unexpected: Branch snapshot not found");
236
275
  return snapshot;
237
276
  };
238
- var sliceMessagesUntil = (messages, message) => {
239
- 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)
240
281
  return messages;
241
- const messageIdx = messages.findIndex((m) => m.id === message.id);
282
+ const messageIdx = messages.findIndex((m) => m.id === messageId);
242
283
  if (messageIdx === -1)
243
284
  throw new Error("Unexpected: Message not found");
244
- return messages.slice(0, messageIdx);
285
+ return messages.slice(0, messageIdx + 1);
245
286
  };
246
- var useVercelAIBranches = (chat) => {
247
- const data = (0, import_react5.useRef)({
287
+ var useVercelAIBranches = (chat, context) => {
288
+ const data = (0, import_react6.useRef)({
248
289
  parentMap: /* @__PURE__ */ new Map(),
249
290
  branchMap: /* @__PURE__ */ new Map(),
250
291
  snapshots: /* @__PURE__ */ new Map()
251
292
  }).current;
252
293
  updateBranchData(data, chat.messages);
253
- const getBranchState = (0, import_react5.useCallback)(
254
- (message) => {
255
- return getBranchStateImpl(data, chat.messages, message);
294
+ const getBranchState = (0, import_react6.useCallback)(
295
+ (messageId) => {
296
+ return getBranchStateImpl(data, chat.messages, messageId);
256
297
  },
257
298
  [data, chat.messages]
258
299
  );
259
- const switchToBranch = (0, import_react5.useCallback)(
260
- (message, branchId) => {
300
+ const switchToBranch = (0, import_react6.useCallback)(
301
+ (messageId, branchId) => {
261
302
  const newMessages = switchToBranchImpl(
262
303
  data,
263
304
  chat.messages,
264
- message,
305
+ messageId,
265
306
  branchId
266
307
  );
267
308
  chat.setMessages(newMessages);
@@ -269,61 +310,66 @@ var useVercelAIBranches = (chat) => {
269
310
  [data, chat.messages, chat.setMessages]
270
311
  );
271
312
  const reloadMaybe = "reload" in chat ? chat.reload : void 0;
272
- const reloadAt = (0, import_react5.useCallback)(
273
- async (message) => {
313
+ const reload = (0, import_react6.useCallback)(
314
+ async (messageId) => {
274
315
  if (!reloadMaybe)
275
316
  throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
276
- const newMessages = sliceMessagesUntil(chat.messages, message);
317
+ const newMessages = sliceMessagesUntil(chat.messages, messageId);
277
318
  chat.setMessages(newMessages);
319
+ context.useThread.getState().scrollToBottom();
278
320
  await reloadMaybe();
279
321
  },
280
- [chat.messages, chat.setMessages, reloadMaybe]
322
+ [context, chat.messages, chat.setMessages, reloadMaybe]
281
323
  );
282
- const editAt = (0, import_react5.useCallback)(
283
- async (message, newMessage) => {
284
- const newMessages = sliceMessagesUntil(chat.messages, message);
324
+ const append = (0, import_react6.useCallback)(
325
+ async (message) => {
326
+ const newMessages = sliceMessagesUntil(chat.messages, message.parentId);
285
327
  chat.setMessages(newMessages);
286
- if (newMessage.content[0]?.type !== "text")
328
+ if (message.content.length !== 1 || message.content[0]?.type !== "text")
287
329
  throw new Error("Only text content is currently supported");
330
+ context.useThread.getState().scrollToBottom();
288
331
  await chat.append({
289
332
  role: "user",
290
- content: newMessage.content[0].text
333
+ content: message.content[0].text
291
334
  });
292
335
  },
293
- [chat.messages, chat.setMessages, chat.append]
336
+ [context, chat.messages, chat.setMessages, chat.append]
294
337
  );
295
- return (0, import_react5.useMemo)(
338
+ return (0, import_react6.useMemo)(
296
339
  () => ({
297
340
  getBranchState,
298
341
  switchToBranch,
299
- editAt,
300
- reloadAt
342
+ append,
343
+ reload
301
344
  }),
302
- [getBranchState, switchToBranch, editAt, reloadAt]
345
+ [getBranchState, switchToBranch, append, reload]
303
346
  );
304
347
  };
305
348
  var hasUpcomingMessage = (thread) => {
306
349
  return thread.isLoading && thread.messages[thread.messages.length - 1]?.role !== "assistant";
307
350
  };
308
351
 
309
- // src/utils/context/ComposerState.ts
310
- var import_react7 = require("react");
352
+ // src/utils/context/useComposerContext.ts
353
+ var import_react8 = require("react");
311
354
 
312
- // src/utils/context/MessageContext.ts
313
- var import_react6 = require("react");
314
- var MessageContext = (0, import_react6.createContext)(null);
355
+ // src/utils/context/useMessageContext.ts
356
+ var import_react7 = require("react");
357
+ var MessageContext = (0, import_react7.createContext)(null);
315
358
  var useMessageContext = () => {
316
- const context = (0, import_react6.useContext)(MessageContext);
359
+ const context = (0, import_react7.useContext)(MessageContext);
317
360
  if (!context)
318
361
  throw new Error("useMessageContext must be used within a MessageProvider");
319
362
  return context;
320
363
  };
321
364
 
322
- // src/utils/context/ComposerState.ts
365
+ // src/utils/context/useComposerContext.ts
323
366
  var useComposerContext = () => {
324
367
  const { useComposer: useAssisstantComposer } = useAssistantContext();
325
- const { useComposer: useMessageComposer } = (0, import_react7.useContext)(MessageContext) ?? {};
326
- return { useComposer: useMessageComposer ?? useAssisstantComposer };
368
+ const { useComposer: useMessageComposer } = (0, import_react8.useContext)(MessageContext) ?? {};
369
+ return {
370
+ useComposer: useMessageComposer ?? useAssisstantComposer,
371
+ type: useMessageComposer ? "message" : "assistant"
372
+ };
327
373
  };
328
374
 
329
375
  // src/primitives/composer/ComposerIf.tsx
@@ -352,16 +398,15 @@ __export(message_exports, {
352
398
  });
353
399
 
354
400
  // src/primitives/message/MessageProvider.tsx
355
- var import_react8 = require("react");
401
+ var import_react9 = require("react");
356
402
  var import_zustand = require("zustand");
357
- var import_shallow = require("zustand/react/shallow");
358
403
  var getIsLast = (thread, message) => {
359
404
  const hasUpcoming = hasUpcomingMessage(thread);
360
405
  return hasUpcoming ? message.id === UPCOMING_MESSAGE_ID : thread.messages[thread.messages.length - 1]?.id === message.id;
361
406
  };
362
407
  var useMessageContext2 = () => {
363
- const { useBranchObserver } = useAssistantContext();
364
- const [context] = (0, import_react8.useState)(() => {
408
+ const [context] = (0, import_react9.useState)(() => {
409
+ const { useThread } = useAssistantContext();
365
410
  const useMessage = (0, import_zustand.create)(() => ({
366
411
  message: null,
367
412
  isLast: false,
@@ -370,10 +415,6 @@ var useMessageContext2 = () => {
370
415
  setIsCopied: () => {
371
416
  },
372
417
  setIsHovering: () => {
373
- },
374
- branchState: {
375
- branchId: 0,
376
- branchCount: 0
377
418
  }
378
419
  }));
379
420
  const useComposer = (0, import_zustand.create)((set, get) => ({
@@ -395,8 +436,8 @@ var useMessageContext2 = () => {
395
436
  const message = useMessage.getState().message;
396
437
  if (message.role !== "user")
397
438
  throw new Error("Editing is only supported for user messages");
398
- useBranchObserver.getState().editAt(message, {
399
- role: "user",
439
+ useThread.getState().append({
440
+ parentId: message.parentId,
400
441
  content: [{ type: "text", text: get().value }]
401
442
  });
402
443
  set({ isEditing: false });
@@ -412,15 +453,12 @@ var MessageProvider = ({
412
453
  message,
413
454
  children
414
455
  }) => {
415
- const { useThread, useBranchObserver } = useAssistantContext();
456
+ const { useThread } = useAssistantContext();
416
457
  const context = useMessageContext2();
417
- const branchState = useBranchObserver(
418
- (0, import_shallow.useShallow)((b) => b.getBranchState(message))
419
- );
420
458
  const isLast = useThread((thread) => getIsLast(thread, message));
421
- const [isCopied, setIsCopied] = (0, import_react8.useState)(false);
422
- const [isHovering, setIsHovering] = (0, import_react8.useState)(false);
423
- (0, import_react8.useMemo)(() => {
459
+ const [isCopied, setIsCopied] = (0, import_react9.useState)(false);
460
+ const [isHovering, setIsHovering] = (0, import_react9.useState)(false);
461
+ (0, import_react9.useMemo)(() => {
424
462
  context.useMessage.setState(
425
463
  {
426
464
  message,
@@ -428,20 +466,19 @@ var MessageProvider = ({
428
466
  isCopied,
429
467
  isHovering,
430
468
  setIsCopied,
431
- setIsHovering,
432
- branchState
469
+ setIsHovering
433
470
  },
434
471
  true
435
472
  );
436
- }, [context, message, isLast, isCopied, isHovering, branchState]);
473
+ }, [context, message, isLast, isCopied, isHovering]);
437
474
  return /* @__PURE__ */ React.createElement(MessageContext.Provider, { value: context }, children);
438
475
  };
439
476
 
440
477
  // src/primitives/message/MessageRoot.tsx
441
478
  var import_primitive2 = require("@radix-ui/primitive");
442
479
  var import_react_primitive3 = require("@radix-ui/react-primitive");
443
- var import_react9 = require("react");
444
- var MessageRoot = (0, import_react9.forwardRef)(
480
+ var import_react10 = require("react");
481
+ var MessageRoot = (0, import_react10.forwardRef)(
445
482
  ({ onMouseEnter, onMouseLeave, ...rest }, ref) => {
446
483
  const { useMessage } = useMessageContext();
447
484
  const setIsHovering = useMessage((s) => s.setIsHovering);
@@ -466,29 +503,21 @@ var MessageRoot = (0, import_react9.forwardRef)(
466
503
  // src/primitives/message/MessageIf.tsx
467
504
  var useMessageIf = (props) => {
468
505
  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
- );
506
+ return useMessage(({ message, isLast, isCopied, isHovering }) => {
507
+ if (props.hasBranches === true && message.branchCount < 2)
508
+ return false;
509
+ if (props.user && message.role !== "user")
510
+ return false;
511
+ if (props.assistant && message.role !== "assistant")
512
+ return false;
513
+ if (props.lastOrHover === true && !isHovering && !isLast)
514
+ return false;
515
+ if (props.copied === true && !isCopied)
516
+ return false;
517
+ if (props.copied === false && isCopied)
518
+ return false;
519
+ return true;
520
+ });
492
521
  };
493
522
  var MessageIf = ({ children, ...query }) => {
494
523
  const result = useMessageIf(query);
@@ -559,13 +588,40 @@ var ThreadMessages = ({ components }) => {
559
588
  message: {
560
589
  id: UPCOMING_MESSAGE_ID,
561
590
  role: "assistant",
562
- content: [{ type: "text", text: "..." }]
591
+ content: [{ type: "text", text: "..." }],
592
+ parentId: messages.at(-1)?.id ?? ROOT_PARENT_ID,
593
+ // TODO fix these (move upcoming message to AssistantContext)
594
+ branchId: 0,
595
+ branchCount: 1,
596
+ createdAt: /* @__PURE__ */ new Date()
563
597
  }
564
598
  },
565
599
  /* @__PURE__ */ React.createElement(AssistantMessage, null)
566
600
  ));
567
601
  };
568
602
 
603
+ // src/primitives/thread/ThreadScrollToBottom.tsx
604
+ var import_primitive3 = require("@radix-ui/primitive");
605
+ var import_react_primitive4 = require("@radix-ui/react-primitive");
606
+ var import_react11 = require("react");
607
+ var ThreadScrollToBottom = (0, import_react11.forwardRef)(({ onClick, ...rest }, ref) => {
608
+ const { useThread } = useAssistantContext();
609
+ const isAtBottom = useThread((s) => s.isAtBottom);
610
+ const handleScrollToBottom = () => {
611
+ const thread = useThread.getState();
612
+ thread.scrollToBottom();
613
+ };
614
+ return /* @__PURE__ */ React.createElement(
615
+ import_react_primitive4.Primitive.button,
616
+ {
617
+ ...rest,
618
+ disabled: isAtBottom,
619
+ ref,
620
+ onClick: (0, import_primitive3.composeEventHandlers)(onClick, handleScrollToBottom)
621
+ }
622
+ );
623
+ });
624
+
569
625
  // src/primitives/composer/index.ts
570
626
  var composer_exports = {};
571
627
  __export(composer_exports, {
@@ -577,33 +633,15 @@ __export(composer_exports, {
577
633
  });
578
634
 
579
635
  // src/primitives/composer/ComposerRoot.tsx
580
- var import_primitive3 = require("@radix-ui/primitive");
636
+ var import_primitive4 = require("@radix-ui/primitive");
581
637
  var import_react_compose_refs2 = require("@radix-ui/react-compose-refs");
582
- var import_react_primitive4 = require("@radix-ui/react-primitive");
583
- var import_react10 = require("react");
584
- var ComposerFormContext = (0, import_react10.createContext)(null);
585
- var useComposerFormContext = () => {
586
- const context = (0, import_react10.useContext)(ComposerFormContext);
587
- if (!context) {
588
- throw new Error(
589
- "Composer compound components cannot be rendered outside the Composer component"
590
- );
591
- }
592
- return context;
593
- };
594
- var ComposerRoot = (0, import_react10.forwardRef)(
638
+ var import_react_primitive5 = require("@radix-ui/react-primitive");
639
+ var import_react12 = require("react");
640
+ var ComposerRoot = (0, import_react12.forwardRef)(
595
641
  ({ onSubmit, ...rest }, forwardedRef) => {
596
642
  const { useComposer } = useComposerContext();
597
- const formRef = (0, import_react10.useRef)(null);
643
+ const formRef = (0, import_react12.useRef)(null);
598
644
  const ref = (0, import_react_compose_refs2.useComposedRefs)(forwardedRef, formRef);
599
- const composerContextValue = (0, import_react10.useMemo)(
600
- () => ({
601
- submit: () => formRef.current?.dispatchEvent(
602
- new Event("submit", { cancelable: true, bubbles: true })
603
- )
604
- }),
605
- []
606
- );
607
645
  const handleSubmit = (e) => {
608
646
  const composerState = useComposer.getState();
609
647
  if (!composerState.isEditing)
@@ -611,73 +649,96 @@ var ComposerRoot = (0, import_react10.forwardRef)(
611
649
  e.preventDefault();
612
650
  composerState.send();
613
651
  };
614
- return /* @__PURE__ */ React.createElement(ComposerFormContext.Provider, { value: composerContextValue }, /* @__PURE__ */ React.createElement(
615
- import_react_primitive4.Primitive.form,
652
+ return /* @__PURE__ */ React.createElement(
653
+ import_react_primitive5.Primitive.form,
616
654
  {
617
655
  ...rest,
618
656
  ref,
619
- onSubmit: (0, import_primitive3.composeEventHandlers)(onSubmit, handleSubmit)
657
+ onSubmit: (0, import_primitive4.composeEventHandlers)(onSubmit, handleSubmit)
620
658
  }
621
- ));
659
+ );
622
660
  }
623
661
  );
624
662
 
625
663
  // src/primitives/composer/ComposerInput.tsx
626
- var import_primitive4 = require("@radix-ui/primitive");
664
+ var import_primitive5 = require("@radix-ui/primitive");
665
+ var import_react_compose_refs3 = require("@radix-ui/react-compose-refs");
627
666
  var import_react_slot = require("@radix-ui/react-slot");
628
- var import_react11 = require("react");
667
+ var import_react13 = require("react");
629
668
  var import_react_textarea_autosize = __toESM(require("react-textarea-autosize"));
630
- var ComposerInput = (0, import_react11.forwardRef)(({ asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
631
- const { useThread } = useAssistantContext();
632
- const isLoading = useThread((t) => t.isLoading);
633
- const { useComposer } = useComposerContext();
634
- const value = useComposer((c) => {
635
- if (!c.isEditing)
636
- return "";
637
- return c.value;
638
- });
639
- const Component = asChild ? import_react_slot.Slot : import_react_textarea_autosize.default;
640
- const composerForm = useComposerFormContext();
641
- const handleKeyPress = (e) => {
642
- if (disabled)
643
- return;
644
- if (e.key === "Escape") {
645
- useComposer.getState().cancel();
646
- }
647
- if (isLoading)
648
- return;
649
- if (e.key === "Enter" && e.shiftKey === false) {
650
- e.preventDefault();
651
- composerForm.submit();
652
- }
653
- };
654
- return /* @__PURE__ */ React.createElement(
655
- Component,
656
- {
657
- value,
658
- ...rest,
659
- ref: forwardedRef,
660
- disabled,
661
- onChange: (0, import_primitive4.composeEventHandlers)(onChange, (e) => {
662
- const composerState = useComposer.getState();
663
- if (!composerState.isEditing)
664
- return;
665
- return composerState.setValue(e.target.value);
666
- }),
667
- onKeyDown: (0, import_primitive4.composeEventHandlers)(onKeyDown, handleKeyPress)
668
- }
669
- );
670
- });
669
+ var ComposerInput = (0, import_react13.forwardRef)(
670
+ ({ autoFocus, asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
671
+ const { useThread } = useAssistantContext();
672
+ const { useComposer, type } = useComposerContext();
673
+ const value = useComposer((c) => {
674
+ if (!c.isEditing)
675
+ return "";
676
+ return c.value;
677
+ });
678
+ const Component = asChild ? import_react_slot.Slot : import_react_textarea_autosize.default;
679
+ const handleKeyPress = (e) => {
680
+ if (disabled)
681
+ return;
682
+ const composer = useComposer.getState();
683
+ if (e.key === "Escape" && composer.canCancel) {
684
+ e.preventDefault();
685
+ useComposer.getState().cancel();
686
+ }
687
+ if (e.key === "Enter" && e.shiftKey === false) {
688
+ const isLoading = useThread.getState().isLoading;
689
+ if (!isLoading) {
690
+ e.preventDefault();
691
+ composer.send();
692
+ }
693
+ }
694
+ };
695
+ const textareaRef = (0, import_react13.useRef)(null);
696
+ const ref = (0, import_react_compose_refs3.useComposedRefs)(forwardedRef, textareaRef);
697
+ const autoFocusEnabled = autoFocus !== false && !disabled;
698
+ const focus = (0, import_react13.useCallback)(() => {
699
+ const textarea = textareaRef.current;
700
+ if (!textarea || !autoFocusEnabled)
701
+ return;
702
+ textarea.focus();
703
+ textarea.setSelectionRange(
704
+ textareaRef.current.value.length,
705
+ textareaRef.current.value.length
706
+ );
707
+ }, [autoFocusEnabled]);
708
+ (0, import_react13.useEffect)(() => focus(), [focus]);
709
+ useOnScrollToBottom(() => {
710
+ if (type === "assistant") {
711
+ focus();
712
+ }
713
+ });
714
+ return /* @__PURE__ */ React.createElement(
715
+ Component,
716
+ {
717
+ value,
718
+ ...rest,
719
+ ref,
720
+ disabled,
721
+ onChange: (0, import_primitive5.composeEventHandlers)(onChange, (e) => {
722
+ const composerState = useComposer.getState();
723
+ if (!composerState.isEditing)
724
+ return;
725
+ return composerState.setValue(e.target.value);
726
+ }),
727
+ onKeyDown: (0, import_primitive5.composeEventHandlers)(onKeyDown, handleKeyPress)
728
+ }
729
+ );
730
+ }
731
+ );
671
732
 
672
733
  // src/primitives/composer/ComposerSend.tsx
673
- var import_react_primitive5 = require("@radix-ui/react-primitive");
674
- var import_react12 = require("react");
675
- var ComposerSend = (0, import_react12.forwardRef)(
734
+ var import_react_primitive6 = require("@radix-ui/react-primitive");
735
+ var import_react14 = require("react");
736
+ var ComposerSend = (0, import_react14.forwardRef)(
676
737
  ({ disabled, ...rest }, ref) => {
677
738
  const { useComposer } = useComposerContext();
678
739
  const hasValue = useComposer((c) => c.isEditing && c.value.length > 0);
679
740
  return /* @__PURE__ */ React.createElement(
680
- import_react_primitive5.Primitive.button,
741
+ import_react_primitive6.Primitive.button,
681
742
  {
682
743
  type: "submit",
683
744
  ...rest,
@@ -689,22 +750,22 @@ var ComposerSend = (0, import_react12.forwardRef)(
689
750
  );
690
751
 
691
752
  // src/primitives/composer/ComposerCancel.tsx
692
- var import_primitive5 = require("@radix-ui/primitive");
693
- var import_react_primitive6 = require("@radix-ui/react-primitive");
694
- var import_react13 = require("react");
695
- var ComposerCancel = (0, import_react13.forwardRef)(({ disabled, onClick, ...rest }, ref) => {
753
+ var import_primitive6 = require("@radix-ui/primitive");
754
+ var import_react_primitive7 = require("@radix-ui/react-primitive");
755
+ var import_react15 = require("react");
756
+ var ComposerCancel = (0, import_react15.forwardRef)(({ disabled, onClick, ...rest }, ref) => {
696
757
  const { useComposer } = useComposerContext();
697
758
  const hasValue = useComposer((c) => c.canCancel);
698
759
  const handleClose = () => {
699
760
  useComposer.getState().cancel();
700
761
  };
701
762
  return /* @__PURE__ */ React.createElement(
702
- import_react_primitive6.Primitive.button,
763
+ import_react_primitive7.Primitive.button,
703
764
  {
704
765
  type: "button",
705
766
  ...rest,
706
767
  ref,
707
- onClick: (0, import_primitive5.composeEventHandlers)(onClick, handleClose),
768
+ onClick: (0, import_primitive6.composeEventHandlers)(onClick, handleClose),
708
769
  disabled: disabled || !hasValue
709
770
  }
710
771
  );
@@ -720,42 +781,64 @@ __export(branchPicker_exports, {
720
781
  Root: () => BranchPickerRoot
721
782
  });
722
783
 
784
+ // src/utils/context/combined/useCombinedStore.ts
785
+ var import_react17 = require("react");
786
+
787
+ // src/utils/context/combined/createCombinedStore.ts
788
+ var import_react16 = require("react");
789
+ var createCombinedStore = (stores) => {
790
+ const subscribe = (callback) => {
791
+ const unsubscribes = stores.map((store) => store.subscribe(callback));
792
+ return () => {
793
+ for (const unsub of unsubscribes) {
794
+ unsub();
795
+ }
796
+ };
797
+ };
798
+ return (selector) => {
799
+ const getSnapshot = () => selector(...stores.map((store) => store.getState()));
800
+ return (0, import_react16.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
801
+ };
802
+ };
803
+
804
+ // src/utils/context/combined/useCombinedStore.ts
805
+ var useCombinedStore = (stores, selector) => {
806
+ const useCombined = (0, import_react17.useMemo)(() => createCombinedStore(stores), stores);
807
+ return useCombined(selector);
808
+ };
809
+
723
810
  // src/actions/useGoToNextBranch.tsx
724
811
  var useGoToNextBranch = () => {
725
- const { useThread, useBranchObserver } = useAssistantContext();
812
+ const { useThread } = useAssistantContext();
726
813
  const { useComposer, useMessage } = useMessageContext();
727
- const isLoading = useThread((s) => s.isLoading);
728
- const isEditing = useComposer((s) => s.isEditing);
729
- const hasNext = useMessage(
730
- ({ branchState: { branchId, branchCount } }) => branchId + 1 < branchCount
814
+ const disabled = useCombinedStore(
815
+ [useThread, useComposer, useMessage],
816
+ (t, c, m) => t.isLoading || c.isEditing || m.message.branchId + 1 >= m.message.branchCount
731
817
  );
732
- if (isLoading || isEditing || !hasNext)
818
+ if (disabled)
733
819
  return null;
734
820
  return () => {
735
- const {
736
- message,
737
- branchState: { branchId }
738
- } = useMessage.getState();
739
- useBranchObserver.getState().switchToBranch(message, branchId + 1);
821
+ const { message } = useMessage.getState();
822
+ useThread.getState().switchToBranch(message.id, message.branchId + 1);
740
823
  };
741
824
  };
742
825
 
743
826
  // src/utils/createActionButton.tsx
744
- var import_react14 = require("react");
745
- var import_react_primitive7 = require("@radix-ui/react-primitive");
746
- var import_primitive6 = require("@radix-ui/primitive");
827
+ var import_react18 = require("react");
828
+ var import_react_primitive8 = require("@radix-ui/react-primitive");
829
+ var import_primitive7 = require("@radix-ui/primitive");
747
830
  var createActionButton = (useActionButton) => {
748
- return (0, import_react14.forwardRef)(
831
+ return (0, import_react18.forwardRef)(
749
832
  (props, forwardedRef) => {
750
833
  const onClick = useActionButton(props);
751
834
  return /* @__PURE__ */ React.createElement(
752
- import_react_primitive7.Primitive.button,
835
+ import_react_primitive8.Primitive.button,
753
836
  {
754
837
  type: "button",
755
838
  disabled: !onClick,
756
839
  ...props,
757
840
  ref: forwardedRef,
758
- onClick: (0, import_primitive6.composeEventHandlers)(props.onClick, onClick ?? void 0)
841
+ onClick: (0, import_primitive7.composeEventHandlers)(props.onClick, onClick ?? void 0)
759
842
  }
760
843
  );
761
844
  }
@@ -767,19 +850,17 @@ var BranchPickerNext = createActionButton(useGoToNextBranch);
767
850
 
768
851
  // src/actions/useGoToPreviousBranch.tsx
769
852
  var useGoToPreviousBranch = () => {
770
- const { useThread, useBranchObserver } = useAssistantContext();
853
+ const { useThread } = useAssistantContext();
771
854
  const { useComposer, useMessage } = useMessageContext();
772
- const isLoading = useThread((s) => s.isLoading);
773
- const isEditing = useComposer((s) => s.isEditing);
774
- const hasNext = useMessage(({ branchState: { branchId } }) => branchId > 0);
775
- if (isLoading || isEditing || !hasNext)
855
+ const disabled = useCombinedStore(
856
+ [useThread, useComposer, useMessage],
857
+ (t, c, m) => t.isLoading || c.isEditing || m.message.branchId <= 0
858
+ );
859
+ if (disabled)
776
860
  return null;
777
861
  return () => {
778
- const {
779
- message,
780
- branchState: { branchId }
781
- } = useMessage.getState();
782
- useBranchObserver.getState().switchToBranch(message, branchId - 1);
862
+ const { message } = useMessage.getState();
863
+ useThread.getState().switchToBranch(message.id, message.branchId - 1);
783
864
  };
784
865
  };
785
866
 
@@ -789,22 +870,22 @@ var BranchPickerPrevious = createActionButton(useGoToPreviousBranch);
789
870
  // src/primitives/branchPicker/BranchPickerCount.tsx
790
871
  var BranchPickerCount = () => {
791
872
  const { useMessage } = useMessageContext();
792
- const branchCount = useMessage((s) => s.branchState.branchCount);
873
+ const branchCount = useMessage((s) => s.message.branchCount);
793
874
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchCount);
794
875
  };
795
876
 
796
877
  // src/primitives/branchPicker/BranchPickerNumber.tsx
797
878
  var BranchPickerNumber = () => {
798
879
  const { useMessage } = useMessageContext();
799
- const branchId = useMessage((s) => s.branchState.branchId);
880
+ const branchId = useMessage((s) => s.message.branchId);
800
881
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchId + 1);
801
882
  };
802
883
 
803
884
  // src/primitives/branchPicker/BranchPickerRoot.tsx
804
- var import_react_primitive8 = require("@radix-ui/react-primitive");
805
- var import_react15 = require("react");
806
- var BranchPickerRoot = (0, import_react15.forwardRef)(({ hideWhenSingleBranch, ...rest }, ref) => {
807
- return /* @__PURE__ */ React.createElement(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0 }, /* @__PURE__ */ React.createElement(import_react_primitive8.Primitive.div, { ...rest, ref }));
885
+ var import_react_primitive9 = require("@radix-ui/react-primitive");
886
+ var import_react19 = require("react");
887
+ var BranchPickerRoot = (0, import_react19.forwardRef)(({ hideWhenSingleBranch, ...rest }, ref) => {
888
+ return /* @__PURE__ */ React.createElement(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0 }, /* @__PURE__ */ React.createElement(import_react_primitive9.Primitive.div, { ...rest, ref }));
808
889
  });
809
890
 
810
891
  // src/primitives/actionBar/index.ts
@@ -817,28 +898,30 @@ __export(actionBar_exports, {
817
898
  });
818
899
 
819
900
  // src/primitives/actionBar/ActionBarRoot.tsx
820
- var import_react_primitive9 = require("@radix-ui/react-primitive");
821
- var import_react16 = require("react");
822
- var ActionBarRoot = (0, import_react16.forwardRef)(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
901
+ var import_react_primitive10 = require("@radix-ui/react-primitive");
902
+ var import_react20 = require("react");
903
+ var ActionBarRoot = (0, import_react20.forwardRef)(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
823
904
  const { useThread } = useAssistantContext();
824
905
  const { useMessage } = useMessageContext();
825
- const hideAndfloatStatus = useMessage((m) => {
826
- const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
827
- if (!autohideEnabled)
906
+ const hideAndfloatStatus = useCombinedStore(
907
+ [useThread, useMessage],
908
+ (t, m) => {
909
+ if (hideWhenBusy && t.isLoading)
910
+ return "hidden" /* Hidden */;
911
+ const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
912
+ if (!autohideEnabled)
913
+ return "normal" /* Normal */;
914
+ if (!m.isHovering)
915
+ return "hidden" /* Hidden */;
916
+ if (autohideFloat === "always" || autohideFloat === "single-branch" && m.message.branchCount <= 1)
917
+ return "floating" /* Floating */;
828
918
  return "normal" /* Normal */;
829
- if (!m.isHovering)
830
- return "hidden" /* Hidden */;
831
- if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branchState.branchCount <= 1)
832
- return "floating" /* Floating */;
833
- return "normal" /* Normal */;
834
- });
835
- const busy = useThread((t) => t.isLoading);
836
- if (hideWhenBusy && busy)
837
- return null;
919
+ }
920
+ );
838
921
  if (hideAndfloatStatus === "hidden" /* Hidden */)
839
922
  return null;
840
923
  return /* @__PURE__ */ React.createElement(
841
- import_react_primitive9.Primitive.div,
924
+ import_react_primitive10.Primitive.div,
842
925
  {
843
926
  "data-floating": hideAndfloatStatus === "floating" /* Floating */,
844
927
  ...rest,
@@ -868,17 +951,19 @@ var ActionBarCopy = createActionButton(useCopyMessage);
868
951
 
869
952
  // src/actions/useReloadMessage.tsx
870
953
  var useReloadMessage = () => {
871
- const { useThread, useBranchObserver } = useAssistantContext();
954
+ const { useThread } = useAssistantContext();
872
955
  const { useMessage } = useMessageContext();
873
- const isLoading = useThread((s) => s.isLoading);
874
- const isAssistant = useMessage((s) => s.message.role === "assistant");
875
- if (isLoading || !isAssistant)
956
+ const disabled = useCombinedStore(
957
+ [useThread, useMessage],
958
+ (t, m) => t.isLoading || m.message.role !== "assistant"
959
+ );
960
+ if (disabled)
876
961
  return null;
877
962
  return () => {
878
963
  const message = useMessage.getState().message;
879
964
  if (message.role !== "assistant")
880
965
  throw new Error("Reloading is only supported on assistant messages");
881
- useBranchObserver.getState().reloadAt(message);
966
+ useThread.getState().reload(message.id);
882
967
  };
883
968
  };
884
969
 
@@ -888,9 +973,11 @@ var ActionBarReload = createActionButton(useReloadMessage);
888
973
  // src/actions/useBeginMessageEdit.tsx
889
974
  var useBeginMessageEdit = () => {
890
975
  const { useMessage, useComposer } = useMessageContext();
891
- const isUser = useMessage((s) => s.message.role === "user");
892
- const isEditing = useComposer((s) => s.isEditing);
893
- if (!isUser || isEditing)
976
+ const disabled = useCombinedStore(
977
+ [useMessage, useComposer],
978
+ (m, c) => m.message.role !== "user" || c.isEditing
979
+ );
980
+ if (disabled)
894
981
  return null;
895
982
  return () => {
896
983
  const { edit } = useComposer.getState();
@@ -901,25 +988,41 @@ var useBeginMessageEdit = () => {
901
988
  // src/primitives/actionBar/ActionBarEdit.tsx
902
989
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
903
990
 
904
- // src/vercel/VercelAIAssistantProvider.tsx
905
- var import_react18 = require("react");
991
+ // src/adapters/vercel/VercelAIAssistantProvider.tsx
992
+ var import_react22 = require("react");
906
993
 
907
- // src/vercel/useDummyAIAssistantContext.tsx
908
- var import_react17 = require("react");
994
+ // src/adapters/vercel/useDummyAIAssistantContext.tsx
995
+ var import_react21 = require("react");
909
996
  var import_zustand2 = require("zustand");
910
997
  var useDummyAIAssistantContext = () => {
911
- const [context] = (0, import_react17.useState)(() => {
998
+ const [context] = (0, import_react21.useState)(() => {
999
+ const scrollToBottomListeners = /* @__PURE__ */ new Set();
912
1000
  const useThread = (0, import_zustand2.create)()(() => ({
913
1001
  messages: [],
914
1002
  isLoading: false,
915
- reload: async () => {
916
- throw new Error("Not implemented");
917
- },
918
1003
  append: async () => {
919
1004
  throw new Error("Not implemented");
920
1005
  },
921
1006
  stop: () => {
922
1007
  throw new Error("Not implemented");
1008
+ },
1009
+ switchToBranch: () => {
1010
+ throw new Error("Not implemented");
1011
+ },
1012
+ reload: async () => {
1013
+ 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
+ };
923
1026
  }
924
1027
  }));
925
1028
  const useComposer = (0, import_zustand2.create)()(() => ({
@@ -934,7 +1037,7 @@ var useDummyAIAssistantContext = () => {
934
1037
  },
935
1038
  send: () => {
936
1039
  useThread.getState().append({
937
- role: "user",
1040
+ parentId: useThread.getState().messages.at(-1)?.id ?? ROOT_PARENT_ID,
938
1041
  content: [{ type: "text", text: useComposer.getState().value }]
939
1042
  });
940
1043
  useComposer.getState().setValue("");
@@ -943,43 +1046,39 @@ var useDummyAIAssistantContext = () => {
943
1046
  useThread.getState().stop();
944
1047
  }
945
1048
  }));
946
- const useBranchObserver = (0, import_zustand2.create)()(() => ({
947
- getBranchState: () => ({
948
- branchId: 0,
949
- branchCount: 1
950
- }),
951
- switchToBranch: () => {
952
- throw new Error("Not implemented");
953
- },
954
- editAt: async () => {
955
- throw new Error("Not implemented");
956
- },
957
- reloadAt: async () => {
958
- throw new Error("Not implemented");
959
- }
960
- }));
961
- return { useThread, useComposer, useBranchObserver };
1049
+ return { useThread, useComposer };
962
1050
  });
963
1051
  return context;
964
1052
  };
965
1053
 
966
- // src/vercel/VercelAIAssistantProvider.tsx
1054
+ // src/adapters/vercel/VercelAIAssistantProvider.tsx
967
1055
  var ThreadMessageCache = /* @__PURE__ */ new WeakMap();
968
- var vercelToThreadMessage = (message) => {
1056
+ var vercelToThreadMessage = (message, parentId, branchId, branchCount) => {
969
1057
  if (message.role !== "user" && message.role !== "assistant")
970
1058
  throw new Error("Unsupported role");
971
1059
  return {
1060
+ parentId,
972
1061
  id: message.id,
973
1062
  role: message.role,
974
- content: [{ type: "text", text: message.content }]
1063
+ content: [{ type: "text", text: message.content }],
1064
+ branchId,
1065
+ branchCount,
1066
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date()
975
1067
  };
976
1068
  };
977
- var vercelToCachedThreadMessages = (messages) => {
978
- return messages.map((m) => {
1069
+ var vercelToCachedThreadMessages = (messages, getBranchState) => {
1070
+ return messages.map((m, idx) => {
979
1071
  const cached = ThreadMessageCache.get(m);
980
- if (cached)
1072
+ const parentId = messages[idx - 1]?.id ?? ROOT_PARENT_ID;
1073
+ const { branchId, branchCount } = getBranchState(m.id);
1074
+ if (cached && cached.parentId === parentId && cached.branchId === branchId && cached.branchCount === branchCount)
981
1075
  return cached;
982
- const newMessage = vercelToThreadMessage(m);
1076
+ const newMessage = vercelToThreadMessage(
1077
+ m,
1078
+ parentId,
1079
+ branchId,
1080
+ branchCount
1081
+ );
983
1082
  ThreadMessageCache.set(m, newMessage);
984
1083
  return newMessage;
985
1084
  });
@@ -990,28 +1089,14 @@ var VercelAIAssistantProvider = ({
990
1089
  }) => {
991
1090
  const context = useDummyAIAssistantContext();
992
1091
  const vercel = "chat" in rest ? rest.chat : rest.assistant;
993
- const messages = (0, import_react18.useMemo)(() => {
994
- return vercelToCachedThreadMessages(vercel.messages);
995
- }, [vercel.messages]);
996
- const maybeReload = "reload" in vercel ? vercel.reload : null;
997
- const reload = (0, import_react18.useCallback)(async () => {
998
- if (!maybeReload)
999
- throw new Error("Reload not supported");
1000
- await maybeReload();
1001
- }, [maybeReload]);
1002
- const append = (0, import_react18.useCallback)(
1003
- async (message) => {
1004
- if (message.content[0]?.type !== "text") {
1005
- throw new Error("Only text content is currently supported");
1006
- }
1007
- await vercel.append({
1008
- role: message.role,
1009
- content: message.content[0].text
1010
- });
1011
- },
1012
- [vercel.append]
1013
- );
1014
- const stop = (0, import_react18.useCallback)(() => {
1092
+ const branches = useVercelAIBranches(vercel, context);
1093
+ const messages = (0, import_react22.useMemo)(() => {
1094
+ return vercelToCachedThreadMessages(
1095
+ vercel.messages,
1096
+ branches.getBranchState
1097
+ );
1098
+ }, [vercel.messages, branches.getBranchState]);
1099
+ const stop = (0, import_react22.useCallback)(() => {
1015
1100
  const lastMessage = vercel.messages.at(-1);
1016
1101
  vercel.stop();
1017
1102
  if (lastMessage?.role === "user") {
@@ -1019,50 +1104,49 @@ var VercelAIAssistantProvider = ({
1019
1104
  }
1020
1105
  }, [vercel.messages, vercel.stop, vercel.setInput]);
1021
1106
  const isLoading = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1022
- (0, import_react18.useMemo)(() => {
1023
- context.useThread.setState(
1024
- {
1025
- messages,
1026
- isLoading,
1027
- reload,
1028
- append,
1029
- stop
1030
- },
1031
- true
1032
- );
1033
- }, [context, messages, reload, append, stop, isLoading]);
1034
- (0, import_react18.useMemo)(() => {
1107
+ (0, import_react22.useMemo)(() => {
1108
+ context.useThread.setState({
1109
+ messages,
1110
+ isLoading,
1111
+ stop,
1112
+ switchToBranch: branches.switchToBranch,
1113
+ append: branches.append,
1114
+ reload: branches.reload
1115
+ });
1116
+ }, [context, messages, isLoading, stop, branches]);
1117
+ (0, import_react22.useMemo)(() => {
1035
1118
  context.useComposer.setState({
1036
1119
  canCancel: isLoading,
1037
1120
  value: vercel.input,
1038
1121
  setValue: vercel.setInput
1039
1122
  });
1040
1123
  }, [context, isLoading, vercel.input, vercel.setInput]);
1041
- const branches = useVercelAIBranches(vercel);
1042
- (0, import_react18.useMemo)(() => {
1043
- context.useBranchObserver.setState(branches, true);
1044
- }, [context, branches]);
1045
1124
  return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
1046
1125
  };
1047
1126
 
1048
- // src/vercel/VercelRSCAssistantProvider.tsx
1049
- var import_react19 = require("react");
1127
+ // src/adapters/vercel/VercelRSCAssistantProvider.tsx
1128
+ var import_react23 = require("react");
1050
1129
  var ThreadMessageCache2 = /* @__PURE__ */ new WeakMap();
1051
- var vercelToThreadMessage2 = (message) => {
1130
+ var vercelToThreadMessage2 = (parentId, message) => {
1052
1131
  if (message.role !== "user" && message.role !== "assistant")
1053
1132
  throw new Error("Unsupported role");
1054
1133
  return {
1134
+ parentId,
1055
1135
  id: message.id,
1056
1136
  role: message.role,
1057
- content: [{ type: "ui", display: message.display }]
1137
+ content: [{ type: "ui", display: message.display }],
1138
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1139
+ branchId: 0,
1140
+ branchCount: 1
1058
1141
  };
1059
1142
  };
1060
1143
  var vercelToCachedThreadMessages2 = (messages) => {
1061
- return messages.map((m) => {
1144
+ return messages.map((m, idx) => {
1062
1145
  const cached = ThreadMessageCache2.get(m);
1063
- if (cached)
1146
+ const parentId = messages[idx - 1]?.id ?? ROOT_PARENT_ID;
1147
+ if (cached && cached.parentId === parentId)
1064
1148
  return cached;
1065
- const newMessage = vercelToThreadMessage2(m);
1149
+ const newMessage = vercelToThreadMessage2(parentId, m);
1066
1150
  ThreadMessageCache2.set(m, newMessage);
1067
1151
  return newMessage;
1068
1152
  });
@@ -1073,22 +1157,22 @@ var VercelRSCAssistantProvider = ({
1073
1157
  append: vercelAppend
1074
1158
  }) => {
1075
1159
  const context = useDummyAIAssistantContext();
1076
- const messages = (0, import_react19.useMemo)(() => {
1160
+ const messages = (0, import_react23.useMemo)(() => {
1077
1161
  return vercelToCachedThreadMessages2(vercelMessages);
1078
1162
  }, [vercelMessages]);
1079
- const append = (0, import_react19.useCallback)(
1163
+ const append = (0, import_react23.useCallback)(
1080
1164
  async (message) => {
1165
+ if (message.parentId !== (context.useThread.getState().messages.at(-1)?.id ?? ROOT_PARENT_ID))
1166
+ throw new Error("Unexpected: Message editing is not supported");
1081
1167
  if (message.content[0]?.type !== "text") {
1082
1168
  throw new Error("Only text content is currently supported");
1083
1169
  }
1084
- await vercelAppend({
1085
- role: message.role,
1086
- content: [{ type: "text", text: message.content[0].text }]
1087
- });
1170
+ context.useThread.getState().scrollToBottom();
1171
+ await vercelAppend(message);
1088
1172
  },
1089
- [vercelAppend]
1173
+ [context, vercelAppend]
1090
1174
  );
1091
- (0, import_react19.useMemo)(() => {
1175
+ (0, import_react23.useMemo)(() => {
1092
1176
  context.useThread.setState({
1093
1177
  messages,
1094
1178
  append