@candor-ui/react 1.3.0 → 1.7.0

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 (74) hide show
  1. package/dist/adapters/ag-ui.d.ts +81 -16
  2. package/dist/adapters/ag-ui.js +308 -1
  3. package/dist/adapters/ai-sdk.d.ts +11 -6
  4. package/dist/adapters/ai-sdk.js +18 -1
  5. package/dist/adapters/claude-agent-sdk.d.ts +123 -14
  6. package/dist/adapters/claude-agent-sdk.js +163 -5
  7. package/dist/adapters/claude-agent-server.d.ts +199 -14
  8. package/dist/adapters/claude-agent-server.js +448 -49
  9. package/dist/adapters/use-ag-ui.d.ts +12 -6
  10. package/dist/adapters/use-ag-ui.js +23 -5
  11. package/dist/adapters/use-candor-chat.d.ts +1 -1
  12. package/dist/adapters/use-candor-chat.js +3 -3
  13. package/dist/adapters/use-claude-agent.d.ts +38 -9
  14. package/dist/adapters/use-claude-agent.js +96 -20
  15. package/dist/blocks/copy-field.d.ts +1 -1
  16. package/dist/blocks/copy-field.js +1 -1
  17. package/dist/blocks/degraded-panel.d.ts +5 -5
  18. package/dist/blocks/locale.d.ts +6 -6
  19. package/dist/blocks/locked.d.ts +8 -8
  20. package/dist/blocks/relative-time.d.ts +6 -6
  21. package/dist/blocks/status-dot.d.ts +3 -3
  22. package/dist/blocks/step-list.d.ts +3 -3
  23. package/dist/blocks/streaming-text.d.ts +3 -3
  24. package/dist/chat/agent-avatar.d.ts +4 -4
  25. package/dist/chat/ai-disclosure.d.ts +37 -0
  26. package/dist/chat/ai-disclosure.js +48 -0
  27. package/dist/chat/attachment-preview.d.ts +6 -6
  28. package/dist/chat/attachment-preview.js +2 -2
  29. package/dist/chat/card-registry.js +1 -1
  30. package/dist/chat/citation.d.ts +3 -3
  31. package/dist/chat/citation.js +6 -4
  32. package/dist/chat/composer.d.ts +31 -20
  33. package/dist/chat/composer.js +10 -6
  34. package/dist/chat/context-meter.d.ts +1 -1
  35. package/dist/chat/context-meter.js +1 -1
  36. package/dist/chat/entity-chip.d.ts +3 -3
  37. package/dist/chat/entity-chip.js +1 -1
  38. package/dist/chat/folded-turns.d.ts +11 -6
  39. package/dist/chat/folded-turns.js +2 -2
  40. package/dist/chat/message-actions.d.ts +6 -6
  41. package/dist/chat/message-actions.js +1 -1
  42. package/dist/chat/message-content.d.ts +13 -7
  43. package/dist/chat/message-content.js +10 -5
  44. package/dist/chat/message.d.ts +64 -37
  45. package/dist/chat/message.js +17 -11
  46. package/dist/chat/payload.d.ts +4 -4
  47. package/dist/chat/payload.js +5 -2
  48. package/dist/chat/permission-prompt.d.ts +129 -0
  49. package/dist/chat/permission-prompt.js +144 -0
  50. package/dist/chat/prompt-suggestions.d.ts +4 -4
  51. package/dist/chat/prompt-suggestions.js +1 -1
  52. package/dist/chat/reasoning.d.ts +3 -3
  53. package/dist/chat/reasoning.js +1 -1
  54. package/dist/chat/thread-divider.d.ts +3 -3
  55. package/dist/chat/thread-event.d.ts +10 -5
  56. package/dist/chat/thread-event.js +3 -2
  57. package/dist/chat/thread.d.ts +23 -7
  58. package/dist/chat/thread.js +29 -6
  59. package/dist/chat/tool-call.d.ts +7 -2
  60. package/dist/chat/tool-call.js +8 -3
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.js +2 -0
  63. package/dist/lib/chat.d.ts +53 -38
  64. package/dist/lib/know.d.ts +14 -12
  65. package/dist/lib/render-prop.d.ts +4 -4
  66. package/dist/lib/safe-markdown.d.ts +1 -1
  67. package/dist/lib/status.d.ts +21 -0
  68. package/dist/lib/status.js +17 -0
  69. package/dist/lib/use-shortcut.d.ts +1 -1
  70. package/dist/lib/use-virtual-list.d.ts +6 -6
  71. package/dist/styles.css +4 -1
  72. package/dist/ui/button.d.ts +1 -1
  73. package/dist/ui/button.js +1 -1
  74. package/package.json +1 -1
@@ -31,14 +31,14 @@ declare function SourceDetail({ source, }: {
31
31
  */
32
32
  declare function Citation({ source, marker, }: {
33
33
  /** The source this marker points to; without it the marker says it's unknown. */
34
- source?: MessageSource;
34
+ source?: MessageSource | undefined;
35
35
  /** The number to show when there's no source. */
36
- marker?: number;
36
+ marker?: number | undefined;
37
37
  }): import("react").JSX.Element;
38
38
  /** The sources under a reply, as chips that open the same detail. */
39
39
  declare function SourceList({ sources, className, }: {
40
40
  /** Every source the reply used, in marker order. */
41
41
  sources: MessageSource[];
42
- className?: string;
42
+ className?: string | undefined;
43
43
  }): import("react").JSX.Element | null;
44
44
  export { Citation, SourceList, SourceDetail };
@@ -40,7 +40,9 @@ const kindOf = (kind, t) => {
40
40
  function SourceDetail({ source, }) {
41
41
  const t = useStrings("citation", citationStrings);
42
42
  const { Icon, noun } = kindOf(source.kind, t);
43
- return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-sm bg-muted text-muted-foreground", children: _jsx(Icon, { className: "size-3" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium leading-snug", children: source.label }), _jsx("p", { className: "text-xs text-muted-foreground", children: source.origin ?? noun })] })] }), source.snippet && (_jsx("blockquote", { className: "max-h-40 overflow-auto rounded-md bg-sunken px-2.5 py-2 text-xs leading-relaxed whitespace-pre-wrap text-muted-foreground", children: source.snippet })), source.provisional && (_jsx("p", { className: "text-xs text-provisional", children: t("notVerified") }))] }));
43
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-sm bg-muted text-muted-foreground", children: _jsx(Icon, { className: "size-3" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium leading-snug", children: source.label }), _jsx("p", { className: "text-xs text-muted-foreground", children: source.origin ?? noun })] })] }), source.snippet && (
44
+ // oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex
45
+ _jsx("blockquote", { tabIndex: 0, className: "max-h-40 overflow-auto rounded-md bg-sunken outline-none focus-visible:ring-[3px] focus-visible:ring-ring/80 focus-visible:ring-inset px-2.5 py-2 text-xs leading-relaxed whitespace-pre-wrap text-muted-foreground", children: source.snippet })), source.provisional && (_jsx("p", { className: "text-xs text-provisional", children: t("notVerified") }))] }));
44
46
  }
45
47
  /**
46
48
  * An inline [n]. Opens the source in a popover on click or Enter, so evidence is
@@ -52,9 +54,9 @@ function Citation({ source, marker, }) {
52
54
  if (!source) {
53
55
  return _jsxs("sup", { className: "text-faint-foreground", children: ["[", marker ?? "?", "]"] });
54
56
  }
55
- return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("sourceLabel", { marker: source.marker, label: source.label }), className: cn("mx-0.5 inline-flex h-4 min-w-4 -translate-y-px items-center justify-center rounded-sm px-1 align-middle text-2xs font-semibold tabular-nums transition-colors duration-150 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40", source.provisional
57
+ return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("sourceLabel", { marker: source.marker, label: source.label }), className: cn("mx-0.5 inline-flex h-4 min-w-4 -translate-y-px items-center justify-center rounded-sm px-1 align-middle text-2xs font-semibold tabular-nums transition-colors duration-150 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/80", source.provisional
56
58
  ? "border border-dashed border-provisional/60 text-provisional hover:bg-muted"
57
- : "bg-accent-weak text-accent-text hover:bg-primary hover:text-primary-foreground"), children: source.marker }) }), _jsx(PopoverContent, { align: "start", className: "w-80 p-3", children: _jsx(SourceDetail, { source: source }) })] }));
59
+ : "bg-accent-weak text-accent-text hover:bg-primary hover:text-primary-foreground"), children: source.marker }) }), _jsx(PopoverContent, { align: "start", className: "w-80 max-w-[calc(100vw-2rem)] p-3", children: _jsx(SourceDetail, { source: source }) })] }));
58
60
  }
59
61
  /** The sources under a reply, as chips that open the same detail. */
60
62
  function SourceList({ sources, className, }) {
@@ -65,7 +67,7 @@ function SourceList({ sources, className, }) {
65
67
  .sort((a, b) => a.marker - b.marker)
66
68
  .map((s) => {
67
69
  const { Icon } = kindOf(s.kind, t);
68
- return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: cn("inline-flex max-w-56 items-center gap-1.5 rounded-md border bg-card py-0.5 pe-2 ps-1 text-xs text-muted-foreground transition-colors duration-150 outline-none hover:border-border-strong hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40", s.provisional && "border-dashed"), children: [_jsx("span", { className: "flex h-4 min-w-4 items-center justify-center rounded-sm bg-muted px-1 text-2xs font-semibold tabular-nums", children: s.marker }), _jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0" }), _jsx("span", { className: "truncate", children: s.label })] }) }), _jsx(PopoverContent, { align: "start", className: "w-80 p-3", children: _jsx(SourceDetail, { source: s }) })] }, s.id));
70
+ return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: cn("relative after:absolute after:inset-x-0 after:-inset-y-0.5 inline-flex max-w-56 items-center gap-1.5 rounded-md border bg-card py-0.5 pe-2 ps-1 text-xs text-muted-foreground transition-colors duration-150 outline-none hover:border-border-strong hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80", s.provisional && "border-dashed"), children: [_jsx("span", { className: "flex h-4 min-w-4 items-center justify-center rounded-sm bg-muted px-1 text-2xs font-semibold tabular-nums", children: s.marker }), _jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0" }), _jsx("span", { className: "truncate", children: s.label })] }) }), _jsx(PopoverContent, { align: "start", className: "w-80 max-w-[calc(100vw-2rem)] p-3", children: _jsx(SourceDetail, { source: s }) })] }, s.id));
69
71
  })] }));
70
72
  }
71
73
  export { Citation, SourceList, SourceDetail };
@@ -22,7 +22,12 @@ export type ComposerState = "ready" | "sending" | "streaming" | {
22
22
  reason: string;
23
23
  } | {
24
24
  kind: "offline";
25
- reason?: string;
25
+ reason?: string | undefined;
26
+ }
27
+ /** Typing stays open, but sending waits, and the box says why: "Your partner is still thinking". */
28
+ | {
29
+ kind: "held";
30
+ reason: string;
26
31
  };
27
32
  /**
28
33
  * Where a person asks. Mentions (@) attach things in the user's system as chips the agent
@@ -33,9 +38,11 @@ export type ComposerState = "ready" | "sending" | "streaming" | {
33
38
  * Enter sends, Shift+Enter breaks a line. While an agent replies the send button
34
39
  * becomes Stop. A locked composer says why; an offline one says so. With
35
40
  * `onAttach`, files can also be pasted or dropped onto it. `tools` adds your
36
- * own controls to the toolbar, such as a dictation button.
41
+ * own controls to the toolbar, such as a dictation button. A held composer
42
+ * (`{ kind: "held", reason }`) keeps typing open and says why sending waits.
43
+ * `textareaRef` focuses the box from outside, e.g. after a new project starts.
37
44
  */
38
- declare function Composer({ value, onChange, onSubmit, onStop, state, scope, onScopeClick, mentions, commands, references, onReferencesChange, attachments, onAttach, accept, onRemoveAttachment, placeholder: placeholderProp, label: labelProp, tools, className, }: {
45
+ declare function Composer({ value, onChange, onSubmit, onStop, state, scope, onScopeClick, mentions, commands, references, onReferencesChange, attachments, onAttach, accept, onRemoveAttachment, placeholder: placeholderProp, label: labelProp, tools, textareaRef, sendShape, className, }: {
39
46
  /** The text in the box. */
40
47
  value: string;
41
48
  /** Called with the new text as the person types. */
@@ -43,38 +50,42 @@ declare function Composer({ value, onChange, onSubmit, onStop, state, scope, onS
43
50
  /** Called on send with the text and the entities mentioned. */
44
51
  onSubmit: (value: string, references: EntityRef[]) => void;
45
52
  /** Shown as Stop while a reply is streaming. */
46
- onStop?: () => void;
47
- /** ready, sending, streaming, locked (with a reason) or offline. */
48
- state?: ComposerState;
53
+ onStop?: (() => void) | undefined;
54
+ /** ready, sending, streaming, locked (with a reason), held (typing open, sending waits, with a reason) or offline. */
55
+ state?: ComposerState | undefined;
49
56
  /** Where the next message's work lands; production turns the pill to a warning. */
50
- scope?: ComposerScope;
57
+ scope?: ComposerScope | undefined;
51
58
  /** Makes the scope pill a button, e.g. to change environment. */
52
- onScopeClick?: () => void;
59
+ onScopeClick?: (() => void) | undefined;
53
60
  /** What @ can insert, grouped under their headings. */
54
- mentions?: MentionOption[];
61
+ mentions?: MentionOption[] | undefined;
55
62
  /** What / can run, only at the start of a message. */
56
- commands?: SlashCommand[];
63
+ commands?: SlashCommand[] | undefined;
57
64
  /** Entities already attached, shown as chips. */
58
- references?: EntityRef[];
65
+ references?: EntityRef[] | undefined;
59
66
  /** Called when a chip is added or removed. */
60
- onReferencesChange?: (references: EntityRef[]) => void;
67
+ onReferencesChange?: ((references: EntityRef[]) => void) | undefined;
61
68
  /** Files attached, with upload progress or an error. */
62
- attachments?: Attachment[];
69
+ attachments?: Attachment[] | undefined;
63
70
  /** Shows an attach button; called with the chosen files. */
64
- onAttach?: (files: File[]) => void;
71
+ onAttach?: ((files: File[]) => void) | undefined;
65
72
  /**
66
73
  * Which files the attach button's picker offers, as for `<input accept>` (e.g. "image/*,.pdf"). Files pasted or
67
74
  * dropped arrive as they are, so check types where you upload.
68
75
  */
69
- accept?: string;
76
+ accept?: string | undefined;
70
77
  /** Called when a file is removed. */
71
- onRemoveAttachment?: (id: string) => void;
78
+ onRemoveAttachment?: ((id: string) => void) | undefined;
72
79
  /** Shown in the empty box; "Ask a question, or describe a change" by default. */
73
- placeholder?: string;
80
+ placeholder?: string | undefined;
74
81
  /** The accessible name of the text box; "Message" by default. */
75
- label?: string;
82
+ label?: string | undefined;
76
83
  /** Your own controls in the toolbar, after the attach button: a dictation button, a model picker. */
77
- tools?: React.ReactNode;
78
- className?: string;
84
+ tools?: React.ReactNode | undefined;
85
+ /** The text box, to focus it or read its selection from outside. */
86
+ textareaRef?: React.Ref<HTMLTextAreaElement> | undefined;
87
+ /** The send button's shape: "square" (the default, rounded corners) or "round". */
88
+ sendShape?: "square" | "round" | undefined;
89
+ className?: string | undefined;
79
90
  }): React.JSX.Element;
80
91
  export { Composer };
@@ -48,24 +48,28 @@ const hasFiles = (e) => Array.from(e.dataTransfer?.types ?? []).includes("Files"
48
48
  * Enter sends, Shift+Enter breaks a line. While an agent replies the send button
49
49
  * becomes Stop. A locked composer says why; an offline one says so. With
50
50
  * `onAttach`, files can also be pasted or dropped onto it. `tools` adds your
51
- * own controls to the toolbar, such as a dictation button.
51
+ * own controls to the toolbar, such as a dictation button. A held composer
52
+ * (`{ kind: "held", reason }`) keeps typing open and says why sending waits.
53
+ * `textareaRef` focuses the box from outside, e.g. after a new project starts.
52
54
  */
53
- function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, onScopeClick, mentions = [], commands = [], references = [], onReferencesChange, attachments = [], onAttach, accept, onRemoveAttachment, placeholder: placeholderProp, label: labelProp, tools, className, }) {
55
+ function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, onScopeClick, mentions = [], commands = [], references = [], onReferencesChange, attachments = [], onAttach, accept, onRemoveAttachment, placeholder: placeholderProp, label: labelProp, tools, textareaRef, sendShape = "square", className, }) {
54
56
  const kindOf = useEntityKinds();
55
57
  const t = useStrings("composer", composerStrings);
56
58
  const placeholder = placeholderProp ?? t("placeholder");
57
59
  const label = labelProp ?? t("label");
58
60
  const id = React.useId();
59
61
  const textarea = React.useRef(null);
62
+ React.useImperativeHandle(textareaRef, () => textarea.current, []);
60
63
  // Files dragged over the composer: a count of enter/leave pairs, since child elements fire their own.
61
64
  const [dragDepth, setDragDepth] = React.useState(0);
62
65
  const fileInput = React.useRef(null);
63
66
  const [trigger, setTrigger] = React.useState(null);
64
67
  const [active, setActive] = React.useState(0);
65
- const locked = typeof state === "object" ? state : null;
68
+ const held = typeof state === "object" && state.kind === "held" ? state : null;
69
+ const locked = typeof state === "object" && state.kind !== "held" ? state : null;
66
70
  const canAttach = Boolean(onAttach) && !locked;
67
71
  const busy = state === "sending" || state === "streaming";
68
- const canSend = !locked && !busy && (value.trim().length > 0 || references.length > 0);
72
+ const canSend = !locked && !held && !busy && (value.trim().length > 0 || references.length > 0);
69
73
  const items = React.useMemo(() => {
70
74
  if (!trigger)
71
75
  return [];
@@ -207,11 +211,11 @@ function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, o
207
211
  onAttach(files);
208
212
  if (!e.clipboardData.getData("text/plain"))
209
213
  e.preventDefault();
210
- }, "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen ? optionId(active) : undefined, "aria-autocomplete": "list", "aria-describedby": locked ? `${id}-locked` : undefined, className: cn("block max-h-60 min-h-12 w-full resize-none bg-transparent px-4 pt-3 pb-1 text-prose leading-relaxed outline-none [field-sizing:content] placeholder:text-faint-foreground disabled:cursor-not-allowed", locked && !value && "min-h-0 h-3 pt-3 pb-0") }), locked && (_jsx("p", { id: `${id}-locked`, className: "flex items-center gap-1.5 px-4 pb-1 text-sm text-muted-foreground", children: locked.kind === "offline" ? locked.reason ?? t("offline") : locked.reason })), _jsxs("div", { className: "flex items-center gap-1.5 px-2 pt-1 pb-2", children: [onAttach && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInput, type: "file", multiple: true, accept: accept, tabIndex: -1, "aria-label": t("attachFiles"), className: "hidden", onChange: (e) => {
214
+ }, "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen ? optionId(active) : undefined, "aria-autocomplete": "list", "aria-describedby": locked ? `${id}-locked` : held ? `${id}-held` : undefined, className: cn("block max-h-60 min-h-12 w-full resize-none bg-transparent px-4 pt-3 pb-1 text-prose leading-relaxed outline-none [field-sizing:content] placeholder:text-faint-foreground disabled:cursor-not-allowed", locked && !value && "min-h-0 h-3 pt-3 pb-0") }), locked && (_jsx("p", { id: `${id}-locked`, className: "flex items-center gap-1.5 px-4 pb-1 text-sm text-muted-foreground", children: locked.kind === "offline" ? locked.reason ?? t("offline") : locked.reason })), held && (_jsx("p", { id: `${id}-held`, "data-slot": "composer-held", className: "px-4 pb-1 text-sm text-muted-foreground", children: held.reason })), _jsxs("div", { className: "flex items-center gap-1.5 px-2 pt-1 pb-2", children: [onAttach && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInput, type: "file", multiple: true, accept: accept, tabIndex: -1, "aria-label": t("attachFiles"), className: "hidden", onChange: (e) => {
211
215
  const files = Array.from(e.target.files ?? []);
212
216
  if (files.length)
213
217
  onAttach(files);
214
218
  e.target.value = "";
215
- } }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("attachFiles"), disabled: Boolean(locked), onClick: () => fileInput.current?.click(), className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors duration-150 hover:bg-accent hover:text-foreground disabled:opacity-40", children: _jsx(Paperclip, { className: "size-4" }) }) }), _jsx(TooltipContent, { children: t("attachFiles") })] })] })), tools, scope && (_jsxs("button", { type: "button", onClick: onScopeClick, disabled: !onScopeClick, className: cn("inline-flex min-w-0 items-center gap-1.5 rounded-lg px-2 py-1 text-xs transition-colors duration-150 enabled:hover:bg-accent", production ? "bg-warning-weak text-warning-text enabled:hover:bg-warning-weak" : "text-muted-foreground"), children: [production ? _jsx(TriangleAlert, { "aria-hidden": true, className: "size-3 shrink-0" }) : _jsx("span", { "aria-hidden": true, className: "size-1.5 shrink-0 rounded-full bg-success" }), _jsxs("span", { className: "truncate", children: [scope.environment, " \u00B7 ", production ? t("production") : (scope.detail ?? t("nonProduction"))] })] })), _jsxs("span", { className: "ms-auto hidden text-2xs text-faint-foreground @lg:inline", children: [_jsx("kbd", { className: "font-sans", children: "\u21B5" }), " ", t("hintSend"), " \u00B7 ", _jsx("kbd", { className: "font-sans", children: "\u21E7\u21B5" }), " ", t("hintNewLine"), " \u00B7 ", _jsx("kbd", { className: "font-sans", children: "@" }), " ", t("hintMention")] }), state === "streaming" && onStop ? (_jsx("button", { type: "button", "aria-label": t("stop"), onClick: onStop, className: "ms-auto flex size-8 items-center justify-center rounded-lg bg-foreground text-background transition-transform duration-150 active:scale-95 @lg:ms-1", children: _jsx(Square, { className: "size-3 fill-current" }) })) : (_jsx("button", { type: "button", "aria-label": t("send"), onClick: submit, disabled: !canSend, "aria-busy": state === "sending", className: "ms-auto flex size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground transition-[background-color,opacity,transform] duration-150 enabled:hover:bg-primary-hover active:scale-95 disabled:opacity-30 @lg:ms-1", children: state === "sending" ? _jsx(LoaderCircle, { className: "size-4 animate-spin" }) : _jsx(ArrowUp, { className: "size-4", strokeWidth: 2.5 }) }))] })] })] }));
219
+ } }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": t("attachFiles"), disabled: Boolean(locked), onClick: () => fileInput.current?.click(), className: "flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors duration-150 hover:bg-accent hover:text-foreground disabled:opacity-40", children: _jsx(Paperclip, { className: "size-4" }) }) }), _jsx(TooltipContent, { children: t("attachFiles") })] })] })), tools, scope && (_jsxs("button", { type: "button", onClick: onScopeClick, disabled: !onScopeClick, className: cn("inline-flex min-w-0 items-center gap-1.5 rounded-lg px-2 py-1 text-xs transition-colors duration-150 enabled:hover:bg-accent", production ? "bg-warning-weak text-warning-text enabled:hover:bg-warning-weak" : "text-muted-foreground"), children: [production ? _jsx(TriangleAlert, { "aria-hidden": true, className: "size-3 shrink-0" }) : _jsx("span", { "aria-hidden": true, className: "size-1.5 shrink-0 rounded-full bg-success" }), _jsxs("span", { className: "truncate", children: [scope.environment, " \u00B7 ", production ? t("production") : (scope.detail ?? t("nonProduction"))] })] })), _jsxs("span", { className: "ms-auto hidden text-2xs text-faint-foreground @lg:inline", children: [_jsx("kbd", { className: "font-sans", children: "\u21B5" }), " ", t("hintSend"), " \u00B7 ", _jsx("kbd", { className: "font-sans", children: "\u21E7\u21B5" }), " ", t("hintNewLine"), " \u00B7 ", _jsx("kbd", { className: "font-sans", children: "@" }), " ", t("hintMention")] }), state === "streaming" && onStop ? (_jsx("button", { type: "button", "aria-label": t("stop"), onClick: onStop, className: cn("ms-auto flex size-8 items-center justify-center bg-foreground text-background transition-transform duration-150 active:scale-95 @lg:ms-1", sendShape === "round" ? "rounded-full" : "rounded-lg"), children: _jsx(Square, { className: "size-3 fill-current" }) })) : (_jsx("button", { type: "button", "aria-label": t("send"), onClick: submit, disabled: !canSend, "aria-busy": state === "sending", className: cn("ms-auto flex size-8 items-center justify-center bg-primary text-primary-foreground transition-[background-color,opacity,transform] duration-150 enabled:hover:bg-primary-hover active:scale-95 disabled:opacity-30 @lg:ms-1", sendShape === "round" ? "rounded-full" : "rounded-lg"), children: state === "sending" ? _jsx(LoaderCircle, { className: "size-4 animate-spin" }) : _jsx(ArrowUp, { className: "size-4", strokeWidth: 2.5 }) }))] })] })] }));
216
220
  }
217
221
  export { Composer };
@@ -18,6 +18,6 @@ declare function ContextMeter({ segments, limit, className, }: {
18
18
  segments: ContextSegment[];
19
19
  /** The model's context window, in tokens. */
20
20
  limit: number;
21
- className?: string;
21
+ className?: string | undefined;
22
22
  }): import("react").JSX.Element;
23
23
  export { ContextMeter };
@@ -40,7 +40,7 @@ function ContextMeter({ segments, limit, className, }) {
40
40
  const warn = pct > 0.8;
41
41
  const r = 7;
42
42
  const c = 2 * Math.PI * r;
43
- return (_jsxs(Popover, { children: [_jsxs(PopoverTrigger, { "aria-label": t("label", { used: tokens(used), limit: tokens(limit) }), className: cn("inline-flex items-center gap-1.5 rounded-md px-1.5 py-1 text-2xs tabular-nums outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40", warn ? "text-warning-text" : "text-muted-foreground", className), children: [_jsxs("svg", { viewBox: "0 0 18 18", className: "size-4 -rotate-90", "aria-hidden": true, children: [_jsx("circle", { cx: "9", cy: "9", r: r, fill: "none", stroke: "var(--muted)", strokeWidth: "2.5" }), _jsx("circle", { cx: "9", cy: "9", r: r, fill: "none", stroke: warn ? "var(--warning)" : "var(--primary)", strokeWidth: "2.5", strokeDasharray: `${pct * c} ${c}`, strokeLinecap: "round" })] }), fmt.number(Math.round(pct * 100) / 100, { style: "percent" })] }), _jsxs(PopoverContent, { align: "start", className: "w-72 p-3", children: [_jsxs("p", { className: "flex items-baseline justify-between text-sm font-medium", children: [t("context"), " ", _jsx("span", { className: "text-xs font-normal text-muted-foreground tabular-nums", children: t("usage", { used: tokens(used), limit: tokens(limit) }) })] }), _jsx("div", { "aria-hidden": true, className: "mt-2 flex h-2 overflow-hidden rounded-full bg-muted", children: segments.map((s) => (_jsx("span", { style: { width: `${(s.tokens / limit) * 100}%`, background: kindColor[s.kind] } }, s.label))) }), _jsx("ul", { className: "mt-3 flex flex-col gap-1.5", children: [...segments]
43
+ return (_jsxs(Popover, { children: [_jsxs(PopoverTrigger, { "aria-label": t("label", { used: tokens(used), limit: tokens(limit) }), className: cn("inline-flex items-center gap-1.5 rounded-md px-1.5 py-1 text-2xs tabular-nums outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/80", warn ? "text-warning-text" : "text-muted-foreground", className), children: [_jsxs("svg", { viewBox: "0 0 18 18", className: "size-4 -rotate-90", "aria-hidden": true, children: [_jsx("circle", { cx: "9", cy: "9", r: r, fill: "none", stroke: "var(--muted)", strokeWidth: "2.5" }), _jsx("circle", { cx: "9", cy: "9", r: r, fill: "none", stroke: warn ? "var(--warning)" : "var(--primary)", strokeWidth: "2.5", strokeDasharray: `${pct * c} ${c}`, strokeLinecap: "round" })] }), fmt.number(Math.round(pct * 100) / 100, { style: "percent" })] }), _jsxs(PopoverContent, { align: "start", className: "w-72 p-3", children: [_jsxs("p", { className: "flex items-baseline justify-between text-sm font-medium", children: [t("context"), " ", _jsx("span", { className: "text-xs font-normal text-muted-foreground tabular-nums", children: t("usage", { used: tokens(used), limit: tokens(limit) }) })] }), _jsx("div", { "aria-hidden": true, className: "mt-2 flex h-2 overflow-hidden rounded-full bg-muted", children: segments.map((s) => (_jsx("span", { style: { width: `${(s.tokens / limit) * 100}%`, background: kindColor[s.kind] } }, s.label))) }), _jsx("ul", { className: "mt-3 flex flex-col gap-1.5", children: [...segments]
44
44
  .sort((a, b) => b.tokens - a.tokens)
45
45
  .map((s) => (_jsxs("li", { className: "flex items-center gap-2 text-xs", children: [_jsx("span", { "aria-hidden": true, className: "size-2 rounded-xs", style: { background: kindColor[s.kind] } }), _jsx("span", { className: "flex-1", children: s.label }), _jsx("span", { className: "text-muted-foreground tabular-nums", children: tokens(s.tokens) })] }, s.label))) }), warn && _jsx("p", { className: "mt-3 text-xs text-warning-text", children: t("nearlyFull") })] })] }));
46
46
  }
@@ -48,9 +48,9 @@ declare function EntityChip({ entity, onOpen, onRemove, className, }: {
48
48
  /** The thing to show: its kind, identifier and optional label. */
49
49
  entity: EntityRef;
50
50
  /** Makes the chip a button that opens the thing. */
51
- onOpen?: (entity: EntityRef) => void;
51
+ onOpen?: ((entity: EntityRef) => void) | undefined;
52
52
  /** Adds a remove button, e.g. in the composer. */
53
- onRemove?: (entity: EntityRef) => void;
54
- className?: string;
53
+ onRemove?: ((entity: EntityRef) => void) | undefined;
54
+ className?: string | undefined;
55
55
  }): React.JSX.Element;
56
56
  export { EntityChip, EntityKindsProvider, useEntityKinds };
@@ -81,6 +81,6 @@ function EntityChip({ entity, onOpen, onRemove, className, }) {
81
81
  : t("title", { noun, name: entity.name });
82
82
  const body = (_jsxs(_Fragment, { children: [_jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0 text-muted-foreground" }), _jsx("span", { className: "truncate", children: shown }), _jsxs("span", { className: "sr-only", children: [" (", title, ")"] })] }));
83
83
  const base = "inline-flex max-w-full items-center gap-1 rounded-sm border bg-card px-1.5 py-px align-baseline font-mono text-[0.92em] leading-[1.35] text-foreground";
84
- return (_jsxs("span", { "data-slot": "entity-chip", "data-kind": entity.kind, title: title, className: cn("inline-flex max-w-full min-w-0 items-center sm:max-w-64", className), children: [onOpen ? (_jsx("button", { type: "button", onClick: () => onOpen(entity), className: cn(base, "cursor-pointer transition-colors duration-150 hover:border-border-strong hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40 outline-none"), children: body })) : (_jsx("span", { className: base, children: body })), onRemove && (_jsx("button", { type: "button", "aria-label": t("remove", { name: shown }), onClick: () => onRemove(entity), className: "-ms-px rounded-e-sm border border-s-0 bg-card px-1 py-px text-faint-foreground hover:text-foreground", children: "\u00D7" }))] }));
84
+ return (_jsxs("span", { "data-slot": "entity-chip", "data-kind": entity.kind, title: title, className: cn("inline-flex max-w-full min-w-0 items-center sm:max-w-64", className), children: [onOpen ? (_jsx("button", { type: "button", onClick: () => onOpen(entity), className: cn(base, "cursor-pointer transition-colors duration-150 hover:border-border-strong hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/80 outline-none"), children: body })) : (_jsx("span", { className: base, children: body })), onRemove && (_jsx("button", { type: "button", "aria-label": t("remove", { name: shown }), onClick: () => onRemove(entity), className: "relative after:absolute after:-inset-1 -ms-px rounded-e-sm border border-s-0 bg-card px-1 py-px text-faint-foreground hover:text-foreground", children: "\u00D7" }))] }));
85
85
  }
86
86
  export { EntityChip, EntityKindsProvider, useEntityKinds };
@@ -15,19 +15,24 @@ export declare const foldedTurnsStrings: {
15
15
  * versions and compared them". The line says how many turns are inside and opens them in place; `summary` is yours
16
16
  * (a sentence, a count of tool calls, a cost). Folding never hides that the turns happened.
17
17
  */
18
- declare function FoldedTurns({ count, summary, defaultOpen, open, onOpenChange, children, className, }: {
18
+ declare function FoldedTurns({ count, summary, defaultOpen, open, onOpenChange, header, children, className, }: {
19
19
  /** How many turns are inside, for the button ("Show 12 turns"). */
20
20
  count: number;
21
21
  /** What happened in them, shown on the folded line: text or your own small summary. */
22
- summary?: React.ReactNode;
22
+ summary?: React.ReactNode | undefined;
23
23
  /** Start open. */
24
- defaultOpen?: boolean;
24
+ defaultOpen?: boolean | undefined;
25
25
  /** Controlled open state. */
26
- open?: boolean;
26
+ open?: boolean | undefined;
27
27
  /** Called when a person opens or closes it. */
28
- onOpenChange?: (open: boolean) => void;
28
+ onOpenChange?: ((open: boolean) => void) | undefined;
29
+ /**
30
+ * Your own folded line instead of the dashed one: a card with a title, how it ended, its cost, actions. The
31
+ * Show/Hide button follows it, so the turns can still be opened.
32
+ */
33
+ header?: React.ReactNode | undefined;
29
34
  /** The turns, usually Messages. */
30
35
  children: React.ReactNode;
31
- className?: string;
36
+ className?: string | undefined;
32
37
  }): React.JSX.Element;
33
38
  export { FoldedTurns };
@@ -15,7 +15,7 @@ export const foldedTurnsStrings = {
15
15
  * versions and compared them". The line says how many turns are inside and opens them in place; `summary` is yours
16
16
  * (a sentence, a count of tool calls, a cost). Folding never hides that the turns happened.
17
17
  */
18
- function FoldedTurns({ count, summary, defaultOpen = false, open, onOpenChange, children, className, }) {
18
+ function FoldedTurns({ count, summary, defaultOpen = false, open, onOpenChange, header, children, className, }) {
19
19
  const t = useStrings("foldedTurns", foldedTurnsStrings);
20
20
  const [own, setOwn] = React.useState(defaultOpen);
21
21
  const isOpen = open ?? own;
@@ -24,6 +24,6 @@ function FoldedTurns({ count, summary, defaultOpen = false, open, onOpenChange,
24
24
  setOwn(next);
25
25
  onOpenChange?.(next);
26
26
  };
27
- return (_jsxs(Collapsible, { open: isOpen, onOpenChange: set, "data-slot": "folded-turns", className: cn("group/folded flex flex-col gap-7", className), children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 rounded-lg border border-dashed px-3 py-2 text-sm", children: [summary && _jsx("div", { className: "min-w-0 flex-1 text-muted-foreground", children: summary }), _jsxs(CollapsibleTrigger, { className: "ms-auto inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs font-medium text-accent-text outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5 transition-transform duration-200 group-data-[state=open]/folded:[transform:rotate(90deg)]" }), t(isOpen ? "hide" : "show", { count })] })] }), _jsx(CollapsibleContent, { className: "flex flex-col gap-7", children: children })] }));
27
+ return (_jsxs(Collapsible, { open: isOpen, onOpenChange: set, "data-slot": "folded-turns", className: cn("group/folded flex flex-col gap-7", className), children: [_jsxs("div", { "data-slot": "folded-turns-line", className: cn("flex flex-wrap items-center gap-x-3 gap-y-1 text-sm", header ? "flex-col items-stretch" : "rounded-lg border border-dashed px-3 py-2"), children: [header ?? (summary && _jsx("div", { className: "min-w-0 flex-1 text-muted-foreground", children: summary })), _jsxs(CollapsibleTrigger, { className: cn("relative after:absolute after:inset-x-0 after:-inset-y-0.5 ms-auto inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs font-medium text-accent-text outline-none hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/80", header && "self-end"), children: [_jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5 transition-transform duration-200 group-data-[state=open]/folded:[transform:rotate(90deg)]" }), t(isOpen ? "hide" : "show", { count })] })] }), _jsx(CollapsibleContent, { className: "flex flex-col gap-7", children: children })] }));
28
28
  }
29
29
  export { FoldedTurns };
@@ -16,15 +16,15 @@ export type Rating = "up" | "down" | null;
16
16
  */
17
17
  declare function MessageActions({ text, onRetry, rating, onRate, meta, className, }: {
18
18
  /** What Copy copies; without it there's no Copy. */
19
- text?: string;
19
+ text?: string | undefined;
20
20
  /** Shows Retry. */
21
- onRetry?: () => void;
21
+ onRetry?: (() => void) | undefined;
22
22
  /** The current rating: up, down or none. */
23
- rating?: Rating;
23
+ rating?: Rating | undefined;
24
24
  /** Shows thumbs up and down. */
25
- onRate?: (rating: Rating) => void;
25
+ onRate?: ((rating: Rating) => void) | undefined;
26
26
  /** Facts after the actions, e.g. the model and cost. */
27
- meta?: React.ReactNode;
28
- className?: string;
27
+ meta?: React.ReactNode | undefined;
28
+ className?: string | undefined;
29
29
  }): React.JSX.Element;
30
30
  export { MessageActions };
@@ -16,7 +16,7 @@ export const messageActionsStrings = {
16
16
  bad: "Bad answer",
17
17
  };
18
18
  function ActionButton({ label, onClick, pressed, children, }) {
19
- return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": label, "aria-pressed": pressed, onClick: onClick, className: "flex size-7 items-center justify-center rounded-md text-faint-foreground transition-colors duration-150 outline-none hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40 aria-pressed:text-accent-text [&_svg]:size-3.5", children: children }) }), _jsx(TooltipContent, { children: label })] }));
19
+ return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": label, "aria-pressed": pressed, onClick: onClick, className: "flex size-7 items-center justify-center rounded-md text-faint-foreground transition-colors duration-150 outline-none hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80 aria-pressed:text-accent-text [&_svg]:size-3.5", children: children }) }), _jsx(TooltipContent, { children: label })] }));
20
20
  }
21
21
  /**
22
22
  * Copy, retry and rate under an agent's reply. Each action appears only when
@@ -15,19 +15,25 @@ declare function Inline({ nodes, }: {
15
15
  /** Inline nodes: text, strong, code, entities, citations. */
16
16
  nodes: InlineNode[];
17
17
  }): React.JSX.Element;
18
- /** A message's blocks, in order. */
19
- declare function MessageContent({ blocks, sources, onOpenEntity, onNavigate, onOpenAttachment, className, }: {
18
+ /**
19
+ * A message's blocks, in order. Build them from text with `plainBlocks(text)` for a person's words (newlines kept, no
20
+ * markdown) and `markdownBlocks(text)` for an agent's answer, both from `safe-markdown`, which installs with this item. Import them from there: they run on the server too. A
21
+ * model's links show as text, never as clickable links.
22
+ */
23
+ declare function MessageContent({ blocks, sources, onOpenEntity, onNavigate, onOpenAttachment, bareCode, className, }: {
20
24
  /** The reply as typed blocks: paragraphs, lists, payloads, tool calls, cards, attachments. */
21
25
  blocks: MessageBlock[];
22
26
  /** Sources the blocks cite by marker. */
23
- sources?: MessageSource[];
27
+ sources?: MessageSource[] | undefined;
24
28
  /** Makes entity chips open their thing. */
25
- onOpenEntity?: (entity: EntityRef) => void;
29
+ onOpenEntity?: ((entity: EntityRef) => void) | undefined;
26
30
  /** Follows in-app links; links from a model are never rendered. */
27
- onNavigate?: (to: string) => void;
31
+ onNavigate?: ((to: string) => void) | undefined;
28
32
  /** Makes attachment tiles buttons that open their file. */
29
- onOpenAttachment?: (attachment: Attachment) => void;
30
- className?: string;
33
+ onOpenAttachment?: ((attachment: Attachment) => void) | undefined;
34
+ /** Code and other payload blocks without their frame and title bar (Payload's `bare`). */
35
+ bareCode?: boolean | undefined;
36
+ className?: string | undefined;
31
37
  }): React.JSX.Element;
32
38
  /** Plain text of a message, for copy. Citations become [n]; entities their API name. */
33
39
  export declare function blocksToText(blocks: MessageBlock[], sources?: MessageSource[]): string;
@@ -42,7 +42,7 @@ function Inline({ nodes, }) {
42
42
  case "orgData":
43
43
  return (_jsx("span", { title: n.source ? t("measuredFrom", { environment: terms.environment, source: n.source }) : t("measured", { environment: terms.environment }), className: "font-medium tabular-nums text-foreground", children: n.text }, i));
44
44
  case "provisional":
45
- return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", "data-provisional": true, className: "inline rounded-xs text-start text-provisional underline decoration-provisional/50 decoration-dotted underline-offset-[3px] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx(Inline, { nodes: n.nodes }), _jsx("span", { className: "sr-only", children: t("notVerified") })] }) }), _jsx(TooltipContent, { children: n.reason ?? t("provisional", { environment: terms.environment }) })] }, i));
45
+ return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { type: "button", "data-provisional": true, className: "inline rounded-xs text-start text-provisional underline decoration-provisional/50 decoration-dotted underline-offset-[3px] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/80", children: [_jsx(Inline, { nodes: n.nodes }), _jsx("span", { className: "sr-only", children: t("notVerified") })] }) }), _jsx(TooltipContent, { children: n.reason ?? t("provisional", { environment: terms.environment }) })] }, i));
46
46
  case "citation":
47
47
  return _jsx(Citation, { source: ctx.sources.get(n.sourceId) }, i);
48
48
  case "entity":
@@ -62,6 +62,7 @@ function AttachmentBlock({ files }) {
62
62
  return _jsx(AttachmentList, { attachments: files, onOpen: ctx.onOpenAttachment });
63
63
  }
64
64
  function Block({ block, }) {
65
+ const { bareCode } = React.useContext(ContentContext);
65
66
  switch (block.kind) {
66
67
  case "paragraph":
67
68
  return (_jsx("p", { children: _jsx(Inline, { nodes: block.nodes }) }));
@@ -79,7 +80,7 @@ function Block({ block, }) {
79
80
  return (_jsx(List, { className: cn("flex flex-col gap-1 ps-5", block.ordered ? "list-decimal" : "list-disc marker:text-faint-foreground"), children: block.items.map((item, i) => (_jsx("li", { className: "ps-0.5", children: _jsx(Inline, { nodes: item }) }, i))) }));
80
81
  }
81
82
  case "payload":
82
- return _jsx(Payload, { language: block.language, text: block.text, title: block.title });
83
+ return _jsx(Payload, { language: block.language, text: block.text, title: block.title, bare: bareCode });
83
84
  case "tools":
84
85
  return _jsx(ToolCallGroup, { calls: block.calls });
85
86
  case "degraded":
@@ -90,9 +91,13 @@ function Block({ block, }) {
90
91
  return _jsx(AttachmentBlock, { files: block.files });
91
92
  }
92
93
  }
93
- /** A message's blocks, in order. */
94
- function MessageContent({ blocks, sources = [], onOpenEntity, onNavigate, onOpenAttachment, className, }) {
95
- const ctx = React.useMemo(() => ({ sources: new Map(sources.map((s) => [s.id, s])), onOpenEntity, onNavigate, onOpenAttachment }), [sources, onOpenEntity, onNavigate, onOpenAttachment]);
94
+ /**
95
+ * A message's blocks, in order. Build them from text with `plainBlocks(text)` for a person's words (newlines kept, no
96
+ * markdown) and `markdownBlocks(text)` for an agent's answer, both from `safe-markdown`, which installs with this item. Import them from there: they run on the server too. A
97
+ * model's links show as text, never as clickable links.
98
+ */
99
+ function MessageContent({ blocks, sources = [], onOpenEntity, onNavigate, onOpenAttachment, bareCode, className, }) {
100
+ const ctx = React.useMemo(() => ({ sources: new Map(sources.map((s) => [s.id, s])), onOpenEntity, onNavigate, onOpenAttachment, bareCode }), [sources, onOpenEntity, onNavigate, onOpenAttachment, bareCode]);
96
101
  return (_jsx(ContentContext.Provider, { value: ctx, children: _jsx("div", { "data-slot": "message-content", className: cn("flex flex-col gap-3 text-prose leading-relaxed", className), children: blocks.map((b, i) => (_jsx(Block, { block: b }, i))) }) }));
97
102
  }
98
103
  /** Plain text of a message, for copy. Citations become [n]; entities their API name. */
@@ -1,4 +1,5 @@
1
1
  import type * as React from "react";
2
+ import type { DateStyle } from "../blocks/locale.js";
2
3
  import type { Attachment, EntityRef, Message as MessageData } from "../lib/chat.js";
3
4
  import { type Rating } from "./message-actions.js";
4
5
  /** Message's fixed words; override them under `message` in LocaleProvider's `strings`. */
@@ -16,7 +17,8 @@ export declare const messageStrings: {
16
17
  /**
17
18
  * One message in a thread.
18
19
  *
19
- * - A person's message sits right, on the user surface.
20
+ * - A person's message sits right, on the user surface, with their name and
21
+ * time under it (`meta` hides or replaces that line).
20
22
  * - An agent's sits left, under its avatar, name and role, with reasoning above
21
23
  * the answer, sources and actions below. While streaming, a caret follows the
22
24
  * last block; an interrupted reply keeps what arrived and says it stopped.
@@ -25,92 +27,117 @@ export declare const messageStrings: {
25
27
  * - A system line is centred and quiet.
26
28
  * - A reply a person checked (or edited) before it went out can say so in a
27
29
  * caption under it, so a reader knows a person stood behind it.
30
+ * - Times are relative ("5 minutes ago") unless `timeFormat` asks for an
31
+ * absolute "time", "date" or "datetime" in the locale.
32
+ *
33
+ * Build `blocks` with `plainBlocks(text)` for a person's words and
34
+ * `markdownBlocks(text)` for an agent's, from `safe-markdown`.
28
35
  */
29
- declare function Message({ message, onOpenEntity, onNavigate, onOpenAttachment, onRetry, onContinue, rating, onRate, reviewedBy, actions, className, }: {
36
+ declare function Message({ message, onOpenEntity, onNavigate, onOpenAttachment, onRetry, onContinue, rating, onRate, reviewedBy, footer, actions, meta, timeFormat, density, bareCode, className, }: {
30
37
  /** The message: author, status, blocks, and optional reasoning, sources and attribution. */
31
38
  message: MessageData;
32
39
  /** Makes entity chips open their thing. */
33
- onOpenEntity?: (entity: EntityRef) => void;
40
+ onOpenEntity?: ((entity: EntityRef) => void) | undefined;
34
41
  /** Follows in-app links. */
35
- onNavigate?: (to: string) => void;
42
+ onNavigate?: ((to: string) => void) | undefined;
36
43
  /** Makes attachment tiles buttons that open their file. */
37
- onOpenAttachment?: (attachment: Attachment) => void;
44
+ onOpenAttachment?: ((attachment: Attachment) => void) | undefined;
38
45
  /** Shows Retry on a failed or finished reply. */
39
- onRetry?: () => void;
46
+ onRetry?: (() => void) | undefined;
40
47
  /** Shows Continue on an interrupted reply. */
41
- onContinue?: () => void;
48
+ onContinue?: (() => void) | undefined;
42
49
  /** The current rating of an agent reply. */
43
- rating?: Rating;
50
+ rating?: Rating | undefined;
44
51
  /** Shows thumbs up and down under agent replies. */
45
- onRate?: (rating: Rating) => void;
52
+ onRate?: ((rating: Rating) => void) | undefined;
46
53
  /** A person who checked this agent reply before it was sent: "Checked by Sam", or "Edited by Sam" when they changed it. */
47
54
  reviewedBy?: {
48
55
  name: string;
49
- at?: string;
50
- edited?: boolean;
51
- };
56
+ at?: string | undefined;
57
+ edited?: boolean | undefined;
58
+ } | undefined;
59
+ /** More lines under an agent reply, after who checked it and above the actions row: e.g. MemoryUsed. */
60
+ footer?: React.ReactNode | undefined;
52
61
  /**
53
62
  * The row under an agent reply (copy, retry, rating). `false` hides it; a node replaces it, e.g. your own buttons.
54
63
  * Model and cost still show, on their own line. To rebuild a message, use MessageHeader, MessageBody and
55
64
  * MessageFooter.
56
65
  */
57
- actions?: false | React.ReactNode;
58
- className?: string;
66
+ actions?: false | React.ReactNode | undefined;
67
+ /** The line under a person's own message: their name and when they sent it. `false` hides it; a node replaces it. */
68
+ meta?: false | React.ReactNode | undefined;
69
+ /** How times show: "relative" (the default, "5 minutes ago") or an absolute "time", "date" or "datetime" in the locale. */
70
+ timeFormat?: "relative" | DateStyle | undefined;
71
+ /** "compact" draws the header row smaller: a 12px name in a 24px row, for dense layouts. Defaults to "default". */
72
+ density?: "default" | "compact" | undefined;
73
+ /** Code and other payload blocks without their frame (Payload's `bare`). */
74
+ bareCode?: boolean | undefined;
75
+ className?: string | undefined;
59
76
  }): React.JSX.Element;
60
77
  /** The row above a reply: who sent it, a detail (their specialty or team), and when. */
61
- declare function MessageHeader({ name, detail, createdAt, children, className, }: {
78
+ declare function MessageHeader({ name, detail, createdAt, timeFormat, density, children, className, }: {
62
79
  /** The sender's displayed name. */
63
80
  name: string;
64
81
  /** A quieter detail after the name: an agent's specialty, a person's team. */
65
- detail?: string;
66
- /** When it was sent; shown as a relative time. */
67
- createdAt?: string;
82
+ detail?: string | undefined;
83
+ /** When it was sent; shown as a relative time unless `timeFormat` says otherwise. */
84
+ createdAt?: string | undefined;
85
+ /** "relative" (the default) or an absolute "time", "date" or "datetime" in the locale. */
86
+ timeFormat?: "relative" | DateStyle | undefined;
87
+ /** "compact": a 12px name in a 24px row. Defaults to "default" (14px in 28px). */
88
+ density?: "default" | "compact" | undefined;
68
89
  /** Anything else for the row, after the time (a badge, a menu). */
69
- children?: React.ReactNode;
70
- className?: string;
90
+ children?: React.ReactNode | undefined;
91
+ className?: string | undefined;
71
92
  }): React.JSX.Element;
72
93
  /** A reply's content: its reasoning, then its blocks, with a caret after the last one while it streams. */
73
- declare function MessageBody({ message, onOpenEntity, onNavigate, onOpenAttachment, className, }: {
94
+ declare function MessageBody({ message, onOpenEntity, onNavigate, onOpenAttachment, bareCode, className, }: {
74
95
  /** The message whose reasoning and blocks to show. */
75
96
  message: MessageData;
76
97
  /** Makes entity chips open their thing. */
77
- onOpenEntity?: (entity: EntityRef) => void;
98
+ onOpenEntity?: ((entity: EntityRef) => void) | undefined;
78
99
  /** Follows in-app links. */
79
- onNavigate?: (to: string) => void;
100
+ onNavigate?: ((to: string) => void) | undefined;
80
101
  /** Makes attachment tiles buttons that open their file. */
81
- onOpenAttachment?: (attachment: Attachment) => void;
82
- className?: string;
102
+ onOpenAttachment?: ((attachment: Attachment) => void) | undefined;
103
+ /** Code and other payload blocks without their frame. */
104
+ bareCode?: boolean | undefined;
105
+ className?: string | undefined;
83
106
  }): React.JSX.Element;
84
107
  /** Model and cost, on a line of their own: what the actions row shows, for when it's hidden or replaced. */
85
108
  declare function MessageAttribution({ attribution, className, }: {
86
109
  /** The service's measured values; nothing shows when both are absent. */
87
- attribution?: MessageData["attribution"];
88
- className?: string;
110
+ attribution?: MessageData["attribution"] | undefined;
111
+ className?: string | undefined;
89
112
  }): React.JSX.Element | null;
90
113
  /**
91
114
  * What sits under a reply: that it stopped or failed, its sources, who checked it, then the actions row with model
92
115
  * and cost. `actions={false}` hides the row and a node replaces it; either way the model and cost stay, on their own
93
116
  * line, because a reader should always see what produced the answer.
94
117
  */
95
- declare function MessageFooter({ message, onRetry, onContinue, rating, onRate, reviewedBy, actions, className, }: {
118
+ declare function MessageFooter({ message, onRetry, onContinue, rating, onRate, reviewedBy, footer, actions, timeFormat, className, }: {
96
119
  /** The message the footer belongs to. */
97
120
  message: MessageData;
98
121
  /** Shows Retry on a failed or finished reply. */
99
- onRetry?: () => void;
122
+ onRetry?: (() => void) | undefined;
100
123
  /** Shows Continue on an interrupted reply. */
101
- onContinue?: () => void;
124
+ onContinue?: (() => void) | undefined;
102
125
  /** The current rating. */
103
- rating?: Rating;
126
+ rating?: Rating | undefined;
104
127
  /** Shows thumbs up and down. */
105
- onRate?: (rating: Rating) => void;
128
+ onRate?: ((rating: Rating) => void) | undefined;
106
129
  /** A person who checked the reply before it was sent. */
107
130
  reviewedBy?: {
108
131
  name: string;
109
- at?: string;
110
- edited?: boolean;
111
- };
132
+ at?: string | undefined;
133
+ edited?: boolean | undefined;
134
+ } | undefined;
135
+ /** More lines after who checked it and above the actions row: e.g. MemoryUsed. */
136
+ footer?: React.ReactNode | undefined;
112
137
  /** `false` hides the actions row, a node replaces it; model and cost still show. */
113
- actions?: false | React.ReactNode;
114
- className?: string;
138
+ actions?: false | React.ReactNode | undefined;
139
+ /** How the reviewer's time shows: "relative" (the default) or an absolute "time", "date" or "datetime". */
140
+ timeFormat?: "relative" | DateStyle | undefined;
141
+ className?: string | undefined;
115
142
  }): React.JSX.Element;
116
143
  export { Message, MessageHeader, MessageBody, MessageFooter, MessageAttribution };