@cntyclub/agent-react 0.9.0 → 0.9.2

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.
@@ -4,7 +4,13 @@ export interface AgentComposerProps {
4
4
  onValueChange: (value: string) => void;
5
5
  /** Submit the trimmed text plus any staged files. */
6
6
  onSubmit: (text: string) => void;
7
+ /** Hard-disable the input entirely (e.g. a pending approval must be resolved). */
7
8
  disabled?: boolean;
9
+ /**
10
+ * A reply is in progress. The textarea stays enabled and focused so the user
11
+ * can start writing their next message, but submitting is blocked until done.
12
+ */
13
+ busy?: boolean;
8
14
  placeholder?: string;
9
15
  className?: string;
10
16
  /** File upload wiring. Omit `onPickFiles` to hide the attach button. */
@@ -19,5 +25,5 @@ export interface AgentComposerProps {
19
25
  * Shift+Enter inserts a newline. Submitting is allowed when there is text OR at
20
26
  * least one staged file.
21
27
  */
22
- export declare function AgentComposer({ acceptAttribute, className, disabled, files, onPickFiles, onRemoveFile, onSubmit, onValueChange, placeholder, value, }: AgentComposerProps): React.JSX.Element;
28
+ export declare function AgentComposer({ acceptAttribute, busy, className, disabled, files, onPickFiles, onRemoveFile, onSubmit, onValueChange, placeholder, value, }: AgentComposerProps): React.JSX.Element;
23
29
  //# sourceMappingURL=agent-composer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-composer.d.ts","sourceRoot":"","sources":["../../src/components/agent-composer.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,qDAAqD;IACrD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACtC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,eAAe,EACf,SAAS,EACT,QAAQ,EACR,KAAK,EACL,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,WAA6B,EAC7B,KAAK,GACN,EAAE,kBAAkB,qBAqHpB"}
1
+ {"version":3,"file":"agent-composer.d.ts","sourceRoot":"","sources":["../../src/components/agent-composer.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,qDAAqD;IACrD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IACtC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,eAAe,EACf,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,KAAK,EACL,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,WAA6B,EAC7B,KAAK,GACN,EAAE,kBAAkB,qBA6HpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"agent-panel.d.ts","sourceRoot":"","sources":["../../src/components/agent-panel.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAO5C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,cAAc,GAAG,gBAAgB,CAAC;IACxC,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,iEAAiE;IACjE,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,kBAAkB,EAClB,IAAI,EACJ,SAAS,EACT,MAAM,EACN,eAAe,EACf,OAAO,EACP,iBAAiB,GAClB,EAAE,eAAe,qBAiTjB"}
1
+ {"version":3,"file":"agent-panel.d.ts","sourceRoot":"","sources":["../../src/components/agent-panel.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAQ5C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,cAAc,GAAG,gBAAgB,CAAC;IACxC,yCAAyC;IACzC,SAAS,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,iEAAiE;IACjE,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,kBAAkB,EAClB,IAAI,EACJ,SAAS,EACT,MAAM,EACN,eAAe,EACf,OAAO,EACP,iBAAiB,GAClB,EAAE,eAAe,qBAkTjB"}
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ export type ChatScrollerProps = React.ComponentProps<"div">;
3
+ /**
4
+ * Scrollable message viewport that stays pinned to the bottom while content
5
+ * grows — including token-by-token streaming, where text is appended to the
6
+ * SAME message rather than adding new ones.
7
+ *
8
+ * Why not the UI kit's ChatMessages autoScroll: that container uses CSS
9
+ * `scroll-smooth`, so a programmatic jump to the bottom emits intermediate
10
+ * `scroll` events mid-animation whose distance-from-bottom is large, which
11
+ * flips its "pinned" flag to false — after the first delta it stops following.
12
+ * Here we scroll INSTANTLY (no smooth), so the only resulting scroll event is
13
+ * at the true bottom and the pin holds for the whole stream. The user can still
14
+ * scroll up to read history (that unpins), and scrolling back down re-pins.
15
+ */
16
+ export declare function ChatScroller({ children, className, ...props }: ChatScrollerProps): React.JSX.Element;
17
+ //# sourceMappingURL=chat-scroller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-scroller.d.ts","sourceRoot":"","sources":["../../src/components/chat-scroller.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,MAAM,iBAAiB,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;AAE5D;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,iBAAiB,qBA8BhF"}
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ export interface CopyButtonProps {
3
+ /** The text copied to the clipboard (the assistant reply's raw markdown). */
4
+ text: string;
5
+ className?: string;
6
+ }
7
+ /** Small ghost icon button that copies `text` and briefly flips to a check. */
8
+ export declare function CopyButton({ className, text }: CopyButtonProps): React.JSX.Element;
9
+ //# sourceMappingURL=copy-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-button.d.ts","sourceRoot":"","sources":["../../src/components/copy-button.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,eAAe,qBAwC9D"}
@@ -1 +1 @@
1
- {"version":3,"file":"message-item.d.ts","sourceRoot":"","sources":["../../src/components/message-item.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,4BAmCjE"}
1
+ {"version":3,"file":"message-item.d.ts","sourceRoot":"","sources":["../../src/components/message-item.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,4BA8CjE"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export { AgentApiClient, AgentApiError } from "./api-client";
2
2
  export { AgentComposer, type AgentComposerProps } from "./components/agent-composer";
3
3
  export { AgentPanel, type AgentPanelProps } from "./components/agent-panel";
4
4
  export { AgentWidget, type AgentWidgetProps } from "./components/agent-widget";
5
+ export { ChatScroller, type ChatScrollerProps } from "./components/chat-scroller";
6
+ export { CopyButton, type CopyButtonProps } from "./components/copy-button";
5
7
  export { DocumentCard } from "./components/document-card";
6
8
  export { MessageItem } from "./components/message-item";
7
9
  export { TaskChecklist } from "./components/task-checklist";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACrF,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACjG,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAClG,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAC/F,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,uBAAuB,EACvB,KAAK,wBAAwB,GAC9B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,eAAe,EACf,UAAU,EACV,QAAQ,EACR,UAAU,EACV,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,UAAU,IAAI,mBAAmB,EACjC,UAAU,IAAI,mBAAmB,EACjC,KAAK,IAAI,cAAc,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACrF,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AACjG,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAClG,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAC/F,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,uBAAuB,EACvB,KAAK,wBAAwB,GAC9B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACjF,YAAY,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,eAAe,EACf,UAAU,EACV,QAAQ,EACR,UAAU,EACV,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,YAAY,EACZ,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,UAAU,IAAI,mBAAmB,EACjC,UAAU,IAAI,mBAAmB,EACjC,KAAK,IAAI,cAAc,GACxB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { Button, cn, ChatMessage, ChatMessageBubble, Markdown, Spinner, Checkbox, Collapsible, CollapsibleTrigger, ChatToolChip, CollapsiblePanel, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Group, Menu, MenuTrigger, MenuPopup, MenuItem, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions, Tooltip, TooltipTrigger, TooltipContent, Chat, ChatMessages, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, useMediaQuery, TooltipProvider, Fab, DataTablePaged } from '@cntyclub/ui-react';
2
- import { FileTextIcon, XIcon, PaperclipIcon, ArrowUpIcon, Maximize2Icon, ListChecksIcon, ChevronDownIcon, CheckIcon, ShieldAlertIcon, CheckCheckIcon, UploadCloudIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, Trash2Icon, SparklesIcon, DownloadIcon, CodeIcon } from 'lucide-react';
3
- import * as React6 from 'react';
1
+ import { Button, cn, Tooltip, TooltipTrigger, TooltipContent, ChatMessage, ChatMessageBubble, Markdown, Spinner, Checkbox, Collapsible, CollapsibleTrigger, ChatToolChip, CollapsiblePanel, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Group, Menu, MenuTrigger, MenuPopup, MenuItem, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions, Chat, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, useMediaQuery, TooltipProvider, Fab, DataTablePaged } from '@cntyclub/ui-react';
2
+ import { FileTextIcon, XIcon, PaperclipIcon, ArrowUpIcon, CheckIcon, CopyIcon, Maximize2Icon, ListChecksIcon, ChevronDownIcon, ShieldAlertIcon, CheckCheckIcon, UploadCloudIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, Trash2Icon, SparklesIcon, DownloadIcon, CodeIcon } from 'lucide-react';
3
+ import * as React8 from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
 
6
6
  // src/api-client.ts
@@ -219,6 +219,7 @@ function formatBytes(bytes) {
219
219
  }
220
220
  function AgentComposer({
221
221
  acceptAttribute: acceptAttribute2,
222
+ busy,
222
223
  className,
223
224
  disabled,
224
225
  files,
@@ -229,14 +230,18 @@ function AgentComposer({
229
230
  placeholder = "Ask anything\u2026",
230
231
  value
231
232
  }) {
232
- const textareaRef = React6.useRef(null);
233
- const inputRef = React6.useRef(null);
234
- const canSubmit = !disabled && (value.trim().length > 0 || files.length > 0);
235
- const submit = React6.useCallback(() => {
233
+ const textareaRef = React8.useRef(null);
234
+ const inputRef = React8.useRef(null);
235
+ const canSubmit = !disabled && !busy && (value.trim().length > 0 || files.length > 0);
236
+ const submit = React8.useCallback(() => {
236
237
  if (!canSubmit) return;
237
238
  onSubmit(value.trim());
239
+ textareaRef.current?.focus();
238
240
  }, [canSubmit, onSubmit, value]);
239
- React6.useEffect(() => {
241
+ React8.useEffect(() => {
242
+ if (!disabled) textareaRef.current?.focus();
243
+ }, [disabled]);
244
+ React8.useEffect(() => {
240
245
  const textarea = textareaRef.current;
241
246
  if (!textarea) return;
242
247
  textarea.style.height = "auto";
@@ -417,6 +422,71 @@ function groupMessages(messages) {
417
422
  flush();
418
423
  return items;
419
424
  }
425
+ function ChatScroller({ children, className, ...props }) {
426
+ const viewportRef = React8.useRef(null);
427
+ const pinnedRef = React8.useRef(true);
428
+ const handleScroll = React8.useCallback(() => {
429
+ const viewport = viewportRef.current;
430
+ if (!viewport) return;
431
+ const distanceFromBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
432
+ pinnedRef.current = distanceFromBottom < 64;
433
+ }, []);
434
+ React8.useEffect(() => {
435
+ const viewport = viewportRef.current;
436
+ if (!viewport || !pinnedRef.current) return;
437
+ viewport.scrollTop = viewport.scrollHeight;
438
+ });
439
+ return /* @__PURE__ */ jsx(
440
+ "div",
441
+ {
442
+ className: cn("flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto p-4", className),
443
+ "data-slot": "chat-scroller",
444
+ onScroll: handleScroll,
445
+ ref: viewportRef,
446
+ ...props,
447
+ children
448
+ }
449
+ );
450
+ }
451
+ function CopyButton({ className, text }) {
452
+ const [copied, setCopied] = React8.useState(false);
453
+ const timerRef = React8.useRef(null);
454
+ React8.useEffect(() => {
455
+ return () => {
456
+ if (timerRef.current) clearTimeout(timerRef.current);
457
+ };
458
+ }, []);
459
+ const copy = React8.useCallback(async () => {
460
+ try {
461
+ await navigator.clipboard.writeText(text);
462
+ } catch {
463
+ return;
464
+ }
465
+ setCopied(true);
466
+ if (timerRef.current) clearTimeout(timerRef.current);
467
+ timerRef.current = setTimeout(() => setCopied(false), 1500);
468
+ }, [text]);
469
+ return /* @__PURE__ */ jsxs(Tooltip, { children: [
470
+ /* @__PURE__ */ jsx(
471
+ TooltipTrigger,
472
+ {
473
+ render: /* @__PURE__ */ jsx(
474
+ Button,
475
+ {
476
+ "aria-label": copied ? "Copied" : "Copy message",
477
+ className: cn("text-muted-foreground", className),
478
+ onClick: copy,
479
+ size: "icon-xs",
480
+ type: "button",
481
+ variant: "ghost",
482
+ children: copied ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-success" }) : /* @__PURE__ */ jsx(CopyIcon, {})
483
+ }
484
+ )
485
+ }
486
+ ),
487
+ /* @__PURE__ */ jsx(TooltipContent, { children: copied ? "Copied!" : "Copy" })
488
+ ] });
489
+ }
420
490
  function MessageItem({ message }) {
421
491
  if (message.role === "user") {
422
492
  const attachments = message.attachments ?? [];
@@ -437,7 +507,19 @@ function MessageItem({ message }) {
437
507
  }
438
508
  if (message.role === "assistant") {
439
509
  if (!message.content || !message.content.trim()) return null;
440
- return /* @__PURE__ */ jsx(ChatMessage, { from: "assistant", children: /* @__PURE__ */ jsx(ChatMessageBubble, { from: "assistant", children: /* @__PURE__ */ jsx(Markdown, { children: message.content }) }) });
510
+ return /* @__PURE__ */ jsxs(ChatMessage, { className: "group", from: "assistant", children: [
511
+ /* @__PURE__ */ jsx(ChatMessageBubble, { from: "assistant", children: /* @__PURE__ */ jsx(Markdown, { children: message.content }) }),
512
+ /* @__PURE__ */ jsx(
513
+ "div",
514
+ {
515
+ className: cn(
516
+ "mt-0.5 flex opacity-0 transition-opacity",
517
+ "group-hover:opacity-100 focus-within:opacity-100"
518
+ ),
519
+ children: /* @__PURE__ */ jsx(CopyButton, { text: message.content })
520
+ }
521
+ )
522
+ ] });
441
523
  }
442
524
  return null;
443
525
  }
@@ -451,8 +533,8 @@ var DEFAULT_PHRASES = [
451
533
  ];
452
534
  function ThinkingIndicator({ className, intervalMs = 1800, phrases }) {
453
535
  const words = phrases && phrases.length ? phrases : DEFAULT_PHRASES;
454
- const [index, setIndex] = React6.useState(0);
455
- React6.useEffect(() => {
536
+ const [index, setIndex] = React8.useState(0);
537
+ React8.useEffect(() => {
456
538
  setIndex(0);
457
539
  const timer = setInterval(() => {
458
540
  setIndex((current) => (current + 1) % words.length);
@@ -481,7 +563,7 @@ function ThinkingIndicator({ className, intervalMs = 1800, phrases }) {
481
563
  className: "fade-in-0 animate-in text-muted-foreground text-xs duration-300",
482
564
  children: [
483
565
  words[index],
484
- "\u2026"
566
+ " \u2026"
485
567
  ]
486
568
  },
487
569
  index
@@ -714,7 +796,7 @@ function DownloadButton({ block, size = "sm" }) {
714
796
  );
715
797
  }
716
798
  function DocumentViewer({ block, onClose }) {
717
- React6.useEffect(() => {
799
+ React8.useEffect(() => {
718
800
  const onKey = (event) => event.key === "Escape" && onClose();
719
801
  window.addEventListener("keydown", onKey);
720
802
  return () => window.removeEventListener("keydown", onKey);
@@ -739,7 +821,7 @@ function DocumentViewer({ block, onClose }) {
739
821
  ] });
740
822
  }
741
823
  function DocumentCard({ block }) {
742
- const [open, setOpen] = React6.useState(false);
824
+ const [open, setOpen] = React8.useState(false);
743
825
  const previewLines = (block.preview || "").split("\n").slice(0, 6).join("\n");
744
826
  return /* @__PURE__ */ jsxs("div", { className: "w-full max-w-full overflow-hidden rounded-xl border border-border bg-card shadow-xs", children: [
745
827
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-border/72 border-b px-3 py-2", children: [
@@ -861,7 +943,7 @@ function StepDetail({
861
943
  ] });
862
944
  }
863
945
  function ToolActivityGroup({ describeToolCall, steps }) {
864
- const [open, setOpen] = React6.useState(false);
946
+ const [open, setOpen] = React8.useState(false);
865
947
  if (steps.length === 0) return null;
866
948
  const last = steps[steps.length - 1];
867
949
  const anyRunning = steps.some((step) => step.status === "running");
@@ -964,19 +1046,19 @@ function AgentPanel({
964
1046
  onClose,
965
1047
  onToggleAgentMode
966
1048
  }) {
967
- const [draft, setDraft] = React6.useState("");
968
- const [view, setView] = React6.useState("chat");
969
- const [files, setFiles] = React6.useState([]);
970
- const [uploadError, setUploadError] = React6.useState(null);
971
- const [dragging, setDragging] = React6.useState(false);
972
- const dragDepth = React6.useRef(0);
1049
+ const [draft, setDraft] = React8.useState("");
1050
+ const [view, setView] = React8.useState("chat");
1051
+ const [files, setFiles] = React8.useState([]);
1052
+ const [uploadError, setUploadError] = React8.useState(null);
1053
+ const [dragging, setDragging] = React8.useState(false);
1054
+ const dragDepth = React8.useRef(0);
973
1055
  const agentName = config.agentName ?? chat.agentInfo?.name ?? "Assistant";
974
1056
  const uploadEnabled = fileUploadEnabled(config);
975
1057
  const suggestions = config.suggestions ?? [
976
1058
  "What can you help me with?",
977
1059
  "Give me a quick summary of my data."
978
1060
  ];
979
- const addFiles = React6.useCallback(
1061
+ const addFiles = React8.useCallback(
980
1062
  (incoming) => {
981
1063
  const accepted = [];
982
1064
  let rejection = null;
@@ -993,10 +1075,10 @@ function AgentPanel({
993
1075
  },
994
1076
  [config]
995
1077
  );
996
- const removeFile = React6.useCallback((index) => {
1078
+ const removeFile = React8.useCallback((index) => {
997
1079
  setFiles((current) => current.filter((_, i) => i !== index));
998
1080
  }, []);
999
- const handleSubmit = React6.useCallback(
1081
+ const handleSubmit = React8.useCallback(
1000
1082
  (text) => {
1001
1083
  const staged = files;
1002
1084
  setDraft("");
@@ -1006,7 +1088,7 @@ function AgentPanel({
1006
1088
  },
1007
1089
  [chat, files]
1008
1090
  );
1009
- const onDragEnter = React6.useCallback(
1091
+ const onDragEnter = React8.useCallback(
1010
1092
  (event) => {
1011
1093
  if (!uploadEnabled || !event.dataTransfer?.types?.includes("Files")) return;
1012
1094
  dragDepth.current += 1;
@@ -1014,12 +1096,12 @@ function AgentPanel({
1014
1096
  },
1015
1097
  [uploadEnabled]
1016
1098
  );
1017
- const onDragLeave = React6.useCallback((event) => {
1099
+ const onDragLeave = React8.useCallback((event) => {
1018
1100
  if (!event.dataTransfer?.types?.includes("Files")) return;
1019
1101
  dragDepth.current = Math.max(0, dragDepth.current - 1);
1020
1102
  if (dragDepth.current === 0) setDragging(false);
1021
1103
  }, []);
1022
- const onDrop = React6.useCallback(
1104
+ const onDrop = React8.useCallback(
1023
1105
  (event) => {
1024
1106
  if (!uploadEnabled) return;
1025
1107
  event.preventDefault();
@@ -1030,7 +1112,7 @@ function AgentPanel({
1030
1112
  },
1031
1113
  [addFiles, uploadEnabled]
1032
1114
  );
1033
- const openHistory = React6.useCallback(() => {
1115
+ const openHistory = React8.useCallback(() => {
1034
1116
  setView("history");
1035
1117
  void chat.refreshConversations();
1036
1118
  }, [chat]);
@@ -1160,7 +1242,7 @@ function AgentPanel({
1160
1242
  },
1161
1243
  conversation.id
1162
1244
  )) }) : /* @__PURE__ */ jsxs(Chat, { children: [
1163
- /* @__PURE__ */ jsx(ChatMessages, { className: cn(agentMode && "mx-auto w-full max-w-3xl"), children: chat.messages.length === 0 && !chat.sending ? /* @__PURE__ */ jsxs(ChatEmptyState, { children: [
1245
+ /* @__PURE__ */ jsx(ChatScroller, { className: cn(agentMode && "mx-auto w-full max-w-3xl"), children: chat.messages.length === 0 && !chat.sending ? /* @__PURE__ */ jsxs(ChatEmptyState, { children: [
1164
1246
  /* @__PURE__ */ jsx(ChatEmptyStateIcon, {}),
1165
1247
  /* @__PURE__ */ jsxs("div", { children: [
1166
1248
  /* @__PURE__ */ jsxs(ChatEmptyStateTitle, { children: [
@@ -1215,8 +1297,9 @@ function AgentPanel({
1215
1297
  AgentComposer,
1216
1298
  {
1217
1299
  acceptAttribute: acceptAttribute(config),
1300
+ busy,
1218
1301
  className: cn(agentMode && "mx-auto w-full max-w-3xl"),
1219
- disabled: busy || chat.pendingApprovals.length > 0,
1302
+ disabled: chat.pendingApprovals.length > 0,
1220
1303
  files,
1221
1304
  onPickFiles: uploadEnabled ? addFiles : void 0,
1222
1305
  onRemoveFile: removeFile,
@@ -1252,16 +1335,16 @@ function buildOptimisticUserMessage(text, files) {
1252
1335
  };
1253
1336
  }
1254
1337
  function useAgentChat(config) {
1255
- const client = React6.useMemo(() => new AgentApiClient(config), [config]);
1256
- const [agentInfo, setAgentInfo] = React6.useState(null);
1257
- const [conversationId, setConversationId] = React6.useState(null);
1258
- const [conversations, setConversations] = React6.useState([]);
1259
- const [messages, setMessages] = React6.useState([]);
1260
- const [pendingApprovals, setPendingApprovals] = React6.useState([]);
1261
- const [sending, setSending] = React6.useState(false);
1262
- const [resolvingApprovalId, setResolvingApprovalId] = React6.useState(null);
1263
- const [error, setError] = React6.useState(null);
1264
- React6.useEffect(() => {
1338
+ const client = React8.useMemo(() => new AgentApiClient(config), [config]);
1339
+ const [agentInfo, setAgentInfo] = React8.useState(null);
1340
+ const [conversationId, setConversationId] = React8.useState(null);
1341
+ const [conversations, setConversations] = React8.useState([]);
1342
+ const [messages, setMessages] = React8.useState([]);
1343
+ const [pendingApprovals, setPendingApprovals] = React8.useState([]);
1344
+ const [sending, setSending] = React8.useState(false);
1345
+ const [resolvingApprovalId, setResolvingApprovalId] = React8.useState(null);
1346
+ const [error, setError] = React8.useState(null);
1347
+ React8.useEffect(() => {
1265
1348
  let cancelled = false;
1266
1349
  client.getConfig().then((info) => {
1267
1350
  if (!cancelled) setAgentInfo(info);
@@ -1271,11 +1354,11 @@ function useAgentChat(config) {
1271
1354
  cancelled = true;
1272
1355
  };
1273
1356
  }, [client]);
1274
- const handleFailure = React6.useCallback((err) => {
1357
+ const handleFailure = React8.useCallback((err) => {
1275
1358
  const message = err instanceof AgentApiError ? err.message : "Something went wrong. Please try again.";
1276
1359
  setError(message);
1277
1360
  }, []);
1278
- const applyResponse = React6.useCallback(
1361
+ const applyResponse = React8.useCallback(
1279
1362
  (response) => {
1280
1363
  if (response.status === "error" && !response.messages?.length) {
1281
1364
  setError(response.message ?? "The assistant could not answer.");
@@ -1297,7 +1380,7 @@ function useAgentChat(config) {
1297
1380
  },
1298
1381
  []
1299
1382
  );
1300
- const handleStreamEvent = React6.useCallback(
1383
+ const handleStreamEvent = React8.useCallback(
1301
1384
  (event, ctx) => {
1302
1385
  switch (event.type) {
1303
1386
  case "conversation":
@@ -1362,7 +1445,7 @@ function useAgentChat(config) {
1362
1445
  []
1363
1446
  );
1364
1447
  const streamingEnabled = config.streaming !== false && typeof ReadableStream !== "undefined";
1365
- const send = React6.useCallback(
1448
+ const send = React8.useCallback(
1366
1449
  async (text, files) => {
1367
1450
  if (sending) return;
1368
1451
  if (!text.trim() && !(files && files.length)) return;
@@ -1393,7 +1476,7 @@ function useAgentChat(config) {
1393
1476
  },
1394
1477
  [applyResponse, client, conversationId, handleFailure, handleStreamEvent, sending, streamingEnabled]
1395
1478
  );
1396
- const resolveApproval = React6.useCallback(
1479
+ const resolveApproval = React8.useCallback(
1397
1480
  async (callId, approve) => {
1398
1481
  if (resolvingApprovalId) return;
1399
1482
  setResolvingApprovalId(callId);
@@ -1422,13 +1505,13 @@ function useAgentChat(config) {
1422
1505
  },
1423
1506
  [applyResponse, client, handleFailure, handleStreamEvent, resolvingApprovalId, streamingEnabled]
1424
1507
  );
1425
- const newChat = React6.useCallback(() => {
1508
+ const newChat = React8.useCallback(() => {
1426
1509
  setConversationId(null);
1427
1510
  setMessages([]);
1428
1511
  setPendingApprovals([]);
1429
1512
  setError(null);
1430
1513
  }, []);
1431
- const refreshConversations = React6.useCallback(async () => {
1514
+ const refreshConversations = React8.useCallback(async () => {
1432
1515
  try {
1433
1516
  const { conversations: list } = await client.listConversations();
1434
1517
  setConversations(list);
@@ -1436,7 +1519,7 @@ function useAgentChat(config) {
1436
1519
  handleFailure(err);
1437
1520
  }
1438
1521
  }, [client, handleFailure]);
1439
- const openConversation = React6.useCallback(
1522
+ const openConversation = React8.useCallback(
1440
1523
  async (id) => {
1441
1524
  setError(null);
1442
1525
  try {
@@ -1450,7 +1533,7 @@ function useAgentChat(config) {
1450
1533
  },
1451
1534
  [client, handleFailure]
1452
1535
  );
1453
- const deleteConversation = React6.useCallback(
1536
+ const deleteConversation = React8.useCallback(
1454
1537
  async (id) => {
1455
1538
  try {
1456
1539
  await client.deleteConversation(id);
@@ -1462,7 +1545,7 @@ function useAgentChat(config) {
1462
1545
  },
1463
1546
  [client, conversationId, handleFailure, newChat]
1464
1547
  );
1465
- const clearError = React6.useCallback(() => setError(null), []);
1548
+ const clearError = React8.useCallback(() => setError(null), []);
1466
1549
  return {
1467
1550
  agentInfo,
1468
1551
  clearError,
@@ -1497,11 +1580,11 @@ function readStored(clientId) {
1497
1580
  }
1498
1581
  }
1499
1582
  function useAlwaysApprove(clientId) {
1500
- const [approved, setApproved] = React6.useState(() => /* @__PURE__ */ new Set());
1501
- React6.useEffect(() => {
1583
+ const [approved, setApproved] = React8.useState(() => /* @__PURE__ */ new Set());
1584
+ React8.useEffect(() => {
1502
1585
  setApproved(readStored(clientId));
1503
1586
  }, [clientId]);
1504
- const persist = React6.useCallback(
1587
+ const persist = React8.useCallback(
1505
1588
  (next) => {
1506
1589
  setApproved(next);
1507
1590
  if (typeof window === "undefined") return;
@@ -1512,8 +1595,8 @@ function useAlwaysApprove(clientId) {
1512
1595
  },
1513
1596
  [clientId]
1514
1597
  );
1515
- const isAlwaysApproved = React6.useCallback((toolName) => approved.has(toolName), [approved]);
1516
- const setAlwaysApprove = React6.useCallback(
1598
+ const isAlwaysApproved = React8.useCallback((toolName) => approved.has(toolName), [approved]);
1599
+ const setAlwaysApprove = React8.useCallback(
1517
1600
  (toolName) => {
1518
1601
  if (approved.has(toolName)) return;
1519
1602
  const next = new Set(approved);
@@ -1522,7 +1605,7 @@ function useAlwaysApprove(clientId) {
1522
1605
  },
1523
1606
  [approved, persist]
1524
1607
  );
1525
- const clearAlwaysApprove = React6.useCallback(
1608
+ const clearAlwaysApprove = React8.useCallback(
1526
1609
  (toolName) => {
1527
1610
  if (!approved.has(toolName)) return;
1528
1611
  const next = new Set(approved);
@@ -1534,16 +1617,16 @@ function useAlwaysApprove(clientId) {
1534
1617
  return { clearAlwaysApprove, isAlwaysApproved, setAlwaysApprove };
1535
1618
  }
1536
1619
  function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
1537
- const [uncontrolledOpen, setUncontrolledOpen] = React6.useState(false);
1620
+ const [uncontrolledOpen, setUncontrolledOpen] = React8.useState(false);
1538
1621
  const open = controlledOpen ?? uncontrolledOpen;
1539
- const setOpen = React6.useCallback(
1622
+ const setOpen = React8.useCallback(
1540
1623
  (next) => {
1541
1624
  setUncontrolledOpen(next);
1542
1625
  onOpenChange?.(next);
1543
1626
  },
1544
1627
  [onOpenChange]
1545
1628
  );
1546
- const [agentMode, setAgentMode] = React6.useState(false);
1629
+ const [agentMode, setAgentMode] = React8.useState(false);
1547
1630
  const isDesktop = useMediaQuery("(min-width: 640px)", {
1548
1631
  defaultSSRValue: true,
1549
1632
  ssr: true
@@ -1553,14 +1636,14 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
1553
1636
  const alwaysApprove = useAlwaysApprove(config.clientId);
1554
1637
  const { pendingApprovals, resolveApproval, resolvingApprovalId } = chat;
1555
1638
  const { isAlwaysApproved } = alwaysApprove;
1556
- React6.useEffect(() => {
1639
+ React8.useEffect(() => {
1557
1640
  if (!open || resolvingApprovalId) return;
1558
1641
  const target = pendingApprovals.find((approval) => isAlwaysApproved(approval.tool_name));
1559
1642
  if (target) void resolveApproval(target.id, true);
1560
1643
  }, [open, pendingApprovals, resolvingApprovalId, isAlwaysApproved, resolveApproval]);
1561
- const firedWriteIds = React6.useRef(/* @__PURE__ */ new Set());
1644
+ const firedWriteIds = React8.useRef(/* @__PURE__ */ new Set());
1562
1645
  const { onWriteSuccess } = config;
1563
- React6.useEffect(() => {
1646
+ React8.useEffect(() => {
1564
1647
  if (!onWriteSuccess) return;
1565
1648
  for (const message of chat.messages) {
1566
1649
  if (message.role !== "tool" || !message.is_write || message.tool_status !== "success") continue;
@@ -1570,9 +1653,9 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
1570
1653
  onWriteSuccess({ arguments: args, toolName: message.tool_name ?? "" });
1571
1654
  }
1572
1655
  }, [chat.messages, onWriteSuccess]);
1573
- const lastNavigatedMessageId = React6.useRef(null);
1656
+ const lastNavigatedMessageId = React8.useRef(null);
1574
1657
  const { messages } = chat;
1575
- React6.useEffect(() => {
1658
+ React8.useEffect(() => {
1576
1659
  if (!open || fullscreen || !config.navigate || !config.pages?.length) return;
1577
1660
  const toolMessages = messages.filter((message) => message.role === "tool");
1578
1661
  const latest = toolMessages[toolMessages.length - 1];
@@ -1584,7 +1667,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
1584
1667
  const path = mapping.buildRoute ? mapping.buildRoute(args) : mapping.route;
1585
1668
  if (path) config.navigate(path);
1586
1669
  }, [config, fullscreen, messages, open]);
1587
- const closePanel = React6.useCallback(() => {
1670
+ const closePanel = React8.useCallback(() => {
1588
1671
  setOpen(false);
1589
1672
  setAgentMode(false);
1590
1673
  }, [setOpen]);
@@ -1663,6 +1746,6 @@ function defineAgentConfig(config) {
1663
1746
  return config;
1664
1747
  }
1665
1748
 
1666
- export { AgentApiClient, AgentApiError, AgentComposer, AgentPanel, AgentWidget, DEFAULT_ACCEPT, DocumentCard, MessageItem, TaskChecklist, ThinkingIndicator, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, resolveToolPresentation, useAgentChat, useAlwaysApprove, validateFile };
1749
+ export { AgentApiClient, AgentApiError, AgentComposer, AgentPanel, AgentWidget, ChatScroller, CopyButton, DEFAULT_ACCEPT, DocumentCard, MessageItem, TaskChecklist, ThinkingIndicator, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, resolveToolPresentation, useAgentChat, useAlwaysApprove, validateFile };
1667
1750
  //# sourceMappingURL=index.js.map
1668
1751
  //# sourceMappingURL=index.js.map