@assistant-ui/react 0.0.4 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.mjs CHANGED
@@ -25,92 +25,22 @@ var ThreadRoot = forwardRef(
25
25
  }
26
26
  );
27
27
 
28
- // src/utils/context/createStoreContext.tsx
29
- import {
30
- createContext,
31
- useContext,
32
- useMemo,
33
- useState
34
- } from "react";
35
- import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector";
36
-
37
- // src/utils/context/shallow.ts
38
- function shallow(objA, objB) {
39
- if (Object.is(objA, objB)) {
40
- return true;
41
- }
42
- if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) {
43
- return false;
44
- }
45
- const keysA = Object.keys(objA);
46
- if (keysA.length !== Object.keys(objB).length) {
47
- return false;
48
- }
49
- for (const keyA of keysA) {
50
- if (!Object.prototype.hasOwnProperty.call(objB, keyA) || !Object.is(objA[keyA], objB[keyA])) {
51
- return false;
52
- }
53
- }
54
- return true;
55
- }
56
-
57
- // src/utils/context/createStoreContext.tsx
58
- var createStoreContext = (providerName) => {
59
- const context = createContext(null);
60
- const StoreProvider = ({ children, ...rest }) => {
61
- const unstableContext = rest;
62
- const [store] = useState(() => {
63
- let state = unstableContext;
64
- const listeners = /* @__PURE__ */ new Set();
65
- return {
66
- subscribe: (cb) => {
67
- listeners.add(cb);
68
- return () => listeners.delete(cb);
69
- },
70
- emit: () => {
71
- for (const listener of listeners) {
72
- listener();
73
- }
74
- },
75
- snapshot: () => {
76
- return state;
77
- },
78
- setState: (value) => {
79
- state = value;
80
- store.emit();
81
- }
82
- };
83
- });
84
- useMemo(
85
- () => store.setState(unstableContext),
86
- Object.values(unstableContext)
87
- );
88
- return /* @__PURE__ */ React.createElement(context.Provider, { value: store }, children);
89
- };
90
- const useStoreContext = (consumerName, selector) => {
91
- const store = useContext(context);
92
- if (!store)
93
- throw new Error(
94
- `${consumerName} can only be used inside ${providerName}.`
95
- );
96
- return useSyncExternalStoreWithSelector(
97
- store.subscribe,
98
- store.snapshot,
99
- store.snapshot,
100
- selector,
101
- shallow
28
+ // src/utils/context/AssistantContext.ts
29
+ import { createContext, useContext } from "react";
30
+ var AssistantContext = createContext(null);
31
+ var useAssistantContext = () => {
32
+ const context = useContext(AssistantContext);
33
+ if (!context)
34
+ throw new Error(
35
+ "useAssistantContext must be used within a AssistantProvider"
102
36
  );
103
- };
104
- return [StoreProvider, useStoreContext];
37
+ return context;
105
38
  };
106
39
 
107
- // src/utils/context/ThreadContext.ts
108
- var [ThreadContextProvider, useThreadContext] = createStoreContext("Thread.Provider");
109
-
110
40
  // src/primitives/thread/ThreadIf.tsx
111
41
  var useThreadIf = (props) => {
112
- return useThreadContext("Thread.If", (s) => {
113
- const thread = s.chat;
42
+ const { useThread } = useAssistantContext();
43
+ return useThread((thread) => {
114
44
  if (props.empty === true && thread.messages.length !== 0)
115
45
  return false;
116
46
  if (props.empty === false && thread.messages.length === 0)
@@ -133,7 +63,7 @@ var ThreadEmpty = ({ children }) => {
133
63
  };
134
64
 
135
65
  // src/primitives/thread/ThreadViewport.tsx
136
- import { forwardRef as forwardRef2, useRef as useRef2, useState as useState2 } from "react";
66
+ import { forwardRef as forwardRef2, useRef as useRef2, useState } from "react";
137
67
  import {
138
68
  Primitive as Primitive2
139
69
  } from "@radix-ui/react-primitive";
@@ -185,7 +115,7 @@ var useOnResizeContent = (ref, callback) => {
185
115
  var ThreadViewport = forwardRef2(({ onScroll, children, ...rest }, forwardedRef) => {
186
116
  const divRef = useRef2(null);
187
117
  const ref = useComposedRefs(forwardedRef, divRef);
188
- const [isAtBottom, setIsAtBottom] = useState2(true);
118
+ const [isAtBottom, setIsAtBottom] = useState(true);
189
119
  useOnResizeContent(divRef, () => {
190
120
  const div = divRef.current;
191
121
  if (!div || !isAtBottom)
@@ -210,8 +140,8 @@ var ThreadViewport = forwardRef2(({ onScroll, children, ...rest }, forwardedRef)
210
140
  );
211
141
  });
212
142
 
213
- // src/utils/hooks/useBranches.tsx
214
- import { useCallback, useMemo as useMemo2, useRef as useRef3 } from "react";
143
+ // src/vercel/useVercelAIBranches.tsx
144
+ import { useCallback, useMemo, useRef as useRef3 } from "react";
215
145
  var ROOT_ID = "__ROOT_ID__";
216
146
  var UPCOMING_MESSAGE_ID = "__UPCOMING_MESSAGE_ID__";
217
147
  var updateBranchData = (data, messages) => {
@@ -278,7 +208,7 @@ var sliceMessagesUntil = (messages, message) => {
278
208
  throw new Error("Unexpected: Message not found");
279
209
  return messages.slice(0, messageIdx);
280
210
  };
281
- var useChatWithBranches = (chat) => {
211
+ var useVercelAIBranches = (chat) => {
282
212
  const data = useRef3({
283
213
  parentMap: /* @__PURE__ */ new Map(),
284
214
  branchMap: /* @__PURE__ */ new Map(),
@@ -315,87 +245,170 @@ var useChatWithBranches = (chat) => {
315
245
  async (message, newMessage) => {
316
246
  const newMessages = sliceMessagesUntil(chat.messages, message);
317
247
  chat.setMessages(newMessages);
318
- await chat.append(newMessage);
248
+ if (newMessage.content[0]?.type !== "text")
249
+ throw new Error("Only text content is currently supported");
250
+ await chat.append({
251
+ role: "user",
252
+ content: newMessage.content[0].text
253
+ });
319
254
  },
320
255
  [chat.messages, chat.setMessages, chat.append]
321
256
  );
322
- return useMemo2(
257
+ return useMemo(
323
258
  () => ({
324
- ...chat,
325
259
  getBranchState,
326
260
  switchToBranch,
327
261
  editAt,
328
262
  reloadAt
329
263
  }),
330
- [chat, getBranchState, switchToBranch, editAt, reloadAt]
264
+ [getBranchState, switchToBranch, editAt, reloadAt]
331
265
  );
332
266
  };
333
267
  var hasUpcomingMessage = (thread) => {
334
268
  return thread.isLoading && thread.messages[thread.messages.length - 1]?.role !== "assistant";
335
269
  };
336
270
 
271
+ // src/utils/context/ComposerState.ts
272
+ import { useContext as useContext3 } from "react";
273
+
274
+ // src/utils/context/MessageContext.ts
275
+ import { createContext as createContext2, useContext as useContext2 } from "react";
276
+ var MessageContext = createContext2(null);
277
+ var useMessageContext = () => {
278
+ const context = useContext2(MessageContext);
279
+ if (!context)
280
+ throw new Error("useMessageContext must be used within a MessageProvider");
281
+ return context;
282
+ };
283
+
284
+ // src/utils/context/ComposerState.ts
285
+ var useComposerContext = () => {
286
+ const { useComposer: useAssisstantComposer } = useAssistantContext();
287
+ const { useComposer: useMessageComposer } = useContext3(MessageContext) ?? {};
288
+ return { useComposer: useMessageComposer ?? useAssisstantComposer };
289
+ };
290
+
291
+ // src/primitives/composer/ComposerIf.tsx
292
+ var useComposerIf = (props) => {
293
+ const { useComposer } = useComposerContext();
294
+ return useComposer((composer) => {
295
+ if (props.editing === true && !composer.isEditing)
296
+ return false;
297
+ if (props.editing === false && composer.isEditing)
298
+ return false;
299
+ return true;
300
+ });
301
+ };
302
+ var ComposerIf = ({ children, ...query }) => {
303
+ const result = useComposerIf(query);
304
+ return result ? children : null;
305
+ };
306
+
337
307
  // src/primitives/message/index.ts
338
308
  var message_exports = {};
339
309
  __export(message_exports, {
340
310
  Content: () => MessageContent,
341
- EditableContent: () => MessageEditableContent,
342
311
  If: () => MessageIf,
343
312
  Provider: () => MessageProvider,
344
313
  Root: () => MessageRoot
345
314
  });
346
315
 
347
316
  // src/primitives/message/MessageProvider.tsx
348
- import { useMemo as useMemo3, useState as useState3 } from "react";
349
-
350
- // src/utils/context/MessageContext.ts
351
- var [MessageContextProvider, useMessageContext] = createStoreContext("Thread.Provider");
352
-
353
- // src/primitives/message/MessageProvider.tsx
317
+ import { useMemo as useMemo2, useState as useState2 } from "react";
318
+ import { create } from "zustand";
319
+ import { useShallow } from "zustand/react/shallow";
320
+ var getIsLast = (thread, message) => {
321
+ const hasUpcoming = hasUpcomingMessage(thread);
322
+ return hasUpcoming ? message.id === UPCOMING_MESSAGE_ID : thread.messages[thread.messages.length - 1]?.id === message.id;
323
+ };
324
+ var useMessageContext2 = () => {
325
+ const { useBranchObserver } = useAssistantContext();
326
+ const [context] = useState2(() => {
327
+ const useMessage = create(() => ({
328
+ message: null,
329
+ isLast: false,
330
+ isCopied: false,
331
+ isHovering: false,
332
+ setIsCopied: () => {
333
+ },
334
+ setIsHovering: () => {
335
+ },
336
+ branchState: {
337
+ branchId: 0,
338
+ branchCount: 0
339
+ }
340
+ }));
341
+ const useComposer = create((set, get) => ({
342
+ isEditing: false,
343
+ canCancel: true,
344
+ edit: () => {
345
+ const message = useMessage.getState().message;
346
+ if (message.role !== "user")
347
+ throw new Error("Editing is only supported for user messages");
348
+ if (message.content[0]?.type !== "text")
349
+ throw new Error("Editing is only supported for text-only messages");
350
+ return set({
351
+ isEditing: true,
352
+ value: message.content[0].text
353
+ });
354
+ },
355
+ cancel: () => set({ isEditing: false }),
356
+ send: () => {
357
+ const message = useMessage.getState().message;
358
+ if (message.role !== "user")
359
+ throw new Error("Editing is only supported for user messages");
360
+ useBranchObserver.getState().editAt(message, {
361
+ role: "user",
362
+ content: [{ type: "text", text: get().value }]
363
+ });
364
+ set({ isEditing: false });
365
+ },
366
+ value: "",
367
+ setValue: (value) => set({ value })
368
+ }));
369
+ return { useMessage, useComposer };
370
+ });
371
+ return context;
372
+ };
354
373
  var MessageProvider = ({
355
374
  message,
356
375
  children
357
376
  }) => {
358
- const getBranchState = useThreadContext(
359
- "Message.Provider",
360
- (s) => s.chat.getBranchState
361
- );
362
- const [editState, setEditState] = useState3({
363
- isEditing: false
364
- });
365
- const [isCopied, setIsCopied] = useState3(false);
366
- const [isHovering, setIsHovering] = useState3(false);
367
- const branchState = useMemo3(
368
- () => getBranchState(message),
369
- [getBranchState, message]
370
- );
371
- return /* @__PURE__ */ React.createElement(
372
- MessageContextProvider,
373
- {
374
- message,
375
- editState,
376
- setEditState,
377
- branchState,
378
- isCopied,
379
- setIsCopied,
380
- isHovering,
381
- setIsHovering
382
- },
383
- children
377
+ const { useThread, useBranchObserver } = useAssistantContext();
378
+ const context = useMessageContext2();
379
+ const branchState = useBranchObserver(
380
+ useShallow((b) => b.getBranchState(message))
384
381
  );
382
+ const isLast = useThread((thread) => getIsLast(thread, message));
383
+ const [isCopied, setIsCopied] = useState2(false);
384
+ const [isHovering, setIsHovering] = useState2(false);
385
+ useMemo2(() => {
386
+ context.useMessage.setState(
387
+ {
388
+ message,
389
+ isLast,
390
+ isCopied,
391
+ isHovering,
392
+ setIsCopied,
393
+ setIsHovering,
394
+ branchState
395
+ },
396
+ true
397
+ );
398
+ }, [context, message, isLast, isCopied, isHovering, branchState]);
399
+ return /* @__PURE__ */ React.createElement(MessageContext.Provider, { value: context }, children);
385
400
  };
386
401
 
387
402
  // src/primitives/message/MessageRoot.tsx
388
- import { forwardRef as forwardRef3 } from "react";
403
+ import { composeEventHandlers as composeEventHandlers2 } from "@radix-ui/primitive";
389
404
  import {
390
405
  Primitive as Primitive3
391
406
  } from "@radix-ui/react-primitive";
392
- import { composeEventHandlers as composeEventHandlers2 } from "@radix-ui/primitive";
407
+ import { forwardRef as forwardRef3 } from "react";
393
408
  var MessageRoot = forwardRef3(
394
409
  ({ onMouseEnter, onMouseLeave, ...rest }, ref) => {
395
- const setIsHovering = useMessageContext(
396
- "Message.Root",
397
- (s) => s.setIsHovering
398
- );
410
+ const { useMessage } = useMessageContext();
411
+ const setIsHovering = useMessage((s) => s.setIsHovering);
399
412
  const handleMouseEnter = () => {
400
413
  setIsHovering(true);
401
414
  };
@@ -415,27 +428,23 @@ var MessageRoot = forwardRef3(
415
428
  );
416
429
 
417
430
  // src/primitives/message/MessageIf.tsx
418
- var isLast = (thread, message) => {
419
- const hasUpcoming = hasUpcomingMessage(thread);
420
- return hasUpcoming ? message.id === UPCOMING_MESSAGE_ID : thread.messages[thread.messages.length - 1]?.id === message.id;
421
- };
422
431
  var useMessageIf = (props) => {
423
- const thread = useThreadContext("Message.If", (s) => s.chat);
424
- return useMessageContext(
425
- "Message.If",
426
- ({ message, editState: { isEditing }, isCopied, isHovering }) => {
427
- const { branchCount } = thread.getBranchState(message);
432
+ const { useMessage } = useMessageContext();
433
+ return useMessage(
434
+ ({
435
+ message,
436
+ isLast,
437
+ isCopied,
438
+ isHovering,
439
+ branchState: { branchCount }
440
+ }) => {
428
441
  if (props.hasBranches === true && branchCount < 2)
429
442
  return false;
430
443
  if (props.user && message.role !== "user")
431
444
  return false;
432
445
  if (props.assistant && message.role !== "assistant")
433
446
  return false;
434
- if (props.editing === true && !isEditing)
435
- return false;
436
- if (props.editing === false && isEditing)
437
- return false;
438
- if (props.lastOrHover === true && !isHovering && !isLast(thread, message))
447
+ if (props.lastOrHover === true && !isHovering && !isLast)
439
448
  return false;
440
449
  if (props.copied === true && !isCopied)
441
450
  return false;
@@ -452,66 +461,40 @@ var MessageIf = ({ children, ...query }) => {
452
461
 
453
462
  // src/primitives/message/MessageContent.tsx
454
463
  var MessageContent = () => {
455
- const content = useMessageContext(
456
- "Message.Content",
457
- (s) => s.message.content
458
- );
459
- return /* @__PURE__ */ React.createElement(React.Fragment, null, content);
464
+ const { useMessage } = useMessageContext();
465
+ const content = useMessage((s) => s.message.content);
466
+ if (content[0]?.type !== "text")
467
+ throw new Error("Unsupported message content type");
468
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, content[0].text);
460
469
  };
461
470
 
462
- // src/primitives/message/MessageEditableContent.tsx
463
- import { forwardRef as forwardRef4 } from "react";
464
- import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
465
- import TextareaAutosize from "react-textarea-autosize";
466
- var MessageEditableContent = forwardRef4(({ onChange, value, ...rest }, forwardedRef) => {
467
- const [editState, setEditState] = useMessageContext(
468
- "Message.EditableContent",
469
- (s) => [s.editState, s.setEditState]
470
- );
471
- const handleChange = (e) => {
472
- setEditState({ isEditing: true, value: e.target.value });
473
- };
474
- if (!editState.isEditing)
475
- throw new Error(
476
- "Message.EditableContent may only be rendered when edit mode is enabled. Consider wrapping the component in <Message.If editing>."
477
- );
478
- return /* @__PURE__ */ React.createElement(
479
- TextareaAutosize,
480
- {
481
- ...rest,
482
- ref: forwardedRef,
483
- onChange: composeEventHandlers3(onChange, handleChange),
484
- value: editState.value || value
485
- }
486
- );
487
- });
488
-
489
471
  // src/primitives/thread/ThreadMessages.tsx
490
472
  var getComponents = (components) => {
491
473
  return {
492
- EditingUserMessage: components.EditingUserMessage ?? components.UserMessage ?? components.Message,
474
+ EditComposer: components.EditComposer ?? components.UserMessage ?? components.Message,
493
475
  UserMessage: components.UserMessage ?? components.Message,
494
476
  AssistantMessage: components.AssistantMessage ?? components.Message
495
477
  };
496
478
  };
497
479
  var ThreadMessages = ({ components }) => {
498
- const chat = useThreadContext("Thread.Messages", (s) => s.chat);
499
- const messages = chat.messages;
500
- const { UserMessage, EditingUserMessage, AssistantMessage } = getComponents(components);
480
+ const { useThread } = useAssistantContext();
481
+ const thread = useThread();
482
+ const messages = thread.messages;
483
+ const { UserMessage, EditComposer, AssistantMessage } = getComponents(components);
501
484
  if (messages.length === 0)
502
485
  return null;
503
486
  return /* @__PURE__ */ React.createElement(React.Fragment, null, messages.map((message, idx) => {
504
487
  return (
505
488
  // biome-ignore lint/suspicious/noArrayIndexKey: fixes a11y issues with branch navigation
506
- /* @__PURE__ */ React.createElement(MessageProvider, { key: idx, message }, /* @__PURE__ */ React.createElement(MessageIf, { user: true, editing: false }, /* @__PURE__ */ React.createElement(UserMessage, null)), /* @__PURE__ */ React.createElement(MessageIf, { user: true, editing: true }, /* @__PURE__ */ React.createElement(EditingUserMessage, null)), /* @__PURE__ */ React.createElement(MessageIf, { assistant: true }, /* @__PURE__ */ React.createElement(AssistantMessage, null)))
489
+ /* @__PURE__ */ React.createElement(MessageProvider, { key: idx, message }, /* @__PURE__ */ React.createElement(MessageIf, { user: true }, /* @__PURE__ */ React.createElement(ComposerIf, { editing: false }, /* @__PURE__ */ React.createElement(UserMessage, null)), /* @__PURE__ */ React.createElement(ComposerIf, { editing: true }, /* @__PURE__ */ React.createElement(EditComposer, null))), /* @__PURE__ */ React.createElement(MessageIf, { assistant: true }, /* @__PURE__ */ React.createElement(AssistantMessage, null)))
507
490
  );
508
- }), hasUpcomingMessage(chat) && /* @__PURE__ */ React.createElement(
491
+ }), hasUpcomingMessage(thread) && /* @__PURE__ */ React.createElement(
509
492
  MessageProvider,
510
493
  {
511
494
  message: {
512
495
  id: UPCOMING_MESSAGE_ID,
513
496
  role: "assistant",
514
- content: "..."
497
+ content: [{ type: "text", text: "..." }]
515
498
  }
516
499
  },
517
500
  /* @__PURE__ */ React.createElement(AssistantMessage, null)
@@ -521,22 +504,29 @@ var ThreadMessages = ({ components }) => {
521
504
  // src/primitives/composer/index.ts
522
505
  var composer_exports = {};
523
506
  __export(composer_exports, {
507
+ Cancel: () => ComposerCancel,
508
+ If: () => ComposerIf,
524
509
  Input: () => ComposerInput,
525
510
  Root: () => ComposerRoot,
526
- Send: () => ComposerSend,
527
- Stop: () => ComposerStop
511
+ Send: () => ComposerSend
528
512
  });
529
513
 
530
514
  // src/primitives/composer/ComposerRoot.tsx
531
- import { createContext as createContext2, forwardRef as forwardRef5, useContext as useContext2, useMemo as useMemo4, useRef as useRef4 } from "react";
515
+ import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
516
+ import { useComposedRefs as useComposedRefs2 } from "@radix-ui/react-compose-refs";
532
517
  import {
533
518
  Primitive as Primitive4
534
519
  } from "@radix-ui/react-primitive";
535
- import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
536
- import { useComposedRefs as useComposedRefs2 } from "@radix-ui/react-compose-refs";
537
- var ComposerContext = createContext2(null);
538
- var useComposerContext = () => {
539
- const context = useContext2(ComposerContext);
520
+ import {
521
+ createContext as createContext3,
522
+ forwardRef as forwardRef4,
523
+ useContext as useContext4,
524
+ useMemo as useMemo3,
525
+ useRef as useRef4
526
+ } from "react";
527
+ var ComposerFormContext = createContext3(null);
528
+ var useComposerFormContext = () => {
529
+ const context = useContext4(ComposerFormContext);
540
530
  if (!context) {
541
531
  throw new Error(
542
532
  "Composer compound components cannot be rendered outside the Composer component"
@@ -544,15 +534,12 @@ var useComposerContext = () => {
544
534
  }
545
535
  return context;
546
536
  };
547
- var ComposerRoot = forwardRef5(
537
+ var ComposerRoot = forwardRef4(
548
538
  ({ onSubmit, ...rest }, forwardedRef) => {
549
- const handleSubmit = useThreadContext(
550
- "Composer.Root",
551
- (s) => s.chat.handleSubmit
552
- );
539
+ const { useComposer } = useComposerContext();
553
540
  const formRef = useRef4(null);
554
541
  const ref = useComposedRefs2(forwardedRef, formRef);
555
- const composerContextValue = useMemo4(
542
+ const composerContextValue = useMemo3(
556
543
  () => ({
557
544
  submit: () => formRef.current?.dispatchEvent(
558
545
  new Event("submit", { cancelable: true, bubbles: true })
@@ -560,108 +547,115 @@ var ComposerRoot = forwardRef5(
560
547
  }),
561
548
  []
562
549
  );
563
- return /* @__PURE__ */ React.createElement(ComposerContext.Provider, { value: composerContextValue }, /* @__PURE__ */ React.createElement(
550
+ const handleSubmit = (e) => {
551
+ const composerState = useComposer.getState();
552
+ if (!composerState.isEditing)
553
+ return;
554
+ e.preventDefault();
555
+ composerState.send();
556
+ };
557
+ return /* @__PURE__ */ React.createElement(ComposerFormContext.Provider, { value: composerContextValue }, /* @__PURE__ */ React.createElement(
564
558
  Primitive4.form,
565
559
  {
566
560
  ...rest,
567
561
  ref,
568
- onSubmit: composeEventHandlers4(onSubmit, handleSubmit)
562
+ onSubmit: composeEventHandlers3(onSubmit, handleSubmit)
569
563
  }
570
564
  ));
571
565
  }
572
566
  );
573
567
 
574
568
  // src/primitives/composer/ComposerInput.tsx
575
- import { forwardRef as forwardRef6 } from "react";
569
+ import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
576
570
  import { Slot } from "@radix-ui/react-slot";
577
- import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
578
- import TextareaAutosize2 from "react-textarea-autosize";
579
- var ComposerInput = forwardRef6(({ asChild, onChange, onKeyDown, ...rest }, forwardedRef) => {
580
- const chat = useThreadContext(
581
- "Composer.Input",
582
- ({ chat: { input, handleInputChange, isLoading } }) => ({
583
- input,
584
- handleInputChange,
585
- isLoading
586
- })
587
- );
588
- const Component = asChild ? Slot : TextareaAutosize2;
589
- const composer = useComposerContext();
571
+ import { forwardRef as forwardRef5 } from "react";
572
+ import TextareaAutosize from "react-textarea-autosize";
573
+ var ComposerInput = forwardRef5(({ asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
574
+ const { useThread } = useAssistantContext();
575
+ const isLoading = useThread((t) => t.isLoading);
576
+ const { useComposer } = useComposerContext();
577
+ const value = useComposer((c) => {
578
+ if (!c.isEditing)
579
+ return "";
580
+ return c.value;
581
+ });
582
+ const Component = asChild ? Slot : TextareaAutosize;
583
+ const composerForm = useComposerFormContext();
590
584
  const handleKeyPress = (e) => {
591
- if (chat.isLoading || rest.disabled)
585
+ if (disabled)
586
+ return;
587
+ if (e.key === "Escape") {
588
+ useComposer.getState().cancel();
589
+ }
590
+ if (isLoading)
592
591
  return;
593
592
  if (e.key === "Enter" && e.shiftKey === false) {
594
593
  e.preventDefault();
595
- composer.submit();
594
+ composerForm.submit();
596
595
  }
597
596
  };
598
597
  return /* @__PURE__ */ React.createElement(
599
598
  Component,
600
599
  {
601
- value: chat.input,
600
+ value,
602
601
  ...rest,
603
602
  ref: forwardedRef,
604
- onChange: composeEventHandlers5(onChange, chat.handleInputChange),
605
- onKeyDown: composeEventHandlers5(onKeyDown, handleKeyPress)
603
+ disabled,
604
+ onChange: composeEventHandlers4(onChange, (e) => {
605
+ const composerState = useComposer.getState();
606
+ if (!composerState.isEditing)
607
+ return;
608
+ return composerState.setValue(e.target.value);
609
+ }),
610
+ onKeyDown: composeEventHandlers4(onKeyDown, handleKeyPress)
606
611
  }
607
612
  );
608
613
  });
609
614
 
610
615
  // src/primitives/composer/ComposerSend.tsx
611
- import { forwardRef as forwardRef7 } from "react";
612
616
  import {
613
617
  Primitive as Primitive5
614
618
  } from "@radix-ui/react-primitive";
615
- var ComposerSend = forwardRef7(
619
+ import { forwardRef as forwardRef6 } from "react";
620
+ var ComposerSend = forwardRef6(
616
621
  ({ disabled, ...rest }, ref) => {
617
- const input = useThreadContext("Composer.Send", (s) => s.chat.input);
622
+ const { useComposer } = useComposerContext();
623
+ const hasValue = useComposer((c) => c.isEditing && c.value.length > 0);
618
624
  return /* @__PURE__ */ React.createElement(
619
625
  Primitive5.button,
620
626
  {
621
627
  type: "submit",
622
628
  ...rest,
623
629
  ref,
624
- disabled: disabled || input.length === 0
630
+ disabled: disabled || !hasValue
625
631
  }
626
632
  );
627
633
  }
628
634
  );
629
635
 
630
- // src/utils/createActionButton.tsx
631
- import { forwardRef as forwardRef8 } from "react";
636
+ // src/primitives/composer/ComposerCancel.tsx
637
+ import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
632
638
  import {
633
639
  Primitive as Primitive6
634
640
  } from "@radix-ui/react-primitive";
635
- import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
636
- var createActionButton = (useActionButton) => {
637
- return forwardRef8(
638
- (props, forwardedRef) => {
639
- const onClick = useActionButton(props);
640
- return /* @__PURE__ */ React.createElement(
641
- Primitive6.button,
642
- {
643
- type: "button",
644
- disabled: !onClick,
645
- ...props,
646
- ref: forwardedRef,
647
- onClick: composeEventHandlers6(props.onClick, onClick ?? void 0)
648
- }
649
- );
641
+ import { forwardRef as forwardRef7 } from "react";
642
+ var ComposerCancel = forwardRef7(({ disabled, onClick, ...rest }, ref) => {
643
+ const { useComposer } = useComposerContext();
644
+ const hasValue = useComposer((c) => c.canCancel);
645
+ const handleClose = () => {
646
+ useComposer.getState().cancel();
647
+ };
648
+ return /* @__PURE__ */ React.createElement(
649
+ Primitive6.button,
650
+ {
651
+ type: "button",
652
+ ...rest,
653
+ ref,
654
+ onClick: composeEventHandlers5(onClick, handleClose),
655
+ disabled: disabled || !hasValue
650
656
  }
651
657
  );
652
- };
653
-
654
- // src/primitives/composer/ComposerStop.tsx
655
- var useComposerStop = () => {
656
- const [isLoading, stop] = useThreadContext("Composer.Stop", (s) => [
657
- s.chat.isLoading,
658
- s.chat.stop
659
- ]);
660
- if (!isLoading)
661
- return null;
662
- return stop;
663
- };
664
- var ComposerStop = createActionButton(useComposerStop);
658
+ });
665
659
 
666
660
  // src/primitives/branchPicker/index.ts
667
661
  var branchPicker_exports = {};
@@ -675,52 +669,66 @@ __export(branchPicker_exports, {
675
669
 
676
670
  // src/actions/useGoToNextBranch.tsx
677
671
  var useGoToNextBranch = () => {
678
- const switchToBranch = useThreadContext(
679
- "BranchPicker.Next",
680
- (s) => s.chat.switchToBranch
672
+ const { useThread, useBranchObserver } = useAssistantContext();
673
+ const { useComposer, useMessage } = useMessageContext();
674
+ const isLoading = useThread((s) => s.isLoading);
675
+ const isEditing = useComposer((s) => s.isEditing);
676
+ const hasNext = useMessage(
677
+ ({ branchState: { branchId, branchCount } }) => branchId + 1 < branchCount
681
678
  );
682
- const context = useMessageContext("BranchPicker.Next", (s) => {
683
- const {
684
- message: message2,
685
- editState: { isEditing },
686
- branchState: { branchId: branchId2, branchCount }
687
- } = s;
688
- if (isEditing || branchCount <= 1 || branchId2 + 1 >= branchCount)
689
- return null;
690
- return { message: message2, branchId: branchId2 };
691
- });
692
- if (!context)
679
+ if (isLoading || isEditing || !hasNext)
693
680
  return null;
694
- const { message, branchId } = context;
695
681
  return () => {
696
- switchToBranch(message, branchId + 1);
682
+ const {
683
+ message,
684
+ branchState: { branchId }
685
+ } = useMessage.getState();
686
+ useBranchObserver.getState().switchToBranch(message, branchId + 1);
697
687
  };
698
688
  };
699
689
 
690
+ // src/utils/createActionButton.tsx
691
+ import { forwardRef as forwardRef8 } from "react";
692
+ import {
693
+ Primitive as Primitive7
694
+ } from "@radix-ui/react-primitive";
695
+ import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
696
+ var createActionButton = (useActionButton) => {
697
+ return forwardRef8(
698
+ (props, forwardedRef) => {
699
+ const onClick = useActionButton(props);
700
+ return /* @__PURE__ */ React.createElement(
701
+ Primitive7.button,
702
+ {
703
+ type: "button",
704
+ disabled: !onClick,
705
+ ...props,
706
+ ref: forwardedRef,
707
+ onClick: composeEventHandlers6(props.onClick, onClick ?? void 0)
708
+ }
709
+ );
710
+ }
711
+ );
712
+ };
713
+
700
714
  // src/primitives/branchPicker/BranchPickerNext.tsx
701
715
  var BranchPickerNext = createActionButton(useGoToNextBranch);
702
716
 
703
717
  // src/actions/useGoToPreviousBranch.tsx
704
718
  var useGoToPreviousBranch = () => {
705
- const switchToBranch = useThreadContext(
706
- "BranchPicker.Previous",
707
- (s) => s.chat.switchToBranch
708
- );
709
- const context = useMessageContext("BranchPicker.Previous", (s) => {
710
- const {
711
- message: message2,
712
- editState: { isEditing },
713
- branchState: { branchId: branchId2, branchCount }
714
- } = s;
715
- if (isEditing || branchCount <= 1 || branchId2 <= 0)
716
- return null;
717
- return { message: message2, branchId: branchId2 };
718
- });
719
- if (!context)
719
+ const { useThread, useBranchObserver } = useAssistantContext();
720
+ const { useComposer, useMessage } = useMessageContext();
721
+ const isLoading = useThread((s) => s.isLoading);
722
+ const isEditing = useComposer((s) => s.isEditing);
723
+ const hasNext = useMessage(({ branchState: { branchId } }) => branchId > 0);
724
+ if (isLoading || isEditing || !hasNext)
720
725
  return null;
721
- const { message, branchId } = context;
722
726
  return () => {
723
- switchToBranch(message, branchId - 1);
727
+ const {
728
+ message,
729
+ branchState: { branchId }
730
+ } = useMessage.getState();
731
+ useBranchObserver.getState().switchToBranch(message, branchId - 1);
724
732
  };
725
733
  };
726
734
 
@@ -729,29 +737,25 @@ var BranchPickerPrevious = createActionButton(useGoToPreviousBranch);
729
737
 
730
738
  // src/primitives/branchPicker/BranchPickerCount.tsx
731
739
  var BranchPickerCount = () => {
732
- const branchCount = useMessageContext(
733
- "BranchPicker.Count",
734
- (s) => s.branchState.branchCount
735
- );
740
+ const { useMessage } = useMessageContext();
741
+ const branchCount = useMessage((s) => s.branchState.branchCount);
736
742
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchCount);
737
743
  };
738
744
 
739
745
  // src/primitives/branchPicker/BranchPickerNumber.tsx
740
746
  var BranchPickerNumber = () => {
741
- const branchId = useMessageContext(
742
- "BranchPicker.Number",
743
- (s) => s.branchState.branchId
744
- );
747
+ const { useMessage } = useMessageContext();
748
+ const branchId = useMessage((s) => s.branchState.branchId);
745
749
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchId + 1);
746
750
  };
747
751
 
748
752
  // src/primitives/branchPicker/BranchPickerRoot.tsx
749
753
  import {
750
- Primitive as Primitive7
754
+ Primitive as Primitive8
751
755
  } from "@radix-ui/react-primitive";
752
756
  import { forwardRef as forwardRef9 } from "react";
753
757
  var BranchPickerRoot = forwardRef9(({ hideWhenSingleBranch, ...rest }, ref) => {
754
- return /* @__PURE__ */ React.createElement(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0 }, /* @__PURE__ */ React.createElement(Primitive7.div, { ...rest, ref }));
758
+ return /* @__PURE__ */ React.createElement(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0 }, /* @__PURE__ */ React.createElement(Primitive8.div, { ...rest, ref }));
755
759
  });
756
760
 
757
761
  // src/primitives/actionBar/index.ts
@@ -765,30 +769,48 @@ __export(actionBar_exports, {
765
769
 
766
770
  // src/primitives/actionBar/ActionBarRoot.tsx
767
771
  import {
768
- Primitive as Primitive8
772
+ Primitive as Primitive9
769
773
  } from "@radix-ui/react-primitive";
770
774
  import { forwardRef as forwardRef10 } from "react";
771
- var ActionBarRoot = forwardRef10(({ hideWhenBusy, hideWhenNotLastOrHover, ...rest }, ref) => {
772
- return /* @__PURE__ */ React.createElement(ThreadIf, { busy: hideWhenBusy ? false : void 0 }, /* @__PURE__ */ React.createElement(MessageIf, { lastOrHover: hideWhenNotLastOrHover ? true : void 0 }, /* @__PURE__ */ React.createElement(Primitive8.div, { ...rest, ref })));
775
+ var ActionBarRoot = forwardRef10(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
776
+ const { useThread } = useAssistantContext();
777
+ const { useMessage } = useMessageContext();
778
+ const hideAndfloatStatus = useMessage((m) => {
779
+ const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
780
+ if (!autohideEnabled)
781
+ return "normal" /* Normal */;
782
+ if (!m.isHovering)
783
+ return "hidden" /* Hidden */;
784
+ if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branchState.branchCount <= 1)
785
+ return "floating" /* Floating */;
786
+ return "normal" /* Normal */;
787
+ });
788
+ const busy = useThread((t) => t.isLoading);
789
+ if (hideWhenBusy && busy)
790
+ return null;
791
+ if (hideAndfloatStatus === "hidden" /* Hidden */)
792
+ return null;
793
+ return /* @__PURE__ */ React.createElement(
794
+ Primitive9.div,
795
+ {
796
+ "data-floating": hideAndfloatStatus === "floating" /* Floating */,
797
+ ...rest,
798
+ ref
799
+ }
800
+ );
773
801
  });
774
802
 
775
803
  // src/actions/useCopyMessage.tsx
776
804
  var useCopyMessage = ({ copiedDuration = 3e3 }) => {
777
- const context = useMessageContext("ActionBar.Copy", (s) => {
778
- const {
779
- editState: { isEditing },
780
- message: { content: content2 },
781
- setIsCopied: setIsCopied2
782
- } = s;
783
- if (isEditing)
784
- return null;
785
- return { content: content2, setIsCopied: setIsCopied2 };
786
- });
787
- if (!context)
805
+ const { useMessage, useComposer } = useMessageContext();
806
+ const isEditing = useComposer((s) => s.isEditing);
807
+ if (isEditing)
788
808
  return null;
789
- const { content, setIsCopied } = context;
790
809
  return () => {
791
- navigator.clipboard.writeText(content);
810
+ const { message, setIsCopied } = useMessage.getState();
811
+ if (message.content[0]?.type !== "text")
812
+ throw new Error("Copying is only supported for text-only messages");
813
+ navigator.clipboard.writeText(message.content[0].text);
792
814
  setIsCopied(true);
793
815
  setTimeout(() => setIsCopied(false), copiedDuration);
794
816
  };
@@ -799,19 +821,18 @@ var ActionBarCopy = createActionButton(useCopyMessage);
799
821
 
800
822
  // src/actions/useReloadMessage.tsx
801
823
  var useReloadMessage = () => {
802
- const [isLoading, reloadAt] = useThreadContext("ActionBar.Reload", (s) => [
803
- s.chat.isLoading,
804
- s.chat.reloadAt
805
- ]);
806
- const message = useMessageContext("ActionBar.Reload", (s) => {
807
- const message2 = s.message;
808
- if (message2.role !== "assistant" || isLoading)
809
- return null;
810
- return message2;
811
- });
812
- if (!message)
824
+ const { useThread, useBranchObserver } = useAssistantContext();
825
+ const { useMessage } = useMessageContext();
826
+ const isLoading = useThread((s) => s.isLoading);
827
+ const isAssistant = useMessage((s) => s.message.role === "assistant");
828
+ if (isLoading || !isAssistant)
813
829
  return null;
814
- return () => reloadAt(message);
830
+ return () => {
831
+ const message = useMessage.getState().message;
832
+ if (message.role !== "assistant")
833
+ throw new Error("Reloading is only supported on assistant messages");
834
+ useBranchObserver.getState().reloadAt(message);
835
+ };
815
836
  };
816
837
 
817
838
  // src/primitives/actionBar/ActionBarReload.tsx
@@ -819,116 +840,154 @@ var ActionBarReload = createActionButton(useReloadMessage);
819
840
 
820
841
  // src/actions/useBeginMessageEdit.tsx
821
842
  var useBeginMessageEdit = () => {
822
- const context = useMessageContext("ActionBar.Edit", (s) => {
823
- const {
824
- message: { content: content2 },
825
- editState: { isEditing },
826
- setEditState: setEditState2
827
- } = s;
828
- if (isEditing)
829
- return null;
830
- return { content: content2, setEditState: setEditState2 };
831
- });
832
- if (!context)
843
+ const { useMessage, useComposer } = useMessageContext();
844
+ const isUser = useMessage((s) => s.message.role === "user");
845
+ const isEditing = useComposer((s) => s.isEditing);
846
+ if (!isUser || isEditing)
833
847
  return null;
834
- const { content, setEditState } = context;
835
848
  return () => {
836
- setEditState({ isEditing: true, value: content });
849
+ const { edit } = useComposer.getState();
850
+ edit();
837
851
  };
838
852
  };
839
853
 
840
854
  // src/primitives/actionBar/ActionBarEdit.tsx
841
855
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
842
856
 
843
- // src/primitives/editBar/index.ts
844
- var editBar_exports = {};
845
- __export(editBar_exports, {
846
- Cancel: () => EditBarCancel,
847
- Root: () => EditBarRoot,
848
- Save: () => EditBarSave
849
- });
850
-
851
- // src/primitives/editBar/EditBarRoot.tsx
852
- import {
853
- Primitive as Primitive9
854
- } from "@radix-ui/react-primitive";
855
- import { forwardRef as forwardRef11 } from "react";
856
- var EditBarRoot = forwardRef11(
857
- ({ ...rest }, ref) => {
858
- return /* @__PURE__ */ React.createElement(Primitive9.div, { ...rest, ref });
859
- }
860
- );
861
-
862
- // src/actions/useSaveMessageEdit.tsx
863
- var useSaveMessageEdit = () => {
864
- const chat = useThreadContext("EditBar.Save", (s) => s.chat);
865
- const context = useMessageContext("EditBar.Save", (s) => {
866
- const { message: message2, editState, setEditState: setEditState2 } = s;
867
- if (!editState.isEditing)
868
- return null;
869
- return { message: message2, content: editState.value, setEditState: setEditState2 };
857
+ // src/vercel/VercelAIAssistantProvider.tsx
858
+ import { useCallback as useCallback2, useMemo as useMemo4, useState as useState3 } from "react";
859
+ import { create as create2 } from "zustand";
860
+ var useAIAssistantContext = () => {
861
+ const [context] = useState3(() => {
862
+ const useThread = create2()(() => ({
863
+ messages: [],
864
+ isLoading: false,
865
+ reload: async () => {
866
+ },
867
+ append: async () => {
868
+ },
869
+ stop: () => {
870
+ }
871
+ }));
872
+ const useComposer = create2()(() => ({
873
+ isEditing: true,
874
+ canCancel: false,
875
+ value: "",
876
+ setValue: () => {
877
+ },
878
+ edit: () => {
879
+ throw new Error("Not implemented");
880
+ },
881
+ send: () => {
882
+ useThread.getState().append({
883
+ role: "user",
884
+ content: [{ type: "text", text: useComposer.getState().value }]
885
+ });
886
+ useComposer.getState().setValue("");
887
+ },
888
+ cancel: () => {
889
+ useThread.getState().stop();
890
+ }
891
+ }));
892
+ const useBranchObserver = create2()(() => ({
893
+ getBranchState: () => ({
894
+ branchId: 0,
895
+ branchCount: 0
896
+ }),
897
+ switchToBranch: () => {
898
+ },
899
+ editAt: async () => {
900
+ },
901
+ reloadAt: async () => {
902
+ }
903
+ }));
904
+ return { useThread, useComposer, useBranchObserver };
870
905
  });
871
- if (!context)
872
- return null;
873
- const { message, content, setEditState } = context;
874
- return () => {
875
- chat.editAt(message, {
876
- ...message,
877
- id: void 0,
878
- // remove id to create a new message
879
- content
880
- });
881
- setEditState({ isEditing: false });
906
+ return context;
907
+ };
908
+ var ThreadMessageCache = /* @__PURE__ */ new WeakMap();
909
+ var vercelToThreadMessage = (message) => {
910
+ if (message.role !== "user" && message.role !== "assistant")
911
+ throw new Error("Unsupported role");
912
+ return {
913
+ id: message.id,
914
+ role: message.role,
915
+ content: [{ type: "text", text: message.content }]
882
916
  };
883
917
  };
884
-
885
- // src/primitives/editBar/EditBarSave.tsx
886
- var EditBarSave = createActionButton(useSaveMessageEdit);
887
-
888
- // src/actions/useCancelMessageEdit.tsx
889
- var useCancelMessageEdit = () => {
890
- const context = useMessageContext("EditBar.Cancel", (s) => {
891
- const {
892
- editState: { isEditing },
893
- setEditState: setEditState2
894
- } = s;
895
- if (!isEditing)
896
- return null;
897
- return { setEditState: setEditState2 };
918
+ var vercelToCachedThreadMessages = (messages) => {
919
+ return messages.map((m) => {
920
+ const cached = ThreadMessageCache.get(m);
921
+ if (cached)
922
+ return cached;
923
+ const newMessage = vercelToThreadMessage(m);
924
+ ThreadMessageCache.set(m, newMessage);
925
+ return newMessage;
898
926
  });
899
- if (!context)
900
- return null;
901
- const { setEditState } = context;
902
- return () => {
903
- setEditState({ isEditing: false });
904
- };
905
927
  };
906
-
907
- // src/primitives/editBar/EditBarCancel.tsx
908
- var EditBarCancel = createActionButton(useCancelMessageEdit);
909
-
910
- // src/vercel/VercelAIThreadProvider.tsx
911
- var VercelAIThreadProvider = ({
912
- chat,
913
- children
914
- }) => {
915
- const branches = useChatWithBranches(chat);
916
- return /* @__PURE__ */ React.createElement(ThreadContextProvider, { chat: branches }, children);
928
+ var VercelAIChatAssistantProvider = ({ chat, children }) => {
929
+ const context = useAIAssistantContext();
930
+ const messages = useMemo4(() => {
931
+ return vercelToCachedThreadMessages(chat.messages);
932
+ }, [chat.messages]);
933
+ const reload = useCallback2(async () => {
934
+ await chat.reload();
935
+ }, [chat.reload]);
936
+ const append = useCallback2(
937
+ async (message) => {
938
+ if (message.content[0]?.type !== "text") {
939
+ throw new Error("Only text content is currently supported");
940
+ }
941
+ await chat.append({
942
+ role: message.role,
943
+ content: message.content[0].text
944
+ });
945
+ },
946
+ [chat.append]
947
+ );
948
+ const stop = useCallback2(() => {
949
+ const lastMessage = chat.messages.at(-1);
950
+ chat.stop();
951
+ if (lastMessage?.role === "user") {
952
+ chat.setInput(lastMessage.content);
953
+ }
954
+ }, [chat.messages, chat.stop, chat.setInput]);
955
+ useMemo4(() => {
956
+ context.useThread.setState(
957
+ {
958
+ messages,
959
+ isLoading: chat.isLoading,
960
+ reload,
961
+ append,
962
+ stop
963
+ },
964
+ true
965
+ );
966
+ }, [context, messages, reload, append, stop, chat.isLoading]);
967
+ useMemo4(() => {
968
+ context.useComposer.setState({
969
+ canCancel: chat.isLoading,
970
+ value: chat.input,
971
+ setValue: chat.setInput
972
+ });
973
+ }, [context, chat.isLoading, chat.input, chat.setInput]);
974
+ const branches = useVercelAIBranches(chat);
975
+ useMemo4(() => {
976
+ context.useBranchObserver.setState(branches, true);
977
+ }, [context, branches]);
978
+ return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
917
979
  };
918
980
  export {
919
981
  actionBar_exports as ActionBarPrimitive,
920
982
  branchPicker_exports as BranchPickerPrimitive,
921
983
  composer_exports as ComposerPrimitive,
922
- editBar_exports as EditBarPrimitive,
923
984
  message_exports as MessagePrimitive,
924
985
  thread_exports as ThreadPrimitive,
925
- VercelAIThreadProvider,
986
+ VercelAIChatAssistantProvider as VercelAIAssistantProvider,
926
987
  useMessageContext as unstable_useMessageContext,
927
988
  useBeginMessageEdit,
928
- useCancelMessageEdit,
929
989
  useCopyMessage,
930
990
  useGoToNextBranch,
931
991
  useGoToPreviousBranch,
932
- useReloadMessage,
933
- useSaveMessageEdit
992
+ useReloadMessage
934
993
  };