@assistant-ui/react 0.0.4 → 0.0.6

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
@@ -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
  };