@assistant-ui/react 0.0.8 → 0.0.10

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