@agents24/chat-react 0.5.6 → 0.5.7

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/styles.css CHANGED
@@ -57,7 +57,7 @@
57
57
  }
58
58
  .a24-scroller__viewport { flex: 1; height: 100%; min-height: 0; min-width: 0; overscroll-behavior: contain; overflow-y: auto; scrollbar-width: none; width: 100%; }
59
59
  .a24-scroller__viewport::-webkit-scrollbar { display: none; }
60
- .a24-scroller__content { display: flex; flex-direction: column; gap: 2rem; min-height: 100%; }
60
+ .a24-scroller__content { display: flex; flex-direction: column; gap: .75rem; min-height: 100%; }
61
61
  .a24-scroller__item { animation: var(--a24-message-enter-animation); flex: 0 0 auto; min-width: 0; overflow: visible; }
62
62
  .a24-scroller__button {
63
63
  align-items: center;
@@ -155,7 +155,6 @@
155
155
  .a24-context-status__sr-only { border: 0; clip: rect(0, 0, 0, 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; white-space: nowrap; width: 1px; }
156
156
  .a24-tool-disclosure { min-width: 0; width: 100%; }
157
157
  .a24-tool-trigger-row { align-items: center; display: flex; gap: .35rem; min-width: 0; }
158
- .a24-tool-trigger-row--text { padding-inline: 6px; }
159
158
  .a24-tool-trigger { align-items: center; appearance: none; background: transparent; border: 0; color: var(--a24-muted); display: flex; font: inherit; font-size: .875rem; gap: .35rem; line-height: 1.35; min-width: 0; padding: .125rem 0; text-align: start; }
160
159
  button.a24-tool-trigger { border-radius: .5rem; cursor: pointer; transition: background-color 140ms ease, color 140ms ease; }
161
160
  button.a24-tool-trigger:hover { color: var(--a24-text); }
@@ -208,9 +207,17 @@ button.a24-tool-trigger:focus-visible { outline: 2px solid var(--a24-focus); out
208
207
  .a24-message-response { box-sizing: border-box; max-width: 100%; min-width: 0; padding-inline: 6px; width: 100%; }
209
208
  .a24-timeline-error { padding-inline: 6px; }
210
209
  .a24-ask-user--resolved { padding-inline: 6px; }
211
- .a24-chat-message { display: flex; flex-direction: column; gap: .5rem; min-width: 0; width: 100%; }
212
- .a24-chat-message--user { max-width: 100%; }
213
- .a24-chat-message--assistant { max-width: 100%; }
210
+ .a24-chat-message { display: flex; flex-direction: column; min-width: 0; width: 100%; }
211
+ .a24-chat-message--user { gap: 0; max-width: 100%; }
212
+ .a24-chat-message--assistant { gap: .5rem; max-width: 100%; }
213
+ .a24-chat-message-action-row--user { align-items: center; align-self: flex-start; display: flex; gap: .5rem; height: 2rem; margin-left: auto; margin-top: .25rem; padding-inline: .25rem; transition: opacity 150ms ease; width: auto; }
214
+ .a24-user-message-actions { color: var(--a24-muted); font-family: inherit; font-size: .75rem; font-weight: 400; gap: .5rem; }
215
+ .a24-user-message-actions time { white-space: nowrap; }
216
+ .a24-user-message-copy { color: var(--a24-muted); height: 1.75rem; padding: 0; position: relative; width: 1.75rem; }
217
+ .a24-user-message-copy:hover { color: var(--a24-text); }
218
+ .a24-user-message-copy__check, .a24-user-message-copy__icon { height: .875rem; inset: 0; margin: auto; position: absolute; width: .875rem; }
219
+ .a24-user-message-copy__icon { opacity: 0; transform: scale(.75); transition: opacity 150ms ease, transform 150ms ease; }
220
+ .a24-chat-message-action-row--user.is-visible .a24-user-message-copy__icon { opacity: 1; transform: scale(1); }
214
221
  .a24-chat-attachment-rows { align-self: flex-end; display: flex; flex-direction: column; gap: .375rem; max-width: 100%; min-width: 0; width: 100%; }
215
222
  .a24-chat-attachments { align-self: stretch; justify-content: safe flex-end; max-width: 100%; scrollbar-width: none; width: 100%; }
216
223
  .a24-chat-attachments[dir="rtl"] { justify-content: safe flex-start; }
@@ -41,4 +41,8 @@ export type AgentChatMessageProps = {
41
41
  };
42
42
  export declare function shouldCollapseUserMessage(content: string, threshold?: number | null | undefined): boolean;
43
43
  export declare function AgentChatUserMessageContent({ collapsedLines, collapseThreshold, content, showLessLabel, showMoreLabel, }: AgentChatUserMessageContentProps): import("react/jsx-runtime").JSX.Element;
44
+ export declare function AgentChatUserMessageActionRow({ children, className, }: {
45
+ children: React.ReactNode;
46
+ className?: string;
47
+ }): import("react/jsx-runtime").JSX.Element;
44
48
  export declare function AgentChatMessage({ actionHandlers, additionalActions, actions, actionsClassName, attachmentContentResolver, className, contentClassName, dir, getHitlActionState, message, nodeActivityMode, onHitlAction, Timeline, renderAssistantContent, renderUserContent, showAssistantAttachments, streamingMessageId, userMessageCollapsedLines, userMessageContent, userMessageCollapseThreshold, userMessageShowLessLabel, userMessageShowMoreLabel, }: AgentChatMessageProps): import("react/jsx-runtime").JSX.Element;
package/dist/ui/index.cjs CHANGED
@@ -44,6 +44,7 @@ __export(ui_exports, {
44
44
  AgentChatToolPart: () => AgentChatToolPart,
45
45
  AgentChatToolRow: () => AgentChatToolRow,
46
46
  AgentChatToolSession: () => AgentChatToolSession,
47
+ AgentChatUserMessageActionRow: () => AgentChatUserMessageActionRow,
47
48
  AgentChatUserMessageContent: () => AgentChatUserMessageContent,
48
49
  AgentResponseTimeline: () => AgentResponseTimeline,
49
50
  AskUserInteraction: () => AskUserInteraction,
@@ -398,8 +399,8 @@ function ChatMessage(input) {
398
399
  const messageProps = Object.assign({}, props);
399
400
  messageProps.align = resolvedAlign;
400
401
  messageProps.className = cn(
401
- "a24-chat-message group flex w-full flex-col gap-2",
402
- isUser ? "a24-chat-message--user is-user max-w-full justify-end" : "a24-chat-message--assistant is-assistant max-w-full",
402
+ "a24-chat-message group flex w-full flex-col",
403
+ isUser ? "a24-chat-message--user is-user max-w-full justify-end gap-0" : "a24-chat-message--assistant is-assistant max-w-full gap-2",
403
404
  className
404
405
  );
405
406
  return React.createElement(
@@ -1089,6 +1090,15 @@ var FEEDBACK_REASONS = [
1089
1090
  ["citation_issue", "Citation issue"],
1090
1091
  ["other", "Other"]
1091
1092
  ];
1093
+ function formatSentAt(value) {
1094
+ if (!value) return null;
1095
+ const date = value instanceof Date ? value : new Date(value);
1096
+ if (Number.isNaN(date.getTime())) return null;
1097
+ return {
1098
+ dateTime: date.toISOString(),
1099
+ label: new Intl.DateTimeFormat(void 0, { timeStyle: "short" }).format(date)
1100
+ };
1101
+ }
1092
1102
  function AgentChatDefaultActions({
1093
1103
  additionalActions,
1094
1104
  dir,
@@ -1099,11 +1109,43 @@ function AgentChatDefaultActions({
1099
1109
  const [feedbackTrigger, setFeedbackTrigger] = React3.useState(null);
1100
1110
  const [reason, setReason] = React3.useState(handlers?.feedback?.reason ?? null);
1101
1111
  const [comment, setComment] = React3.useState(handlers?.feedback?.comment ?? "");
1112
+ const [optimisticCopied, setOptimisticCopied] = React3.useState(false);
1113
+ const copyResetTimer = React3.useRef(null);
1102
1114
  React3.useEffect(() => {
1103
1115
  setReason(handlers?.feedback?.reason ?? null);
1104
1116
  setComment(handlers?.feedback?.comment ?? "");
1105
1117
  }, [handlers?.feedback?.comment, handlers?.feedback?.reason]);
1106
- if (!handlers || message.role !== "assistant" || message.isFinal === false) return null;
1118
+ React3.useEffect(() => () => {
1119
+ if (copyResetTimer.current !== null) window.clearTimeout(copyResetTimer.current);
1120
+ }, []);
1121
+ const copied = handlers?.copied ?? optimisticCopied;
1122
+ const copyMessage = () => {
1123
+ handlers?.onCopy?.(message.content || "", message.id);
1124
+ if (handlers?.copied !== void 0) return;
1125
+ setOptimisticCopied(true);
1126
+ if (copyResetTimer.current !== null) window.clearTimeout(copyResetTimer.current);
1127
+ copyResetTimer.current = window.setTimeout(() => {
1128
+ copyResetTimer.current = null;
1129
+ setOptimisticCopied(false);
1130
+ }, 1200);
1131
+ };
1132
+ if (!handlers || message.isFinal === false) return null;
1133
+ if (message.role === "user") {
1134
+ const sentAt = formatSentAt(message.createdAt);
1135
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(ChatMessageActions, { className: "a24-user-message-actions", children: [
1136
+ sentAt ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("time", { dateTime: sentAt.dateTime, children: sentAt.label }) : null,
1137
+ handlers.onCopy ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1138
+ ChatMessageAction,
1139
+ {
1140
+ label: "Copy",
1141
+ onClick: copyMessage,
1142
+ tooltip: copied ? "Copied!" : "Copy to clipboard",
1143
+ className: "a24-user-message-copy",
1144
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Check, { className: "a24-user-message-copy__check text-green-600" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Copy, { className: "a24-user-message-copy__icon" })
1145
+ }
1146
+ ) : null
1147
+ ] });
1148
+ }
1107
1149
  const canWriteFeedback = Boolean(message.runId && handlers.onSetFeedback);
1108
1150
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(ChatMessageActions, { children: [
1109
1151
  handlers.onRetry ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -1214,9 +1256,9 @@ function AgentChatDefaultActions({
1214
1256
  ChatMessageAction,
1215
1257
  {
1216
1258
  label: "Copy",
1217
- onClick: () => handlers.onCopy?.(message.content || "", message.id),
1218
- tooltip: handlers.copied ? "Copied!" : "Copy to clipboard",
1219
- children: handlers.copied ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Check, { className: "size-4 text-green-600" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Copy, { className: "size-4" })
1259
+ onClick: copyMessage,
1260
+ tooltip: copied ? "Copied!" : "Copy to clipboard",
1261
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Check, { className: "size-4 text-green-600" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Copy, { className: "size-4" })
1220
1262
  }
1221
1263
  ) : null
1222
1264
  ] });
@@ -2149,10 +2191,9 @@ function ToolDisclosure({
2149
2191
  const [open, setOpen] = React6.useState(false);
2150
2192
  const contentId = React6.useId();
2151
2193
  const expandable = React6.Children.toArray(children).length > 0;
2152
- const textOnlyRoot = Boolean(root && !grouped && !showOrb);
2153
2194
  if (!expandable) {
2154
2195
  const triggerClassName = cn("a24-tool-trigger", root && "a24-tool-trigger--root", root && !showOrb && "a24-tool-trigger--without-orb", error && "a24-tool-trigger--error", loading && "a24-tool-trigger--active");
2155
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: cn("a24-tool-trigger-row", textOnlyRoot && "a24-tool-trigger-row--text"), children: [
2196
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-tool-trigger-row", children: [
2156
2197
  onSelect && part ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
2157
2198
  showOrb ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityOrb, { label: statusLabel }) : null,
2158
2199
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label })
@@ -2164,7 +2205,7 @@ function ToolDisclosure({
2164
2205
  ] });
2165
2206
  }
2166
2207
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-tool-disclosure", children: [
2167
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: cn("a24-tool-trigger-row", textOnlyRoot && "a24-tool-trigger-row--text"), children: [
2208
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-tool-trigger-row", children: [
2168
2209
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2169
2210
  "button",
2170
2211
  {
@@ -2670,6 +2711,41 @@ function AgentChatUserMessageContent({
2670
2711
  ) : null
2671
2712
  ] });
2672
2713
  }
2714
+ function AgentChatUserMessageActionRow({
2715
+ children,
2716
+ className
2717
+ }) {
2718
+ const rowRef = React8.useRef(null);
2719
+ const [visible, setVisible] = React8.useState(false);
2720
+ React8.useEffect(() => {
2721
+ const message = rowRef.current?.closest('[data-slot="message"]');
2722
+ if (!message) return;
2723
+ const show = () => setVisible(true);
2724
+ const hide = () => setVisible(false);
2725
+ const hideWhenFocusLeaves = (event) => {
2726
+ if (!message.contains(event.relatedTarget)) hide();
2727
+ };
2728
+ message.addEventListener("mouseenter", show);
2729
+ message.addEventListener("mouseleave", hide);
2730
+ message.addEventListener("focusin", show);
2731
+ message.addEventListener("focusout", hideWhenFocusLeaves);
2732
+ return () => {
2733
+ message.removeEventListener("mouseenter", show);
2734
+ message.removeEventListener("mouseleave", hide);
2735
+ message.removeEventListener("focusin", show);
2736
+ message.removeEventListener("focusout", hideWhenFocusLeaves);
2737
+ };
2738
+ }, []);
2739
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2740
+ "div",
2741
+ {
2742
+ ref: rowRef,
2743
+ className: cn("a24-chat-message-action-row--user", visible && "is-visible", className),
2744
+ style: { opacity: visible ? 1 : 0, pointerEvents: visible ? "auto" : "none" },
2745
+ children
2746
+ }
2747
+ );
2748
+ }
2673
2749
  function AgentChatMessage({
2674
2750
  actionHandlers,
2675
2751
  additionalActions,
@@ -2708,49 +2784,58 @@ function AgentChatMessage({
2708
2784
  message
2709
2785
  }
2710
2786
  ) : null);
2711
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ChatMessage, { className, dir, from: message.role, children: [
2712
- showAttachments ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2713
- ChatAttachmentRows,
2714
- {
2715
- attachments: message.attachments || [],
2716
- className: "mb-2",
2717
- dir,
2718
- resolveContent: attachmentContentResolver
2719
- }
2720
- ) : null,
2721
- shouldRenderContent ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2722
- ChatMessageContent,
2723
- {
2724
- className: cn(isAssistant ? "bg-transparent p-0" : void 0, contentClassName),
2725
- dir,
2726
- from: message.role,
2727
- children: [
2728
- isAssistant ? renderAssistantContent ? renderAssistantContent({ isStreaming, message }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2729
- Timeline,
2730
- {
2731
- getHitlActionState,
2732
- isLoading: isStreaming,
2733
- message,
2734
- nodeActivityMode,
2735
- onHitlAction,
2736
- parts: message.parts
2737
- }
2738
- ) : null,
2739
- !isAssistant ? renderUserContent ? renderUserContent({ message }) : resolvedUserContent ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2740
- AgentChatUserMessageContent,
2741
- {
2742
- collapsedLines: userMessageCollapsedLines,
2743
- collapseThreshold: userMessageCollapseThreshold,
2744
- content: resolvedUserContent,
2745
- showLessLabel: userMessageShowLessLabel,
2746
- showMoreLabel: userMessageShowMoreLabel
2747
- }
2748
- ) : null : null
2749
- ]
2750
- }
2751
- ) : null,
2752
- renderedActions ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: cn(dir === "rtl" ? "mt-1 h-9 w-full" : "mt-1 h-9 w-full -translate-x-2", actionsClassName), children: renderedActions }) : null
2753
- ] });
2787
+ const defaultUserActions = !actions && Boolean(actionHandlers) && !isAssistant;
2788
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2789
+ ChatMessage,
2790
+ {
2791
+ className,
2792
+ dir,
2793
+ from: message.role,
2794
+ children: [
2795
+ showAttachments ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2796
+ ChatAttachmentRows,
2797
+ {
2798
+ attachments: message.attachments || [],
2799
+ className: "mb-2",
2800
+ dir,
2801
+ resolveContent: attachmentContentResolver
2802
+ }
2803
+ ) : null,
2804
+ shouldRenderContent ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2805
+ ChatMessageContent,
2806
+ {
2807
+ className: cn(isAssistant ? "bg-transparent p-0" : void 0, contentClassName),
2808
+ dir,
2809
+ from: message.role,
2810
+ children: [
2811
+ isAssistant ? renderAssistantContent ? renderAssistantContent({ isStreaming, message }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2812
+ Timeline,
2813
+ {
2814
+ getHitlActionState,
2815
+ isLoading: isStreaming,
2816
+ message,
2817
+ nodeActivityMode,
2818
+ onHitlAction,
2819
+ parts: message.parts
2820
+ }
2821
+ ) : null,
2822
+ !isAssistant ? renderUserContent ? renderUserContent({ message }) : resolvedUserContent ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2823
+ AgentChatUserMessageContent,
2824
+ {
2825
+ collapsedLines: userMessageCollapsedLines,
2826
+ collapseThreshold: userMessageCollapseThreshold,
2827
+ content: resolvedUserContent,
2828
+ showLessLabel: userMessageShowLessLabel,
2829
+ showMoreLabel: userMessageShowMoreLabel
2830
+ }
2831
+ ) : null : null
2832
+ ]
2833
+ }
2834
+ ) : null,
2835
+ renderedActions ? defaultUserActions ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AgentChatUserMessageActionRow, { className: actionsClassName, children: renderedActions }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: cn(dir === "rtl" ? "mt-1 h-9 w-full" : "mt-1 h-9 w-full -translate-x-2", actionsClassName), children: renderedActions }) : null
2836
+ ]
2837
+ }
2838
+ );
2754
2839
  }
2755
2840
 
2756
2841
  // src/ui/agent-chat-context-status.tsx
@@ -3156,6 +3241,7 @@ function useAudioRecorder(onRecorded, options = {}) {
3156
3241
  AgentChatToolPart,
3157
3242
  AgentChatToolRow,
3158
3243
  AgentChatToolSession,
3244
+ AgentChatUserMessageActionRow,
3159
3245
  AgentChatUserMessageContent,
3160
3246
  AgentResponseTimeline,
3161
3247
  AskUserInteraction,