@agents24/chat-react 0.5.28 → 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 (37) 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 +10 -14
  6. package/dist/runtime/index.cjs.map +1 -1
  7. package/dist/runtime/index.js +12 -85
  8. package/dist/runtime/index.js.map +1 -1
  9. package/dist/runtime/input-contract.d.ts +6 -3
  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 -106
  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 -107
  18. package/dist/scaffold/index.js.map +1 -1
  19. package/dist/ui/agent-chat-composer.d.ts +7 -2
  20. package/dist/ui/composer-attachments.d.ts +1 -1
  21. package/dist/ui/index.cjs +688 -539
  22. package/dist/ui/index.cjs.map +1 -1
  23. package/dist/ui/index.d.ts +1 -0
  24. package/dist/ui/index.js +650 -540
  25. package/dist/ui/index.js.map +1 -1
  26. package/dist/ui/use-agent-chat-composer.d.ts +48 -0
  27. package/dist/ui/use-audio-recorder.d.ts +1 -1
  28. package/package.json +10 -6
  29. package/scaffold/inherit.css +4 -0
  30. package/scaffold/src/app.tsx +2 -0
  31. package/scaffold/src/chat.css +24 -0
  32. package/scaffold/src/components/chat/chat-composer.tsx +14 -105
  33. package/scaffold/src/components/chat/chat-shell.tsx +2 -1
  34. package/scaffold/src/components/chat/theme-menu.tsx +3 -1
  35. package/scaffold/src/components/theme-provider.tsx +10 -8
  36. package/scaffold/src/index.css +8 -8
  37. 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 { canonicalInputMimeType, 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,15 +593,9 @@ function ChatComposer({
648
593
  className: "sr-only",
649
594
  type: "file",
650
595
  multiple: true,
651
- accept: attachmentMimeTypes.join(","),
596
+ accept: composer.fileAccept,
652
597
  onChange: (event) => {
653
- const selected = Array.from(event.target.files ?? []);
654
- const accepted = selected.filter((file) => attachmentMimeTypes.includes(canonicalInputMimeType(file.type)));
655
- draftRevisionRef.current += 1;
656
- setFiles((current) => [...current, ...accepted.map(createAgentChatComposerFile)]);
657
- setAttachmentError(
658
- accepted.length === selected.length ? null : "One or more selected file types are not supported by this Agent."
659
- );
598
+ composer.addFiles(Array.from(event.target.files ?? []));
660
599
  event.currentTarget.value = "";
661
600
  }
662
601
  }
@@ -676,16 +615,8 @@ function ChatComposer({
676
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",
677
616
  isExpanded ? "py-2" : "h-9 py-2"
678
617
  ),
679
- onChange: (event) => {
680
- draftRevisionRef.current += 1;
681
- setText(event.target.value);
682
- },
683
- onKeyDown: (event) => {
684
- if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
685
- event.preventDefault();
686
- void submit();
687
- }
688
- }
618
+ onChange: (event) => setText(event.target.value),
619
+ onKeyDown: composer.onKeyDown
689
620
  }
690
621
  ),
691
622
  /* @__PURE__ */ jsxs3("div", { className: "agents24-composer-actions flex items-center gap-1", children: [
@@ -717,7 +648,7 @@ function ChatComposer({
717
648
  ) : validationError ? /* @__PURE__ */ jsxs3(Tooltip, { children: [
718
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, {}) }) }) }),
719
650
  /* @__PURE__ */ jsx7(TooltipContent, { children: validationError })
720
- ] }) : /* @__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, {}) })
721
652
  ] })
722
653
  ]
723
654
  }
@@ -1213,7 +1144,7 @@ var options = [
1213
1144
  { value: "dark", label: "Dark", icon: Moon }
1214
1145
  ];
1215
1146
  function ThemeMenu() {
1216
- const { appearance, setAppearance } = useTheme();
1147
+ const { appearance, setAppearance, inherited } = useTheme();
1217
1148
  const [open, setOpen] = React5.useState(false);
1218
1149
  const rootRef = React5.useRef(null);
1219
1150
  const firstItemRef = React5.useRef(null);
@@ -1235,6 +1166,7 @@ function ThemeMenu() {
1235
1166
  document.removeEventListener("keydown", closeOnEscape);
1236
1167
  };
1237
1168
  }, [open]);
1169
+ if (inherited) return null;
1238
1170
  const label = appearance === "system" ? "System" : appearance === "light" ? "Light" : "Dark";
1239
1171
  return /* @__PURE__ */ jsxs9("div", { ref: rootRef, className: "relative", children: [
1240
1172
  /* @__PURE__ */ jsx15(
@@ -2082,6 +2014,7 @@ function ChatShell({
2082
2014
  runtime,
2083
2015
  sidebar
2084
2016
  }) {
2017
+ const Composer = components?.Composer || ChatComposer;
2085
2018
  const { controller } = runtime;
2086
2019
  const isUploading = controller.runState === "uploading";
2087
2020
  const isRunning = controller.runState === "streaming" || controller.runState === "reconnecting" || controller.runState === "paused" || controller.runState === "cancelling";
@@ -2239,7 +2172,7 @@ function ChatShell({
2239
2172
  }
2240
2173
  ),
2241
2174
  /* @__PURE__ */ jsx24(
2242
- ChatComposer,
2175
+ Composer,
2243
2176
  {
2244
2177
  restoredDraft,
2245
2178
  inputContract: runtime.inputContract || LOADING_INPUT_CONTRACT,
@@ -2316,6 +2249,7 @@ function Agents24ChatScaffold({ appearanceController, theme, ...props }) {
2316
2249
  return /* @__PURE__ */ jsx26(ThemeProvider, { controller: appearanceController, theme, children: /* @__PURE__ */ jsx26(TooltipProvider, { children: /* @__PURE__ */ jsx26(App, { ...props }) }) });
2317
2250
  }
2318
2251
  export {
2252
+ ChatComposer as Agents24ChatComposer,
2319
2253
  Agents24ChatScaffold,
2320
2254
  App as Agents24ChatScaffoldApp,
2321
2255
  ThemeProvider as Agents24ChatThemeProvider,