@assistant-ui/react 0.0.3 → 0.0.5

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)
@@ -211,7 +141,7 @@ var ThreadViewport = forwardRef2(({ onScroll, children, ...rest }, forwardedRef)
211
141
  });
212
142
 
213
143
  // src/utils/hooks/useBranches.tsx
214
- import { useCallback, useMemo as useMemo2, useRef as useRef3 } from "react";
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) => {
@@ -319,80 +249,154 @@ var useChatWithBranches = (chat) => {
319
249
  },
320
250
  [chat.messages, chat.setMessages, chat.append]
321
251
  );
322
- return useMemo2(
252
+ return useMemo(
323
253
  () => ({
324
- ...chat,
325
254
  getBranchState,
326
255
  switchToBranch,
327
256
  editAt,
328
257
  reloadAt
329
258
  }),
330
- [chat, getBranchState, switchToBranch, editAt, reloadAt]
259
+ [getBranchState, switchToBranch, editAt, reloadAt]
331
260
  );
332
261
  };
262
+ var hasUpcomingMessage = (thread) => {
263
+ return thread.isLoading && thread.messages[thread.messages.length - 1]?.role !== "assistant";
264
+ };
265
+
266
+ // src/utils/context/ComposerState.ts
267
+ import { useContext as useContext3 } from "react";
268
+
269
+ // src/utils/context/MessageContext.ts
270
+ import { createContext as createContext2, useContext as useContext2 } from "react";
271
+ var MessageContext = createContext2(null);
272
+ var useMessageContext = () => {
273
+ const context = useContext2(MessageContext);
274
+ if (!context)
275
+ throw new Error("useMessageContext must be used within a MessageProvider");
276
+ return context;
277
+ };
278
+
279
+ // src/utils/context/ComposerState.ts
280
+ var useComposerContext = () => {
281
+ const { useComposer: useAssisstantComposer } = useAssistantContext();
282
+ const { useComposer: useMessageComposer } = useContext3(MessageContext) ?? {};
283
+ return { useComposer: useMessageComposer ?? useAssisstantComposer };
284
+ };
285
+
286
+ // src/primitives/composer/ComposerIf.tsx
287
+ var useComposerIf = (props) => {
288
+ const { useComposer } = useComposerContext();
289
+ return useComposer((composer) => {
290
+ if (props.editing === true && !composer.isEditing)
291
+ return false;
292
+ if (props.editing === false && composer.isEditing)
293
+ return false;
294
+ return true;
295
+ });
296
+ };
297
+ var ComposerIf = ({ children, ...query }) => {
298
+ const result = useComposerIf(query);
299
+ return result ? children : null;
300
+ };
333
301
 
334
302
  // src/primitives/message/index.ts
335
303
  var message_exports = {};
336
304
  __export(message_exports, {
337
305
  Content: () => MessageContent,
338
- EditableContent: () => MessageEditableContent,
339
306
  If: () => MessageIf,
340
307
  Provider: () => MessageProvider,
341
308
  Root: () => MessageRoot
342
309
  });
343
310
 
344
311
  // src/primitives/message/MessageProvider.tsx
345
- import { useMemo as useMemo3, useState as useState3 } from "react";
346
-
347
- // src/utils/context/MessageContext.ts
348
- var [MessageContextProvider, useMessageContext] = createStoreContext("Thread.Provider");
349
-
350
- // src/primitives/message/MessageProvider.tsx
312
+ import { useMemo as useMemo2, useState as useState2 } from "react";
313
+ import { create } from "zustand";
314
+ import { useShallow } from "zustand/react/shallow";
315
+ var getIsLast = (thread, message) => {
316
+ const hasUpcoming = hasUpcomingMessage(thread);
317
+ return hasUpcoming ? message.id === UPCOMING_MESSAGE_ID : thread.messages[thread.messages.length - 1]?.id === message.id;
318
+ };
319
+ var useMessageContext2 = () => {
320
+ const { useBranchObserver } = useAssistantContext();
321
+ const [context] = useState2(() => {
322
+ const useMessage = create(() => ({
323
+ message: null,
324
+ isLast: false,
325
+ isCopied: false,
326
+ isHovering: false,
327
+ setIsCopied: () => {
328
+ },
329
+ setIsHovering: () => {
330
+ },
331
+ branchState: {
332
+ branchId: 0,
333
+ branchCount: 0
334
+ }
335
+ }));
336
+ const useComposer = create((set, get) => ({
337
+ isEditing: false,
338
+ canCancel: true,
339
+ edit: () => set({
340
+ isEditing: true,
341
+ value: useMessage.getState().message.content
342
+ }),
343
+ cancel: () => set({ isEditing: false }),
344
+ send: () => {
345
+ const message = useMessage.getState().message;
346
+ useBranchObserver.getState().editAt(message, {
347
+ ...message,
348
+ id: void 0,
349
+ // remove id to create a new message
350
+ content: get().value
351
+ });
352
+ set({ isEditing: false });
353
+ },
354
+ value: "",
355
+ setValue: (value) => set({ value })
356
+ }));
357
+ return { useMessage, useComposer };
358
+ });
359
+ return context;
360
+ };
351
361
  var MessageProvider = ({
352
362
  message,
353
363
  children
354
364
  }) => {
355
- const getBranchState = useThreadContext(
356
- "Message.Provider",
357
- (s) => s.chat.getBranchState
358
- );
359
- const [editState, setEditState] = useState3({
360
- isEditing: false
361
- });
362
- const [isCopied, setIsCopied] = useState3(false);
363
- const [isHovering, setIsHovering] = useState3(false);
364
- const branchState = useMemo3(
365
- () => getBranchState(message),
366
- [getBranchState, message]
367
- );
368
- return /* @__PURE__ */ React.createElement(
369
- MessageContextProvider,
370
- {
371
- message,
372
- editState,
373
- setEditState,
374
- branchState,
375
- isCopied,
376
- setIsCopied,
377
- isHovering,
378
- setIsHovering
379
- },
380
- children
365
+ const { useThread, useBranchObserver } = useAssistantContext();
366
+ const context = useMessageContext2();
367
+ const branchState = useBranchObserver(
368
+ useShallow((b) => b.getBranchState(message))
381
369
  );
370
+ const isLast = useThread((thread) => getIsLast(thread, message));
371
+ const [isCopied, setIsCopied] = useState2(false);
372
+ const [isHovering, setIsHovering] = useState2(false);
373
+ useMemo2(() => {
374
+ context.useMessage.setState(
375
+ {
376
+ message,
377
+ isLast,
378
+ isCopied,
379
+ isHovering,
380
+ setIsCopied,
381
+ setIsHovering,
382
+ branchState
383
+ },
384
+ true
385
+ );
386
+ }, [context, message, isLast, isCopied, isHovering, branchState]);
387
+ return /* @__PURE__ */ React.createElement(MessageContext.Provider, { value: context }, children);
382
388
  };
383
389
 
384
390
  // src/primitives/message/MessageRoot.tsx
385
- import { forwardRef as forwardRef3 } from "react";
391
+ import { composeEventHandlers as composeEventHandlers2 } from "@radix-ui/primitive";
386
392
  import {
387
393
  Primitive as Primitive3
388
394
  } from "@radix-ui/react-primitive";
389
- import { composeEventHandlers as composeEventHandlers2 } from "@radix-ui/primitive";
395
+ import { forwardRef as forwardRef3 } from "react";
390
396
  var MessageRoot = forwardRef3(
391
397
  ({ onMouseEnter, onMouseLeave, ...rest }, ref) => {
392
- const setIsHovering = useMessageContext(
393
- "Message.Root",
394
- (s) => s.setIsHovering
395
- );
398
+ const { useMessage } = useMessageContext();
399
+ const setIsHovering = useMessage((s) => s.setIsHovering);
396
400
  const handleMouseEnter = () => {
397
401
  setIsHovering(true);
398
402
  };
@@ -413,22 +417,22 @@ var MessageRoot = forwardRef3(
413
417
 
414
418
  // src/primitives/message/MessageIf.tsx
415
419
  var useMessageIf = (props) => {
416
- const thread = useThreadContext("Message.If", (s) => s.chat);
417
- return useMessageContext(
418
- "Message.If",
419
- ({ message, editState: { isEditing }, isCopied, isHovering }) => {
420
- const { branchCount } = thread.getBranchState(message);
420
+ const { useMessage } = useMessageContext();
421
+ return useMessage(
422
+ ({
423
+ message,
424
+ isLast,
425
+ isCopied,
426
+ isHovering,
427
+ branchState: { branchCount }
428
+ }) => {
421
429
  if (props.hasBranches === true && branchCount < 2)
422
430
  return false;
423
431
  if (props.user && message.role !== "user")
424
432
  return false;
425
433
  if (props.assistant && message.role !== "assistant")
426
434
  return false;
427
- if (props.editing === true && !isEditing)
428
- return false;
429
- if (props.editing === false && isEditing)
430
- return false;
431
- if (props.unstable_hoveringOrLast === true && !isHovering && thread.messages[thread.messages.length - 1]?.id !== message.id)
435
+ if (props.lastOrHover === true && !isHovering && !isLast)
432
436
  return false;
433
437
  if (props.copied === true && !isCopied)
434
438
  return false;
@@ -445,60 +449,32 @@ var MessageIf = ({ children, ...query }) => {
445
449
 
446
450
  // src/primitives/message/MessageContent.tsx
447
451
  var MessageContent = () => {
448
- const content = useMessageContext(
449
- "Message.Content",
450
- (s) => s.message.content
451
- );
452
+ const { useMessage } = useMessageContext();
453
+ const content = useMessage((s) => s.message.content);
452
454
  return /* @__PURE__ */ React.createElement(React.Fragment, null, content);
453
455
  };
454
456
 
455
- // src/primitives/message/MessageEditableContent.tsx
456
- import { forwardRef as forwardRef4 } from "react";
457
- import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
458
- import TextareaAutosize from "react-textarea-autosize";
459
- var MessageEditableContent = forwardRef4(({ onChange, value, ...rest }, forwardedRef) => {
460
- const [editState, setEditState] = useMessageContext(
461
- "Message.EditableContent",
462
- (s) => [s.editState, s.setEditState]
463
- );
464
- const handleChange = (e) => {
465
- setEditState({ isEditing: true, value: e.target.value });
466
- };
467
- if (!editState.isEditing)
468
- throw new Error(
469
- "Message.EditableContent may only be rendered when edit mode is enabled. Consider wrapping the component in <Message.If editing>."
470
- );
471
- return /* @__PURE__ */ React.createElement(
472
- TextareaAutosize,
473
- {
474
- ...rest,
475
- ref: forwardedRef,
476
- onChange: composeEventHandlers3(onChange, handleChange),
477
- value: editState.value || value
478
- }
479
- );
480
- });
481
-
482
457
  // src/primitives/thread/ThreadMessages.tsx
483
458
  var getComponents = (components) => {
484
459
  return {
485
- EditingUserMessage: components.EditingUserMessage ?? components.UserMessage ?? components.Message,
460
+ EditComposer: components.EditComposer ?? components.UserMessage ?? components.Message,
486
461
  UserMessage: components.UserMessage ?? components.Message,
487
462
  AssistantMessage: components.AssistantMessage ?? components.Message
488
463
  };
489
464
  };
490
465
  var ThreadMessages = ({ components }) => {
491
- const chat = useThreadContext("Thread.Messages", (s) => s.chat);
492
- const messages = chat.messages;
493
- const { UserMessage, EditingUserMessage, AssistantMessage } = getComponents(components);
466
+ const { useThread } = useAssistantContext();
467
+ const thread = useThread();
468
+ const messages = thread.messages;
469
+ const { UserMessage, EditComposer, AssistantMessage } = getComponents(components);
494
470
  if (messages.length === 0)
495
471
  return null;
496
472
  return /* @__PURE__ */ React.createElement(React.Fragment, null, messages.map((message, idx) => {
497
473
  return (
498
474
  // biome-ignore lint/suspicious/noArrayIndexKey: fixes a11y issues with branch navigation
499
- /* @__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)))
475
+ /* @__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)))
500
476
  );
501
- }), chat.isLoading && chat.messages[chat.messages.length - 1]?.role !== "assistant" && /* @__PURE__ */ React.createElement(
477
+ }), hasUpcomingMessage(thread) && /* @__PURE__ */ React.createElement(
502
478
  MessageProvider,
503
479
  {
504
480
  message: {
@@ -514,22 +490,29 @@ var ThreadMessages = ({ components }) => {
514
490
  // src/primitives/composer/index.ts
515
491
  var composer_exports = {};
516
492
  __export(composer_exports, {
493
+ Cancel: () => ComposerCancel,
494
+ If: () => ComposerIf,
517
495
  Input: () => ComposerInput,
518
496
  Root: () => ComposerRoot,
519
- Send: () => ComposerSend,
520
- Stop: () => ComposerStop
497
+ Send: () => ComposerSend
521
498
  });
522
499
 
523
500
  // src/primitives/composer/ComposerRoot.tsx
524
- import { createContext as createContext2, forwardRef as forwardRef5, useContext as useContext2, useMemo as useMemo4, useRef as useRef4 } from "react";
501
+ import { composeEventHandlers as composeEventHandlers3 } from "@radix-ui/primitive";
502
+ import { useComposedRefs as useComposedRefs2 } from "@radix-ui/react-compose-refs";
525
503
  import {
526
504
  Primitive as Primitive4
527
505
  } from "@radix-ui/react-primitive";
528
- import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
529
- import { useComposedRefs as useComposedRefs2 } from "@radix-ui/react-compose-refs";
530
- var ComposerContext = createContext2(null);
531
- var useComposerContext = () => {
532
- const context = useContext2(ComposerContext);
506
+ import {
507
+ createContext as createContext3,
508
+ forwardRef as forwardRef4,
509
+ useContext as useContext4,
510
+ useMemo as useMemo3,
511
+ useRef as useRef4
512
+ } from "react";
513
+ var ComposerFormContext = createContext3(null);
514
+ var useComposerFormContext = () => {
515
+ const context = useContext4(ComposerFormContext);
533
516
  if (!context) {
534
517
  throw new Error(
535
518
  "Composer compound components cannot be rendered outside the Composer component"
@@ -537,15 +520,12 @@ var useComposerContext = () => {
537
520
  }
538
521
  return context;
539
522
  };
540
- var ComposerRoot = forwardRef5(
523
+ var ComposerRoot = forwardRef4(
541
524
  ({ onSubmit, ...rest }, forwardedRef) => {
542
- const handleSubmit = useThreadContext(
543
- "Composer.Root",
544
- (s) => s.chat.handleSubmit
545
- );
525
+ const { useComposer } = useComposerContext();
546
526
  const formRef = useRef4(null);
547
527
  const ref = useComposedRefs2(forwardedRef, formRef);
548
- const composerContextValue = useMemo4(
528
+ const composerContextValue = useMemo3(
549
529
  () => ({
550
530
  submit: () => formRef.current?.dispatchEvent(
551
531
  new Event("submit", { cancelable: true, bubbles: true })
@@ -553,108 +533,115 @@ var ComposerRoot = forwardRef5(
553
533
  }),
554
534
  []
555
535
  );
556
- return /* @__PURE__ */ React.createElement(ComposerContext.Provider, { value: composerContextValue }, /* @__PURE__ */ React.createElement(
536
+ const handleSubmit = (e) => {
537
+ const composerState = useComposer.getState();
538
+ if (!composerState.isEditing)
539
+ return;
540
+ e.preventDefault();
541
+ composerState.send();
542
+ };
543
+ return /* @__PURE__ */ React.createElement(ComposerFormContext.Provider, { value: composerContextValue }, /* @__PURE__ */ React.createElement(
557
544
  Primitive4.form,
558
545
  {
559
546
  ...rest,
560
547
  ref,
561
- onSubmit: composeEventHandlers4(onSubmit, handleSubmit)
548
+ onSubmit: composeEventHandlers3(onSubmit, handleSubmit)
562
549
  }
563
550
  ));
564
551
  }
565
552
  );
566
553
 
567
554
  // src/primitives/composer/ComposerInput.tsx
568
- import { forwardRef as forwardRef6 } from "react";
555
+ import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
569
556
  import { Slot } from "@radix-ui/react-slot";
570
- import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
571
- import TextareaAutosize2 from "react-textarea-autosize";
572
- var ComposerInput = forwardRef6(({ asChild, onChange, onKeyDown, ...rest }, forwardedRef) => {
573
- const chat = useThreadContext(
574
- "Composer.Input",
575
- ({ chat: { input, handleInputChange, isLoading } }) => ({
576
- input,
577
- handleInputChange,
578
- isLoading
579
- })
580
- );
581
- const Component = asChild ? Slot : TextareaAutosize2;
582
- const composer = useComposerContext();
557
+ import { forwardRef as forwardRef5 } from "react";
558
+ import TextareaAutosize from "react-textarea-autosize";
559
+ var ComposerInput = forwardRef5(({ asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
560
+ const { useThread } = useAssistantContext();
561
+ const isLoading = useThread((t) => t.isLoading);
562
+ const { useComposer } = useComposerContext();
563
+ const value = useComposer((c) => {
564
+ if (!c.isEditing)
565
+ return "";
566
+ return c.value;
567
+ });
568
+ const Component = asChild ? Slot : TextareaAutosize;
569
+ const composerForm = useComposerFormContext();
583
570
  const handleKeyPress = (e) => {
584
- if (chat.isLoading || rest.disabled)
571
+ if (disabled)
572
+ return;
573
+ if (e.key === "Escape") {
574
+ useComposer.getState().cancel();
575
+ }
576
+ if (isLoading)
585
577
  return;
586
578
  if (e.key === "Enter" && e.shiftKey === false) {
587
579
  e.preventDefault();
588
- composer.submit();
580
+ composerForm.submit();
589
581
  }
590
582
  };
591
583
  return /* @__PURE__ */ React.createElement(
592
584
  Component,
593
585
  {
594
- value: chat.input,
586
+ value,
595
587
  ...rest,
596
588
  ref: forwardedRef,
597
- onChange: composeEventHandlers5(onChange, chat.handleInputChange),
598
- onKeyDown: composeEventHandlers5(onKeyDown, handleKeyPress)
589
+ disabled,
590
+ onChange: composeEventHandlers4(onChange, (e) => {
591
+ const composerState = useComposer.getState();
592
+ if (!composerState.isEditing)
593
+ return;
594
+ return composerState.setValue(e.target.value);
595
+ }),
596
+ onKeyDown: composeEventHandlers4(onKeyDown, handleKeyPress)
599
597
  }
600
598
  );
601
599
  });
602
600
 
603
601
  // src/primitives/composer/ComposerSend.tsx
604
- import { forwardRef as forwardRef7 } from "react";
605
602
  import {
606
603
  Primitive as Primitive5
607
604
  } from "@radix-ui/react-primitive";
608
- var ComposerSend = forwardRef7(
605
+ import { forwardRef as forwardRef6 } from "react";
606
+ var ComposerSend = forwardRef6(
609
607
  ({ disabled, ...rest }, ref) => {
610
- const input = useThreadContext("Composer.Send", (s) => s.chat.input);
608
+ const { useComposer } = useComposerContext();
609
+ const hasValue = useComposer((c) => c.isEditing && c.value.length > 0);
611
610
  return /* @__PURE__ */ React.createElement(
612
611
  Primitive5.button,
613
612
  {
614
613
  type: "submit",
615
614
  ...rest,
616
615
  ref,
617
- disabled: disabled || input.length === 0
616
+ disabled: disabled || !hasValue
618
617
  }
619
618
  );
620
619
  }
621
620
  );
622
621
 
623
- // src/utils/createActionButton.tsx
624
- import { forwardRef as forwardRef8 } from "react";
622
+ // src/primitives/composer/ComposerCancel.tsx
623
+ import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
625
624
  import {
626
625
  Primitive as Primitive6
627
626
  } from "@radix-ui/react-primitive";
628
- import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
629
- var createActionButton = (useActionButton) => {
630
- return forwardRef8(
631
- (props, forwardedRef) => {
632
- const onClick = useActionButton(props);
633
- return /* @__PURE__ */ React.createElement(
634
- Primitive6.button,
635
- {
636
- type: "button",
637
- disabled: !onClick,
638
- ...props,
639
- ref: forwardedRef,
640
- onClick: composeEventHandlers6(props.onClick, onClick ?? void 0)
641
- }
642
- );
627
+ import { forwardRef as forwardRef7 } from "react";
628
+ var ComposerCancel = forwardRef7(({ disabled, onClick, ...rest }, ref) => {
629
+ const { useComposer } = useComposerContext();
630
+ const hasValue = useComposer((c) => c.canCancel);
631
+ const handleClose = () => {
632
+ useComposer.getState().cancel();
633
+ };
634
+ return /* @__PURE__ */ React.createElement(
635
+ Primitive6.button,
636
+ {
637
+ type: "button",
638
+ ...rest,
639
+ ref,
640
+ onClick: composeEventHandlers5(onClick, handleClose),
641
+ disabled: disabled || !hasValue
643
642
  }
644
643
  );
645
- };
646
-
647
- // src/primitives/composer/ComposerStop.tsx
648
- var useComposerStop = () => {
649
- const [isLoading, stop] = useThreadContext("Composer.Stop", (s) => [
650
- s.chat.isLoading,
651
- s.chat.stop
652
- ]);
653
- if (!isLoading)
654
- return null;
655
- return stop;
656
- };
657
- var ComposerStop = createActionButton(useComposerStop);
644
+ });
658
645
 
659
646
  // src/primitives/branchPicker/index.ts
660
647
  var branchPicker_exports = {};
@@ -668,52 +655,66 @@ __export(branchPicker_exports, {
668
655
 
669
656
  // src/actions/useGoToNextBranch.tsx
670
657
  var useGoToNextBranch = () => {
671
- const switchToBranch = useThreadContext(
672
- "BranchPicker.Next",
673
- (s) => s.chat.switchToBranch
658
+ const { useThread, useBranchObserver } = useAssistantContext();
659
+ const { useComposer, useMessage } = useMessageContext();
660
+ const isLoading = useThread((s) => s.isLoading);
661
+ const isEditing = useComposer((s) => s.isEditing);
662
+ const hasNext = useMessage(
663
+ ({ branchState: { branchId, branchCount } }) => branchId + 1 < branchCount
674
664
  );
675
- const context = useMessageContext("BranchPicker.Next", (s) => {
676
- const {
677
- message: message2,
678
- editState: { isEditing },
679
- branchState: { branchId: branchId2, branchCount }
680
- } = s;
681
- if (isEditing || branchCount <= 1 || branchId2 + 1 >= branchCount)
682
- return null;
683
- return { message: message2, branchId: branchId2 };
684
- });
685
- if (!context)
665
+ if (isLoading || isEditing || !hasNext)
686
666
  return null;
687
- const { message, branchId } = context;
688
667
  return () => {
689
- switchToBranch(message, branchId + 1);
668
+ const {
669
+ message,
670
+ branchState: { branchId }
671
+ } = useMessage.getState();
672
+ useBranchObserver.getState().switchToBranch(message, branchId + 1);
690
673
  };
691
674
  };
692
675
 
676
+ // src/utils/createActionButton.tsx
677
+ import { forwardRef as forwardRef8 } from "react";
678
+ import {
679
+ Primitive as Primitive7
680
+ } from "@radix-ui/react-primitive";
681
+ import { composeEventHandlers as composeEventHandlers6 } from "@radix-ui/primitive";
682
+ var createActionButton = (useActionButton) => {
683
+ return forwardRef8(
684
+ (props, forwardedRef) => {
685
+ const onClick = useActionButton(props);
686
+ return /* @__PURE__ */ React.createElement(
687
+ Primitive7.button,
688
+ {
689
+ type: "button",
690
+ disabled: !onClick,
691
+ ...props,
692
+ ref: forwardedRef,
693
+ onClick: composeEventHandlers6(props.onClick, onClick ?? void 0)
694
+ }
695
+ );
696
+ }
697
+ );
698
+ };
699
+
693
700
  // src/primitives/branchPicker/BranchPickerNext.tsx
694
701
  var BranchPickerNext = createActionButton(useGoToNextBranch);
695
702
 
696
703
  // src/actions/useGoToPreviousBranch.tsx
697
704
  var useGoToPreviousBranch = () => {
698
- const switchToBranch = useThreadContext(
699
- "BranchPicker.Previous",
700
- (s) => s.chat.switchToBranch
701
- );
702
- const context = useMessageContext("BranchPicker.Previous", (s) => {
703
- const {
704
- message: message2,
705
- editState: { isEditing },
706
- branchState: { branchId: branchId2, branchCount }
707
- } = s;
708
- if (isEditing || branchCount <= 1 || branchId2 <= 0)
709
- return null;
710
- return { message: message2, branchId: branchId2 };
711
- });
712
- if (!context)
705
+ const { useThread, useBranchObserver } = useAssistantContext();
706
+ const { useComposer, useMessage } = useMessageContext();
707
+ const isLoading = useThread((s) => s.isLoading);
708
+ const isEditing = useComposer((s) => s.isEditing);
709
+ const hasNext = useMessage(({ branchState: { branchId } }) => branchId > 0);
710
+ if (isLoading || isEditing || !hasNext)
713
711
  return null;
714
- const { message, branchId } = context;
715
712
  return () => {
716
- switchToBranch(message, branchId - 1);
713
+ const {
714
+ message,
715
+ branchState: { branchId }
716
+ } = useMessage.getState();
717
+ useBranchObserver.getState().switchToBranch(message, branchId - 1);
717
718
  };
718
719
  };
719
720
 
@@ -722,29 +723,25 @@ var BranchPickerPrevious = createActionButton(useGoToPreviousBranch);
722
723
 
723
724
  // src/primitives/branchPicker/BranchPickerCount.tsx
724
725
  var BranchPickerCount = () => {
725
- const branchCount = useMessageContext(
726
- "BranchPicker.Count",
727
- (s) => s.branchState.branchCount
728
- );
726
+ const { useMessage } = useMessageContext();
727
+ const branchCount = useMessage((s) => s.branchState.branchCount);
729
728
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchCount);
730
729
  };
731
730
 
732
731
  // src/primitives/branchPicker/BranchPickerNumber.tsx
733
732
  var BranchPickerNumber = () => {
734
- const branchId = useMessageContext(
735
- "BranchPicker.Number",
736
- (s) => s.branchState.branchId
737
- );
733
+ const { useMessage } = useMessageContext();
734
+ const branchId = useMessage((s) => s.branchState.branchId);
738
735
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchId + 1);
739
736
  };
740
737
 
741
738
  // src/primitives/branchPicker/BranchPickerRoot.tsx
742
739
  import {
743
- Primitive as Primitive7
740
+ Primitive as Primitive8
744
741
  } from "@radix-ui/react-primitive";
745
742
  import { forwardRef as forwardRef9 } from "react";
746
743
  var BranchPickerRoot = forwardRef9(({ hideWhenSingleBranch, ...rest }, ref) => {
747
- return /* @__PURE__ */ React.createElement(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0 }, /* @__PURE__ */ React.createElement(Primitive7.div, { ...rest, ref }));
744
+ return /* @__PURE__ */ React.createElement(MessageIf, { hasBranches: hideWhenSingleBranch ? true : void 0 }, /* @__PURE__ */ React.createElement(Primitive8.div, { ...rest, ref }));
748
745
  });
749
746
 
750
747
  // src/primitives/actionBar/index.ts
@@ -758,29 +755,48 @@ __export(actionBar_exports, {
758
755
 
759
756
  // src/primitives/actionBar/ActionBarRoot.tsx
760
757
  import {
761
- Primitive as Primitive8
758
+ Primitive as Primitive9
762
759
  } from "@radix-ui/react-primitive";
763
760
  import { forwardRef as forwardRef10 } from "react";
764
- var ActionBarRoot = forwardRef10(({ ...rest }, ref) => {
765
- return /* @__PURE__ */ React.createElement(Primitive8.div, { ...rest, ref });
761
+ var ActionBarRoot = forwardRef10(({ hideWhenBusy, autohide, autohideFloat, ...rest }, ref) => {
762
+ const { useThread } = useAssistantContext();
763
+ const { useMessage } = useMessageContext();
764
+ const hideAndfloatStatus = useMessage((m) => {
765
+ const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
766
+ if (!autohideEnabled)
767
+ return "normal" /* Normal */;
768
+ if (!m.isHovering)
769
+ return "hidden" /* Hidden */;
770
+ if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branchState.branchCount <= 1)
771
+ return "floating" /* Floating */;
772
+ return "normal" /* Normal */;
773
+ });
774
+ const busy = useThread((t) => t.isLoading);
775
+ if (hideWhenBusy && busy)
776
+ return null;
777
+ if (hideAndfloatStatus === "hidden" /* Hidden */)
778
+ return null;
779
+ return /* @__PURE__ */ React.createElement(
780
+ Primitive9.div,
781
+ {
782
+ "data-floating": hideAndfloatStatus === "floating" /* Floating */,
783
+ ...rest,
784
+ ref
785
+ }
786
+ );
766
787
  });
767
788
 
768
789
  // src/actions/useCopyMessage.tsx
769
790
  var useCopyMessage = ({ copiedDuration = 3e3 }) => {
770
- const context = useMessageContext("ActionBar.Copy", (s) => {
771
- const {
772
- editState: { isEditing },
773
- message: { content: content2 },
774
- setIsCopied: setIsCopied2
775
- } = s;
776
- if (isEditing)
777
- return null;
778
- return { content: content2, setIsCopied: setIsCopied2 };
779
- });
780
- if (!context)
791
+ const { useMessage, useComposer } = useMessageContext();
792
+ const isEditing = useComposer((s) => s.isEditing);
793
+ if (isEditing)
781
794
  return null;
782
- const { content, setIsCopied } = context;
783
795
  return () => {
796
+ const {
797
+ message: { content },
798
+ setIsCopied
799
+ } = useMessage.getState();
784
800
  navigator.clipboard.writeText(content);
785
801
  setIsCopied(true);
786
802
  setTimeout(() => setIsCopied(false), copiedDuration);
@@ -792,19 +808,15 @@ var ActionBarCopy = createActionButton(useCopyMessage);
792
808
 
793
809
  // src/actions/useReloadMessage.tsx
794
810
  var useReloadMessage = () => {
795
- const [isLoading, reloadAt] = useThreadContext("ActionBar.Reload", (s) => [
796
- s.chat.isLoading,
797
- s.chat.reloadAt
798
- ]);
799
- const message = useMessageContext("ActionBar.Reload", (s) => {
800
- const message2 = s.message;
801
- if (message2.role !== "assistant" || isLoading)
802
- return null;
803
- return message2;
804
- });
805
- if (!message)
811
+ const { useThread, useBranchObserver } = useAssistantContext();
812
+ const { useMessage } = useMessageContext();
813
+ const isLoading = useThread((s) => s.isLoading);
814
+ const isAssistant = useMessage((s) => s.message.role === "assistant");
815
+ if (isLoading || !isAssistant)
806
816
  return null;
807
- return () => reloadAt(message);
817
+ return () => {
818
+ useBranchObserver.getState().reloadAt(useMessage.getState().message);
819
+ };
808
820
  };
809
821
 
810
822
  // src/primitives/actionBar/ActionBarReload.tsx
@@ -812,116 +824,136 @@ var ActionBarReload = createActionButton(useReloadMessage);
812
824
 
813
825
  // src/actions/useBeginMessageEdit.tsx
814
826
  var useBeginMessageEdit = () => {
815
- const context = useMessageContext("ActionBar.Edit", (s) => {
816
- const {
817
- message: { content: content2 },
818
- editState: { isEditing },
819
- setEditState: setEditState2
820
- } = s;
821
- if (isEditing)
822
- return null;
823
- return { content: content2, setEditState: setEditState2 };
824
- });
825
- if (!context)
827
+ const { useMessage, useComposer } = useMessageContext();
828
+ const isUser = useMessage((s) => s.message.role === "user");
829
+ const isEditing = useComposer((s) => s.isEditing);
830
+ if (!isUser || isEditing)
826
831
  return null;
827
- const { content, setEditState } = context;
828
832
  return () => {
829
- setEditState({ isEditing: true, value: content });
833
+ const { edit } = useComposer.getState();
834
+ edit();
830
835
  };
831
836
  };
832
837
 
833
838
  // src/primitives/actionBar/ActionBarEdit.tsx
834
839
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
835
840
 
836
- // src/primitives/editBar/index.ts
837
- var editBar_exports = {};
838
- __export(editBar_exports, {
839
- Cancel: () => EditBarCancel,
840
- Root: () => EditBarRoot,
841
- Save: () => EditBarSave
842
- });
843
-
844
- // src/primitives/editBar/EditBarRoot.tsx
845
- import {
846
- Primitive as Primitive9
847
- } from "@radix-ui/react-primitive";
848
- import { forwardRef as forwardRef11 } from "react";
849
- var EditBarRoot = forwardRef11(
850
- ({ ...rest }, ref) => {
851
- return /* @__PURE__ */ React.createElement(Primitive9.div, { ...rest, ref });
852
- }
853
- );
854
-
855
- // src/actions/useSaveMessageEdit.tsx
856
- var useSaveMessageEdit = () => {
857
- const chat = useThreadContext("EditBar.Save", (s) => s.chat);
858
- const context = useMessageContext("EditBar.Save", (s) => {
859
- const { message: message2, editState, setEditState: setEditState2 } = s;
860
- if (!editState.isEditing)
861
- return null;
862
- return { message: message2, content: editState.value, setEditState: setEditState2 };
863
- });
864
- if (!context)
865
- return null;
866
- const { message, content, setEditState } = context;
867
- return () => {
868
- chat.editAt(message, {
869
- ...message,
870
- id: void 0,
871
- // remove id to create a new message
872
- content
873
- });
874
- setEditState({ isEditing: false });
875
- };
876
- };
877
-
878
- // src/primitives/editBar/EditBarSave.tsx
879
- var EditBarSave = createActionButton(useSaveMessageEdit);
880
-
881
- // src/actions/useCancelMessageEdit.tsx
882
- var useCancelMessageEdit = () => {
883
- const context = useMessageContext("EditBar.Cancel", (s) => {
884
- const {
885
- editState: { isEditing },
886
- setEditState: setEditState2
887
- } = s;
888
- if (!isEditing)
889
- return null;
890
- return { setEditState: setEditState2 };
841
+ // src/vercel/VercelAIAssistantProvider.tsx
842
+ import { useMemo as useMemo4, useState as useState3 } from "react";
843
+ import { create as create2 } from "zustand";
844
+ var useAIAssistantContext = () => {
845
+ const [context] = useState3(() => {
846
+ const useThread = create2()(() => ({
847
+ messages: [],
848
+ setMessages: () => {
849
+ },
850
+ isLoading: false,
851
+ reload: async () => {
852
+ },
853
+ append: async () => {
854
+ },
855
+ stop: () => {
856
+ }
857
+ }));
858
+ const useComposer = create2()(() => ({
859
+ isEditing: true,
860
+ canCancel: false,
861
+ value: "",
862
+ setValue: () => {
863
+ },
864
+ edit: () => {
865
+ throw new Error("Not implemented");
866
+ },
867
+ send: () => {
868
+ useThread.getState().append({
869
+ content: useComposer.getState().value,
870
+ role: "user",
871
+ createdAt: /* @__PURE__ */ new Date()
872
+ });
873
+ useComposer.getState().setValue("");
874
+ },
875
+ cancel: () => {
876
+ useThread.getState().stop();
877
+ }
878
+ }));
879
+ const useBranchObserver = create2()(() => ({
880
+ getBranchState: () => ({
881
+ branchId: 0,
882
+ branchCount: 0
883
+ }),
884
+ switchToBranch: () => {
885
+ },
886
+ editAt: async () => {
887
+ },
888
+ reloadAt: async () => {
889
+ }
890
+ }));
891
+ return { useThread, useComposer, useBranchObserver };
891
892
  });
892
- if (!context)
893
- return null;
894
- const { setEditState } = context;
895
- return () => {
896
- setEditState({ isEditing: false });
897
- };
893
+ return context;
898
894
  };
899
-
900
- // src/primitives/editBar/EditBarCancel.tsx
901
- var EditBarCancel = createActionButton(useCancelMessageEdit);
902
-
903
- // src/vercel/VercelAIThreadProvider.tsx
904
- var VercelAIThreadProvider = ({
895
+ var VercelAIAssistantProvider = ({
905
896
  chat,
906
897
  children
907
898
  }) => {
899
+ const context = useAIAssistantContext();
900
+ useMemo4(() => {
901
+ context.useThread.setState(
902
+ {
903
+ messages: chat.messages,
904
+ setMessages: (value) => {
905
+ chat.setMessages(value);
906
+ },
907
+ isLoading: chat.isLoading,
908
+ reload: async () => {
909
+ await chat.reload();
910
+ },
911
+ append: async (message) => {
912
+ await chat.append(message);
913
+ },
914
+ stop: () => {
915
+ const lastMessage = chat.messages.at(-1);
916
+ chat.stop();
917
+ if (lastMessage?.role === "user") {
918
+ chat.setInput(lastMessage.content);
919
+ }
920
+ }
921
+ },
922
+ true
923
+ );
924
+ }, [context, chat]);
925
+ useMemo4(() => {
926
+ context.useComposer.setState({
927
+ canCancel: chat.isLoading,
928
+ value: chat.input,
929
+ setValue: chat.setInput
930
+ });
931
+ }, [context, chat.isLoading, chat.input, chat.setInput]);
908
932
  const branches = useChatWithBranches(chat);
909
- return /* @__PURE__ */ React.createElement(ThreadContextProvider, { chat: branches }, children);
933
+ useMemo4(() => {
934
+ context.useBranchObserver.setState(
935
+ {
936
+ getBranchState: (message) => branches.getBranchState(message),
937
+ switchToBranch: (message, branchId) => branches.switchToBranch(message, branchId),
938
+ editAt: async (message, newMessage) => branches.editAt(message, newMessage),
939
+ reloadAt: async (message) => branches.reloadAt(message)
940
+ },
941
+ true
942
+ );
943
+ }, [context, branches]);
944
+ return /* @__PURE__ */ React.createElement(AssistantContext.Provider, { value: context }, children);
910
945
  };
911
946
  export {
912
947
  actionBar_exports as ActionBarPrimitive,
913
948
  branchPicker_exports as BranchPickerPrimitive,
914
949
  composer_exports as ComposerPrimitive,
915
- editBar_exports as EditBarPrimitive,
916
950
  message_exports as MessagePrimitive,
917
951
  thread_exports as ThreadPrimitive,
918
- VercelAIThreadProvider,
952
+ VercelAIAssistantProvider as VercelAIThreadProvider,
919
953
  useMessageContext as unstable_useMessageContext,
920
954
  useBeginMessageEdit,
921
- useCancelMessageEdit,
922
955
  useCopyMessage,
923
956
  useGoToNextBranch,
924
957
  useGoToPreviousBranch,
925
- useReloadMessage,
926
- useSaveMessageEdit
958
+ useReloadMessage
927
959
  };