@assistant-ui/react 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.mjs CHANGED
@@ -25,92 +25,22 @@ var ThreadRoot = forwardRef(
25
25
  }
26
26
  );
27
27
 
28
- // src/utils/context/createStoreContext.tsx
29
- import {
30
- createContext,
31
- useContext,
32
- useMemo,
33
- useState
34
- } from "react";
35
- import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector";
36
-
37
- // src/utils/context/shallow.ts
38
- function shallow(objA, objB) {
39
- if (Object.is(objA, objB)) {
40
- return true;
41
- }
42
- if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) {
43
- return false;
44
- }
45
- const keysA = Object.keys(objA);
46
- if (keysA.length !== Object.keys(objB).length) {
47
- return false;
48
- }
49
- for (const keyA of keysA) {
50
- if (!Object.prototype.hasOwnProperty.call(objB, keyA) || !Object.is(objA[keyA], objB[keyA])) {
51
- return false;
52
- }
53
- }
54
- return true;
55
- }
56
-
57
- // src/utils/context/createStoreContext.tsx
58
- var createStoreContext = (providerName) => {
59
- const context = createContext(null);
60
- const StoreProvider = ({ children, ...rest }) => {
61
- const unstableContext = rest;
62
- const [store] = useState(() => {
63
- let state = unstableContext;
64
- const listeners = /* @__PURE__ */ new Set();
65
- return {
66
- subscribe: (cb) => {
67
- listeners.add(cb);
68
- return () => listeners.delete(cb);
69
- },
70
- emit: () => {
71
- for (const listener of listeners) {
72
- listener();
73
- }
74
- },
75
- snapshot: () => {
76
- return state;
77
- },
78
- setState: (value) => {
79
- state = value;
80
- store.emit();
81
- }
82
- };
83
- });
84
- useMemo(
85
- () => store.setState(unstableContext),
86
- Object.values(unstableContext)
87
- );
88
- return /* @__PURE__ */ React.createElement(context.Provider, { value: store }, children);
89
- };
90
- const useStoreContext = (consumerName, selector) => {
91
- const store = useContext(context);
92
- if (!store)
93
- throw new Error(
94
- `${consumerName} can only be used inside ${providerName}.`
95
- );
96
- return useSyncExternalStoreWithSelector(
97
- store.subscribe,
98
- store.snapshot,
99
- store.snapshot,
100
- selector,
101
- shallow
28
+ // src/utils/context/AssistantContext.ts
29
+ import { createContext, useContext } from "react";
30
+ var AssistantContext = createContext(null);
31
+ var useAssistantContext = () => {
32
+ const context = useContext(AssistantContext);
33
+ if (!context)
34
+ throw new Error(
35
+ "useAssistantContext must be used within a AssistantProvider"
102
36
  );
103
- };
104
- return [StoreProvider, useStoreContext];
37
+ return context;
105
38
  };
106
39
 
107
- // src/utils/context/ThreadContext.ts
108
- var [ThreadContextProvider, useThreadContext] = createStoreContext("Thread.Provider");
109
-
110
40
  // src/primitives/thread/ThreadIf.tsx
111
41
  var useThreadIf = (props) => {
112
- return useThreadContext("Thread.If", (s) => {
113
- const thread = s.chat;
42
+ const { useThread } = useAssistantContext();
43
+ return useThread((thread) => {
114
44
  if (props.empty === true && thread.messages.length !== 0)
115
45
  return false;
116
46
  if (props.empty === false && thread.messages.length === 0)
@@ -133,7 +63,7 @@ var ThreadEmpty = ({ children }) => {
133
63
  };
134
64
 
135
65
  // src/primitives/thread/ThreadViewport.tsx
136
- import { forwardRef as forwardRef2, useRef as useRef2, useState as useState2 } from "react";
66
+ import { forwardRef as forwardRef2, useRef as useRef2, useState } from "react";
137
67
  import {
138
68
  Primitive as Primitive2
139
69
  } from "@radix-ui/react-primitive";
@@ -185,7 +115,7 @@ var useOnResizeContent = (ref, callback) => {
185
115
  var ThreadViewport = forwardRef2(({ onScroll, children, ...rest }, forwardedRef) => {
186
116
  const divRef = useRef2(null);
187
117
  const ref = useComposedRefs(forwardedRef, divRef);
188
- const [isAtBottom, setIsAtBottom] = useState2(true);
118
+ const [isAtBottom, setIsAtBottom] = useState(true);
189
119
  useOnResizeContent(divRef, () => {
190
120
  const div = divRef.current;
191
121
  if (!div || !isAtBottom)
@@ -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
  };