@assistant-ui/react 0.0.7 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.mjs CHANGED
@@ -11,6 +11,7 @@ __export(thread_exports, {
11
11
  If: () => ThreadIf,
12
12
  Messages: () => ThreadMessages,
13
13
  Root: () => ThreadRoot,
14
+ ScrollToBottom: () => ThreadScrollToBottom,
14
15
  Viewport: () => ThreadViewport
15
16
  });
16
17
 
@@ -68,7 +69,7 @@ import { useComposedRefs } from "@radix-ui/react-compose-refs";
68
69
  import {
69
70
  Primitive as Primitive2
70
71
  } from "@radix-ui/react-primitive";
71
- import { forwardRef as forwardRef2, useRef as useRef2, useState } from "react";
72
+ import { forwardRef as forwardRef2, useRef as useRef3 } from "react";
72
73
 
73
74
  // src/utils/hooks/useOnResizeContent.tsx
74
75
  import { useLayoutEffect, useRef } from "react";
@@ -109,22 +110,55 @@ var useOnResizeContent = (ref, callback) => {
109
110
  }, [ref.current]);
110
111
  };
111
112
 
113
+ // src/utils/hooks/useOnScrollToBottom.tsx
114
+ import { useEffect, useRef as useRef2 } from "react";
115
+ var useOnScrollToBottom = (callback) => {
116
+ const callbackRef = useRef2(callback);
117
+ callbackRef.current = callback;
118
+ const { useThread } = useAssistantContext();
119
+ useEffect(() => {
120
+ return useThread.getState().onScrollToBottom(() => {
121
+ callbackRef.current();
122
+ });
123
+ }, [useThread]);
124
+ };
125
+
112
126
  // src/primitives/thread/ThreadViewport.tsx
113
- var ThreadViewport = forwardRef2(({ onScroll, children, ...rest }, forwardedRef) => {
114
- const divRef = useRef2(null);
127
+ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...rest }, forwardedRef) => {
128
+ const messagesEndRef = useRef3(null);
129
+ const divRef = useRef3(null);
115
130
  const ref = useComposedRefs(forwardedRef, divRef);
116
- const [isAtBottom, setIsAtBottom] = useState(true);
131
+ const { useThread } = useAssistantContext();
132
+ const firstRenderRef = useRef3(true);
133
+ const lastScrollTop = useRef3(0);
134
+ const scrollToBottom = () => {
135
+ const div = messagesEndRef.current;
136
+ if (!div || !autoScroll)
137
+ return;
138
+ const behavior = firstRenderRef.current ? "instant" : "auto";
139
+ firstRenderRef.current = false;
140
+ useThread.setState({ isAtBottom: true });
141
+ div.scrollIntoView({ behavior });
142
+ };
117
143
  useOnResizeContent(divRef, () => {
118
- const div = divRef.current;
119
- if (!div || !isAtBottom)
144
+ if (!useThread.getState().isAtBottom)
120
145
  return;
121
- div.scrollTop = div.scrollHeight;
146
+ scrollToBottom();
147
+ });
148
+ useOnScrollToBottom(() => {
149
+ scrollToBottom();
122
150
  });
123
151
  const handleScroll = () => {
124
152
  const div = divRef.current;
125
153
  if (!div)
126
154
  return;
127
- setIsAtBottom(div.scrollHeight - div.scrollTop <= div.clientHeight + 50);
155
+ const isAtBottom = useThread.getState().isAtBottom;
156
+ const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
157
+ if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
158
+ } else if (newIsAtBottom !== isAtBottom) {
159
+ useThread.setState({ isAtBottom: newIsAtBottom });
160
+ }
161
+ lastScrollTop.current = div.scrollTop;
128
162
  };
129
163
  return /* @__PURE__ */ React.createElement(
130
164
  Primitive2.div,
@@ -133,19 +167,24 @@ var ThreadViewport = forwardRef2(({ onScroll, children, ...rest }, forwardedRef)
133
167
  onScroll: composeEventHandlers(onScroll, handleScroll),
134
168
  ref
135
169
  },
136
- children
170
+ children,
171
+ /* @__PURE__ */ React.createElement("div", { ref: messagesEndRef })
137
172
  );
138
173
  });
139
174
 
140
- // src/vercel/useVercelAIBranches.tsx
141
- import { useCallback, useMemo, useRef as useRef3 } from "react";
142
- var ROOT_ID = "__ROOT_ID__";
175
+ // src/adapters/vercel/useVercelAIBranches.tsx
176
+ import { useCallback, useMemo, useRef as useRef4 } from "react";
177
+
178
+ // src/utils/context/stores/AssistantTypes.ts
179
+ var ROOT_PARENT_ID = "__ROOT_ID__";
180
+
181
+ // src/adapters/vercel/useVercelAIBranches.tsx
143
182
  var UPCOMING_MESSAGE_ID = "__UPCOMING_MESSAGE_ID__";
144
183
  var updateBranchData = (data, messages) => {
145
184
  for (let i = 0; i < messages.length; i++) {
146
185
  const child = messages[i];
147
186
  const childId = child.id;
148
- const parentId = messages[i - 1]?.id ?? ROOT_ID;
187
+ const parentId = messages[i - 1]?.id ?? ROOT_PARENT_ID;
149
188
  data.parentMap.set(childId, parentId);
150
189
  const parentArray = data.branchMap.get(parentId);
151
190
  if (!parentArray) {
@@ -156,32 +195,32 @@ var updateBranchData = (data, messages) => {
156
195
  data.snapshots.set(childId, messages);
157
196
  }
158
197
  };
159
- var getParentId = (data, messages, message) => {
160
- if (message.id === UPCOMING_MESSAGE_ID) {
198
+ var getParentId = (data, messages, messageId) => {
199
+ if (messageId === UPCOMING_MESSAGE_ID) {
161
200
  const parent = messages.at(-1);
162
201
  if (!parent)
163
- return ROOT_ID;
202
+ return ROOT_PARENT_ID;
164
203
  return parent.id;
165
204
  }
166
- const parentId = data.parentMap.get(message.id);
205
+ const parentId = data.parentMap.get(messageId);
167
206
  if (!parentId)
168
207
  throw new Error("Unexpected: Message parent not found");
169
208
  return parentId;
170
209
  };
171
- var getBranchStateImpl = (data, messages, message) => {
172
- const parentId = getParentId(data, messages, message);
210
+ var getBranchStateImpl = (data, messages, messageId) => {
211
+ const parentId = getParentId(data, messages, messageId);
173
212
  const branches = data.branchMap.get(parentId) ?? [];
174
- 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);
175
214
  if (branchId === -1)
176
215
  throw new Error("Unexpected: Message not found in parent children");
177
- const upcomingOffset = message.id === UPCOMING_MESSAGE_ID ? 1 : 0;
216
+ const upcomingOffset = messageId === UPCOMING_MESSAGE_ID ? 1 : 0;
178
217
  return {
179
218
  branchId,
180
219
  branchCount: branches.length + upcomingOffset
181
220
  };
182
221
  };
183
- var switchToBranchImpl = (data, messages, message, branchId) => {
184
- const parentId = getParentId(data, messages, message);
222
+ var switchToBranchImpl = (data, messages, messageId, branchId) => {
223
+ const parentId = getParentId(data, messages, messageId);
185
224
  const branches = data.branchMap.get(parentId);
186
225
  if (!branches)
187
226
  throw new Error("Unexpected: Parent children not found");
@@ -190,40 +229,42 @@ var switchToBranchImpl = (data, messages, message, branchId) => {
190
229
  throw new Error("Unexpected: Requested branch not found");
191
230
  if (branchId < 0 || branchId >= branches.length)
192
231
  throw new Error("Switch to branch called with a branch index out of range");
193
- if (newMessageId === message.id)
232
+ if (newMessageId === messageId)
194
233
  return messages;
195
234
  const snapshot = data.snapshots.get(newMessageId);
196
235
  if (!snapshot)
197
236
  throw new Error("Unexpected: Branch snapshot not found");
198
237
  return snapshot;
199
238
  };
200
- var sliceMessagesUntil = (messages, message) => {
201
- 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)
202
243
  return messages;
203
- const messageIdx = messages.findIndex((m) => m.id === message.id);
244
+ const messageIdx = messages.findIndex((m) => m.id === messageId);
204
245
  if (messageIdx === -1)
205
246
  throw new Error("Unexpected: Message not found");
206
- return messages.slice(0, messageIdx);
247
+ return messages.slice(0, messageIdx + 1);
207
248
  };
208
- var useVercelAIBranches = (chat) => {
209
- const data = useRef3({
249
+ var useVercelAIBranches = (chat, context) => {
250
+ const data = useRef4({
210
251
  parentMap: /* @__PURE__ */ new Map(),
211
252
  branchMap: /* @__PURE__ */ new Map(),
212
253
  snapshots: /* @__PURE__ */ new Map()
213
254
  }).current;
214
255
  updateBranchData(data, chat.messages);
215
256
  const getBranchState = useCallback(
216
- (message) => {
217
- return getBranchStateImpl(data, chat.messages, message);
257
+ (messageId) => {
258
+ return getBranchStateImpl(data, chat.messages, messageId);
218
259
  },
219
260
  [data, chat.messages]
220
261
  );
221
262
  const switchToBranch = useCallback(
222
- (message, branchId) => {
263
+ (messageId, branchId) => {
223
264
  const newMessages = switchToBranchImpl(
224
265
  data,
225
266
  chat.messages,
226
- message,
267
+ messageId,
227
268
  branchId
228
269
  );
229
270
  chat.setMessages(newMessages);
@@ -231,47 +272,49 @@ var useVercelAIBranches = (chat) => {
231
272
  [data, chat.messages, chat.setMessages]
232
273
  );
233
274
  const reloadMaybe = "reload" in chat ? chat.reload : void 0;
234
- const reloadAt = useCallback(
235
- async (message) => {
275
+ const reload = useCallback(
276
+ async (messageId) => {
236
277
  if (!reloadMaybe)
237
278
  throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
238
- const newMessages = sliceMessagesUntil(chat.messages, message);
279
+ const newMessages = sliceMessagesUntil(chat.messages, messageId);
239
280
  chat.setMessages(newMessages);
281
+ context.useThread.getState().scrollToBottom();
240
282
  await reloadMaybe();
241
283
  },
242
- [chat.messages, chat.setMessages, reloadMaybe]
284
+ [context, chat.messages, chat.setMessages, reloadMaybe]
243
285
  );
244
- const editAt = useCallback(
245
- async (message, newMessage) => {
246
- const newMessages = sliceMessagesUntil(chat.messages, message);
286
+ const append = useCallback(
287
+ async (message) => {
288
+ const newMessages = sliceMessagesUntil(chat.messages, message.parentId);
247
289
  chat.setMessages(newMessages);
248
- if (newMessage.content[0]?.type !== "text")
290
+ if (message.content.length !== 1 || message.content[0]?.type !== "text")
249
291
  throw new Error("Only text content is currently supported");
292
+ context.useThread.getState().scrollToBottom();
250
293
  await chat.append({
251
294
  role: "user",
252
- content: newMessage.content[0].text
295
+ content: message.content[0].text
253
296
  });
254
297
  },
255
- [chat.messages, chat.setMessages, chat.append]
298
+ [context, chat.messages, chat.setMessages, chat.append]
256
299
  );
257
300
  return useMemo(
258
301
  () => ({
259
302
  getBranchState,
260
303
  switchToBranch,
261
- editAt,
262
- reloadAt
304
+ append,
305
+ reload
263
306
  }),
264
- [getBranchState, switchToBranch, editAt, reloadAt]
307
+ [getBranchState, switchToBranch, append, reload]
265
308
  );
266
309
  };
267
310
  var hasUpcomingMessage = (thread) => {
268
311
  return thread.isLoading && thread.messages[thread.messages.length - 1]?.role !== "assistant";
269
312
  };
270
313
 
271
- // src/utils/context/ComposerState.ts
314
+ // src/utils/context/useComposerContext.ts
272
315
  import { useContext as useContext3 } from "react";
273
316
 
274
- // src/utils/context/MessageContext.ts
317
+ // src/utils/context/useMessageContext.ts
275
318
  import { createContext as createContext2, useContext as useContext2 } from "react";
276
319
  var MessageContext = createContext2(null);
277
320
  var useMessageContext = () => {
@@ -281,11 +324,14 @@ var useMessageContext = () => {
281
324
  return context;
282
325
  };
283
326
 
284
- // src/utils/context/ComposerState.ts
327
+ // src/utils/context/useComposerContext.ts
285
328
  var useComposerContext = () => {
286
329
  const { useComposer: useAssisstantComposer } = useAssistantContext();
287
330
  const { useComposer: useMessageComposer } = useContext3(MessageContext) ?? {};
288
- return { useComposer: useMessageComposer ?? useAssisstantComposer };
331
+ return {
332
+ useComposer: useMessageComposer ?? useAssisstantComposer,
333
+ type: useMessageComposer ? "message" : "assistant"
334
+ };
289
335
  };
290
336
 
291
337
  // src/primitives/composer/ComposerIf.tsx
@@ -314,16 +360,15 @@ __export(message_exports, {
314
360
  });
315
361
 
316
362
  // src/primitives/message/MessageProvider.tsx
317
- import { useMemo as useMemo2, useState as useState2 } from "react";
363
+ import { useMemo as useMemo2, useState } from "react";
318
364
  import { create } from "zustand";
319
- import { useShallow } from "zustand/react/shallow";
320
365
  var getIsLast = (thread, message) => {
321
366
  const hasUpcoming = hasUpcomingMessage(thread);
322
367
  return hasUpcoming ? message.id === UPCOMING_MESSAGE_ID : thread.messages[thread.messages.length - 1]?.id === message.id;
323
368
  };
324
369
  var useMessageContext2 = () => {
325
- const { useBranchObserver } = useAssistantContext();
326
- const [context] = useState2(() => {
370
+ const [context] = useState(() => {
371
+ const { useThread } = useAssistantContext();
327
372
  const useMessage = create(() => ({
328
373
  message: null,
329
374
  isLast: false,
@@ -332,10 +377,6 @@ var useMessageContext2 = () => {
332
377
  setIsCopied: () => {
333
378
  },
334
379
  setIsHovering: () => {
335
- },
336
- branchState: {
337
- branchId: 0,
338
- branchCount: 0
339
380
  }
340
381
  }));
341
382
  const useComposer = create((set, get) => ({
@@ -357,8 +398,8 @@ var useMessageContext2 = () => {
357
398
  const message = useMessage.getState().message;
358
399
  if (message.role !== "user")
359
400
  throw new Error("Editing is only supported for user messages");
360
- useBranchObserver.getState().editAt(message, {
361
- role: "user",
401
+ useThread.getState().append({
402
+ parentId: message.parentId,
362
403
  content: [{ type: "text", text: get().value }]
363
404
  });
364
405
  set({ isEditing: false });
@@ -374,14 +415,11 @@ var MessageProvider = ({
374
415
  message,
375
416
  children
376
417
  }) => {
377
- const { useThread, useBranchObserver } = useAssistantContext();
418
+ const { useThread } = useAssistantContext();
378
419
  const context = useMessageContext2();
379
- const branchState = useBranchObserver(
380
- useShallow((b) => b.getBranchState(message))
381
- );
382
420
  const isLast = useThread((thread) => getIsLast(thread, message));
383
- const [isCopied, setIsCopied] = useState2(false);
384
- const [isHovering, setIsHovering] = useState2(false);
421
+ const [isCopied, setIsCopied] = useState(false);
422
+ const [isHovering, setIsHovering] = useState(false);
385
423
  useMemo2(() => {
386
424
  context.useMessage.setState(
387
425
  {
@@ -390,12 +428,11 @@ var MessageProvider = ({
390
428
  isCopied,
391
429
  isHovering,
392
430
  setIsCopied,
393
- setIsHovering,
394
- branchState
431
+ setIsHovering
395
432
  },
396
433
  true
397
434
  );
398
- }, [context, message, isLast, isCopied, isHovering, branchState]);
435
+ }, [context, message, isLast, isCopied, isHovering]);
399
436
  return /* @__PURE__ */ React.createElement(MessageContext.Provider, { value: context }, children);
400
437
  };
401
438
 
@@ -430,29 +467,21 @@ var MessageRoot = forwardRef3(
430
467
  // src/primitives/message/MessageIf.tsx
431
468
  var useMessageIf = (props) => {
432
469
  const { useMessage } = useMessageContext();
433
- return useMessage(
434
- ({
435
- message,
436
- isLast,
437
- isCopied,
438
- isHovering,
439
- branchState: { branchCount }
440
- }) => {
441
- if (props.hasBranches === true && branchCount < 2)
442
- return false;
443
- if (props.user && message.role !== "user")
444
- return false;
445
- if (props.assistant && message.role !== "assistant")
446
- return false;
447
- if (props.lastOrHover === true && !isHovering && !isLast)
448
- return false;
449
- if (props.copied === true && !isCopied)
450
- return false;
451
- if (props.copied === false && isCopied)
452
- return false;
453
- return true;
454
- }
455
- );
470
+ return useMessage(({ message, isLast, isCopied, isHovering }) => {
471
+ if (props.hasBranches === true && message.branchCount < 2)
472
+ return false;
473
+ if (props.user && message.role !== "user")
474
+ return false;
475
+ if (props.assistant && message.role !== "assistant")
476
+ return false;
477
+ if (props.lastOrHover === true && !isHovering && !isLast)
478
+ return false;
479
+ if (props.copied === true && !isCopied)
480
+ return false;
481
+ if (props.copied === false && isCopied)
482
+ return false;
483
+ return true;
484
+ });
456
485
  };
457
486
  var MessageIf = ({ children, ...query }) => {
458
487
  const result = useMessageIf(query);
@@ -523,13 +552,42 @@ var ThreadMessages = ({ components }) => {
523
552
  message: {
524
553
  id: UPCOMING_MESSAGE_ID,
525
554
  role: "assistant",
526
- content: [{ type: "text", text: "..." }]
555
+ content: [{ type: "text", text: "..." }],
556
+ parentId: messages.at(-1)?.id ?? ROOT_PARENT_ID,
557
+ // TODO fix these (move upcoming message to AssistantContext)
558
+ branchId: 0,
559
+ branchCount: 1,
560
+ createdAt: /* @__PURE__ */ new Date()
527
561
  }
528
562
  },
529
563
  /* @__PURE__ */ React.createElement(AssistantMessage, null)
530
564
  ));
531
565
  };
532
566
 
567
+ // src/primitives/thread/ThreadScrollToBottom.tsx
568
+ import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
569
+ import {
570
+ Primitive as Primitive4
571
+ } from "@radix-ui/react-primitive";
572
+ import { forwardRef as forwardRef4 } from "react";
573
+ var ThreadScrollToBottom = forwardRef4(({ onClick, ...rest }, ref) => {
574
+ const { useThread } = useAssistantContext();
575
+ const isAtBottom = useThread((s) => s.isAtBottom);
576
+ const handleScrollToBottom = () => {
577
+ const thread = useThread.getState();
578
+ thread.scrollToBottom();
579
+ };
580
+ return /* @__PURE__ */ React.createElement(
581
+ Primitive4.button,
582
+ {
583
+ ...rest,
584
+ disabled: isAtBottom,
585
+ ref,
586
+ onClick: composeEventHandlers3(onClick, handleScrollToBottom)
587
+ }
588
+ );
589
+ });
590
+
533
591
  // src/primitives/composer/index.ts
534
592
  var composer_exports = {};
535
593
  __export(composer_exports, {
@@ -541,41 +599,17 @@ __export(composer_exports, {
541
599
  });
542
600
 
543
601
  // src/primitives/composer/ComposerRoot.tsx
544
- import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
602
+ import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
545
603
  import { useComposedRefs as useComposedRefs2 } from "@radix-ui/react-compose-refs";
546
604
  import {
547
- Primitive as Primitive4
605
+ Primitive as Primitive5
548
606
  } from "@radix-ui/react-primitive";
549
- import {
550
- createContext as createContext3,
551
- forwardRef as forwardRef4,
552
- useContext as useContext4,
553
- useMemo as useMemo3,
554
- useRef as useRef4
555
- } from "react";
556
- var ComposerFormContext = createContext3(null);
557
- var useComposerFormContext = () => {
558
- const context = useContext4(ComposerFormContext);
559
- if (!context) {
560
- throw new Error(
561
- "Composer compound components cannot be rendered outside the Composer component"
562
- );
563
- }
564
- return context;
565
- };
566
- var ComposerRoot = forwardRef4(
607
+ import { forwardRef as forwardRef5, useRef as useRef5 } from "react";
608
+ var ComposerRoot = forwardRef5(
567
609
  ({ onSubmit, ...rest }, forwardedRef) => {
568
610
  const { useComposer } = useComposerContext();
569
- const formRef = useRef4(null);
611
+ const formRef = useRef5(null);
570
612
  const ref = useComposedRefs2(forwardedRef, formRef);
571
- const composerContextValue = useMemo3(
572
- () => ({
573
- submit: () => formRef.current?.dispatchEvent(
574
- new Event("submit", { cancelable: true, bubbles: true })
575
- )
576
- }),
577
- []
578
- );
579
613
  const handleSubmit = (e) => {
580
614
  const composerState = useComposer.getState();
581
615
  if (!composerState.isEditing)
@@ -583,75 +617,103 @@ var ComposerRoot = forwardRef4(
583
617
  e.preventDefault();
584
618
  composerState.send();
585
619
  };
586
- return /* @__PURE__ */ React.createElement(ComposerFormContext.Provider, { value: composerContextValue }, /* @__PURE__ */ React.createElement(
587
- Primitive4.form,
620
+ return /* @__PURE__ */ React.createElement(
621
+ Primitive5.form,
588
622
  {
589
623
  ...rest,
590
624
  ref,
591
- onSubmit: composeEventHandlers3(onSubmit, handleSubmit)
625
+ onSubmit: composeEventHandlers4(onSubmit, handleSubmit)
592
626
  }
593
- ));
627
+ );
594
628
  }
595
629
  );
596
630
 
597
631
  // src/primitives/composer/ComposerInput.tsx
598
- import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
632
+ import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
633
+ import { useComposedRefs as useComposedRefs3 } from "@radix-ui/react-compose-refs";
599
634
  import { Slot } from "@radix-ui/react-slot";
600
- import { forwardRef as forwardRef5 } from "react";
635
+ import {
636
+ forwardRef as forwardRef6,
637
+ useCallback as useCallback2,
638
+ useEffect as useEffect2,
639
+ useRef as useRef6
640
+ } from "react";
601
641
  import TextareaAutosize from "react-textarea-autosize";
602
- var ComposerInput = forwardRef5(({ asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
603
- const { useThread } = useAssistantContext();
604
- const isLoading = useThread((t) => t.isLoading);
605
- const { useComposer } = useComposerContext();
606
- const value = useComposer((c) => {
607
- if (!c.isEditing)
608
- return "";
609
- return c.value;
610
- });
611
- const Component = asChild ? Slot : TextareaAutosize;
612
- const composerForm = useComposerFormContext();
613
- const handleKeyPress = (e) => {
614
- if (disabled)
615
- return;
616
- if (e.key === "Escape") {
617
- useComposer.getState().cancel();
618
- }
619
- if (isLoading)
620
- return;
621
- if (e.key === "Enter" && e.shiftKey === false) {
622
- e.preventDefault();
623
- composerForm.submit();
624
- }
625
- };
626
- return /* @__PURE__ */ React.createElement(
627
- Component,
628
- {
629
- value,
630
- ...rest,
631
- ref: forwardedRef,
632
- disabled,
633
- onChange: composeEventHandlers4(onChange, (e) => {
634
- const composerState = useComposer.getState();
635
- if (!composerState.isEditing)
636
- return;
637
- return composerState.setValue(e.target.value);
638
- }),
639
- onKeyDown: composeEventHandlers4(onKeyDown, handleKeyPress)
640
- }
641
- );
642
- });
642
+ var ComposerInput = forwardRef6(
643
+ ({ autoFocus, asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
644
+ const { useThread } = useAssistantContext();
645
+ const { useComposer, type } = useComposerContext();
646
+ const value = useComposer((c) => {
647
+ if (!c.isEditing)
648
+ return "";
649
+ return c.value;
650
+ });
651
+ const Component = asChild ? Slot : TextareaAutosize;
652
+ const handleKeyPress = (e) => {
653
+ if (disabled)
654
+ return;
655
+ const composer = useComposer.getState();
656
+ if (e.key === "Escape" && composer.canCancel) {
657
+ e.preventDefault();
658
+ useComposer.getState().cancel();
659
+ }
660
+ if (e.key === "Enter" && e.shiftKey === false) {
661
+ const isLoading = useThread.getState().isLoading;
662
+ if (!isLoading) {
663
+ e.preventDefault();
664
+ composer.send();
665
+ }
666
+ }
667
+ };
668
+ const textareaRef = useRef6(null);
669
+ const ref = useComposedRefs3(forwardedRef, textareaRef);
670
+ const autoFocusEnabled = autoFocus !== false && !disabled;
671
+ const focus = useCallback2(() => {
672
+ const textarea = textareaRef.current;
673
+ if (!textarea || !autoFocusEnabled)
674
+ return;
675
+ textarea.focus();
676
+ textarea.setSelectionRange(
677
+ textareaRef.current.value.length,
678
+ textareaRef.current.value.length
679
+ );
680
+ }, [autoFocusEnabled]);
681
+ useEffect2(() => focus(), [focus]);
682
+ useOnScrollToBottom(() => {
683
+ if (type === "assistant") {
684
+ focus();
685
+ }
686
+ });
687
+ return /* @__PURE__ */ React.createElement(
688
+ Component,
689
+ {
690
+ value,
691
+ ...rest,
692
+ ref,
693
+ disabled,
694
+ onChange: composeEventHandlers5(onChange, (e) => {
695
+ const composerState = useComposer.getState();
696
+ if (!composerState.isEditing)
697
+ return;
698
+ return composerState.setValue(e.target.value);
699
+ }),
700
+ onKeyDown: composeEventHandlers5(onKeyDown, handleKeyPress)
701
+ }
702
+ );
703
+ }
704
+ );
643
705
 
644
706
  // src/primitives/composer/ComposerSend.tsx
645
707
  import {
646
- Primitive as Primitive5
708
+ Primitive as Primitive6
647
709
  } from "@radix-ui/react-primitive";
648
- import { forwardRef as forwardRef6 } from "react";
649
- var ComposerSend = forwardRef6(
710
+ import { forwardRef as forwardRef7 } from "react";
711
+ var ComposerSend = forwardRef7(
650
712
  ({ disabled, ...rest }, ref) => {
651
713
  const { useComposer } = useComposerContext();
652
714
  const hasValue = useComposer((c) => c.isEditing && c.value.length > 0);
653
715
  return /* @__PURE__ */ React.createElement(
654
- Primitive5.button,
716
+ Primitive6.button,
655
717
  {
656
718
  type: "submit",
657
719
  ...rest,
@@ -663,24 +725,24 @@ var ComposerSend = forwardRef6(
663
725
  );
664
726
 
665
727
  // src/primitives/composer/ComposerCancel.tsx
666
- import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
728
+ import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
667
729
  import {
668
- Primitive as Primitive6
730
+ Primitive as Primitive7
669
731
  } from "@radix-ui/react-primitive";
670
- import { forwardRef as forwardRef7 } from "react";
671
- var ComposerCancel = forwardRef7(({ disabled, onClick, ...rest }, ref) => {
732
+ import { forwardRef as forwardRef8 } from "react";
733
+ var ComposerCancel = forwardRef8(({ disabled, onClick, ...rest }, ref) => {
672
734
  const { useComposer } = useComposerContext();
673
735
  const hasValue = useComposer((c) => c.canCancel);
674
736
  const handleClose = () => {
675
737
  useComposer.getState().cancel();
676
738
  };
677
739
  return /* @__PURE__ */ React.createElement(
678
- Primitive6.button,
740
+ Primitive7.button,
679
741
  {
680
742
  type: "button",
681
743
  ...rest,
682
744
  ref,
683
- onClick: composeEventHandlers5(onClick, handleClose),
745
+ onClick: composeEventHandlers6(onClick, handleClose),
684
746
  disabled: disabled || !hasValue
685
747
  }
686
748
  );
@@ -696,44 +758,66 @@ __export(branchPicker_exports, {
696
758
  Root: () => BranchPickerRoot
697
759
  });
698
760
 
761
+ // src/utils/context/combined/useCombinedStore.ts
762
+ import { useMemo as useMemo3 } from "react";
763
+
764
+ // src/utils/context/combined/createCombinedStore.ts
765
+ import { useSyncExternalStore } from "react";
766
+ var createCombinedStore = (stores) => {
767
+ const subscribe = (callback) => {
768
+ const unsubscribes = stores.map((store) => store.subscribe(callback));
769
+ return () => {
770
+ for (const unsub of unsubscribes) {
771
+ unsub();
772
+ }
773
+ };
774
+ };
775
+ return (selector) => {
776
+ const getSnapshot = () => selector(...stores.map((store) => store.getState()));
777
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
778
+ };
779
+ };
780
+
781
+ // src/utils/context/combined/useCombinedStore.ts
782
+ var useCombinedStore = (stores, selector) => {
783
+ const useCombined = useMemo3(() => createCombinedStore(stores), stores);
784
+ return useCombined(selector);
785
+ };
786
+
699
787
  // src/actions/useGoToNextBranch.tsx
700
788
  var useGoToNextBranch = () => {
701
- const { useThread, useBranchObserver } = useAssistantContext();
789
+ const { useThread } = useAssistantContext();
702
790
  const { useComposer, useMessage } = useMessageContext();
703
- const isLoading = useThread((s) => s.isLoading);
704
- const isEditing = useComposer((s) => s.isEditing);
705
- const hasNext = useMessage(
706
- ({ branchState: { branchId, branchCount } }) => branchId + 1 < branchCount
791
+ const disabled = useCombinedStore(
792
+ [useThread, useComposer, useMessage],
793
+ (t, c, m) => t.isLoading || c.isEditing || m.message.branchId + 1 >= m.message.branchCount
707
794
  );
708
- if (isLoading || isEditing || !hasNext)
795
+ if (disabled)
709
796
  return null;
710
797
  return () => {
711
- const {
712
- message,
713
- branchState: { branchId }
714
- } = useMessage.getState();
715
- useBranchObserver.getState().switchToBranch(message, branchId + 1);
798
+ const { message } = useMessage.getState();
799
+ useThread.getState().switchToBranch(message.id, message.branchId + 1);
716
800
  };
717
801
  };
718
802
 
719
803
  // src/utils/createActionButton.tsx
720
- import { forwardRef as forwardRef8 } from "react";
804
+ import { forwardRef as forwardRef9 } from "react";
721
805
  import {
722
- Primitive as Primitive7
806
+ Primitive as Primitive8
723
807
  } from "@radix-ui/react-primitive";
724
- import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
808
+ import { composeEventHandlers as composeEventHandlers7 } from "@radix-ui/primitive";
725
809
  var createActionButton = (useActionButton) => {
726
- return forwardRef8(
810
+ return forwardRef9(
727
811
  (props, forwardedRef) => {
728
812
  const onClick = useActionButton(props);
729
813
  return /* @__PURE__ */ React.createElement(
730
- Primitive7.button,
814
+ Primitive8.button,
731
815
  {
732
816
  type: "button",
733
817
  disabled: !onClick,
734
818
  ...props,
735
819
  ref: forwardedRef,
736
- onClick: composeEventHandlers6(props.onClick, onClick ?? void 0)
820
+ onClick: composeEventHandlers7(props.onClick, onClick ?? void 0)
737
821
  }
738
822
  );
739
823
  }
@@ -745,19 +829,17 @@ var BranchPickerNext = createActionButton(useGoToNextBranch);
745
829
 
746
830
  // src/actions/useGoToPreviousBranch.tsx
747
831
  var useGoToPreviousBranch = () => {
748
- const { useThread, useBranchObserver } = useAssistantContext();
832
+ const { useThread } = useAssistantContext();
749
833
  const { useComposer, useMessage } = useMessageContext();
750
- const isLoading = useThread((s) => s.isLoading);
751
- const isEditing = useComposer((s) => s.isEditing);
752
- const hasNext = useMessage(({ branchState: { branchId } }) => branchId > 0);
753
- if (isLoading || isEditing || !hasNext)
834
+ const disabled = useCombinedStore(
835
+ [useThread, useComposer, useMessage],
836
+ (t, c, m) => t.isLoading || c.isEditing || m.message.branchId <= 0
837
+ );
838
+ if (disabled)
754
839
  return null;
755
840
  return () => {
756
- const {
757
- message,
758
- branchState: { branchId }
759
- } = useMessage.getState();
760
- useBranchObserver.getState().switchToBranch(message, branchId - 1);
841
+ const { message } = useMessage.getState();
842
+ useThread.getState().switchToBranch(message.id, message.branchId - 1);
761
843
  };
762
844
  };
763
845
 
@@ -767,24 +849,24 @@ var BranchPickerPrevious = createActionButton(useGoToPreviousBranch);
767
849
  // src/primitives/branchPicker/BranchPickerCount.tsx
768
850
  var BranchPickerCount = () => {
769
851
  const { useMessage } = useMessageContext();
770
- const branchCount = useMessage((s) => s.branchState.branchCount);
852
+ const branchCount = useMessage((s) => s.message.branchCount);
771
853
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchCount);
772
854
  };
773
855
 
774
856
  // src/primitives/branchPicker/BranchPickerNumber.tsx
775
857
  var BranchPickerNumber = () => {
776
858
  const { useMessage } = useMessageContext();
777
- const branchId = useMessage((s) => s.branchState.branchId);
859
+ const branchId = useMessage((s) => s.message.branchId);
778
860
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchId + 1);
779
861
  };
780
862
 
781
863
  // src/primitives/branchPicker/BranchPickerRoot.tsx
782
864
  import {
783
- Primitive as Primitive8
865
+ Primitive as Primitive9
784
866
  } from "@radix-ui/react-primitive";
785
- import { forwardRef as forwardRef9 } from "react";
786
- var BranchPickerRoot = forwardRef9(({ hideWhenSingleBranch, ...rest }, ref) => {
787
- return /* @__PURE__ */ React.createElement(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0 }, /* @__PURE__ */ React.createElement(Primitive8.div, { ...rest, ref }));
867
+ import { forwardRef as forwardRef10 } from "react";
868
+ var BranchPickerRoot = forwardRef10(({ hideWhenSingleBranch, ...rest }, ref) => {
869
+ return /* @__PURE__ */ React.createElement(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0 }, /* @__PURE__ */ React.createElement(Primitive9.div, { ...rest, ref }));
788
870
  });
789
871
 
790
872
  // src/primitives/actionBar/index.ts
@@ -798,29 +880,31 @@ __export(actionBar_exports, {
798
880
 
799
881
  // src/primitives/actionBar/ActionBarRoot.tsx
800
882
  import {
801
- Primitive as Primitive9
883
+ Primitive as Primitive10
802
884
  } from "@radix-ui/react-primitive";
803
- import { forwardRef as forwardRef10 } from "react";
804
- var ActionBarRoot = forwardRef10(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
885
+ import { forwardRef as forwardRef11 } from "react";
886
+ var ActionBarRoot = forwardRef11(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
805
887
  const { useThread } = useAssistantContext();
806
888
  const { useMessage } = useMessageContext();
807
- const hideAndfloatStatus = useMessage((m) => {
808
- const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
809
- if (!autohideEnabled)
889
+ const hideAndfloatStatus = useCombinedStore(
890
+ [useThread, useMessage],
891
+ (t, m) => {
892
+ if (hideWhenBusy && t.isLoading)
893
+ return "hidden" /* Hidden */;
894
+ const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
895
+ if (!autohideEnabled)
896
+ return "normal" /* Normal */;
897
+ if (!m.isHovering)
898
+ return "hidden" /* Hidden */;
899
+ if (autohideFloat === "always" || autohideFloat === "single-branch" && m.message.branchCount <= 1)
900
+ return "floating" /* Floating */;
810
901
  return "normal" /* Normal */;
811
- if (!m.isHovering)
812
- return "hidden" /* Hidden */;
813
- if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branchState.branchCount <= 1)
814
- return "floating" /* Floating */;
815
- return "normal" /* Normal */;
816
- });
817
- const busy = useThread((t) => t.isLoading);
818
- if (hideWhenBusy && busy)
819
- return null;
902
+ }
903
+ );
820
904
  if (hideAndfloatStatus === "hidden" /* Hidden */)
821
905
  return null;
822
906
  return /* @__PURE__ */ React.createElement(
823
- Primitive9.div,
907
+ Primitive10.div,
824
908
  {
825
909
  "data-floating": hideAndfloatStatus === "floating" /* Floating */,
826
910
  ...rest,
@@ -850,17 +934,19 @@ var ActionBarCopy = createActionButton(useCopyMessage);
850
934
 
851
935
  // src/actions/useReloadMessage.tsx
852
936
  var useReloadMessage = () => {
853
- const { useThread, useBranchObserver } = useAssistantContext();
937
+ const { useThread } = useAssistantContext();
854
938
  const { useMessage } = useMessageContext();
855
- const isLoading = useThread((s) => s.isLoading);
856
- const isAssistant = useMessage((s) => s.message.role === "assistant");
857
- if (isLoading || !isAssistant)
939
+ const disabled = useCombinedStore(
940
+ [useThread, useMessage],
941
+ (t, m) => t.isLoading || m.message.role !== "assistant"
942
+ );
943
+ if (disabled)
858
944
  return null;
859
945
  return () => {
860
946
  const message = useMessage.getState().message;
861
947
  if (message.role !== "assistant")
862
948
  throw new Error("Reloading is only supported on assistant messages");
863
- useBranchObserver.getState().reloadAt(message);
949
+ useThread.getState().reload(message.id);
864
950
  };
865
951
  };
866
952
 
@@ -870,9 +956,11 @@ var ActionBarReload = createActionButton(useReloadMessage);
870
956
  // src/actions/useBeginMessageEdit.tsx
871
957
  var useBeginMessageEdit = () => {
872
958
  const { useMessage, useComposer } = useMessageContext();
873
- const isUser = useMessage((s) => s.message.role === "user");
874
- const isEditing = useComposer((s) => s.isEditing);
875
- if (!isUser || isEditing)
959
+ const disabled = useCombinedStore(
960
+ [useMessage, useComposer],
961
+ (m, c) => m.message.role !== "user" || c.isEditing
962
+ );
963
+ if (disabled)
876
964
  return null;
877
965
  return () => {
878
966
  const { edit } = useComposer.getState();
@@ -883,25 +971,41 @@ var useBeginMessageEdit = () => {
883
971
  // src/primitives/actionBar/ActionBarEdit.tsx
884
972
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
885
973
 
886
- // src/vercel/VercelAIAssistantProvider.tsx
887
- import { useCallback as useCallback2, useMemo as useMemo4 } from "react";
974
+ // src/adapters/vercel/VercelAIAssistantProvider.tsx
975
+ import { useCallback as useCallback3, useMemo as useMemo4 } from "react";
888
976
 
889
- // src/vercel/useDummyAIAssistantContext.tsx
890
- import { useState as useState3 } from "react";
977
+ // src/adapters/vercel/useDummyAIAssistantContext.tsx
978
+ import { useState as useState2 } from "react";
891
979
  import { create as create2 } from "zustand";
892
980
  var useDummyAIAssistantContext = () => {
893
- const [context] = useState3(() => {
981
+ const [context] = useState2(() => {
982
+ const scrollToBottomListeners = /* @__PURE__ */ new Set();
894
983
  const useThread = create2()(() => ({
895
984
  messages: [],
896
985
  isLoading: false,
897
- reload: async () => {
898
- throw new Error("Not implemented");
899
- },
900
986
  append: async () => {
901
987
  throw new Error("Not implemented");
902
988
  },
903
989
  stop: () => {
904
990
  throw new Error("Not implemented");
991
+ },
992
+ switchToBranch: () => {
993
+ throw new Error("Not implemented");
994
+ },
995
+ reload: async () => {
996
+ throw new Error("Not implemented");
997
+ },
998
+ isAtBottom: true,
999
+ scrollToBottom: () => {
1000
+ for (const listener of scrollToBottomListeners) {
1001
+ listener();
1002
+ }
1003
+ },
1004
+ onScrollToBottom: (callback) => {
1005
+ scrollToBottomListeners.add(callback);
1006
+ return () => {
1007
+ scrollToBottomListeners.delete(callback);
1008
+ };
905
1009
  }
906
1010
  }));
907
1011
  const useComposer = create2()(() => ({
@@ -916,7 +1020,7 @@ var useDummyAIAssistantContext = () => {
916
1020
  },
917
1021
  send: () => {
918
1022
  useThread.getState().append({
919
- role: "user",
1023
+ parentId: useThread.getState().messages.at(-1)?.id ?? ROOT_PARENT_ID,
920
1024
  content: [{ type: "text", text: useComposer.getState().value }]
921
1025
  });
922
1026
  useComposer.getState().setValue("");
@@ -925,43 +1029,39 @@ var useDummyAIAssistantContext = () => {
925
1029
  useThread.getState().stop();
926
1030
  }
927
1031
  }));
928
- const useBranchObserver = create2()(() => ({
929
- getBranchState: () => ({
930
- branchId: 0,
931
- branchCount: 1
932
- }),
933
- switchToBranch: () => {
934
- throw new Error("Not implemented");
935
- },
936
- editAt: async () => {
937
- throw new Error("Not implemented");
938
- },
939
- reloadAt: async () => {
940
- throw new Error("Not implemented");
941
- }
942
- }));
943
- return { useThread, useComposer, useBranchObserver };
1032
+ return { useThread, useComposer };
944
1033
  });
945
1034
  return context;
946
1035
  };
947
1036
 
948
- // src/vercel/VercelAIAssistantProvider.tsx
1037
+ // src/adapters/vercel/VercelAIAssistantProvider.tsx
949
1038
  var ThreadMessageCache = /* @__PURE__ */ new WeakMap();
950
- var vercelToThreadMessage = (message) => {
1039
+ var vercelToThreadMessage = (message, parentId, branchId, branchCount) => {
951
1040
  if (message.role !== "user" && message.role !== "assistant")
952
1041
  throw new Error("Unsupported role");
953
1042
  return {
1043
+ parentId,
954
1044
  id: message.id,
955
1045
  role: message.role,
956
- content: [{ type: "text", text: message.content }]
1046
+ content: [{ type: "text", text: message.content }],
1047
+ branchId,
1048
+ branchCount,
1049
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date()
957
1050
  };
958
1051
  };
959
- var vercelToCachedThreadMessages = (messages) => {
960
- return messages.map((m) => {
1052
+ var vercelToCachedThreadMessages = (messages, getBranchState) => {
1053
+ return messages.map((m, idx) => {
961
1054
  const cached = ThreadMessageCache.get(m);
962
- if (cached)
1055
+ const parentId = messages[idx - 1]?.id ?? ROOT_PARENT_ID;
1056
+ const { branchId, branchCount } = getBranchState(m.id);
1057
+ if (cached && cached.parentId === parentId && cached.branchId === branchId && cached.branchCount === branchCount)
963
1058
  return cached;
964
- const newMessage = vercelToThreadMessage(m);
1059
+ const newMessage = vercelToThreadMessage(
1060
+ m,
1061
+ parentId,
1062
+ branchId,
1063
+ branchCount
1064
+ );
965
1065
  ThreadMessageCache.set(m, newMessage);
966
1066
  return newMessage;
967
1067
  });
@@ -972,28 +1072,14 @@ var VercelAIAssistantProvider = ({
972
1072
  }) => {
973
1073
  const context = useDummyAIAssistantContext();
974
1074
  const vercel = "chat" in rest ? rest.chat : rest.assistant;
1075
+ const branches = useVercelAIBranches(vercel, context);
975
1076
  const messages = useMemo4(() => {
976
- return vercelToCachedThreadMessages(vercel.messages);
977
- }, [vercel.messages]);
978
- const maybeReload = "reload" in vercel ? vercel.reload : null;
979
- const reload = useCallback2(async () => {
980
- if (!maybeReload)
981
- throw new Error("Reload not supported");
982
- await maybeReload();
983
- }, [maybeReload]);
984
- const append = useCallback2(
985
- async (message) => {
986
- if (message.content[0]?.type !== "text") {
987
- throw new Error("Only text content is currently supported");
988
- }
989
- await vercel.append({
990
- role: message.role,
991
- content: message.content[0].text
992
- });
993
- },
994
- [vercel.append]
995
- );
996
- const stop = useCallback2(() => {
1077
+ return vercelToCachedThreadMessages(
1078
+ vercel.messages,
1079
+ branches.getBranchState
1080
+ );
1081
+ }, [vercel.messages, branches.getBranchState]);
1082
+ const stop = useCallback3(() => {
997
1083
  const lastMessage = vercel.messages.at(-1);
998
1084
  vercel.stop();
999
1085
  if (lastMessage?.role === "user") {
@@ -1002,17 +1088,15 @@ var VercelAIAssistantProvider = ({
1002
1088
  }, [vercel.messages, vercel.stop, vercel.setInput]);
1003
1089
  const isLoading = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1004
1090
  useMemo4(() => {
1005
- context.useThread.setState(
1006
- {
1007
- messages,
1008
- isLoading,
1009
- reload,
1010
- append,
1011
- stop
1012
- },
1013
- true
1014
- );
1015
- }, [context, messages, reload, append, stop, isLoading]);
1091
+ context.useThread.setState({
1092
+ messages,
1093
+ isLoading,
1094
+ stop,
1095
+ switchToBranch: branches.switchToBranch,
1096
+ append: branches.append,
1097
+ reload: branches.reload
1098
+ });
1099
+ }, [context, messages, isLoading, stop, branches]);
1016
1100
  useMemo4(() => {
1017
1101
  context.useComposer.setState({
1018
1102
  canCancel: isLoading,
@@ -1020,34 +1104,35 @@ var VercelAIAssistantProvider = ({
1020
1104
  setValue: vercel.setInput
1021
1105
  });
1022
1106
  }, [context, isLoading, vercel.input, vercel.setInput]);
1023
- const branches = useVercelAIBranches(vercel);
1024
- useMemo4(() => {
1025
- context.useBranchObserver.setState(branches, true);
1026
- }, [context, branches]);
1027
1107
  return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
1028
1108
  };
1029
1109
 
1030
- // src/vercel/VercelRSCAssistantProvider.tsx
1110
+ // src/adapters/vercel/VercelRSCAssistantProvider.tsx
1031
1111
  import {
1032
- useCallback as useCallback3,
1112
+ useCallback as useCallback4,
1033
1113
  useMemo as useMemo5
1034
1114
  } from "react";
1035
1115
  var ThreadMessageCache2 = /* @__PURE__ */ new WeakMap();
1036
- var vercelToThreadMessage2 = (message) => {
1116
+ var vercelToThreadMessage2 = (parentId, message) => {
1037
1117
  if (message.role !== "user" && message.role !== "assistant")
1038
1118
  throw new Error("Unsupported role");
1039
1119
  return {
1120
+ parentId,
1040
1121
  id: message.id,
1041
1122
  role: message.role,
1042
- content: [{ type: "ui", display: message.display }]
1123
+ content: [{ type: "ui", display: message.display }],
1124
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1125
+ branchId: 0,
1126
+ branchCount: 1
1043
1127
  };
1044
1128
  };
1045
1129
  var vercelToCachedThreadMessages2 = (messages) => {
1046
- return messages.map((m) => {
1130
+ return messages.map((m, idx) => {
1047
1131
  const cached = ThreadMessageCache2.get(m);
1048
- if (cached)
1132
+ const parentId = messages[idx - 1]?.id ?? ROOT_PARENT_ID;
1133
+ if (cached && cached.parentId === parentId)
1049
1134
  return cached;
1050
- const newMessage = vercelToThreadMessage2(m);
1135
+ const newMessage = vercelToThreadMessage2(parentId, m);
1051
1136
  ThreadMessageCache2.set(m, newMessage);
1052
1137
  return newMessage;
1053
1138
  });
@@ -1061,17 +1146,17 @@ var VercelRSCAssistantProvider = ({
1061
1146
  const messages = useMemo5(() => {
1062
1147
  return vercelToCachedThreadMessages2(vercelMessages);
1063
1148
  }, [vercelMessages]);
1064
- const append = useCallback3(
1149
+ const append = useCallback4(
1065
1150
  async (message) => {
1151
+ if (message.parentId !== (context.useThread.getState().messages.at(-1)?.id ?? ROOT_PARENT_ID))
1152
+ throw new Error("Unexpected: Message editing is not supported");
1066
1153
  if (message.content[0]?.type !== "text") {
1067
1154
  throw new Error("Only text content is currently supported");
1068
1155
  }
1069
- await vercelAppend({
1070
- role: message.role,
1071
- content: [{ type: "text", text: message.content[0].text }]
1072
- });
1156
+ context.useThread.getState().scrollToBottom();
1157
+ await vercelAppend(message);
1073
1158
  },
1074
- [vercelAppend]
1159
+ [context, vercelAppend]
1075
1160
  );
1076
1161
  useMemo5(() => {
1077
1162
  context.useThread.setState({