@agents24/chat-react 0.5.29 → 0.5.30

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.
Files changed (36) hide show
  1. package/README.md +23 -1
  2. package/dist/chunk-INO5DSSJ.js +86 -0
  3. package/dist/chunk-INO5DSSJ.js.map +1 -0
  4. package/dist/registry/agents24-chat.json +249 -0
  5. package/dist/runtime/index.cjs +1 -1
  6. package/dist/runtime/index.cjs.map +1 -1
  7. package/dist/runtime/index.js +11 -78
  8. package/dist/runtime/index.js.map +1 -1
  9. package/dist/runtime/input-contract.d.ts +3 -1
  10. package/dist/scaffold/app.d.ts +2 -0
  11. package/dist/scaffold/components/chat/chat-composer.d.ts +2 -3
  12. package/dist/scaffold/components/chat/theme-menu.d.ts +1 -1
  13. package/dist/scaffold/components/theme-provider.d.ts +2 -1
  14. package/dist/scaffold/index.cjs +44 -102
  15. package/dist/scaffold/index.cjs.map +1 -1
  16. package/dist/scaffold/index.d.ts +2 -0
  17. package/dist/scaffold/index.js +41 -103
  18. package/dist/scaffold/index.js.map +1 -1
  19. package/dist/ui/agent-chat-composer.d.ts +7 -2
  20. package/dist/ui/index.cjs +688 -540
  21. package/dist/ui/index.cjs.map +1 -1
  22. package/dist/ui/index.d.ts +1 -0
  23. package/dist/ui/index.js +650 -541
  24. package/dist/ui/index.js.map +1 -1
  25. package/dist/ui/use-agent-chat-composer.d.ts +48 -0
  26. package/dist/ui/use-audio-recorder.d.ts +1 -1
  27. package/package.json +8 -4
  28. package/scaffold/inherit.css +4 -0
  29. package/scaffold/src/app.tsx +2 -0
  30. package/scaffold/src/chat.css +24 -0
  31. package/scaffold/src/components/chat/chat-composer.tsx +14 -97
  32. package/scaffold/src/components/chat/chat-shell.tsx +2 -1
  33. package/scaffold/src/components/chat/theme-menu.tsx +3 -1
  34. package/scaffold/src/components/theme-provider.tsx +10 -8
  35. package/scaffold/src/index.css +8 -8
  36. package/scaffold/src/index.tsx +5 -0
@@ -12,3 +12,5 @@ export type { Agents24ChatAppearance, Agents24ChatAppearanceController, Agents24
12
12
  export type { Agents24ChatIdentity, Agents24ChatIdentityState } from "./identity";
13
13
  export type { ChatHitlResponse, Agents24ChatScaffoldComponents, Agents24ChatTimelineSlotProps, } from "./app";
14
14
  export { TooltipProvider as Agents24ChatTooltipProvider };
15
+ export { ChatComposer as Agents24ChatComposer } from "./components/chat/chat-composer";
16
+ export type { Agents24ChatComposerProps } from "./components/chat/chat-composer";
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  // scaffold/src/app.tsx
2
4
  import { createBrowserVisibility } from "@agents24/client/browser";
3
5
  import {
@@ -21,13 +23,8 @@ import * as React2 from "react";
21
23
  import {
22
24
  AgentChatContextStatus,
23
25
  ChatAttachmentRows,
24
- chatComposerAction,
25
- createAgentChatComposerFile,
26
- revokeAgentChatComposerFiles,
27
- useAudioRecorder,
28
- useChatComposerFocus
26
+ useAgentChatComposer
29
27
  } from "@agents24/chat-react/ui";
30
- import { attachmentFileAccept, inputMimeTypes, validateAgentChatSubmission } from "@agents24/chat-react/runtime";
31
28
 
32
29
  // scaffold/src/components/ui/button.tsx
33
30
  import { cva } from "class-variance-authority";
@@ -119,11 +116,11 @@ function ThemeProvider({
119
116
  const [portalContainer, setPortalContainer] = React.useState(null);
120
117
  const appearance = controller?.appearance ?? internalAppearance;
121
118
  React.useEffect(() => {
122
- if (!controller) setInternalAppearance(storedAppearance());
119
+ if (theme !== "inherit" && !controller) setInternalAppearance(storedAppearance());
123
120
  setMounted(true);
124
- }, [controller]);
121
+ }, [controller, theme]);
125
122
  React.useEffect(() => {
126
- if (!mounted || typeof window === "undefined" || typeof window.matchMedia !== "function") return;
123
+ if (theme === "inherit" || !mounted || typeof window === "undefined" || typeof window.matchMedia !== "function") return;
127
124
  const media = window.matchMedia("(prefers-color-scheme: dark)");
128
125
  const apply = () => setResolvedAppearance(
129
126
  appearance === "system" ? media.matches ? "dark" : "light" : appearance
@@ -131,8 +128,9 @@ function ThemeProvider({
131
128
  apply();
132
129
  media.addEventListener("change", apply);
133
130
  return () => media.removeEventListener("change", apply);
134
- }, [appearance, mounted]);
131
+ }, [appearance, mounted, theme]);
135
132
  const change = React.useCallback((next) => {
133
+ if (theme === "inherit") return;
136
134
  if (controller) {
137
135
  controller.setAppearance(next);
138
136
  return;
@@ -142,12 +140,12 @@ function ThemeProvider({
142
140
  } catch {
143
141
  }
144
142
  setInternalAppearance(next);
145
- }, [controller]);
146
- return /* @__PURE__ */ jsx2(ThemeContext.Provider, { value: { appearance, portalContainer, setAppearance: change }, children: /* @__PURE__ */ jsx2(
143
+ }, [controller, theme]);
144
+ return /* @__PURE__ */ jsx2(ThemeContext.Provider, { value: { appearance, inherited: theme === "inherit", portalContainer, setAppearance: change }, children: /* @__PURE__ */ jsx2(
147
145
  "div",
148
146
  {
149
147
  ref: setPortalContainer,
150
- className: `${resolvedAppearance} size-full`,
148
+ className: theme === "inherit" ? "size-full" : `${resolvedAppearance} size-full`,
151
149
  "data-agents24-theme": theme,
152
150
  "data-agents24-theme-root": "",
153
151
  children
@@ -498,49 +496,31 @@ function ChatComposer({
498
496
  restoredDraft,
499
497
  utilityRow = "inline"
500
498
  }) {
501
- const [text, setText] = React2.useState("");
502
- const [files, setFiles] = React2.useState([]);
503
- const [attachmentError, setAttachmentError] = React2.useState(null);
504
499
  const inputRef = React2.useRef(null);
505
500
  const textareaRef = React2.useRef(null);
506
- const filesRef = React2.useRef([]);
507
- const inFlightFilesRef = React2.useRef([]);
508
- const submittingRef = React2.useRef(false);
509
- const draftRevisionRef = React2.useRef(0);
510
- const attachmentMimeTypes = inputMimeTypes(inputContract);
511
- const allowAttachments = attachmentMimeTypes.length > 0;
512
- const recordingAllowed = inputContract.modalities.audio.recording_enabled;
513
- React2.useEffect(() => {
514
- filesRef.current = files;
515
- }, [files]);
516
- React2.useEffect(() => () => {
517
- revokeAgentChatComposerFiles(filesRef.current);
518
- revokeAgentChatComposerFiles(inFlightFilesRef.current);
519
- }, []);
520
- const focusComposer = useChatComposerFocus({
521
- textareaRef,
522
- disabled: Boolean(disabled),
501
+ const composer = useAgentChatComposer({
502
+ inputContract,
503
+ disabled,
504
+ isRunning,
505
+ isCancelling,
523
506
  focusKey,
524
507
  restoredDraft,
525
- onRestore: (draft) => {
526
- draftRevisionRef.current += 1;
527
- revokeAgentChatComposerFiles(filesRef.current);
528
- setText(draft.text);
529
- setFiles(draft.files);
530
- }
508
+ textareaRef,
509
+ onSubmit: ({ text: text2, files: files2 }) => onSubmit(text2, files2)
531
510
  });
532
- const addRecording = React2.useCallback((file) => {
533
- draftRevisionRef.current += 1;
534
- setFiles((current) => [...current, createAgentChatComposerFile(file)]);
535
- }, []);
536
- const recorder = useAudioRecorder(addRecording);
537
- const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
538
- const validationError = validateAgentChatSubmission(
539
- inputContract,
511
+ const {
540
512
  text,
541
- files.map((file) => ({ data: file.source || new Uint8Array(), mediaType: file.mediaType }))
542
- );
543
- const action = chatComposerAction({ hasDraft: Boolean(text.trim() || files.length), isRunning, isCancelling });
513
+ files,
514
+ setText,
515
+ validationError,
516
+ attachmentError,
517
+ recorder,
518
+ recordingAllowed,
519
+ allowAttachments,
520
+ action,
521
+ submit
522
+ } = composer;
523
+ const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
544
524
  const selectedAgent = agents.find((item) => item.id === agentId);
545
525
  const selectedModel = models.find((item) => item.id === modelId);
546
526
  const modelSelector = models.length > 0 ? /* @__PURE__ */ jsx7("div", { className: "w-fit max-w-40", children: /* @__PURE__ */ jsxs3(Select, { disabled: disabled || isRunning, onValueChange: onModelChange, value: modelId || void 0, children: [
@@ -580,35 +560,6 @@ function ChatComposer({
580
560
  ] }) : modelSelector;
581
561
  const hasContextWindow = Number.isFinite(Number(contextWindow?.max_tokens)) && Number(contextWindow?.max_tokens) > 0;
582
562
  const showUtilityRow = utilityRow === "below";
583
- const submit = async () => {
584
- if (disabled || isCancelling || submittingRef.current || validationError) return;
585
- const value = text;
586
- const selectedFiles = files;
587
- const clearedRevision = draftRevisionRef.current + 1;
588
- draftRevisionRef.current = clearedRevision;
589
- submittingRef.current = true;
590
- inFlightFilesRef.current = selectedFiles;
591
- setText("");
592
- setFiles([]);
593
- focusComposer();
594
- try {
595
- await onSubmit(value, selectedFiles);
596
- revokeAgentChatComposerFiles(selectedFiles);
597
- inFlightFilesRef.current = [];
598
- } catch {
599
- if (draftRevisionRef.current === clearedRevision) {
600
- draftRevisionRef.current += 1;
601
- setText(value);
602
- setFiles(selectedFiles);
603
- inFlightFilesRef.current = [];
604
- } else {
605
- revokeAgentChatComposerFiles(selectedFiles);
606
- inFlightFilesRef.current = [];
607
- }
608
- } finally {
609
- submittingRef.current = false;
610
- }
611
- };
612
563
  return /* @__PURE__ */ jsxs3(
613
564
  "div",
614
565
  {
@@ -620,13 +571,7 @@ function ChatComposer({
620
571
  {
621
572
  attachments: files,
622
573
  onRemove: (attachment) => {
623
- if (!attachment.id) return;
624
- draftRevisionRef.current += 1;
625
- setFiles((current) => {
626
- const removed = current.find((file) => file.id === attachment.id);
627
- if (removed) revokeAgentChatComposerFiles([removed]);
628
- return current.filter((file) => file.id !== attachment.id);
629
- });
574
+ if (attachment.id) composer.removeFile(attachment.id);
630
575
  }
631
576
  }
632
577
  ) }),
@@ -648,11 +593,9 @@ function ChatComposer({
648
593
  className: "sr-only",
649
594
  type: "file",
650
595
  multiple: true,
651
- accept: attachmentFileAccept(attachmentMimeTypes),
596
+ accept: composer.fileAccept,
652
597
  onChange: (event) => {
653
- const selected = Array.from(event.target.files ?? []);
654
- setFiles((current) => [...current, ...selected.map(createAgentChatComposerFile)]);
655
- setAttachmentError(null);
598
+ composer.addFiles(Array.from(event.target.files ?? []));
656
599
  event.currentTarget.value = "";
657
600
  }
658
601
  }
@@ -672,16 +615,8 @@ function ChatComposer({
672
615
  "agents24-composer-textarea max-h-56 min-h-9 field-sizing-content resize-none overflow-y-auto border-0 bg-transparent px-2 shadow-none focus-visible:ring-0 dark:bg-transparent",
673
616
  isExpanded ? "py-2" : "h-9 py-2"
674
617
  ),
675
- onChange: (event) => {
676
- draftRevisionRef.current += 1;
677
- setText(event.target.value);
678
- },
679
- onKeyDown: (event) => {
680
- if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
681
- event.preventDefault();
682
- void submit();
683
- }
684
- }
618
+ onChange: (event) => setText(event.target.value),
619
+ onKeyDown: composer.onKeyDown
685
620
  }
686
621
  ),
687
622
  /* @__PURE__ */ jsxs3("div", { className: "agents24-composer-actions flex items-center gap-1", children: [
@@ -713,7 +648,7 @@ function ChatComposer({
713
648
  ) : validationError ? /* @__PURE__ */ jsxs3(Tooltip, { children: [
714
649
  /* @__PURE__ */ jsx7(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx7("span", { tabIndex: 0, "aria-label": validationError, children: /* @__PURE__ */ jsx7(Button, { size: "icon", "aria-label": isRunning ? "Queue message" : "Send message", disabled: true, onClick: () => void submit(), children: /* @__PURE__ */ jsx7(ArrowUp, {}) }) }) }),
715
650
  /* @__PURE__ */ jsx7(TooltipContent, { children: validationError })
716
- ] }) : /* @__PURE__ */ jsx7(Button, { size: "icon", "aria-label": isRunning ? "Queue message" : "Send message", disabled, onClick: () => void submit(), children: /* @__PURE__ */ jsx7(ArrowUp, {}) })
651
+ ] }) : /* @__PURE__ */ jsx7(Button, { size: "icon", "aria-label": isRunning ? "Queue message" : "Send message", disabled: disabled || !composer.canSubmit, onClick: () => void submit(), children: /* @__PURE__ */ jsx7(ArrowUp, {}) })
717
652
  ] })
718
653
  ]
719
654
  }
@@ -1209,7 +1144,7 @@ var options = [
1209
1144
  { value: "dark", label: "Dark", icon: Moon }
1210
1145
  ];
1211
1146
  function ThemeMenu() {
1212
- const { appearance, setAppearance } = useTheme();
1147
+ const { appearance, setAppearance, inherited } = useTheme();
1213
1148
  const [open, setOpen] = React5.useState(false);
1214
1149
  const rootRef = React5.useRef(null);
1215
1150
  const firstItemRef = React5.useRef(null);
@@ -1231,6 +1166,7 @@ function ThemeMenu() {
1231
1166
  document.removeEventListener("keydown", closeOnEscape);
1232
1167
  };
1233
1168
  }, [open]);
1169
+ if (inherited) return null;
1234
1170
  const label = appearance === "system" ? "System" : appearance === "light" ? "Light" : "Dark";
1235
1171
  return /* @__PURE__ */ jsxs9("div", { ref: rootRef, className: "relative", children: [
1236
1172
  /* @__PURE__ */ jsx15(
@@ -2078,6 +2014,7 @@ function ChatShell({
2078
2014
  runtime,
2079
2015
  sidebar
2080
2016
  }) {
2017
+ const Composer = components?.Composer || ChatComposer;
2081
2018
  const { controller } = runtime;
2082
2019
  const isUploading = controller.runState === "uploading";
2083
2020
  const isRunning = controller.runState === "streaming" || controller.runState === "reconnecting" || controller.runState === "paused" || controller.runState === "cancelling";
@@ -2235,7 +2172,7 @@ function ChatShell({
2235
2172
  }
2236
2173
  ),
2237
2174
  /* @__PURE__ */ jsx24(
2238
- ChatComposer,
2175
+ Composer,
2239
2176
  {
2240
2177
  restoredDraft,
2241
2178
  inputContract: runtime.inputContract || LOADING_INPUT_CONTRACT,
@@ -2312,6 +2249,7 @@ function Agents24ChatScaffold({ appearanceController, theme, ...props }) {
2312
2249
  return /* @__PURE__ */ jsx26(ThemeProvider, { controller: appearanceController, theme, children: /* @__PURE__ */ jsx26(TooltipProvider, { children: /* @__PURE__ */ jsx26(App, { ...props }) }) });
2313
2250
  }
2314
2251
  export {
2252
+ ChatComposer as Agents24ChatComposer,
2315
2253
  Agents24ChatScaffold,
2316
2254
  App as Agents24ChatScaffoldApp,
2317
2255
  ThemeProvider as Agents24ChatThemeProvider,