@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.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({