@assistant-ui/react 0.0.4 → 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,83 +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
  };
333
262
  var hasUpcomingMessage = (thread) => {
334
263
  return thread.isLoading && thread.messages[thread.messages.length - 1]?.role !== "assistant";
335
264
  };
336
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
+ };
301
+
337
302
  // src/primitives/message/index.ts
338
303
  var message_exports = {};
339
304
  __export(message_exports, {
340
305
  Content: () => MessageContent,
341
- EditableContent: () => MessageEditableContent,
342
306
  If: () => MessageIf,
343
307
  Provider: () => MessageProvider,
344
308
  Root: () => MessageRoot
345
309
  });
346
310
 
347
311
  // 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
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
+ };
354
361
  var MessageProvider = ({
355
362
  message,
356
363
  children
357
364
  }) => {
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
365
+ const { useThread, useBranchObserver } = useAssistantContext();
366
+ const context = useMessageContext2();
367
+ const branchState = useBranchObserver(
368
+ useShallow((b) => b.getBranchState(message))
384
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);
385
388
  };
386
389
 
387
390
  // src/primitives/message/MessageRoot.tsx
388
- import { forwardRef as forwardRef3 } from "react";
391
+ import { composeEventHandlers as composeEventHandlers2 } from "@radix-ui/primitive";
389
392
  import {
390
393
  Primitive as Primitive3
391
394
  } from "@radix-ui/react-primitive";
392
- import { composeEventHandlers as composeEventHandlers2 } from "@radix-ui/primitive";
395
+ import { forwardRef as forwardRef3 } from "react";
393
396
  var MessageRoot = forwardRef3(
394
397
  ({ onMouseEnter, onMouseLeave, ...rest }, ref) => {
395
- const setIsHovering = useMessageContext(
396
- "Message.Root",
397
- (s) => s.setIsHovering
398
- );
398
+ const { useMessage } = useMessageContext();
399
+ const setIsHovering = useMessage((s) => s.setIsHovering);
399
400
  const handleMouseEnter = () => {
400
401
  setIsHovering(true);
401
402
  };
@@ -415,27 +416,23 @@ var MessageRoot = forwardRef3(
415
416
  );
416
417
 
417
418
  // 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
419
  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);
420
+ const { useMessage } = useMessageContext();
421
+ return useMessage(
422
+ ({
423
+ message,
424
+ isLast,
425
+ isCopied,
426
+ isHovering,
427
+ branchState: { branchCount }
428
+ }) => {
428
429
  if (props.hasBranches === true && branchCount < 2)
429
430
  return false;
430
431
  if (props.user && message.role !== "user")
431
432
  return false;
432
433
  if (props.assistant && message.role !== "assistant")
433
434
  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))
435
+ if (props.lastOrHover === true && !isHovering && !isLast)
439
436
  return false;
440
437
  if (props.copied === true && !isCopied)
441
438
  return false;
@@ -452,60 +449,32 @@ var MessageIf = ({ children, ...query }) => {
452
449
 
453
450
  // src/primitives/message/MessageContent.tsx
454
451
  var MessageContent = () => {
455
- const content = useMessageContext(
456
- "Message.Content",
457
- (s) => s.message.content
458
- );
452
+ const { useMessage } = useMessageContext();
453
+ const content = useMessage((s) => s.message.content);
459
454
  return /* @__PURE__ */ React.createElement(React.Fragment, null, content);
460
455
  };
461
456
 
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
457
  // src/primitives/thread/ThreadMessages.tsx
490
458
  var getComponents = (components) => {
491
459
  return {
492
- EditingUserMessage: components.EditingUserMessage ?? components.UserMessage ?? components.Message,
460
+ EditComposer: components.EditComposer ?? components.UserMessage ?? components.Message,
493
461
  UserMessage: components.UserMessage ?? components.Message,
494
462
  AssistantMessage: components.AssistantMessage ?? components.Message
495
463
  };
496
464
  };
497
465
  var ThreadMessages = ({ components }) => {
498
- const chat = useThreadContext("Thread.Messages", (s) => s.chat);
499
- const messages = chat.messages;
500
- 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);
501
470
  if (messages.length === 0)
502
471
  return null;
503
472
  return /* @__PURE__ */ React.createElement(React.Fragment, null, messages.map((message, idx) => {
504
473
  return (
505
474
  // 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)))
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)))
507
476
  );
508
- }), hasUpcomingMessage(chat) && /* @__PURE__ */ React.createElement(
477
+ }), hasUpcomingMessage(thread) && /* @__PURE__ */ React.createElement(
509
478
  MessageProvider,
510
479
  {
511
480
  message: {
@@ -521,22 +490,29 @@ var ThreadMessages = ({ components }) => {
521
490
  // src/primitives/composer/index.ts
522
491
  var composer_exports = {};
523
492
  __export(composer_exports, {
493
+ Cancel: () => ComposerCancel,
494
+ If: () => ComposerIf,
524
495
  Input: () => ComposerInput,
525
496
  Root: () => ComposerRoot,
526
- Send: () => ComposerSend,
527
- Stop: () => ComposerStop
497
+ Send: () => ComposerSend
528
498
  });
529
499
 
530
500
  // src/primitives/composer/ComposerRoot.tsx
531
- 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";
532
503
  import {
533
504
  Primitive as Primitive4
534
505
  } 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);
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);
540
516
  if (!context) {
541
517
  throw new Error(
542
518
  "Composer compound components cannot be rendered outside the Composer component"
@@ -544,15 +520,12 @@ var useComposerContext = () => {
544
520
  }
545
521
  return context;
546
522
  };
547
- var ComposerRoot = forwardRef5(
523
+ var ComposerRoot = forwardRef4(
548
524
  ({ onSubmit, ...rest }, forwardedRef) => {
549
- const handleSubmit = useThreadContext(
550
- "Composer.Root",
551
- (s) => s.chat.handleSubmit
552
- );
525
+ const { useComposer } = useComposerContext();
553
526
  const formRef = useRef4(null);
554
527
  const ref = useComposedRefs2(forwardedRef, formRef);
555
- const composerContextValue = useMemo4(
528
+ const composerContextValue = useMemo3(
556
529
  () => ({
557
530
  submit: () => formRef.current?.dispatchEvent(
558
531
  new Event("submit", { cancelable: true, bubbles: true })
@@ -560,108 +533,115 @@ var ComposerRoot = forwardRef5(
560
533
  }),
561
534
  []
562
535
  );
563
- 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(
564
544
  Primitive4.form,
565
545
  {
566
546
  ...rest,
567
547
  ref,
568
- onSubmit: composeEventHandlers4(onSubmit, handleSubmit)
548
+ onSubmit: composeEventHandlers3(onSubmit, handleSubmit)
569
549
  }
570
550
  ));
571
551
  }
572
552
  );
573
553
 
574
554
  // src/primitives/composer/ComposerInput.tsx
575
- import { forwardRef as forwardRef6 } from "react";
555
+ import { composeEventHandlers as composeEventHandlers4 } from "@radix-ui/primitive";
576
556
  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();
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();
590
570
  const handleKeyPress = (e) => {
591
- if (chat.isLoading || rest.disabled)
571
+ if (disabled)
572
+ return;
573
+ if (e.key === "Escape") {
574
+ useComposer.getState().cancel();
575
+ }
576
+ if (isLoading)
592
577
  return;
593
578
  if (e.key === "Enter" && e.shiftKey === false) {
594
579
  e.preventDefault();
595
- composer.submit();
580
+ composerForm.submit();
596
581
  }
597
582
  };
598
583
  return /* @__PURE__ */ React.createElement(
599
584
  Component,
600
585
  {
601
- value: chat.input,
586
+ value,
602
587
  ...rest,
603
588
  ref: forwardedRef,
604
- onChange: composeEventHandlers5(onChange, chat.handleInputChange),
605
- 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)
606
597
  }
607
598
  );
608
599
  });
609
600
 
610
601
  // src/primitives/composer/ComposerSend.tsx
611
- import { forwardRef as forwardRef7 } from "react";
612
602
  import {
613
603
  Primitive as Primitive5
614
604
  } from "@radix-ui/react-primitive";
615
- var ComposerSend = forwardRef7(
605
+ import { forwardRef as forwardRef6 } from "react";
606
+ var ComposerSend = forwardRef6(
616
607
  ({ disabled, ...rest }, ref) => {
617
- const input = useThreadContext("Composer.Send", (s) => s.chat.input);
608
+ const { useComposer } = useComposerContext();
609
+ const hasValue = useComposer((c) => c.isEditing && c.value.length > 0);
618
610
  return /* @__PURE__ */ React.createElement(
619
611
  Primitive5.button,
620
612
  {
621
613
  type: "submit",
622
614
  ...rest,
623
615
  ref,
624
- disabled: disabled || input.length === 0
616
+ disabled: disabled || !hasValue
625
617
  }
626
618
  );
627
619
  }
628
620
  );
629
621
 
630
- // src/utils/createActionButton.tsx
631
- import { forwardRef as forwardRef8 } from "react";
622
+ // src/primitives/composer/ComposerCancel.tsx
623
+ import { composeEventHandlers as composeEventHandlers5 } from "@radix-ui/primitive";
632
624
  import {
633
625
  Primitive as Primitive6
634
626
  } 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
- );
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
650
642
  }
651
643
  );
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);
644
+ });
665
645
 
666
646
  // src/primitives/branchPicker/index.ts
667
647
  var branchPicker_exports = {};
@@ -675,52 +655,66 @@ __export(branchPicker_exports, {
675
655
 
676
656
  // src/actions/useGoToNextBranch.tsx
677
657
  var useGoToNextBranch = () => {
678
- const switchToBranch = useThreadContext(
679
- "BranchPicker.Next",
680
- (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
681
664
  );
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)
665
+ if (isLoading || isEditing || !hasNext)
693
666
  return null;
694
- const { message, branchId } = context;
695
667
  return () => {
696
- switchToBranch(message, branchId + 1);
668
+ const {
669
+ message,
670
+ branchState: { branchId }
671
+ } = useMessage.getState();
672
+ useBranchObserver.getState().switchToBranch(message, branchId + 1);
697
673
  };
698
674
  };
699
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
+
700
700
  // src/primitives/branchPicker/BranchPickerNext.tsx
701
701
  var BranchPickerNext = createActionButton(useGoToNextBranch);
702
702
 
703
703
  // src/actions/useGoToPreviousBranch.tsx
704
704
  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)
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)
720
711
  return null;
721
- const { message, branchId } = context;
722
712
  return () => {
723
- switchToBranch(message, branchId - 1);
713
+ const {
714
+ message,
715
+ branchState: { branchId }
716
+ } = useMessage.getState();
717
+ useBranchObserver.getState().switchToBranch(message, branchId - 1);
724
718
  };
725
719
  };
726
720
 
@@ -729,29 +723,25 @@ var BranchPickerPrevious = createActionButton(useGoToPreviousBranch);
729
723
 
730
724
  // src/primitives/branchPicker/BranchPickerCount.tsx
731
725
  var BranchPickerCount = () => {
732
- const branchCount = useMessageContext(
733
- "BranchPicker.Count",
734
- (s) => s.branchState.branchCount
735
- );
726
+ const { useMessage } = useMessageContext();
727
+ const branchCount = useMessage((s) => s.branchState.branchCount);
736
728
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchCount);
737
729
  };
738
730
 
739
731
  // src/primitives/branchPicker/BranchPickerNumber.tsx
740
732
  var BranchPickerNumber = () => {
741
- const branchId = useMessageContext(
742
- "BranchPicker.Number",
743
- (s) => s.branchState.branchId
744
- );
733
+ const { useMessage } = useMessageContext();
734
+ const branchId = useMessage((s) => s.branchState.branchId);
745
735
  return /* @__PURE__ */ React.createElement(React.Fragment, null, branchId + 1);
746
736
  };
747
737
 
748
738
  // src/primitives/branchPicker/BranchPickerRoot.tsx
749
739
  import {
750
- Primitive as Primitive7
740
+ Primitive as Primitive8
751
741
  } from "@radix-ui/react-primitive";
752
742
  import { forwardRef as forwardRef9 } from "react";
753
743
  var BranchPickerRoot = forwardRef9(({ hideWhenSingleBranch, ...rest }, ref) => {
754
- 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 }));
755
745
  });
756
746
 
757
747
  // src/primitives/actionBar/index.ts
@@ -765,29 +755,48 @@ __export(actionBar_exports, {
765
755
 
766
756
  // src/primitives/actionBar/ActionBarRoot.tsx
767
757
  import {
768
- Primitive as Primitive8
758
+ Primitive as Primitive9
769
759
  } from "@radix-ui/react-primitive";
770
760
  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 })));
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
+ );
773
787
  });
774
788
 
775
789
  // src/actions/useCopyMessage.tsx
776
790
  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)
791
+ const { useMessage, useComposer } = useMessageContext();
792
+ const isEditing = useComposer((s) => s.isEditing);
793
+ if (isEditing)
788
794
  return null;
789
- const { content, setIsCopied } = context;
790
795
  return () => {
796
+ const {
797
+ message: { content },
798
+ setIsCopied
799
+ } = useMessage.getState();
791
800
  navigator.clipboard.writeText(content);
792
801
  setIsCopied(true);
793
802
  setTimeout(() => setIsCopied(false), copiedDuration);
@@ -799,19 +808,15 @@ var ActionBarCopy = createActionButton(useCopyMessage);
799
808
 
800
809
  // src/actions/useReloadMessage.tsx
801
810
  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)
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)
813
816
  return null;
814
- return () => reloadAt(message);
817
+ return () => {
818
+ useBranchObserver.getState().reloadAt(useMessage.getState().message);
819
+ };
815
820
  };
816
821
 
817
822
  // src/primitives/actionBar/ActionBarReload.tsx
@@ -819,116 +824,136 @@ var ActionBarReload = createActionButton(useReloadMessage);
819
824
 
820
825
  // src/actions/useBeginMessageEdit.tsx
821
826
  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)
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)
833
831
  return null;
834
- const { content, setEditState } = context;
835
832
  return () => {
836
- setEditState({ isEditing: true, value: content });
833
+ const { edit } = useComposer.getState();
834
+ edit();
837
835
  };
838
836
  };
839
837
 
840
838
  // src/primitives/actionBar/ActionBarEdit.tsx
841
839
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
842
840
 
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 };
870
- });
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 });
882
- };
883
- };
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 };
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 };
898
892
  });
899
- if (!context)
900
- return null;
901
- const { setEditState } = context;
902
- return () => {
903
- setEditState({ isEditing: false });
904
- };
893
+ return context;
905
894
  };
906
-
907
- // src/primitives/editBar/EditBarCancel.tsx
908
- var EditBarCancel = createActionButton(useCancelMessageEdit);
909
-
910
- // src/vercel/VercelAIThreadProvider.tsx
911
- var VercelAIThreadProvider = ({
895
+ var VercelAIAssistantProvider = ({
912
896
  chat,
913
897
  children
914
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]);
915
932
  const branches = useChatWithBranches(chat);
916
- 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);
917
945
  };
918
946
  export {
919
947
  actionBar_exports as ActionBarPrimitive,
920
948
  branchPicker_exports as BranchPickerPrimitive,
921
949
  composer_exports as ComposerPrimitive,
922
- editBar_exports as EditBarPrimitive,
923
950
  message_exports as MessagePrimitive,
924
951
  thread_exports as ThreadPrimitive,
925
- VercelAIThreadProvider,
952
+ VercelAIAssistantProvider as VercelAIThreadProvider,
926
953
  useMessageContext as unstable_useMessageContext,
927
954
  useBeginMessageEdit,
928
- useCancelMessageEdit,
929
955
  useCopyMessage,
930
956
  useGoToNextBranch,
931
957
  useGoToPreviousBranch,
932
- useReloadMessage,
933
- useSaveMessageEdit
958
+ useReloadMessage
934
959
  };