@assistant-ui/react 0.0.20 → 0.0.22

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
@@ -4,6 +4,156 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // src/actions/useCopyMessage.tsx
8
+ import { useCallback } from "react";
9
+
10
+ // src/context/MessageContext.ts
11
+ import { createContext, useContext } from "react";
12
+ var MessageContext = createContext(null);
13
+ var useMessageContext = () => {
14
+ const context = useContext(MessageContext);
15
+ if (!context)
16
+ throw new Error(
17
+ "This component can only be used inside a component passed to <ThreadPrimitive.Messages components={...} />."
18
+ );
19
+ return context;
20
+ };
21
+
22
+ // src/utils/combined/useCombinedStore.ts
23
+ import { useMemo } from "react";
24
+
25
+ // src/utils/combined/createCombinedStore.ts
26
+ import { useSyncExternalStore } from "react";
27
+ var createCombinedStore = (stores) => {
28
+ const subscribe = (callback) => {
29
+ const unsubscribes = stores.map((store) => store.subscribe(callback));
30
+ return () => {
31
+ for (const unsub of unsubscribes) {
32
+ unsub();
33
+ }
34
+ };
35
+ };
36
+ return (selector) => {
37
+ const getSnapshot = () => selector(...stores.map((store) => store.getState()));
38
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
39
+ };
40
+ };
41
+
42
+ // src/utils/combined/useCombinedStore.ts
43
+ var useCombinedStore = (stores, selector) => {
44
+ const useCombined = useMemo(() => createCombinedStore(stores), stores);
45
+ return useCombined(selector);
46
+ };
47
+
48
+ // src/utils/getMessageText.tsx
49
+ var getMessageText = (message) => {
50
+ const textParts = message.content.filter(
51
+ (part) => part.type === "text"
52
+ );
53
+ return textParts.map((part) => part.text).join("\n\n");
54
+ };
55
+
56
+ // src/actions/useCopyMessage.tsx
57
+ var useCopyMessage = ({ copiedDuration = 3e3 }) => {
58
+ const { useMessage, useComposer } = useMessageContext();
59
+ const hasCopyableContent = useCombinedStore(
60
+ [useMessage, useComposer],
61
+ (m, c) => {
62
+ return c.isEditing || m.message.content.some((c2) => c2.type === "text");
63
+ }
64
+ );
65
+ const callback = useCallback(() => {
66
+ const { isEditing, value: composerValue } = useComposer.getState();
67
+ const { message, setIsCopied } = useMessage.getState();
68
+ const valueToCopy = isEditing ? composerValue : getMessageText(message);
69
+ navigator.clipboard.writeText(valueToCopy);
70
+ setIsCopied(true);
71
+ setTimeout(() => setIsCopied(false), copiedDuration);
72
+ }, [useComposer, useMessage, copiedDuration]);
73
+ if (!hasCopyableContent) return null;
74
+ return callback;
75
+ };
76
+
77
+ // src/actions/useReloadMessage.tsx
78
+ import { useCallback as useCallback2 } from "react";
79
+
80
+ // src/context/ThreadContext.ts
81
+ import { createContext as createContext2, useContext as useContext2 } from "react";
82
+ var ThreadContext = createContext2(null);
83
+ var useThreadContext = () => {
84
+ const context = useContext2(ThreadContext);
85
+ if (!context)
86
+ throw new Error("This component must be used within an AssistantProvider.");
87
+ return context;
88
+ };
89
+
90
+ // src/actions/useReloadMessage.tsx
91
+ var useReloadMessage = () => {
92
+ const { useThread, useViewport } = useThreadContext();
93
+ const { useMessage } = useMessageContext();
94
+ const disabled = useCombinedStore(
95
+ [useThread, useMessage],
96
+ (t, m) => t.isRunning || m.message.role !== "assistant"
97
+ );
98
+ const callback = useCallback2(() => {
99
+ const { parentId } = useMessage.getState();
100
+ useThread.getState().startRun(parentId);
101
+ useViewport.getState().scrollToBottom();
102
+ }, [useMessage, useThread, useViewport]);
103
+ if (disabled) return null;
104
+ return callback;
105
+ };
106
+
107
+ // src/actions/useBeginMessageEdit.tsx
108
+ import { useCallback as useCallback3 } from "react";
109
+ var useBeginMessageEdit = () => {
110
+ const { useMessage, useComposer } = useMessageContext();
111
+ const disabled = useCombinedStore(
112
+ [useMessage, useComposer],
113
+ (m, c) => m.message.role !== "user" || c.isEditing
114
+ );
115
+ const callback = useCallback3(() => {
116
+ const { edit } = useComposer.getState();
117
+ edit();
118
+ }, [useComposer]);
119
+ if (disabled) return null;
120
+ return callback;
121
+ };
122
+
123
+ // src/actions/useGoToNextBranch.tsx
124
+ import { useCallback as useCallback4 } from "react";
125
+ var useGoToNextBranch = () => {
126
+ const { useThread } = useThreadContext();
127
+ const { useMessage, useComposer } = useMessageContext();
128
+ const disabled = useCombinedStore(
129
+ [useMessage, useComposer],
130
+ (m, c) => c.isEditing || m.branches.indexOf(m.message.id) + 1 >= m.branches.length
131
+ );
132
+ const callback = useCallback4(() => {
133
+ const { message, branches } = useMessage.getState();
134
+ useThread.getState().switchToBranch(branches[branches.indexOf(message.id) + 1]);
135
+ }, [useMessage, useThread]);
136
+ if (disabled) return null;
137
+ return callback;
138
+ };
139
+
140
+ // src/actions/useGoToPreviousBranch.tsx
141
+ import { useCallback as useCallback5 } from "react";
142
+ var useGoToPreviousBranch = () => {
143
+ const { useThread } = useThreadContext();
144
+ const { useMessage, useComposer } = useMessageContext();
145
+ const disabled = useCombinedStore(
146
+ [useMessage, useComposer],
147
+ (m, c) => c.isEditing || m.branches.indexOf(m.message.id) <= 0
148
+ );
149
+ const callback = useCallback5(() => {
150
+ const { message, branches } = useMessage.getState();
151
+ useThread.getState().switchToBranch(branches[branches.indexOf(message.id) - 1]);
152
+ }, [useMessage, useThread]);
153
+ if (disabled) return null;
154
+ return callback;
155
+ };
156
+
7
157
  // src/primitives/thread/index.ts
8
158
  var thread_exports = {};
9
159
  __export(thread_exports, {
@@ -28,21 +178,9 @@ var ThreadRoot = forwardRef(
28
178
  }
29
179
  );
30
180
 
31
- // src/utils/context/AssistantContext.ts
32
- import { createContext, useContext } from "react";
33
- var AssistantContext = createContext(null);
34
- var useAssistantContext = () => {
35
- const context = useContext(AssistantContext);
36
- if (!context)
37
- throw new Error(
38
- "This component must be used within a AssistantProvider."
39
- );
40
- return context;
41
- };
42
-
43
181
  // src/primitives/thread/ThreadIf.tsx
44
182
  var useThreadIf = (props) => {
45
- const { useThread } = useAssistantContext();
183
+ const { useThread } = useThreadContext();
46
184
  return useThread((thread) => {
47
185
  if (props.empty === true && thread.messages.length !== 0) return false;
48
186
  if (props.empty === false && thread.messages.length === 0) return false;
@@ -113,7 +251,7 @@ import { useCallbackRef as useCallbackRef2 } from "@radix-ui/react-use-callback-
113
251
  import { useEffect as useEffect2 } from "react";
114
252
  var useOnScrollToBottom = (callback) => {
115
253
  const callbackRef = useCallbackRef2(callback);
116
- const { useViewport } = useAssistantContext();
254
+ const { useViewport } = useThreadContext();
117
255
  useEffect2(() => {
118
256
  return useViewport.getState().onScrollToBottom(() => {
119
257
  callbackRef();
@@ -127,7 +265,7 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
127
265
  const messagesEndRef = useRef(null);
128
266
  const divRef = useRef(null);
129
267
  const ref = useComposedRefs(forwardedRef, divRef);
130
- const { useViewport } = useAssistantContext();
268
+ const { useViewport } = useThreadContext();
131
269
  const firstRenderRef = useRef(true);
132
270
  const isScrollingToBottomRef = useRef(false);
133
271
  const lastScrollTop = useRef(0);
@@ -175,78 +313,23 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
175
313
  );
176
314
  });
177
315
 
178
- // src/utils/context/useComposerContext.ts
179
- import { useContext as useContext3 } from "react";
180
-
181
- // src/utils/context/useMessageContext.ts
182
- import { createContext as createContext2, useContext as useContext2 } from "react";
183
- var MessageContext = createContext2(null);
184
- var useMessageContext = () => {
185
- const context = useContext2(MessageContext);
186
- if (!context)
187
- throw new Error(
188
- "This component must be used within a MessagePrimitive.Provider."
189
- );
190
- return context;
191
- };
192
-
193
- // src/utils/context/useComposerContext.ts
194
- var useComposerContext = () => {
195
- const { useComposer: useAssisstantComposer } = useAssistantContext();
196
- const { useComposer: useMessageComposer } = useContext3(MessageContext) ?? {};
197
- return {
198
- useComposer: useMessageComposer ?? useAssisstantComposer,
199
- type: useMessageComposer ? "message" : "assistant"
200
- };
201
- };
202
-
203
- // src/primitives/composer/ComposerIf.tsx
204
- var useComposerIf = (props) => {
205
- const { useComposer } = useComposerContext();
206
- return useComposer((composer) => {
207
- if (props.editing === true && !composer.isEditing) return false;
208
- if (props.editing === false && composer.isEditing) return false;
209
- return true;
210
- });
211
- };
212
- var ComposerIf = ({ children, ...query }) => {
213
- const result = useComposerIf(query);
214
- return result ? children : null;
215
- };
216
-
217
- // src/primitives/message/index.ts
218
- var message_exports = {};
219
- __export(message_exports, {
220
- Content: () => MessageContent,
221
- If: () => MessageIf,
222
- InProgress: () => MessageInProgress,
223
- Provider: () => MessageProvider,
224
- Root: () => MessageRoot
225
- });
226
-
227
- // src/primitives/message/MessageProvider.tsx
228
- import { useMemo, useState } from "react";
316
+ // src/context/providers/MessageProvider.tsx
317
+ import { useEffect as useEffect3, useState } from "react";
229
318
  import { create as create2 } from "zustand";
230
319
 
231
- // src/utils/context/getMessageText.tsx
232
- var getMessageText = (message) => {
233
- const textParts = message.content.filter(
234
- (part) => part.type === "text"
235
- );
236
- return textParts.map((part) => part.text).join("\n\n");
237
- };
320
+ // src/context/stores/MessageComposer.ts
321
+ import { create } from "zustand";
238
322
 
239
- // src/utils/context/stores/ComposerStore.ts
240
- import {
241
- create
242
- } from "zustand";
323
+ // src/context/stores/BaseComposer.ts
243
324
  var makeBaseComposer = (set) => ({
244
325
  value: "",
245
326
  setValue: (value) => {
246
327
  set({ value });
247
328
  }
248
329
  });
249
- var makeMessageComposerStore = ({
330
+
331
+ // src/context/stores/MessageComposer.ts
332
+ var makeEditComposerStore = ({
250
333
  onEdit,
251
334
  onSend
252
335
  }) => create()((set, get, store) => ({
@@ -267,34 +350,30 @@ var makeMessageComposerStore = ({
267
350
  return true;
268
351
  }
269
352
  }));
270
- var makeThreadComposerStore = (useThread) => create()((set, get, store) => {
271
- return {
272
- ...makeBaseComposer(set, get, store),
273
- isEditing: true,
274
- send: () => {
275
- const { value } = get();
276
- set({ value: "" });
277
- useThread.getState().append({
278
- parentId: useThread.getState().messages.at(-1)?.id ?? null,
279
- content: [{ type: "text", text: value }]
280
- });
281
- },
282
- cancel: () => {
283
- const thread = useThread.getState();
284
- if (!thread.isRunning) return false;
285
- useThread.getState().cancelRun();
286
- return true;
287
- }
288
- };
289
- });
290
353
 
291
- // src/primitives/message/MessageProvider.tsx
354
+ // src/context/providers/MessageProvider.tsx
292
355
  import { jsx as jsx4 } from "react/jsx-runtime";
293
356
  var getIsLast = (thread, message) => {
294
357
  return thread.messages[thread.messages.length - 1]?.id === message.id;
295
358
  };
296
- var useMessageContext2 = () => {
297
- const { useThread } = useAssistantContext();
359
+ var syncMessage = (thread, useMessage, messageIndex) => {
360
+ const parentId = thread.messages[messageIndex - 1]?.id ?? null;
361
+ const message = thread.messages[messageIndex];
362
+ if (!message) return;
363
+ const isLast = getIsLast(thread, message);
364
+ const branches = thread.getBranches(message.id);
365
+ const currentState = useMessage.getState();
366
+ if (currentState.message === message && currentState.parentId === parentId && currentState.branches === branches && currentState.isLast === isLast)
367
+ return;
368
+ useMessage.setState({
369
+ message,
370
+ parentId,
371
+ branches,
372
+ isLast
373
+ });
374
+ };
375
+ var useMessageContext2 = (messageIndex) => {
376
+ const { useThread } = useThreadContext();
298
377
  const [context] = useState(() => {
299
378
  const useMessage = create2((set) => ({
300
379
  message: null,
@@ -314,7 +393,7 @@ var useMessageContext2 = () => {
314
393
  set({ isHovering: value });
315
394
  }
316
395
  }));
317
- const useComposer = makeMessageComposerStore({
396
+ const useComposer = makeEditComposerStore({
318
397
  onEdit: () => {
319
398
  const message = useMessage.getState().message;
320
399
  if (message.role !== "user")
@@ -339,58 +418,51 @@ var useMessageContext2 = () => {
339
418
  });
340
419
  }
341
420
  });
421
+ syncMessage(useThread.getState(), useMessage, messageIndex);
342
422
  return { useMessage, useComposer };
343
423
  });
424
+ useEffect3(() => {
425
+ return useThread.subscribe((thread) => {
426
+ syncMessage(thread, context.useMessage, messageIndex);
427
+ });
428
+ }, [context, useThread, messageIndex]);
344
429
  return context;
345
430
  };
346
431
  var MessageProvider = ({
347
- message,
348
- parentId,
432
+ messageIndex,
349
433
  children
350
434
  }) => {
351
- const { useThread } = useAssistantContext();
352
- const context = useMessageContext2();
353
- const isLast = useThread((thread) => getIsLast(thread, message));
354
- const branches = useThread((thread) => thread.getBranches(message.id));
355
- useMemo(() => {
356
- context.useMessage.setState({
357
- message,
358
- parentId,
359
- branches,
360
- isLast
361
- });
362
- }, [context, message, parentId, branches, isLast]);
435
+ const context = useMessageContext2(messageIndex);
363
436
  return /* @__PURE__ */ jsx4(MessageContext.Provider, { value: context, children });
364
437
  };
365
438
 
366
- // src/primitives/message/MessageRoot.tsx
367
- import { composeEventHandlers as composeEventHandlers2 } from "@radix-ui/primitive";
368
- import {
369
- Primitive as Primitive3
370
- } from "@radix-ui/react-primitive";
371
- import { forwardRef as forwardRef3 } from "react";
372
- import { jsx as jsx5 } from "react/jsx-runtime";
373
- var MessageRoot = forwardRef3(
374
- ({ onMouseEnter, onMouseLeave, ...rest }, ref) => {
375
- const { useMessage } = useMessageContext();
376
- const setIsHovering = useMessage((s) => s.setIsHovering);
377
- const handleMouseEnter = () => {
378
- setIsHovering(true);
379
- };
380
- const handleMouseLeave = () => {
381
- setIsHovering(false);
382
- };
383
- return /* @__PURE__ */ jsx5(
384
- Primitive3.div,
385
- {
386
- ...rest,
387
- ref,
388
- onMouseEnter: composeEventHandlers2(onMouseEnter, handleMouseEnter),
389
- onMouseLeave: composeEventHandlers2(onMouseLeave, handleMouseLeave)
390
- }
391
- );
392
- }
393
- );
439
+ // src/context/ComposerContext.ts
440
+ import { useContext as useContext3, useMemo as useMemo2 } from "react";
441
+ var useComposerContext = () => {
442
+ const { useComposer } = useThreadContext();
443
+ const { useComposer: useEditComposer } = useContext3(MessageContext) ?? {};
444
+ return useMemo2(
445
+ () => ({
446
+ useComposer: useEditComposer ?? useComposer,
447
+ type: useEditComposer ? "edit" : "new"
448
+ }),
449
+ [useEditComposer, useComposer]
450
+ );
451
+ };
452
+
453
+ // src/primitives/composer/ComposerIf.tsx
454
+ var useComposerIf = (props) => {
455
+ const { useComposer } = useComposerContext();
456
+ return useComposer((composer) => {
457
+ if (props.editing === true && !composer.isEditing) return false;
458
+ if (props.editing === false && composer.isEditing) return false;
459
+ return true;
460
+ });
461
+ };
462
+ var ComposerIf = ({ children, ...query }) => {
463
+ const result = useComposerIf(query);
464
+ return result ? children : null;
465
+ };
394
466
 
395
467
  // src/primitives/message/MessageIf.tsx
396
468
  var useMessageIf = (props) => {
@@ -410,162 +482,8 @@ var MessageIf = ({ children, ...query }) => {
410
482
  return result ? children : null;
411
483
  };
412
484
 
413
- // src/utils/context/combined/useCombinedStore.ts
414
- import { useMemo as useMemo2 } from "react";
415
-
416
- // src/utils/context/combined/createCombinedStore.ts
417
- import { useSyncExternalStore } from "react";
418
- var createCombinedStore = (stores) => {
419
- const subscribe = (callback) => {
420
- const unsubscribes = stores.map((store) => store.subscribe(callback));
421
- return () => {
422
- for (const unsub of unsubscribes) {
423
- unsub();
424
- }
425
- };
426
- };
427
- return (selector) => {
428
- const getSnapshot = () => selector(...stores.map((store) => store.getState()));
429
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
430
- };
431
- };
432
-
433
- // src/utils/context/combined/useCombinedStore.ts
434
- var useCombinedStore = (stores, selector) => {
435
- const useCombined = useMemo2(() => createCombinedStore(stores), stores);
436
- return useCombined(selector);
437
- };
438
-
439
- // src/utils/context/useContentPartContext.ts
440
- import { createContext as createContext3, useContext as useContext4 } from "react";
441
- var ContentPartContext = createContext3(null);
442
- var useContentPartContext = () => {
443
- const context = useContext4(ContentPartContext);
444
- if (!context)
445
- throw new Error(
446
- "This component must be used within a ContentPartPrimitive.Provider."
447
- );
448
- return context;
449
- };
450
-
451
- // src/primitives/contentPart/ContentPartInProgressIndicator.tsx
452
- var ContentPartInProgressIndicator = () => {
453
- const { useMessage } = useMessageContext();
454
- const { useContentPart } = useContentPartContext();
455
- const indicator = useCombinedStore(
456
- [useMessage, useContentPart],
457
- (m, c) => c.status === "in_progress" ? m.inProgressIndicator : null
458
- );
459
- return indicator;
460
- };
461
-
462
- // src/primitives/contentPart/ContentPartProvider.tsx
463
- import { useMemo as useMemo3, useState as useState2 } from "react";
464
- import { create as create3 } from "zustand";
465
- import { jsx as jsx6 } from "react/jsx-runtime";
466
- var useContentPartContext2 = () => {
467
- const [context] = useState2(() => {
468
- const useContentPart = create3(() => ({
469
- part: null,
470
- status: "done"
471
- }));
472
- return { useContentPart };
473
- });
474
- return context;
475
- };
476
- var ContentPartProvider = ({
477
- part,
478
- status,
479
- children
480
- }) => {
481
- const context = useContentPartContext2();
482
- useMemo3(() => {
483
- context.useContentPart.setState(
484
- {
485
- part,
486
- status
487
- },
488
- true
489
- );
490
- }, [context, part, status]);
491
- return /* @__PURE__ */ jsx6(ContentPartContext.Provider, { value: context, children });
492
- };
493
-
494
- // src/primitives/message/MessageContent.tsx
495
- import { Fragment, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
496
- var defaultComponents = {
497
- Text: ({ part }) => /* @__PURE__ */ jsxs2(Fragment, { children: [
498
- part.text,
499
- /* @__PURE__ */ jsx7(ContentPartInProgressIndicator, {})
500
- ] }),
501
- Image: () => null,
502
- UI: ({ part }) => part.display,
503
- tools: {
504
- Fallback: () => null
505
- }
506
- };
507
- var MessageContent = ({
508
- components: {
509
- Text = defaultComponents.Text,
510
- Image = defaultComponents.Image,
511
- UI = defaultComponents.UI,
512
- tools: { by_name = {}, Fallback = defaultComponents.tools.Fallback } = {}
513
- } = {}
514
- }) => {
515
- const { useMessage } = useMessageContext();
516
- const message = useMessage((s) => s.message);
517
- const content = message.content;
518
- const status = message.role === "assistant" ? message.status : "done";
519
- return /* @__PURE__ */ jsx7(Fragment, { children: content.map((part, i) => {
520
- const key = i;
521
- const type = part.type;
522
- let component = null;
523
- switch (type) {
524
- case "text":
525
- component = /* @__PURE__ */ jsx7(Text, { part });
526
- break;
527
- case "image":
528
- component = /* @__PURE__ */ jsx7(Image, { part });
529
- break;
530
- case "ui":
531
- component = /* @__PURE__ */ jsx7(UI, { part });
532
- break;
533
- case "tool-call": {
534
- const Tool = by_name[part.name] || Fallback;
535
- component = /* @__PURE__ */ jsx7(Tool, { part });
536
- break;
537
- }
538
- default:
539
- throw new Error(`Unknown content part type: ${type}`);
540
- }
541
- return /* @__PURE__ */ jsx7(
542
- ContentPartProvider,
543
- {
544
- part,
545
- status: i === content.length - 1 ? status : "done",
546
- children: component
547
- },
548
- key
549
- );
550
- }) });
551
- };
552
-
553
- // src/primitives/message/MessageInProgress.tsx
554
- import {
555
- Primitive as Primitive4
556
- } from "@radix-ui/react-primitive";
557
- import { forwardRef as forwardRef4, useMemo as useMemo4 } from "react";
558
- import { jsx as jsx8 } from "react/jsx-runtime";
559
- var MessageInProgress = forwardRef4((props, ref) => {
560
- const { useMessage } = useMessageContext();
561
- useMemo4(() => {
562
- useMessage.getState().setInProgressIndicator(/* @__PURE__ */ jsx8(Primitive4.div, { ...props, ref }));
563
- }, [useMessage, props, ref]);
564
- return null;
565
- });
566
-
567
485
  // src/primitives/thread/ThreadMessages.tsx
568
- import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
486
+ import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
569
487
  var getComponents = (components) => {
570
488
  return {
571
489
  EditComposer: components.EditComposer ?? components.UserMessage ?? components.Message,
@@ -574,64 +492,62 @@ var getComponents = (components) => {
574
492
  };
575
493
  };
576
494
  var ThreadMessages = ({ components }) => {
577
- const { useThread } = useAssistantContext();
578
- const thread = useThread();
579
- const messages = thread.messages;
495
+ const { useThread } = useThreadContext();
496
+ const messagesLength = useThread((t) => t.messages.length);
580
497
  const { UserMessage, EditComposer, AssistantMessage } = getComponents(components);
581
- if (messages.length === 0) return null;
582
- return /* @__PURE__ */ jsx9(Fragment2, { children: messages.map((message, idx) => {
583
- const parentId = messages[idx - 1]?.id ?? null;
584
- return /* @__PURE__ */ jsxs3(
498
+ if (messagesLength === 0) return null;
499
+ return /* @__PURE__ */ jsx5(Fragment, { children: new Array(messagesLength).fill(null).map((_, idx) => {
500
+ const messageIndex = idx;
501
+ return /* @__PURE__ */ jsxs2(
585
502
  MessageProvider,
586
503
  {
587
- message,
588
- parentId,
504
+ messageIndex,
589
505
  children: [
590
- /* @__PURE__ */ jsxs3(MessageIf, { user: true, children: [
591
- /* @__PURE__ */ jsx9(ComposerIf, { editing: false, children: /* @__PURE__ */ jsx9(UserMessage, {}) }),
592
- /* @__PURE__ */ jsx9(ComposerIf, { editing: true, children: /* @__PURE__ */ jsx9(EditComposer, {}) })
506
+ /* @__PURE__ */ jsxs2(MessageIf, { user: true, children: [
507
+ /* @__PURE__ */ jsx5(ComposerIf, { editing: false, children: /* @__PURE__ */ jsx5(UserMessage, {}) }),
508
+ /* @__PURE__ */ jsx5(ComposerIf, { editing: true, children: /* @__PURE__ */ jsx5(EditComposer, {}) })
593
509
  ] }),
594
- /* @__PURE__ */ jsx9(MessageIf, { assistant: true, children: /* @__PURE__ */ jsx9(AssistantMessage, {}) })
510
+ /* @__PURE__ */ jsx5(MessageIf, { assistant: true, children: /* @__PURE__ */ jsx5(AssistantMessage, {}) })
595
511
  ]
596
512
  },
597
- parentId ?? "__ROOT__"
513
+ messageIndex
598
514
  );
599
515
  }) });
600
516
  };
601
517
 
602
518
  // src/primitives/thread/ThreadScrollToBottom.tsx
603
- import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
519
+ import { composeEventHandlers as composeEventHandlers2 } from "@radix-ui/primitive";
604
520
  import {
605
- Primitive as Primitive5
521
+ Primitive as Primitive3
606
522
  } from "@radix-ui/react-primitive";
607
- import { forwardRef as forwardRef5 } from "react";
608
- import { jsx as jsx10 } from "react/jsx-runtime";
609
- var ThreadScrollToBottom = forwardRef5(({ onClick, ...rest }, ref) => {
610
- const { useViewport } = useAssistantContext();
523
+ import { forwardRef as forwardRef3 } from "react";
524
+ import { jsx as jsx6 } from "react/jsx-runtime";
525
+ var ThreadScrollToBottom = forwardRef3(({ onClick, ...rest }, ref) => {
526
+ const { useViewport } = useThreadContext();
611
527
  const isAtBottom = useViewport((s) => s.isAtBottom);
612
528
  const handleScrollToBottom = () => {
613
529
  useViewport.getState().scrollToBottom();
614
530
  };
615
- return /* @__PURE__ */ jsx10(
616
- Primitive5.button,
531
+ return /* @__PURE__ */ jsx6(
532
+ Primitive3.button,
617
533
  {
618
534
  ...rest,
619
535
  disabled: isAtBottom,
620
536
  ref,
621
- onClick: composeEventHandlers3(onClick, handleScrollToBottom)
537
+ onClick: composeEventHandlers2(onClick, handleScrollToBottom)
622
538
  }
623
539
  );
624
540
  });
625
541
 
626
542
  // src/primitives/thread/ThreadSuggestion.tsx
627
- import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
543
+ import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
628
544
  import {
629
- Primitive as Primitive6
545
+ Primitive as Primitive4
630
546
  } from "@radix-ui/react-primitive";
631
- import { forwardRef as forwardRef6 } from "react";
632
- import { jsx as jsx11 } from "react/jsx-runtime";
633
- var ThreadSuggestion = forwardRef6(({ onClick, prompt, method, autoSend: send, ...rest }, ref) => {
634
- const { useThread, useComposer } = useAssistantContext();
547
+ import { forwardRef as forwardRef4 } from "react";
548
+ import { jsx as jsx7 } from "react/jsx-runtime";
549
+ var ThreadSuggestion = forwardRef4(({ onClick, prompt, method, autoSend: send, ...rest }, ref) => {
550
+ const { useThread, useComposer } = useThreadContext();
635
551
  const isDisabled = useThread((t) => t.isRunning);
636
552
  const handleApplySuggestion = () => {
637
553
  const thread = useThread.getState();
@@ -641,13 +557,13 @@ var ThreadSuggestion = forwardRef6(({ onClick, prompt, method, autoSend: send, .
641
557
  composer.send();
642
558
  }
643
559
  };
644
- return /* @__PURE__ */ jsx11(
645
- Primitive6.button,
560
+ return /* @__PURE__ */ jsx7(
561
+ Primitive4.button,
646
562
  {
647
563
  ...rest,
648
564
  disabled: isDisabled,
649
565
  ref,
650
- onClick: composeEventHandlers4(onClick, handleApplySuggestion)
566
+ onClick: composeEventHandlers3(onClick, handleApplySuggestion)
651
567
  }
652
568
  );
653
569
  });
@@ -663,16 +579,16 @@ __export(composer_exports, {
663
579
  });
664
580
 
665
581
  // src/primitives/composer/ComposerRoot.tsx
666
- import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
582
+ import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
667
583
  import { useComposedRefs as useComposedRefs2 } from "@radix-ui/react-compose-refs";
668
584
  import {
669
- Primitive as Primitive7
585
+ Primitive as Primitive5
670
586
  } from "@radix-ui/react-primitive";
671
- import { forwardRef as forwardRef7, useRef as useRef2 } from "react";
672
- import { jsx as jsx12 } from "react/jsx-runtime";
673
- var ComposerRoot = forwardRef7(
587
+ import { forwardRef as forwardRef5, useRef as useRef2 } from "react";
588
+ import { jsx as jsx8 } from "react/jsx-runtime";
589
+ var ComposerRoot = forwardRef5(
674
590
  ({ onSubmit, ...rest }, forwardedRef) => {
675
- const { useViewport } = useAssistantContext();
591
+ const { useViewport } = useThreadContext();
676
592
  const { useComposer } = useComposerContext();
677
593
  const formRef = useRef2(null);
678
594
  const ref = useComposedRefs2(forwardedRef, formRef);
@@ -683,32 +599,32 @@ var ComposerRoot = forwardRef7(
683
599
  composerState.send();
684
600
  useViewport.getState().scrollToBottom();
685
601
  };
686
- return /* @__PURE__ */ jsx12(
687
- Primitive7.form,
602
+ return /* @__PURE__ */ jsx8(
603
+ Primitive5.form,
688
604
  {
689
605
  ...rest,
690
606
  ref,
691
- onSubmit: composeEventHandlers5(onSubmit, handleSubmit)
607
+ onSubmit: composeEventHandlers4(onSubmit, handleSubmit)
692
608
  }
693
609
  );
694
610
  }
695
611
  );
696
612
 
697
613
  // src/primitives/composer/ComposerInput.tsx
698
- import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
614
+ import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
699
615
  import { useComposedRefs as useComposedRefs3 } from "@radix-ui/react-compose-refs";
700
616
  import { Slot } from "@radix-ui/react-slot";
701
617
  import {
702
- forwardRef as forwardRef8,
703
- useCallback,
704
- useEffect as useEffect3,
618
+ forwardRef as forwardRef6,
619
+ useCallback as useCallback6,
620
+ useEffect as useEffect4,
705
621
  useRef as useRef3
706
622
  } from "react";
707
623
  import TextareaAutosize from "react-textarea-autosize";
708
- import { jsx as jsx13 } from "react/jsx-runtime";
709
- var ComposerInput = forwardRef8(
624
+ import { jsx as jsx9 } from "react/jsx-runtime";
625
+ var ComposerInput = forwardRef6(
710
626
  ({ autoFocus = false, asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
711
- const { useThread, useViewport } = useAssistantContext();
627
+ const { useThread, useViewport } = useThreadContext();
712
628
  const { useComposer, type } = useComposerContext();
713
629
  const value = useComposer((c) => {
714
630
  if (!c.isEditing) return "";
@@ -734,7 +650,7 @@ var ComposerInput = forwardRef8(
734
650
  const textareaRef = useRef3(null);
735
651
  const ref = useComposedRefs3(forwardedRef, textareaRef);
736
652
  const autoFocusEnabled = autoFocus && !disabled;
737
- const focus = useCallback(() => {
653
+ const focus = useCallback6(() => {
738
654
  const textarea = textareaRef.current;
739
655
  if (!textarea || !autoFocusEnabled) return;
740
656
  textarea.focus();
@@ -743,25 +659,25 @@ var ComposerInput = forwardRef8(
743
659
  textareaRef.current.value.length
744
660
  );
745
661
  }, [autoFocusEnabled]);
746
- useEffect3(() => focus(), [focus]);
662
+ useEffect4(() => focus(), [focus]);
747
663
  useOnScrollToBottom(() => {
748
- if (type === "assistant") {
664
+ if (type === "new") {
749
665
  focus();
750
666
  }
751
667
  });
752
- return /* @__PURE__ */ jsx13(
668
+ return /* @__PURE__ */ jsx9(
753
669
  Component,
754
670
  {
755
671
  value,
756
672
  ...rest,
757
673
  ref,
758
674
  disabled,
759
- onChange: composeEventHandlers6(onChange, (e) => {
675
+ onChange: composeEventHandlers5(onChange, (e) => {
760
676
  const composerState = useComposer.getState();
761
677
  if (!composerState.isEditing) return;
762
678
  return composerState.setValue(e.target.value);
763
679
  }),
764
- onKeyDown: composeEventHandlers6(onKeyDown, handleKeyPress)
680
+ onKeyDown: composeEventHandlers5(onKeyDown, handleKeyPress)
765
681
  }
766
682
  );
767
683
  }
@@ -769,16 +685,16 @@ var ComposerInput = forwardRef8(
769
685
 
770
686
  // src/primitives/composer/ComposerSend.tsx
771
687
  import {
772
- Primitive as Primitive8
688
+ Primitive as Primitive6
773
689
  } from "@radix-ui/react-primitive";
774
- import { forwardRef as forwardRef9 } from "react";
775
- import { jsx as jsx14 } from "react/jsx-runtime";
776
- var ComposerSend = forwardRef9(
690
+ import { forwardRef as forwardRef7 } from "react";
691
+ import { jsx as jsx10 } from "react/jsx-runtime";
692
+ var ComposerSend = forwardRef7(
777
693
  ({ disabled, ...rest }, ref) => {
778
694
  const { useComposer } = useComposerContext();
779
695
  const hasValue = useComposer((c) => c.isEditing && c.value.length > 0);
780
- return /* @__PURE__ */ jsx14(
781
- Primitive8.button,
696
+ return /* @__PURE__ */ jsx10(
697
+ Primitive6.button,
782
698
  {
783
699
  type: "submit",
784
700
  ...rest,
@@ -790,122 +706,253 @@ var ComposerSend = forwardRef9(
790
706
  );
791
707
 
792
708
  // src/primitives/composer/ComposerCancel.tsx
793
- import { composeEventHandlers as composeEventHandlers7 } from "@radix-ui/primitive";
709
+ import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
794
710
  import {
795
- Primitive as Primitive9
711
+ Primitive as Primitive7
796
712
  } from "@radix-ui/react-primitive";
797
- import { forwardRef as forwardRef10 } from "react";
798
- import { jsx as jsx15 } from "react/jsx-runtime";
799
- var ComposerCancel = forwardRef10(({ onClick, ...rest }, ref) => {
713
+ import { forwardRef as forwardRef8 } from "react";
714
+ import { jsx as jsx11 } from "react/jsx-runtime";
715
+ var ComposerCancel = forwardRef8(({ onClick, ...rest }, ref) => {
800
716
  const { useComposer } = useComposerContext();
801
717
  const handleCancel = () => {
802
718
  useComposer.getState().cancel();
803
719
  };
804
- return /* @__PURE__ */ jsx15(
805
- Primitive9.button,
720
+ return /* @__PURE__ */ jsx11(
721
+ Primitive7.button,
806
722
  {
807
723
  type: "button",
808
724
  ...rest,
809
725
  ref,
810
- onClick: composeEventHandlers7(onClick, handleCancel)
726
+ onClick: composeEventHandlers6(onClick, handleCancel)
811
727
  }
812
728
  );
813
729
  });
814
730
 
815
- // src/primitives/branchPicker/index.ts
816
- var branchPicker_exports = {};
817
- __export(branchPicker_exports, {
818
- Count: () => BranchPickerCount,
819
- Next: () => BranchPickerNext,
820
- Number: () => BranchPickerNumber,
821
- Previous: () => BranchPickerPrevious,
822
- Root: () => BranchPickerRoot
731
+ // src/primitives/message/index.ts
732
+ var message_exports = {};
733
+ __export(message_exports, {
734
+ Content: () => MessageContent,
735
+ If: () => MessageIf,
736
+ InProgress: () => MessageInProgress,
737
+ Root: () => MessageRoot
823
738
  });
824
739
 
825
- // src/actions/useGoToNextBranch.tsx
826
- import { useCallback as useCallback2 } from "react";
827
- var useGoToNextBranch = () => {
828
- const { useThread } = useAssistantContext();
829
- const { useMessage, useComposer } = useMessageContext();
830
- const disabled = useCombinedStore(
831
- [useMessage, useComposer],
832
- (m, c) => c.isEditing || m.branches.indexOf(m.message.id) + 1 >= m.branches.length
833
- );
834
- const callback = useCallback2(() => {
835
- const { message, branches } = useMessage.getState();
836
- useThread.getState().switchToBranch(branches[branches.indexOf(message.id) + 1]);
837
- }, [useMessage, useThread]);
838
- if (disabled) return null;
839
- return callback;
840
- };
841
-
842
- // src/utils/createActionButton.tsx
843
- import { composeEventHandlers as composeEventHandlers8 } from "@radix-ui/primitive";
740
+ // src/primitives/message/MessageRoot.tsx
741
+ import { composeEventHandlers as composeEventHandlers7 } from "@radix-ui/primitive";
844
742
  import {
845
- Primitive as Primitive10
743
+ Primitive as Primitive8
846
744
  } from "@radix-ui/react-primitive";
847
- import { forwardRef as forwardRef11 } from "react";
848
- import { jsx as jsx16 } from "react/jsx-runtime";
849
- var createActionButton = (useActionButton) => {
850
- return forwardRef11(
851
- (props, forwardedRef) => {
852
- const onClick = useActionButton(props);
853
- return /* @__PURE__ */ jsx16(
854
- Primitive10.button,
855
- {
856
- type: "button",
857
- disabled: !onClick,
858
- ...props,
859
- ref: forwardedRef,
860
- onClick: composeEventHandlers8(props.onClick, onClick ?? void 0)
861
- }
862
- );
863
- }
864
- );
865
- };
745
+ import { forwardRef as forwardRef9 } from "react";
746
+ import { jsx as jsx12 } from "react/jsx-runtime";
747
+ var MessageRoot = forwardRef9(
748
+ ({ onMouseEnter, onMouseLeave, ...rest }, ref) => {
749
+ const { useMessage } = useMessageContext();
750
+ const setIsHovering = useMessage((s) => s.setIsHovering);
751
+ const handleMouseEnter = () => {
752
+ setIsHovering(true);
753
+ };
754
+ const handleMouseLeave = () => {
755
+ setIsHovering(false);
756
+ };
757
+ return /* @__PURE__ */ jsx12(
758
+ Primitive8.div,
759
+ {
760
+ ...rest,
761
+ ref,
762
+ onMouseEnter: composeEventHandlers7(onMouseEnter, handleMouseEnter),
763
+ onMouseLeave: composeEventHandlers7(onMouseLeave, handleMouseLeave)
764
+ }
765
+ );
766
+ }
767
+ );
866
768
 
867
- // src/primitives/branchPicker/BranchPickerNext.tsx
868
- var BranchPickerNext = createActionButton(useGoToNextBranch);
769
+ // src/context/providers/ContentPartProvider.tsx
770
+ import { useEffect as useEffect5, useState as useState2 } from "react";
771
+ import { create as create3 } from "zustand";
869
772
 
870
- // src/actions/useGoToPreviousBranch.tsx
871
- import { useCallback as useCallback3 } from "react";
872
- var useGoToPreviousBranch = () => {
873
- const { useThread } = useAssistantContext();
874
- const { useMessage, useComposer } = useMessageContext();
875
- const disabled = useCombinedStore(
876
- [useMessage, useComposer],
877
- (m, c) => c.isEditing || m.branches.indexOf(m.message.id) <= 0
878
- );
879
- const callback = useCallback3(() => {
880
- const { message, branches } = useMessage.getState();
881
- useThread.getState().switchToBranch(branches[branches.indexOf(message.id) - 1]);
882
- }, [useMessage, useThread]);
883
- if (disabled) return null;
884
- return callback;
773
+ // src/context/ContentPartContext.ts
774
+ import { createContext as createContext3, useContext as useContext4 } from "react";
775
+ var ContentPartContext = createContext3(
776
+ null
777
+ );
778
+ var useContentPartContext = () => {
779
+ const context = useContext4(ContentPartContext);
780
+ if (!context)
781
+ throw new Error(
782
+ "This component can only be used inside a component passed to <MessagePrimitive.Content components={...} >."
783
+ );
784
+ return context;
885
785
  };
886
786
 
887
- // src/primitives/branchPicker/BranchPickerPrevious.tsx
888
- var BranchPickerPrevious = createActionButton(useGoToPreviousBranch);
889
-
890
- // src/primitives/branchPicker/BranchPickerCount.tsx
891
- import { Fragment as Fragment3, jsx as jsx17 } from "react/jsx-runtime";
892
- var BranchPickerCount = () => {
893
- const { useMessage } = useMessageContext();
894
- const branchCount = useMessage((s) => s.branches.length);
895
- return /* @__PURE__ */ jsx17(Fragment3, { children: branchCount });
787
+ // src/context/providers/ContentPartProvider.tsx
788
+ import { jsx as jsx13 } from "react/jsx-runtime";
789
+ var syncContentPart = ({ message }, useContentPart, partIndex) => {
790
+ const part = message.content[partIndex];
791
+ if (!part) return;
792
+ const messageStatus = message.role === "assistant" ? message.status : "done";
793
+ const status = partIndex === message.content.length - 1 ? messageStatus : "done";
794
+ useContentPart.setState({ part, status });
896
795
  };
897
-
898
- // src/primitives/branchPicker/BranchPickerNumber.tsx
899
- import { Fragment as Fragment4, jsx as jsx18 } from "react/jsx-runtime";
900
- var BranchPickerNumber = () => {
796
+ var useContentPartContext2 = (partIndex) => {
901
797
  const { useMessage } = useMessageContext();
902
- const branchIdx = useMessage((s) => s.branches.indexOf(s.message.id));
903
- return /* @__PURE__ */ jsx18(Fragment4, { children: branchIdx + 1 });
904
- };
905
-
906
- // src/primitives/branchPicker/BranchPickerRoot.tsx
907
- import {
908
- Primitive as Primitive11
798
+ const [context] = useState2(() => {
799
+ const useContentPart = create3(() => ({
800
+ part: { type: "text", text: "" },
801
+ status: "done"
802
+ }));
803
+ syncContentPart(useMessage.getState(), useContentPart, partIndex);
804
+ return { useContentPart };
805
+ });
806
+ useEffect5(() => {
807
+ return useMessage.subscribe((message) => {
808
+ syncContentPart(message, context.useContentPart, partIndex);
809
+ });
810
+ }, [context, useMessage, partIndex]);
811
+ return context;
812
+ };
813
+ var ContentPartProvider = ({
814
+ partIndex,
815
+ children
816
+ }) => {
817
+ const context = useContentPartContext2(partIndex);
818
+ return /* @__PURE__ */ jsx13(ContentPartContext.Provider, { value: context, children });
819
+ };
820
+
821
+ // src/primitives/contentPart/ContentPartInProgressIndicator.tsx
822
+ var ContentPartInProgressIndicator = () => {
823
+ const { useMessage } = useMessageContext();
824
+ const { useContentPart } = useContentPartContext();
825
+ const indicator = useCombinedStore(
826
+ [useMessage, useContentPart],
827
+ (m, c) => c.status === "in_progress" ? m.inProgressIndicator : null
828
+ );
829
+ return indicator;
830
+ };
831
+
832
+ // src/primitives/message/MessageContent.tsx
833
+ import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs3 } from "react/jsx-runtime";
834
+ var defaultComponents = {
835
+ Text: ({ part }) => /* @__PURE__ */ jsxs3(Fragment2, { children: [
836
+ part.text,
837
+ /* @__PURE__ */ jsx14(ContentPartInProgressIndicator, {})
838
+ ] }),
839
+ Image: () => null,
840
+ UI: ({ part }) => part.display,
841
+ tools: {
842
+ Fallback: () => null
843
+ }
844
+ };
845
+ var MessageContent = ({
846
+ components: {
847
+ Text = defaultComponents.Text,
848
+ Image = defaultComponents.Image,
849
+ UI = defaultComponents.UI,
850
+ tools: { by_name = {}, Fallback = defaultComponents.tools.Fallback } = {}
851
+ } = {}
852
+ }) => {
853
+ const { useMessage } = useMessageContext();
854
+ const message = useMessage((s) => s.message);
855
+ const content = message.content;
856
+ return /* @__PURE__ */ jsx14(Fragment2, { children: content.map((part, i) => {
857
+ const partIndex = i;
858
+ const type = part.type;
859
+ let component = null;
860
+ switch (type) {
861
+ case "text":
862
+ component = /* @__PURE__ */ jsx14(Text, { part });
863
+ break;
864
+ case "image":
865
+ component = /* @__PURE__ */ jsx14(Image, { part });
866
+ break;
867
+ case "ui":
868
+ component = /* @__PURE__ */ jsx14(UI, { part });
869
+ break;
870
+ case "tool-call": {
871
+ const Tool = by_name[part.name] || Fallback;
872
+ component = /* @__PURE__ */ jsx14(Tool, { part });
873
+ break;
874
+ }
875
+ default:
876
+ throw new Error(`Unknown content part type: ${type}`);
877
+ }
878
+ return /* @__PURE__ */ jsx14(ContentPartProvider, { partIndex, children: component }, partIndex);
879
+ }) });
880
+ };
881
+
882
+ // src/primitives/message/MessageInProgress.tsx
883
+ import {
884
+ Primitive as Primitive9
885
+ } from "@radix-ui/react-primitive";
886
+ import { forwardRef as forwardRef10, useMemo as useMemo3 } from "react";
887
+ import { jsx as jsx15 } from "react/jsx-runtime";
888
+ var MessageInProgress = forwardRef10((props, ref) => {
889
+ const { useMessage } = useMessageContext();
890
+ useMemo3(() => {
891
+ useMessage.getState().setInProgressIndicator(/* @__PURE__ */ jsx15(Primitive9.span, { ...props, ref }));
892
+ }, [useMessage, props, ref]);
893
+ return null;
894
+ });
895
+
896
+ // src/primitives/branchPicker/index.ts
897
+ var branchPicker_exports = {};
898
+ __export(branchPicker_exports, {
899
+ Count: () => BranchPickerCount,
900
+ Next: () => BranchPickerNext,
901
+ Number: () => BranchPickerNumber,
902
+ Previous: () => BranchPickerPrevious,
903
+ Root: () => BranchPickerRoot
904
+ });
905
+
906
+ // src/utils/createActionButton.tsx
907
+ import { composeEventHandlers as composeEventHandlers8 } from "@radix-ui/primitive";
908
+ import {
909
+ Primitive as Primitive10
910
+ } from "@radix-ui/react-primitive";
911
+ import { forwardRef as forwardRef11 } from "react";
912
+ import { jsx as jsx16 } from "react/jsx-runtime";
913
+ var createActionButton = (useActionButton) => {
914
+ return forwardRef11(
915
+ (props, forwardedRef) => {
916
+ const onClick = useActionButton(props);
917
+ return /* @__PURE__ */ jsx16(
918
+ Primitive10.button,
919
+ {
920
+ type: "button",
921
+ disabled: !onClick,
922
+ ...props,
923
+ ref: forwardedRef,
924
+ onClick: composeEventHandlers8(props.onClick, onClick ?? void 0)
925
+ }
926
+ );
927
+ }
928
+ );
929
+ };
930
+
931
+ // src/primitives/branchPicker/BranchPickerNext.tsx
932
+ var BranchPickerNext = createActionButton(useGoToNextBranch);
933
+
934
+ // src/primitives/branchPicker/BranchPickerPrevious.tsx
935
+ var BranchPickerPrevious = createActionButton(useGoToPreviousBranch);
936
+
937
+ // src/primitives/branchPicker/BranchPickerCount.tsx
938
+ import { Fragment as Fragment3, jsx as jsx17 } from "react/jsx-runtime";
939
+ var BranchPickerCount = () => {
940
+ const { useMessage } = useMessageContext();
941
+ const branchCount = useMessage((s) => s.branches.length);
942
+ return /* @__PURE__ */ jsx17(Fragment3, { children: branchCount });
943
+ };
944
+
945
+ // src/primitives/branchPicker/BranchPickerNumber.tsx
946
+ import { Fragment as Fragment4, jsx as jsx18 } from "react/jsx-runtime";
947
+ var BranchPickerNumber = () => {
948
+ const { useMessage } = useMessageContext();
949
+ const branchIdx = useMessage((s) => s.branches.indexOf(s.message.id));
950
+ return /* @__PURE__ */ jsx18(Fragment4, { children: branchIdx + 1 });
951
+ };
952
+
953
+ // src/primitives/branchPicker/BranchPickerRoot.tsx
954
+ import {
955
+ Primitive as Primitive11
909
956
  } from "@radix-ui/react-primitive";
910
957
  import { forwardRef as forwardRef12 } from "react";
911
958
  import { jsx as jsx19 } from "react/jsx-runtime";
@@ -929,7 +976,7 @@ import {
929
976
  import { forwardRef as forwardRef13 } from "react";
930
977
  import { jsx as jsx20 } from "react/jsx-runtime";
931
978
  var ActionBarRoot = forwardRef13(({ hideWhenRunning, autohide, autohideFloat, ...rest }, ref) => {
932
- const { useThread } = useAssistantContext();
979
+ const { useThread } = useThreadContext();
933
980
  const { useMessage } = useMessageContext();
934
981
  const hideAndfloatStatus = useCombinedStore(
935
982
  [useThread, useMessage],
@@ -954,162 +1001,31 @@ var ActionBarRoot = forwardRef13(({ hideWhenRunning, autohide, autohideFloat, ..
954
1001
  );
955
1002
  });
956
1003
 
957
- // src/actions/useCopyMessage.tsx
958
- import { useCallback as useCallback4 } from "react";
959
- var useCopyMessage = ({ copiedDuration = 3e3 }) => {
960
- const { useMessage, useComposer } = useMessageContext();
961
- const hasCopyableContent = useCombinedStore(
962
- [useMessage, useComposer],
963
- (m, c) => {
964
- return c.isEditing || m.message.content.some((c2) => c2.type === "text");
965
- }
966
- );
967
- const callback = useCallback4(() => {
968
- const { isEditing, value: composerValue } = useComposer.getState();
969
- const { message, setIsCopied } = useMessage.getState();
970
- const valueToCopy = isEditing ? composerValue : getMessageText(message);
971
- navigator.clipboard.writeText(valueToCopy);
972
- setIsCopied(true);
973
- setTimeout(() => setIsCopied(false), copiedDuration);
974
- }, [useComposer, useMessage, copiedDuration]);
975
- if (!hasCopyableContent) return null;
976
- return callback;
977
- };
978
-
979
1004
  // src/primitives/actionBar/ActionBarCopy.tsx
980
1005
  var ActionBarCopy = createActionButton(useCopyMessage);
981
1006
 
982
- // src/actions/useReloadMessage.tsx
983
- import { useCallback as useCallback5 } from "react";
984
- var useReloadMessage = () => {
985
- const { useThread, useViewport } = useAssistantContext();
986
- const { useMessage } = useMessageContext();
987
- const disabled = useCombinedStore(
988
- [useThread, useMessage],
989
- (t, m) => t.isRunning || m.message.role !== "assistant"
990
- );
991
- const callback = useCallback5(() => {
992
- const { parentId } = useMessage.getState();
993
- useThread.getState().startRun(parentId);
994
- useViewport.getState().scrollToBottom();
995
- }, [useMessage, useThread, useViewport]);
996
- if (disabled) return null;
997
- return callback;
998
- };
999
-
1000
1007
  // src/primitives/actionBar/ActionBarReload.tsx
1001
1008
  var ActionBarReload = createActionButton(useReloadMessage);
1002
1009
 
1003
- // src/actions/useBeginMessageEdit.tsx
1004
- import { useCallback as useCallback6 } from "react";
1005
- var useBeginMessageEdit = () => {
1006
- const { useMessage, useComposer } = useMessageContext();
1007
- const disabled = useCombinedStore(
1008
- [useMessage, useComposer],
1009
- (m, c) => m.message.role !== "user" || c.isEditing
1010
- );
1011
- const callback = useCallback6(() => {
1012
- const { edit } = useComposer.getState();
1013
- edit();
1014
- }, [useComposer]);
1015
- if (disabled) return null;
1016
- return callback;
1017
- };
1018
-
1019
1010
  // src/primitives/actionBar/ActionBarEdit.tsx
1020
1011
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
1021
1012
 
1022
1013
  // src/primitives/contentPart/index.ts
1023
1014
  var contentPart_exports = {};
1024
1015
  __export(contentPart_exports, {
1025
- InProgressIndicator: () => ContentPartInProgressIndicator,
1026
- Provider: () => ContentPartProvider
1016
+ InProgressIndicator: () => ContentPartInProgressIndicator
1027
1017
  });
1028
1018
 
1029
- // src/adapters/vercel/VercelAIAssistantProvider.tsx
1030
- import { useEffect as useEffect6 } from "react";
1031
-
1032
- // src/adapters/core/AssistantProvider.tsx
1033
- import {
1034
- useEffect as useEffect4,
1035
- useInsertionEffect,
1036
- useRef as useRef4,
1037
- useState as useState3
1038
- } from "react";
1039
- import { create as create5 } from "zustand";
1019
+ // src/runtime/vercel-ai/rsc/useVercelRSCRuntime.tsx
1020
+ import { useEffect as useEffect7, useInsertionEffect, useState as useState3 } from "react";
1040
1021
 
1041
- // src/utils/context/stores/ViewportStore.tsx
1022
+ // src/runtime/vercel-ai/rsc/VercelRSCRuntime.tsx
1042
1023
  import { create as create4 } from "zustand";
1043
- var makeViewportStore = () => {
1044
- const scrollToBottomListeners = /* @__PURE__ */ new Set();
1045
- return create4(() => ({
1046
- isAtBottom: true,
1047
- scrollToBottom: () => {
1048
- for (const listener of scrollToBottomListeners) {
1049
- listener();
1050
- }
1051
- },
1052
- onScrollToBottom: (callback) => {
1053
- scrollToBottomListeners.add(callback);
1054
- return () => {
1055
- scrollToBottomListeners.delete(callback);
1056
- };
1057
- }
1058
- }));
1059
- };
1060
-
1061
- // src/adapters/core/AssistantProvider.tsx
1062
- import { jsx as jsx21 } from "react/jsx-runtime";
1063
- var makeThreadStore = (runtimeRef) => {
1064
- const useThread = create5(() => ({
1065
- messages: runtimeRef.current.messages,
1066
- isRunning: runtimeRef.current.isRunning,
1067
- getBranches: (messageId) => runtimeRef.current.getBranches(messageId),
1068
- switchToBranch: (branchId) => runtimeRef.current.switchToBranch(branchId),
1069
- startRun: (parentId) => runtimeRef.current.startRun(parentId),
1070
- append: (message) => runtimeRef.current.append(message),
1071
- cancelRun: () => runtimeRef.current.cancelRun()
1072
- }));
1073
- const onRuntimeUpdate = () => {
1074
- useThread.setState({
1075
- messages: runtimeRef.current.messages,
1076
- isRunning: runtimeRef.current.isRunning
1077
- });
1078
- };
1079
- return {
1080
- useThread,
1081
- onRuntimeUpdate
1082
- };
1083
- };
1084
- var AssistantProvider = ({ children, runtime }) => {
1085
- const runtimeRef = useRef4(runtime);
1086
- useInsertionEffect(() => {
1087
- runtimeRef.current = runtime;
1088
- });
1089
- const [{ context, onRuntimeUpdate }] = useState3(() => {
1090
- const { useThread, onRuntimeUpdate: onRuntimeUpdate2 } = makeThreadStore(runtimeRef);
1091
- const useViewport = makeViewportStore();
1092
- const useComposer = makeThreadComposerStore(useThread);
1093
- return {
1094
- context: {
1095
- useViewport,
1096
- useThread,
1097
- useComposer
1098
- },
1099
- onRuntimeUpdate: onRuntimeUpdate2
1100
- };
1101
- });
1102
- useEffect4(() => {
1103
- onRuntimeUpdate();
1104
- return runtime.subscribe(onRuntimeUpdate);
1105
- }, [onRuntimeUpdate, runtime]);
1106
- return /* @__PURE__ */ jsx21(AssistantContext.Provider, { value: context, children });
1107
- };
1108
1024
 
1109
- // src/adapters/core/vercel-use-chat/useVercelUseChatRuntime.tsx
1110
- import { useEffect as useEffect5, useInsertionEffect as useInsertionEffect2, useMemo as useMemo5, useState as useState4 } from "react";
1025
+ // src/runtime/vercel-ai/rsc/useVercelRSCSync.tsx
1026
+ import { useEffect as useEffect6, useMemo as useMemo4 } from "react";
1111
1027
 
1112
- // src/adapters/ThreadMessageConverter.ts
1028
+ // src/runtime/vercel-ai/utils/ThreadMessageConverter.ts
1113
1029
  var ThreadMessageConverter = class {
1114
1030
  cache = /* @__PURE__ */ new WeakMap();
1115
1031
  convertMessages(converter, messages) {
@@ -1122,17 +1038,129 @@ var ThreadMessageConverter = class {
1122
1038
  }
1123
1039
  };
1124
1040
 
1125
- // src/adapters/vercel/VercelThreadMessage.tsx
1126
- var symbolInnerMessage = Symbol("innerMessage");
1127
- var symbolInnerRSCMessage = Symbol("innerRSCMessage");
1128
- var getVercelMessage = (message) => {
1129
- return message[symbolInnerMessage];
1130
- };
1041
+ // src/runtime/vercel-ai/rsc/getVercelRSCMessage.tsx
1042
+ var symbolInnerRSCMessage = Symbol("innerVercelRSCMessage");
1131
1043
  var getVercelRSCMessage = (message) => {
1132
1044
  return message[symbolInnerRSCMessage];
1133
1045
  };
1134
1046
 
1135
- // src/adapters/idUtils.tsx
1047
+ // src/runtime/vercel-ai/rsc/useVercelRSCSync.tsx
1048
+ var vercelToThreadMessage = (converter, rawMessage) => {
1049
+ const message = converter(rawMessage);
1050
+ return {
1051
+ id: message.id,
1052
+ role: message.role,
1053
+ content: [{ type: "ui", display: message.display }],
1054
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1055
+ ...{ status: "done" },
1056
+ [symbolInnerRSCMessage]: rawMessage
1057
+ };
1058
+ };
1059
+ var useVercelRSCSync = (adapter, updateData) => {
1060
+ const [converter, convertCallback] = useMemo4(() => {
1061
+ const rscConverter = adapter.convertMessage ?? ((m) => m);
1062
+ const convertCallback2 = (m, cache) => {
1063
+ if (cache) return cache;
1064
+ return vercelToThreadMessage(rscConverter, m);
1065
+ };
1066
+ return [new ThreadMessageConverter(), convertCallback2];
1067
+ }, [adapter.convertMessage]);
1068
+ useEffect6(() => {
1069
+ updateData(converter.convertMessages(convertCallback, adapter.messages));
1070
+ }, [updateData, converter, convertCallback, adapter.messages]);
1071
+ };
1072
+
1073
+ // src/runtime/vercel-ai/rsc/VercelRSCRuntime.tsx
1074
+ var EMPTY_BRANCHES = Object.freeze([]);
1075
+ var VercelRSCRuntime = class {
1076
+ constructor(adapter) {
1077
+ this.adapter = adapter;
1078
+ this.useAdapter = create4(() => ({
1079
+ adapter
1080
+ }));
1081
+ }
1082
+ useAdapter;
1083
+ _subscriptions = /* @__PURE__ */ new Set();
1084
+ isRunning = false;
1085
+ messages = [];
1086
+ withRunning = (callback) => {
1087
+ this.isRunning = true;
1088
+ return callback.finally(() => {
1089
+ this.isRunning = false;
1090
+ });
1091
+ };
1092
+ getBranches() {
1093
+ return EMPTY_BRANCHES;
1094
+ }
1095
+ switchToBranch() {
1096
+ throw new Error(
1097
+ "Branch switching is not supported by VercelRSCAssistantProvider."
1098
+ );
1099
+ }
1100
+ async append(message) {
1101
+ if (message.parentId !== (this.messages.at(-1)?.id ?? null)) {
1102
+ if (!this.adapter.edit)
1103
+ throw new Error(
1104
+ "Message editing is not enabled, please provide an edit callback to VercelRSCAssistantProvider."
1105
+ );
1106
+ await this.withRunning(this.adapter.edit(message));
1107
+ } else {
1108
+ await this.withRunning(this.adapter.append(message));
1109
+ }
1110
+ }
1111
+ async startRun(parentId) {
1112
+ if (!this.adapter.reload)
1113
+ throw new Error(
1114
+ "Message reloading is not enabled, please provide a reload callback to VercelRSCAssistantProvider."
1115
+ );
1116
+ await this.withRunning(this.adapter.reload(parentId));
1117
+ }
1118
+ cancelRun() {
1119
+ if (process.env["NODE_ENV"] === "development") {
1120
+ console.warn(
1121
+ "Run cancellation is not supported by VercelRSCAssistantProvider."
1122
+ );
1123
+ }
1124
+ }
1125
+ subscribe(callback) {
1126
+ this._subscriptions.add(callback);
1127
+ return () => this._subscriptions.delete(callback);
1128
+ }
1129
+ onAdapterUpdated() {
1130
+ if (this.useAdapter.getState().adapter !== this.adapter) {
1131
+ this.useAdapter.setState({ adapter: this.adapter });
1132
+ }
1133
+ }
1134
+ updateData = (messages) => {
1135
+ this.messages = messages;
1136
+ for (const callback of this._subscriptions) callback();
1137
+ };
1138
+ unstable_synchronizer = () => {
1139
+ const { adapter } = this.useAdapter();
1140
+ useVercelRSCSync(adapter, this.updateData);
1141
+ return null;
1142
+ };
1143
+ };
1144
+
1145
+ // src/runtime/vercel-ai/rsc/useVercelRSCRuntime.tsx
1146
+ var useVercelRSCRuntime = (adapter) => {
1147
+ const [runtime] = useState3(() => new VercelRSCRuntime(adapter));
1148
+ useInsertionEffect(() => {
1149
+ runtime.adapter = adapter;
1150
+ });
1151
+ useEffect7(() => {
1152
+ runtime.onAdapterUpdated();
1153
+ });
1154
+ return runtime;
1155
+ };
1156
+
1157
+ // src/runtime/vercel-ai/ui/use-chat/useVercelUseChatRuntime.tsx
1158
+ import { useEffect as useEffect10, useInsertionEffect as useInsertionEffect2, useState as useState4 } from "react";
1159
+
1160
+ // src/runtime/vercel-ai/ui/VercelAIRuntime.tsx
1161
+ import { create as create5 } from "zustand";
1162
+
1163
+ // src/runtime/utils/idUtils.tsx
1136
1164
  import { customAlphabet } from "nanoid/non-secure";
1137
1165
  var generateId = customAlphabet(
1138
1166
  "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
@@ -1141,7 +1169,7 @@ var generateId = customAlphabet(
1141
1169
  var optimisticPrefix = "__optimistic__";
1142
1170
  var generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
1143
1171
 
1144
- // src/adapters/MessageRepository.tsx
1172
+ // src/runtime/utils/MessageRepository.tsx
1145
1173
  var findHead = (message) => {
1146
1174
  if (message.next) return findHead(message.next);
1147
1175
  return message;
@@ -1210,10 +1238,10 @@ var MessageRepository = class {
1210
1238
  level: prev ? prev.level + 1 : 0
1211
1239
  };
1212
1240
  this.messages.set(message.id, newItem);
1241
+ this.performOp(prev, newItem, "link");
1213
1242
  if (this.head === prev) {
1214
1243
  this.head = newItem;
1215
1244
  }
1216
- this.performOp(prev, newItem, "link");
1217
1245
  }
1218
1246
  appendOptimisticMessage(parentId, message) {
1219
1247
  let optimisticId;
@@ -1230,14 +1258,14 @@ var MessageRepository = class {
1230
1258
  }
1231
1259
  deleteMessage(messageId, replacementId) {
1232
1260
  const message = this.messages.get(messageId);
1233
- const replacement = replacementId ? this.messages.get(replacementId) : null;
1234
1261
  if (!message)
1235
1262
  throw new Error(
1236
1263
  "MessageRepository(deleteMessage): Optimistic message not found. This is likely an internal bug in assistant-ui."
1237
1264
  );
1265
+ const replacement = replacementId === void 0 ? message.prev : replacementId === null ? null : this.messages.get(replacementId);
1238
1266
  if (replacement === void 0)
1239
1267
  throw new Error(
1240
- "MessageRepository(deleteMessage): New message not found. This is likely an internal bug in assistant-ui."
1268
+ "MessageRepository(deleteMessage): Replacement not found. This is likely an internal bug in assistant-ui."
1241
1269
  );
1242
1270
  for (const child of message.children) {
1243
1271
  const childMessage = this.messages.get(child);
@@ -1247,11 +1275,11 @@ var MessageRepository = class {
1247
1275
  );
1248
1276
  this.performOp(replacement, childMessage, "relink");
1249
1277
  }
1278
+ this.performOp(null, message, "cut");
1250
1279
  this.messages.delete(messageId);
1251
1280
  if (this.head === message) {
1252
- this.head = replacement;
1281
+ this.head = replacement ? findHead(replacement) : null;
1253
1282
  }
1254
- this.performOp(null, message, "cut");
1255
1283
  }
1256
1284
  getBranches(messageId) {
1257
1285
  const message = this.messages.get(messageId);
@@ -1292,7 +1320,13 @@ var MessageRepository = class {
1292
1320
  }
1293
1321
  };
1294
1322
 
1295
- // src/adapters/core/vercel-use-chat/VercelUseChatRuntime.tsx
1323
+ // src/runtime/vercel-ai/ui/getVercelAIMessage.tsx
1324
+ var symbolInnerAIMessage = Symbol("innerVercelAIUIMessage");
1325
+ var getVercelAIMessage = (message) => {
1326
+ return message[symbolInnerAIMessage];
1327
+ };
1328
+
1329
+ // src/runtime/vercel-ai/ui/utils/sliceMessagesUntil.tsx
1296
1330
  var sliceMessagesUntil = (messages, messageId) => {
1297
1331
  if (messageId == null) return [];
1298
1332
  const messageIdx = messages.findIndex((m) => m.id === messageId);
@@ -1300,18 +1334,97 @@ var sliceMessagesUntil = (messages, messageId) => {
1300
1334
  throw new Error(
1301
1335
  "useVercelAIThreadState: Message not found. This is liekly an internal bug in assistant-ui."
1302
1336
  );
1303
- return messages.slice(0, messageIdx + 1);
1337
+ return messages.slice(0, messageIdx + 1);
1338
+ };
1339
+
1340
+ // src/runtime/vercel-ai/ui/utils/useVercelAIComposerSync.tsx
1341
+ import { useEffect as useEffect8 } from "react";
1342
+ var useVercelAIComposerSync = (vercel) => {
1343
+ const { useComposer } = useThreadContext();
1344
+ useEffect8(() => {
1345
+ useComposer.setState({
1346
+ value: vercel.input,
1347
+ setValue: vercel.setInput
1348
+ });
1349
+ }, [useComposer, vercel.input, vercel.setInput]);
1350
+ };
1351
+
1352
+ // src/runtime/vercel-ai/ui/utils/useVercelAIThreadSync.tsx
1353
+ import { useEffect as useEffect9, useMemo as useMemo5 } from "react";
1354
+ var getIsRunning = (vercel) => {
1355
+ if ("isLoading" in vercel) return vercel.isLoading;
1356
+ return vercel.status === "in_progress";
1357
+ };
1358
+ var vercelToThreadMessage2 = (message, status) => {
1359
+ const common = {
1360
+ id: message.id,
1361
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1362
+ [symbolInnerAIMessage]: message
1363
+ };
1364
+ switch (message.role) {
1365
+ case "user":
1366
+ return {
1367
+ ...common,
1368
+ role: "user",
1369
+ content: [{ type: "text", text: message.content }]
1370
+ };
1371
+ case "assistant":
1372
+ return {
1373
+ ...common,
1374
+ role: "assistant",
1375
+ content: [
1376
+ ...message.content ? [{ type: "text", text: message.content }] : [],
1377
+ ...message.toolInvocations?.map(
1378
+ (t) => ({
1379
+ type: "tool-call",
1380
+ name: t.toolName,
1381
+ args: t.args,
1382
+ result: "result" in t ? t.result : void 0
1383
+ })
1384
+ ) ?? []
1385
+ ],
1386
+ status
1387
+ };
1388
+ default:
1389
+ throw new Error(
1390
+ `You have a message with an unsupported role. The role ${message.role} is not supported.`
1391
+ );
1392
+ }
1393
+ };
1394
+ var useVercelAIThreadSync = (vercel, updateData) => {
1395
+ const isRunning = getIsRunning(vercel);
1396
+ const converter = useMemo5(() => new ThreadMessageConverter(), []);
1397
+ useEffect9(() => {
1398
+ const lastMessageId = vercel.messages.at(-1)?.id;
1399
+ const convertCallback = (message, cache) => {
1400
+ const status = lastMessageId === message.id && isRunning ? "in_progress" : "done";
1401
+ if (cache && (cache.role === "user" || cache.status === status))
1402
+ return cache;
1403
+ return vercelToThreadMessage2(message, status);
1404
+ };
1405
+ const messages = converter.convertMessages(
1406
+ convertCallback,
1407
+ vercel.messages
1408
+ );
1409
+ updateData(isRunning, messages);
1410
+ }, [updateData, isRunning, vercel.messages, converter]);
1304
1411
  };
1412
+
1413
+ // src/runtime/vercel-ai/ui/VercelAIRuntime.tsx
1305
1414
  var hasUpcomingMessage = (isRunning, messages) => {
1306
1415
  return isRunning && messages[messages.length - 1]?.role !== "assistant";
1307
1416
  };
1308
- var VercelUseChatRuntime = class {
1417
+ var VercelAIRuntime = class {
1309
1418
  constructor(vercel) {
1310
1419
  this.vercel = vercel;
1420
+ this.useVercel = create5(() => ({
1421
+ vercel
1422
+ }));
1311
1423
  }
1312
1424
  _subscriptions = /* @__PURE__ */ new Set();
1313
1425
  repository = new MessageRepository();
1314
1426
  assistantOptimisticId = null;
1427
+ useVercel;
1315
1428
  messages = [];
1316
1429
  isRunning = false;
1317
1430
  getBranches(messageId) {
@@ -1319,9 +1432,7 @@ var VercelUseChatRuntime = class {
1319
1432
  }
1320
1433
  switchToBranch(branchId) {
1321
1434
  this.repository.switchToBranch(branchId);
1322
- this.vercel.setMessages(
1323
- this.repository.getMessages().map(getVercelMessage).filter((m) => m != null)
1324
- );
1435
+ this.updateVercelMessages(this.repository.getMessages());
1325
1436
  }
1326
1437
  async append(message) {
1327
1438
  if (message.content.length !== 1 || message.content[0]?.type !== "text")
@@ -1347,20 +1458,44 @@ var VercelUseChatRuntime = class {
1347
1458
  await reloadMaybe();
1348
1459
  }
1349
1460
  cancelRun() {
1350
- const lastMessage = this.vercel.messages.at(-1);
1461
+ const previousMessage = this.vercel.messages.at(-1);
1351
1462
  this.vercel.stop();
1352
- if (lastMessage?.role === "user") {
1353
- this.vercel.setInput(lastMessage.content);
1463
+ if (this.assistantOptimisticId) {
1464
+ this.repository.deleteMessage(this.assistantOptimisticId);
1465
+ this.assistantOptimisticId = null;
1466
+ }
1467
+ let messages = this.repository.getMessages();
1468
+ if (previousMessage?.role === "user" && previousMessage.id === messages.at(-1)?.id) {
1469
+ this.vercel.setInput(previousMessage.content);
1470
+ this.repository.deleteMessage(previousMessage.id);
1471
+ messages = this.repository.getMessages();
1472
+ }
1473
+ setTimeout(() => {
1474
+ this.updateVercelMessages(messages);
1475
+ }, 0);
1476
+ }
1477
+ subscribe(callback) {
1478
+ this._subscriptions.add(callback);
1479
+ return () => this._subscriptions.delete(callback);
1480
+ }
1481
+ updateVercelMessages = (messages) => {
1482
+ this.vercel.setMessages(
1483
+ messages.map(getVercelAIMessage).filter((m) => m != null)
1484
+ );
1485
+ };
1486
+ onVercelUpdated() {
1487
+ if (this.useVercel.getState().vercel !== this.vercel) {
1488
+ this.useVercel.setState({ vercel: this.vercel });
1354
1489
  }
1355
1490
  }
1356
- updateData(isRunning, vm) {
1491
+ updateData = (isRunning, vm) => {
1357
1492
  for (let i = 0; i < vm.length; i++) {
1358
1493
  const message = vm[i];
1359
1494
  const parent = vm[i - 1];
1360
1495
  this.repository.addOrUpdateMessage(parent?.id ?? null, message);
1361
1496
  }
1362
1497
  if (this.assistantOptimisticId) {
1363
- this.repository.deleteMessage(this.assistantOptimisticId, null);
1498
+ this.repository.deleteMessage(this.assistantOptimisticId);
1364
1499
  this.assistantOptimisticId = null;
1365
1500
  }
1366
1501
  if (hasUpcomingMessage(isRunning, vm)) {
@@ -1378,211 +1513,194 @@ var VercelUseChatRuntime = class {
1378
1513
  this.messages = this.repository.getMessages();
1379
1514
  this.isRunning = isRunning;
1380
1515
  for (const callback of this._subscriptions) callback();
1381
- }
1382
- subscribe(callback) {
1383
- this._subscriptions.add(callback);
1384
- return () => this._subscriptions.delete(callback);
1385
- }
1386
- };
1387
-
1388
- // src/adapters/core/vercel-use-chat/useVercelUseChatRuntime.tsx
1389
- var getIsRunning = (vercel) => {
1390
- if ("isLoading" in vercel) return vercel.isLoading;
1391
- return vercel.status === "in_progress";
1392
- };
1393
- var vercelToThreadMessage = (message, status) => {
1394
- const common = {
1395
- id: message.id,
1396
- createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1397
- [symbolInnerMessage]: message
1398
1516
  };
1399
- switch (message.role) {
1400
- case "user":
1401
- return {
1402
- ...common,
1403
- role: "user",
1404
- content: [{ type: "text", text: message.content }]
1405
- };
1406
- case "assistant":
1407
- return {
1408
- ...common,
1409
- role: "assistant",
1410
- content: [
1411
- ...message.content ? [{ type: "text", text: message.content }] : [],
1412
- ...message.toolInvocations?.map(
1413
- (t) => ({
1414
- type: "tool-call",
1415
- name: t.toolName,
1416
- args: t.args,
1417
- result: "result" in t ? t.result : void 0
1418
- })
1419
- ) ?? []
1420
- ],
1421
- status
1422
- };
1423
- default:
1424
- throw new Error(
1425
- `You have a message with an unsupported role. The role ${message.role} is not supported.`
1426
- );
1427
- }
1517
+ unstable_synchronizer = () => {
1518
+ const { vercel } = this.useVercel();
1519
+ useVercelAIThreadSync(vercel, this.updateData);
1520
+ useVercelAIComposerSync(vercel);
1521
+ return null;
1522
+ };
1428
1523
  };
1429
- var useVercelUseChatRuntime = (vercel) => {
1430
- const [runtime] = useState4(() => new VercelUseChatRuntime(vercel));
1524
+
1525
+ // src/runtime/vercel-ai/ui/use-chat/useVercelUseChatRuntime.tsx
1526
+ var useVercelUseChatRuntime = (chatHelpers) => {
1527
+ const [runtime] = useState4(() => new VercelAIRuntime(chatHelpers));
1431
1528
  useInsertionEffect2(() => {
1432
- runtime.vercel = vercel;
1529
+ runtime.vercel = chatHelpers;
1530
+ });
1531
+ useEffect10(() => {
1532
+ runtime.onVercelUpdated();
1433
1533
  });
1434
- const isRunning = getIsRunning(vercel);
1435
- const converter = useMemo5(() => new ThreadMessageConverter(), []);
1436
- const messages = useMemo5(() => {
1437
- const lastMessageId = vercel.messages.at(-1)?.id;
1438
- const convertCallback = (message, cache) => {
1439
- const status = lastMessageId === message.id && isRunning ? "in_progress" : "done";
1440
- if (cache && (cache.role === "user" || cache.status === status))
1441
- return cache;
1442
- return vercelToThreadMessage(message, status);
1443
- };
1444
- return converter.convertMessages(convertCallback, vercel.messages);
1445
- }, [isRunning, vercel.messages, converter]);
1446
- useEffect5(() => {
1447
- runtime.updateData(isRunning, messages);
1448
- }, [runtime, isRunning, messages]);
1449
1534
  return runtime;
1450
1535
  };
1451
1536
 
1452
- // src/adapters/vercel/VercelAIAssistantProvider.tsx
1453
- import { jsx as jsx22, jsxs as jsxs4 } from "react/jsx-runtime";
1454
- var VercelAIAssistantProvider = ({
1455
- children,
1456
- ...rest
1457
- }) => {
1458
- const vercel = "chat" in rest ? rest.chat : rest.assistant;
1459
- const runtime = useVercelUseChatRuntime(vercel);
1460
- return /* @__PURE__ */ jsxs4(AssistantProvider, { runtime, children: [
1461
- /* @__PURE__ */ jsx22(ComposerSync, { vercel }),
1462
- children
1463
- ] });
1464
- };
1465
- var ComposerSync = ({
1466
- vercel
1467
- }) => {
1468
- const { useComposer } = useAssistantContext();
1469
- useEffect6(() => {
1470
- useComposer.setState({
1471
- value: vercel.input,
1472
- setValue: vercel.setInput
1473
- });
1474
- }, [useComposer, vercel.input, vercel.setInput]);
1475
- return null;
1537
+ // src/runtime/vercel-ai/ui/use-assistant/useVercelUseAssistantRuntime.tsx
1538
+ import { useEffect as useEffect11, useInsertionEffect as useInsertionEffect3, useState as useState5 } from "react";
1539
+ var useVercelUseAssistantRuntime = (assistantHelpers) => {
1540
+ const [runtime] = useState5(() => new VercelAIRuntime(assistantHelpers));
1541
+ useInsertionEffect3(() => {
1542
+ runtime.vercel = assistantHelpers;
1543
+ });
1544
+ useEffect11(() => {
1545
+ runtime.onVercelUpdated();
1546
+ });
1547
+ return runtime;
1476
1548
  };
1477
1549
 
1478
- // src/adapters/core/vercel-rsc/useVercelRSCRuntime.tsx
1479
- import { useEffect as useEffect7, useInsertionEffect as useInsertionEffect3, useMemo as useMemo6, useState as useState5 } from "react";
1550
+ // src/context/providers/AssistantRuntimeProvider.tsx
1551
+ import { memo } from "react";
1480
1552
 
1481
- // src/adapters/core/vercel-rsc/VercelRSCRuntime.tsx
1482
- var EMPTY_BRANCHES = Object.freeze([]);
1483
- var VercelRSCRuntime = class {
1484
- constructor(adapter) {
1485
- this.adapter = adapter;
1486
- }
1487
- _subscriptions = /* @__PURE__ */ new Set();
1488
- isRunning = false;
1489
- messages = [];
1490
- withRunning = (callback) => {
1491
- this.isRunning = true;
1492
- return callback.finally(() => {
1493
- this.isRunning = false;
1494
- });
1495
- };
1496
- getBranches() {
1497
- return EMPTY_BRANCHES;
1498
- }
1499
- switchToBranch() {
1500
- throw new Error(
1501
- "Branch switching is not supported by VercelRSCAssistantProvider."
1502
- );
1503
- }
1504
- async append(message) {
1505
- if (message.parentId !== (this.messages.at(-1)?.id ?? null)) {
1506
- if (!this.adapter.edit)
1507
- throw new Error(
1508
- "Message editing is not enabled, please provide an edit callback to VercelRSCAssistantProvider."
1509
- );
1510
- await this.withRunning(this.adapter.edit(message));
1511
- } else {
1512
- await this.withRunning(this.adapter.append(message));
1513
- }
1514
- }
1515
- async startRun(parentId) {
1516
- if (!this.adapter.reload)
1517
- throw new Error(
1518
- "Message reloading is not enabled, please provide a reload callback to VercelRSCAssistantProvider."
1519
- );
1520
- await this.withRunning(this.adapter.reload(parentId));
1521
- }
1522
- cancelRun() {
1523
- if (process.env["NODE_ENV"] === "development") {
1524
- console.warn(
1525
- "Run cancellation is not supported by VercelRSCAssistantProvider."
1526
- );
1553
+ // src/context/providers/ThreadProvider.tsx
1554
+ import { useEffect as useEffect12, useInsertionEffect as useInsertionEffect4, useRef as useRef4, useState as useState6 } from "react";
1555
+
1556
+ // src/context/stores/Composer.ts
1557
+ import { create as create6 } from "zustand";
1558
+ var makeComposerStore = (useThread) => create6()((set, get, store) => {
1559
+ return {
1560
+ ...makeBaseComposer(set, get, store),
1561
+ isEditing: true,
1562
+ send: () => {
1563
+ const { setValue, value } = get();
1564
+ setValue("");
1565
+ useThread.getState().append({
1566
+ parentId: useThread.getState().messages.at(-1)?.id ?? null,
1567
+ content: [{ type: "text", text: value }]
1568
+ });
1569
+ },
1570
+ cancel: () => {
1571
+ const thread = useThread.getState();
1572
+ if (!thread.isRunning) return false;
1573
+ useThread.getState().cancelRun();
1574
+ return true;
1527
1575
  }
1528
- }
1529
- updateData(messages) {
1530
- this.messages = messages;
1531
- for (const callback of this._subscriptions) callback();
1532
- }
1533
- subscribe(callback) {
1534
- this._subscriptions.add(callback);
1535
- return () => this._subscriptions.delete(callback);
1536
- }
1537
- };
1576
+ };
1577
+ });
1538
1578
 
1539
- // src/adapters/core/vercel-rsc/useVercelRSCRuntime.tsx
1540
- var vercelToThreadMessage2 = (converter, rawMessage) => {
1541
- const message = converter(rawMessage);
1579
+ // src/context/stores/Thread.ts
1580
+ import { create as create7 } from "zustand";
1581
+ var makeThreadStore = (runtimeRef) => {
1582
+ const useThread = create7(() => ({
1583
+ messages: runtimeRef.current.messages,
1584
+ isRunning: runtimeRef.current.isRunning,
1585
+ getBranches: (messageId) => runtimeRef.current.getBranches(messageId),
1586
+ switchToBranch: (branchId) => runtimeRef.current.switchToBranch(branchId),
1587
+ startRun: (parentId) => runtimeRef.current.startRun(parentId),
1588
+ append: (message) => runtimeRef.current.append(message),
1589
+ cancelRun: () => runtimeRef.current.cancelRun()
1590
+ }));
1591
+ const onRuntimeUpdate = () => {
1592
+ useThread.setState({
1593
+ messages: runtimeRef.current.messages,
1594
+ isRunning: runtimeRef.current.isRunning
1595
+ });
1596
+ };
1542
1597
  return {
1543
- id: message.id,
1544
- role: message.role,
1545
- content: [{ type: "ui", display: message.display }],
1546
- createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1547
- ...{ status: "done" },
1548
- [symbolInnerRSCMessage]: rawMessage
1598
+ useThread,
1599
+ onRuntimeUpdate
1549
1600
  };
1550
1601
  };
1551
- var useVercelRSCRuntime = (adapter) => {
1552
- const [runtime] = useState5(() => new VercelRSCRuntime(adapter));
1553
- useInsertionEffect3(() => {
1554
- runtime.adapter = adapter;
1602
+
1603
+ // src/context/stores/ThreadViewport.tsx
1604
+ import { create as create8 } from "zustand";
1605
+ var makeThreadViewportStore = () => {
1606
+ const scrollToBottomListeners = /* @__PURE__ */ new Set();
1607
+ return create8(() => ({
1608
+ isAtBottom: true,
1609
+ scrollToBottom: () => {
1610
+ for (const listener of scrollToBottomListeners) {
1611
+ listener();
1612
+ }
1613
+ },
1614
+ onScrollToBottom: (callback) => {
1615
+ scrollToBottomListeners.add(callback);
1616
+ return () => {
1617
+ scrollToBottomListeners.delete(callback);
1618
+ };
1619
+ }
1620
+ }));
1621
+ };
1622
+
1623
+ // src/context/providers/ThreadProvider.tsx
1624
+ import { jsx as jsx21, jsxs as jsxs4 } from "react/jsx-runtime";
1625
+ var ThreadProvider = ({
1626
+ children,
1627
+ runtime
1628
+ }) => {
1629
+ const runtimeRef = useRef4(runtime);
1630
+ useInsertionEffect4(() => {
1631
+ runtimeRef.current = runtime;
1555
1632
  });
1556
- const [converter, convertCallback] = useMemo6(() => {
1557
- const rscConverter = adapter.convertMessage ?? ((m) => m);
1558
- const convertCallback2 = (m, cache) => {
1559
- if (cache) return cache;
1560
- return vercelToThreadMessage2(rscConverter, m);
1633
+ const [{ context, onRuntimeUpdate }] = useState6(() => {
1634
+ const { useThread, onRuntimeUpdate: onRuntimeUpdate2 } = makeThreadStore(runtimeRef);
1635
+ const useViewport = makeThreadViewportStore();
1636
+ const useComposer = makeComposerStore(useThread);
1637
+ return {
1638
+ context: {
1639
+ useViewport,
1640
+ useThread,
1641
+ useComposer
1642
+ },
1643
+ onRuntimeUpdate: onRuntimeUpdate2
1561
1644
  };
1562
- return [new ThreadMessageConverter(), convertCallback2];
1563
- }, [adapter.convertMessage]);
1564
- useEffect7(() => {
1565
- runtime.updateData(
1566
- converter.convertMessages(convertCallback, adapter.messages)
1567
- );
1568
- }, [runtime, converter, convertCallback, adapter.messages]);
1569
- return runtime;
1645
+ });
1646
+ useEffect12(() => {
1647
+ onRuntimeUpdate();
1648
+ return runtime.subscribe(onRuntimeUpdate);
1649
+ }, [onRuntimeUpdate, runtime]);
1650
+ const RuntimeSynchronizer = runtime.unstable_synchronizer;
1651
+ return /* @__PURE__ */ jsxs4(ThreadContext.Provider, { value: context, children: [
1652
+ RuntimeSynchronizer && /* @__PURE__ */ jsx21(RuntimeSynchronizer, {}),
1653
+ children
1654
+ ] });
1570
1655
  };
1571
1656
 
1572
- // src/adapters/vercel/VercelRSCAssistantProvider.tsx
1657
+ // src/context/providers/AssistantRuntimeProvider.tsx
1658
+ import { jsx as jsx22 } from "react/jsx-runtime";
1659
+ var AssistantRuntimeProviderImpl = ({ children, runtime }) => {
1660
+ return /* @__PURE__ */ jsx22(ThreadProvider, { runtime, children });
1661
+ };
1662
+ var AssistantRuntimeProvider = memo(AssistantRuntimeProviderImpl);
1663
+
1664
+ // src/runtime/vercel-deprecated/VercelAIAssistantProvider.tsx
1573
1665
  import { jsx as jsx23 } from "react/jsx-runtime";
1666
+ var VercelUseChatRuntimeProvider = ({
1667
+ chat,
1668
+ children
1669
+ }) => {
1670
+ const runtime = useVercelUseChatRuntime(chat);
1671
+ return /* @__PURE__ */ jsx23(AssistantRuntimeProvider, { runtime, children });
1672
+ };
1673
+ var VercelUseAssistantRuntimeProvider = ({
1674
+ assistant,
1675
+ children
1676
+ }) => {
1677
+ const runtime = useVercelUseAssistantRuntime(assistant);
1678
+ return /* @__PURE__ */ jsx23(AssistantRuntimeProvider, { runtime, children });
1679
+ };
1680
+ var VercelAIAssistantProvider = ({
1681
+ children,
1682
+ ...rest
1683
+ }) => {
1684
+ if ("chat" in rest) {
1685
+ return /* @__PURE__ */ jsx23(VercelUseChatRuntimeProvider, { chat: rest.chat, children });
1686
+ }
1687
+ return /* @__PURE__ */ jsx23(VercelUseAssistantRuntimeProvider, { assistant: rest.assistant, children });
1688
+ };
1689
+
1690
+ // src/runtime/vercel-deprecated/VercelRSCAssistantProvider.tsx
1691
+ import { jsx as jsx24 } from "react/jsx-runtime";
1574
1692
  var VercelRSCAssistantProvider = ({
1575
1693
  children,
1576
1694
  ...adapter
1577
1695
  }) => {
1578
1696
  const runtime = useVercelRSCRuntime(adapter);
1579
- return /* @__PURE__ */ jsx23(AssistantProvider, { runtime, children });
1697
+ return /* @__PURE__ */ jsx24(AssistantRuntimeProvider, { runtime, children });
1580
1698
  };
1581
1699
 
1582
- // src/adapters/core/local/useLocalRuntime.tsx
1583
- import { useInsertionEffect as useInsertionEffect4, useState as useState6 } from "react";
1700
+ // src/runtime/local/useLocalRuntime.tsx
1701
+ import { useInsertionEffect as useInsertionEffect5, useState as useState7 } from "react";
1584
1702
 
1585
- // src/adapters/core/local/LocalRuntime.tsx
1703
+ // src/runtime/local/LocalRuntime.tsx
1586
1704
  var LocalRuntime = class {
1587
1705
  constructor(adapter) {
1588
1706
  this.adapter = adapter;
@@ -1665,16 +1783,16 @@ var LocalRuntime = class {
1665
1783
  }
1666
1784
  };
1667
1785
 
1668
- // src/adapters/core/local/useLocalRuntime.tsx
1786
+ // src/runtime/local/useLocalRuntime.tsx
1669
1787
  var useLocalRuntime = (adapter) => {
1670
- const [runtime] = useState6(() => new LocalRuntime(adapter));
1671
- useInsertionEffect4(() => {
1788
+ const [runtime] = useState7(() => new LocalRuntime(adapter));
1789
+ useInsertionEffect5(() => {
1672
1790
  runtime.adapter = adapter;
1673
1791
  });
1674
1792
  return runtime;
1675
1793
  };
1676
1794
 
1677
- // src/adapters/core/local/vercel/VercelModelAdapter.tsx
1795
+ // src/runtime/local/vercel/VercelModelAdapter.tsx
1678
1796
  import { streamText } from "ai";
1679
1797
  var VercelModelAdapter = class {
1680
1798
  constructor(model) {
@@ -1717,6 +1835,7 @@ var VercelModelAdapter = class {
1717
1835
  };
1718
1836
  export {
1719
1837
  actionBar_exports as ActionBarPrimitive,
1838
+ AssistantRuntimeProvider,
1720
1839
  branchPicker_exports as BranchPickerPrimitive,
1721
1840
  composer_exports as ComposerPrimitive,
1722
1841
  contentPart_exports as ContentPartPrimitive,
@@ -1724,15 +1843,20 @@ export {
1724
1843
  thread_exports as ThreadPrimitive,
1725
1844
  VercelAIAssistantProvider,
1726
1845
  VercelRSCAssistantProvider,
1727
- AssistantProvider as unstable_AssistantProvider,
1846
+ getVercelAIMessage,
1847
+ getVercelRSCMessage,
1728
1848
  VercelModelAdapter as unstable_VercelModelAdapter,
1729
- getVercelMessage as unstable_getVercelMessage,
1730
- getVercelRSCMessage as unstable_getVercelRSCMessage,
1849
+ useComposerContext as unstable_useComposerContext,
1850
+ useContentPartContext as unstable_useContentPartContext,
1731
1851
  useLocalRuntime as unstable_useLocalRuntime,
1732
1852
  useMessageContext as unstable_useMessageContext,
1853
+ useThreadContext as unstable_useThreadContext,
1733
1854
  useBeginMessageEdit,
1734
1855
  useCopyMessage,
1735
1856
  useGoToNextBranch,
1736
1857
  useGoToPreviousBranch,
1737
- useReloadMessage
1858
+ useReloadMessage,
1859
+ useVercelRSCRuntime,
1860
+ useVercelUseAssistantRuntime,
1861
+ useVercelUseChatRuntime
1738
1862
  };