@candor-ui/react 2.1.0 → 2.3.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.
- package/dist/adapters/use-ag-ui.d.ts +1 -1
- package/dist/adapters/use-candor-chat.d.ts +1 -1
- package/dist/adapters/use-claude-agent.d.ts +1 -1
- package/dist/blocks/copy-field.js +4 -1
- package/dist/blocks/degraded-panel.js +3 -1
- package/dist/blocks/locked.js +5 -1
- package/dist/blocks/status-dot.d.ts +14 -3
- package/dist/blocks/status-dot.js +17 -22
- package/dist/blocks/step-list.d.ts +2 -1
- package/dist/blocks/step-list.js +19 -27
- package/dist/blocks/streaming-text.js +3 -1
- package/dist/blocks/surface.d.ts +1 -1
- package/dist/chat/attachment-preview.js +5 -2
- package/dist/chat/card-registry.js +3 -1
- package/dist/chat/citation.js +2 -2
- package/dist/chat/composer.js +19 -53
- package/dist/chat/context-meter.js +4 -1
- package/dist/chat/entity-chip.js +1 -1
- package/dist/chat/folded-turns.js +3 -1
- package/dist/chat/message-actions.js +5 -2
- package/dist/chat/message.js +9 -5
- package/dist/chat/payload.js +1 -1
- package/dist/chat/permission-prompt.js +6 -1
- package/dist/chat/prompt-suggestions.js +4 -1
- package/dist/chat/reasoning.js +6 -1
- package/dist/chat/thread.js +19 -8
- package/dist/chat/tool-call.js +6 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/lib/motion.d.ts +451 -0
- package/dist/lib/motion.js +350 -0
- package/dist/styles.css +543 -44
- package/dist/ui/accordion.d.ts +23 -0
- package/dist/ui/accordion.js +45 -0
- package/dist/ui/alert.d.ts +20 -0
- package/dist/ui/alert.js +51 -0
- package/dist/ui/button.d.ts +1 -1
- package/dist/ui/button.js +5 -3
- package/dist/ui/checkbox.d.ts +14 -0
- package/dist/ui/checkbox.js +32 -0
- package/dist/ui/collapsible.d.ts +3 -2
- package/dist/ui/collapsible.js +6 -3
- package/dist/ui/label.d.ts +10 -0
- package/dist/ui/label.js +16 -0
- package/dist/ui/popover.d.ts +1 -1
- package/dist/ui/popover.js +4 -2
- package/dist/ui/progress.d.ts +29 -0
- package/dist/ui/progress.js +27 -0
- package/dist/ui/skeleton.d.ts +1 -1
- package/dist/ui/skeleton.js +5 -2
- package/dist/ui/slider.d.ts +28 -0
- package/dist/ui/slider.js +41 -0
- package/dist/ui/switch.d.ts +14 -0
- package/dist/ui/switch.js +23 -0
- package/dist/ui/tooltip.d.ts +1 -1
- package/dist/ui/tooltip.js +4 -2
- package/package.json +1 -1
|
@@ -5,6 +5,8 @@ import { ChevronRight } from "lucide-react";
|
|
|
5
5
|
import { cn } from "../lib/utils.js";
|
|
6
6
|
import { useStrings } from "../blocks/locale.js";
|
|
7
7
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js";
|
|
8
|
+
import { motion } from "../lib/motion.js";
|
|
9
|
+
const turnMotion = motion("folded-turns", "open", "reveal");
|
|
8
10
|
/** FoldedTurns' fixed words; override them under `foldedTurns` in LocaleProvider's `strings`. */
|
|
9
11
|
export const foldedTurnsStrings = {
|
|
10
12
|
show: { one: "Show {count} turn", other: "Show {count} turns" },
|
|
@@ -24,6 +26,6 @@ function FoldedTurns({ count, summary, defaultOpen = false, open, onOpenChange,
|
|
|
24
26
|
setOwn(next);
|
|
25
27
|
onOpenChange?.(next);
|
|
26
28
|
};
|
|
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
|
|
29
|
+
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, style: turnMotion, className: "rtl:-scale-x-100 size-3.5 cd-reveal-turn group-data-[state=open]/folded:[transform:rotate(90deg)]" }), t(isOpen ? "hide" : "show", { count })] })] }), _jsx(CollapsibleContent, { style: turnMotion, className: "flex flex-col gap-7", children: children })] }));
|
|
28
30
|
}
|
|
29
31
|
export { FoldedTurns };
|
|
@@ -6,6 +6,9 @@ import { cn } from "../lib/utils.js";
|
|
|
6
6
|
import { useCopy } from "../lib/use-copy.js";
|
|
7
7
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
|
|
8
8
|
import { useStrings } from "../blocks/locale.js";
|
|
9
|
+
import { motion } from "../lib/motion.js";
|
|
10
|
+
/** The tick or cross after the clipboard write settles. */
|
|
11
|
+
const tickMotion = motion("message-actions", "tick", "pop");
|
|
9
12
|
/** MessageActions' fixed words; override them under `messageActions` in LocaleProvider's `strings`. */
|
|
10
13
|
export const messageActionsStrings = {
|
|
11
14
|
copy: "Copy",
|
|
@@ -16,7 +19,7 @@ export const messageActionsStrings = {
|
|
|
16
19
|
bad: "Bad answer",
|
|
17
20
|
};
|
|
18
21
|
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
|
|
22
|
+
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 cd-tint 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
23
|
}
|
|
21
24
|
/**
|
|
22
25
|
* Copy, retry and rate under an agent's reply. Each action appears only when
|
|
@@ -28,6 +31,6 @@ function ActionButton({ label, onClick, pressed, children, }) {
|
|
|
28
31
|
function MessageActions({ text, onRetry, rating, onRate, meta, className, }) {
|
|
29
32
|
const { state, copy } = useCopy();
|
|
30
33
|
const t = useStrings("messageActions", messageActionsStrings);
|
|
31
|
-
return (_jsxs("div", { "data-slot": "message-actions", className: cn("flex items-center gap-0.5", className), children: [text !== undefined && (_jsx(ActionButton, { label: state === "copied" ? t("copied") : state === "failed" ? t("copyFailed") : t("copy"), onClick: () => copy(text), children: state === "copied" ? (_jsx(Check, { "data-copy": "copied", className: "text-success-text
|
|
34
|
+
return (_jsxs("div", { "data-slot": "message-actions", className: cn("flex items-center gap-0.5", className), children: [text !== undefined && (_jsx(ActionButton, { label: state === "copied" ? t("copied") : state === "failed" ? t("copyFailed") : t("copy"), onClick: () => copy(text), children: state === "copied" ? (_jsx(Check, { "data-copy": "copied", style: tickMotion, className: "text-success-text cd-pop" })) : state === "failed" ? (_jsx(X, { "data-copy": "failed", style: tickMotion, className: "text-destructive-text cd-pop" })) : (_jsx(Copy, { "data-copy": state })) })), onRetry && (_jsx(ActionButton, { label: t("retry"), onClick: onRetry, children: _jsx(RotateCw, {}) })), onRate && (_jsxs(_Fragment, { children: [_jsx(ActionButton, { label: t("good"), pressed: rating === "up", onClick: () => onRate(rating === "up" ? null : "up"), children: _jsx(ThumbsUp, { className: cn(rating === "up" && "fill-current") }) }), _jsx(ActionButton, { label: t("bad"), pressed: rating === "down", onClick: () => onRate(rating === "down" ? null : "down"), children: _jsx(ThumbsDown, { className: cn(rating === "down" && "fill-current") }) })] })), meta && _jsx("span", { className: "ms-1.5 truncate text-xs text-faint-foreground", children: meta })] }));
|
|
32
35
|
}
|
|
33
36
|
export { MessageActions };
|
package/dist/chat/message.js
CHANGED
|
@@ -11,6 +11,10 @@ import { MessageActions } from "./message-actions.js";
|
|
|
11
11
|
import { Reasoning } from "./reasoning.js";
|
|
12
12
|
import { ThreadEvent } from "./thread-event.js";
|
|
13
13
|
import { useStrings } from "../blocks/locale.js";
|
|
14
|
+
import { motion } from "../lib/motion.js";
|
|
15
|
+
/** A message that arrived after the thread first drew rises in (Thread marks it `data-arrived`); loaded history is still. */
|
|
16
|
+
const arriveMotion = motion("message", "arrive", "arrive");
|
|
17
|
+
const caretMotion = motion("message", "caret", "live", { dur: 1000 });
|
|
14
18
|
/** Message's fixed words; override them under `message` in LocaleProvider's `strings`. */
|
|
15
19
|
export const messageStrings = {
|
|
16
20
|
you: "You",
|
|
@@ -50,14 +54,14 @@ function Message({ message, onOpenEntity, onNavigate, onOpenAttachment, onRetry,
|
|
|
50
54
|
return (_jsx(ThreadEvent, { "data-slot": "message", "data-role": "system", className: className, children: _jsx(MessageContent, { blocks: message.blocks, className: "text-xs leading-normal" }) }));
|
|
51
55
|
}
|
|
52
56
|
if (author.role === "user") {
|
|
53
|
-
return (_jsx("div", { "data-slot": "message", "data-role": "user", className: cn("flex justify-end ps-10 sm:ps-24
|
|
57
|
+
return (_jsx("div", { "data-slot": "message", "data-role": "user", style: arriveMotion, className: cn("flex justify-end ps-10 sm:ps-24 data-[arrived]:cd-arrive", className), children: _jsxs("div", { className: "flex max-w-full flex-col items-end gap-1", children: [_jsx("div", { className: "rounded-xl rounded-ee-sm bg-surface-user px-3.5 py-2.5", children: _jsx(MessageContent, { blocks: message.blocks, onOpenEntity: onOpenEntity, onNavigate: onNavigate, onOpenAttachment: onOpenAttachment, bareCode: bareCode, className: "gap-2" }) }), meta === undefined ? (_jsxs("span", { "data-slot": "message-meta", className: "px-1 text-2xs text-faint-foreground", children: [author.name ?? t("you"), message.createdAt && (_jsxs(_Fragment, { children: [" · ", _jsx(RelativeTime, { date: message.createdAt, format: timeFormat })] }))] })) : (meta)] }) }));
|
|
54
58
|
}
|
|
55
59
|
if (author.role === "person") {
|
|
56
|
-
return (_jsxs("article", { "data-slot": "message", "data-role": "person", "aria-label": author.team ? t("personLabel", { name: author.name, team: author.team }) : author.name, className: cn("flex gap-3
|
|
60
|
+
return (_jsxs("article", { "data-slot": "message", "data-role": "person", "aria-label": author.team ? t("personLabel", { name: author.name, team: author.team }) : author.name, style: arriveMotion, className: cn("flex gap-3 data-[arrived]:cd-arrive", className), children: [_jsx("span", { "aria-hidden": true, className: "mt-px flex size-7 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground", children: _jsx(UserRound, { className: "size-4" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(MessageHeader, { name: author.name, detail: author.team, createdAt: message.createdAt, timeFormat: timeFormat, density: density }), _jsx(MessageContent, { blocks: message.blocks, sources: message.sources, onOpenEntity: onOpenEntity, onNavigate: onNavigate, onOpenAttachment: onOpenAttachment, bareCode: bareCode, className: "mt-1" })] })] }));
|
|
57
61
|
}
|
|
58
62
|
const { agent } = author;
|
|
59
63
|
const streaming = message.status === "streaming";
|
|
60
|
-
return (_jsxs("article", { "data-slot": "message", "data-role": "agent", "data-status": message.status, "aria-busy": streaming, "aria-label": t("agentLabel", { name: agent.name, noun: roleOf(agent.role).noun }), className: cn("group/message flex gap-3
|
|
64
|
+
return (_jsxs("article", { "data-slot": "message", "data-role": "agent", "data-status": message.status, "aria-busy": streaming, "aria-label": t("agentLabel", { name: agent.name, noun: roleOf(agent.role).noun }), style: arriveMotion, className: cn("group/message flex gap-3 data-[arrived]:cd-arrive", className), children: [_jsx(AgentAvatar, { agent: agent, className: "mt-px" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx(MessageHeader, { name: agent.name, detail: agent.specialty, createdAt: message.createdAt, timeFormat: timeFormat, density: density }), _jsxs("div", { className: "mt-1 flex flex-col gap-3", children: [_jsx(MessageBody, { message: message, onOpenEntity: onOpenEntity, onNavigate: onNavigate, onOpenAttachment: onOpenAttachment, bareCode: bareCode }), _jsx(MessageFooter, { message: message, onRetry: onRetry, onContinue: onContinue, rating: rating, onRate: onRate, reviewedBy: reviewedBy, footer: footer, actions: actions, timeFormat: timeFormat })] })] })] }));
|
|
61
65
|
}
|
|
62
66
|
/** The row above a reply: who sent it, a detail (their specialty or team), and when. */
|
|
63
67
|
function MessageHeader({ name, detail, createdAt, timeFormat, density = "default", children, className, }) {
|
|
@@ -67,7 +71,7 @@ function MessageHeader({ name, detail, createdAt, timeFormat, density = "default
|
|
|
67
71
|
function MessageBody({ message, onOpenEntity, onNavigate, onOpenAttachment, bareCode, className, }) {
|
|
68
72
|
const streaming = message.status === "streaming";
|
|
69
73
|
const thinking = streaming && message.blocks.length === 0;
|
|
70
|
-
return (_jsxs("div", { "data-slot": "message-body", className: cn("flex flex-col gap-3", className), children: [message.reasoning && message.reasoning.length > 0 && _jsx(Reasoning, { entries: message.reasoning, thinking: thinking, thoughtFor: message.thoughtFor }), message.blocks.length > 0 && (_jsxs("div", { className: "relative", children: [_jsx(MessageContent, { blocks: message.blocks, sources: message.sources, onOpenEntity: onOpenEntity, onNavigate: onNavigate, onOpenAttachment: onOpenAttachment, bareCode: bareCode }), streaming && _jsx("span", { "aria-hidden": true, className: "ms-0.5 inline-block h-[1.05em] w-[2px] translate-y-[0.18em] rounded-full bg-primary
|
|
74
|
+
return (_jsxs("div", { "data-slot": "message-body", className: cn("flex flex-col gap-3", className), children: [message.reasoning && message.reasoning.length > 0 && _jsx(Reasoning, { entries: message.reasoning, thinking: thinking, thoughtFor: message.thoughtFor }), message.blocks.length > 0 && (_jsxs("div", { className: "relative", children: [_jsx(MessageContent, { blocks: message.blocks, sources: message.sources, onOpenEntity: onOpenEntity, onNavigate: onNavigate, onOpenAttachment: onOpenAttachment, bareCode: bareCode }), streaming && _jsx("span", { "aria-hidden": true, style: caretMotion, className: "ms-0.5 inline-block h-[1.05em] w-[2px] translate-y-[0.18em] rounded-full bg-primary cd-live-caret" })] }))] }));
|
|
71
75
|
}
|
|
72
76
|
/** Model and cost, on a line of their own: what the actions row shows, for when it's hidden or replaced. */
|
|
73
77
|
function MessageAttribution({ attribution, className, }) {
|
|
@@ -86,6 +90,6 @@ function MessageFooter({ message, onRetry, onContinue, rating, onRate, reviewedB
|
|
|
86
90
|
const streaming = message.status === "streaming";
|
|
87
91
|
const settled = !streaming && message.status !== "failed";
|
|
88
92
|
const meta = [message.attribution?.model, message.attribution?.cost].filter(Boolean).join(" · ");
|
|
89
|
-
return (_jsxs("div", { "data-slot": "message-footer", className: cn("flex flex-col gap-3 empty:hidden", className), children: [message.status === "interrupted" && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [_jsx(CircleStop, { "aria-hidden": true, className: "size-3.5" }), t("interrupted"), onContinue && (_jsx("button", { type: "button", onClick: onContinue, className: "relative after:absolute after:inset-x-0 after:-inset-y-1 font-medium text-accent-text hover:underline", children: t("continue") }))] })), message.status === "failed" && _jsx(DegradedPanel, { reason: t("failed"), missing: t("failedMissing"), onRetry: onRetry }), !streaming && message.sources && message.sources.length > 0 && _jsx(SourceList, { sources: message.sources }), reviewedBy && (_jsxs("p", { "data-slot": "message-reviewed", className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [_jsx(UserCheck, { "aria-hidden": true, className: "size-3.5" }), t(reviewedBy.edited ? "editedBy" : "checkedBy", { name: reviewedBy.name }), reviewedBy.at && (_jsxs(_Fragment, { children: [" · ", _jsx(RelativeTime, { date: reviewedBy.at, format: timeFormat })] }))] })), footer, settled && actions === undefined && (_jsx(MessageActions, { text: blocksToText(message.blocks, message.sources), onRetry: onRetry, rating: rating, onRate: onRate, meta: meta || undefined, className: "-ms-1.5 opacity-100
|
|
93
|
+
return (_jsxs("div", { "data-slot": "message-footer", className: cn("flex flex-col gap-3 empty:hidden", className), children: [message.status === "interrupted" && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [_jsx(CircleStop, { "aria-hidden": true, className: "size-3.5" }), t("interrupted"), onContinue && (_jsx("button", { type: "button", onClick: onContinue, className: "relative after:absolute after:inset-x-0 after:-inset-y-1 font-medium text-accent-text hover:underline", children: t("continue") }))] })), message.status === "failed" && _jsx(DegradedPanel, { reason: t("failed"), missing: t("failedMissing"), onRetry: onRetry }), !streaming && message.sources && message.sources.length > 0 && _jsx(SourceList, { sources: message.sources }), reviewedBy && (_jsxs("p", { "data-slot": "message-reviewed", className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [_jsx(UserCheck, { "aria-hidden": true, className: "size-3.5" }), t(reviewedBy.edited ? "editedBy" : "checkedBy", { name: reviewedBy.name }), reviewedBy.at && (_jsxs(_Fragment, { children: [" · ", _jsx(RelativeTime, { date: reviewedBy.at, format: timeFormat })] }))] })), footer, settled && actions === undefined && (_jsx(MessageActions, { text: blocksToText(message.blocks, message.sources), onRetry: onRetry, rating: rating, onRate: onRate, meta: meta || undefined, className: "-ms-1.5 opacity-100 cd-tint sm:opacity-0 sm:group-hover/message:opacity-100 sm:group-focus-within/message:opacity-100" })), settled && actions !== undefined && actions !== false && actions, settled && actions !== undefined && _jsx(MessageAttribution, { attribution: message.attribution })] }));
|
|
90
94
|
}
|
|
91
95
|
export { Message, MessageHeader, MessageBody, MessageFooter, MessageAttribution };
|
package/dist/chat/payload.js
CHANGED
|
@@ -44,7 +44,7 @@ function Payload({ language, text, title, bare = false, highlight, className, })
|
|
|
44
44
|
// oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
45
45
|
_jsx("pre", { "data-slot": "payload", "data-bare": "", "data-language": language, "data-syntax": syntax, tabIndex: 0, className: cn("max-h-80 overflow-auto rounded-md bg-sunken outline-none focus-visible:ring-[3px] focus-visible:ring-ring/80 focus-visible:ring-inset px-3 py-2.5 font-mono text-xs leading-relaxed text-foreground", className), children: code }));
|
|
46
46
|
}
|
|
47
|
-
return (_jsxs("figure", { "data-slot": "payload", "data-language": language, "data-syntax": syntax, className: cn("overflow-hidden rounded-lg border bg-sunken", className), children: [_jsxs("figcaption", { className: "flex items-center justify-between gap-2 border-b px-3 py-1.5", children: [_jsxs("span", { className: "truncate text-xs text-muted-foreground", children: [_jsx("span", { className: "font-medium text-foreground", children: languageLabel(language, t) }), title && _jsxs("span", { children: [" \u00B7 ", title] })] }), _jsx("button", { type: "button", onClick: () => copy(text), "aria-live": "polite", className: "relative after:absolute after:inset-x-0 after:-inset-y-0.5 inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs text-muted-foreground
|
|
47
|
+
return (_jsxs("figure", { "data-slot": "payload", "data-language": language, "data-syntax": syntax, className: cn("overflow-hidden rounded-lg border bg-sunken", className), children: [_jsxs("figcaption", { className: "flex items-center justify-between gap-2 border-b px-3 py-1.5", children: [_jsxs("span", { className: "truncate text-xs text-muted-foreground", children: [_jsx("span", { className: "font-medium text-foreground", children: languageLabel(language, t) }), title && _jsxs("span", { children: [" \u00B7 ", title] })] }), _jsx("button", { type: "button", onClick: () => copy(text), "aria-live": "polite", className: "relative after:absolute after:inset-x-0 after:-inset-y-0.5 inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs text-muted-foreground cd-tint outline-none hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80", children: state === "copied" ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "size-3 text-success-text" }), " ", t("copied")] })) : state === "failed" ? (_jsxs(_Fragment, { children: [_jsx(X, { className: "size-3 text-destructive-text" }), " ", t("copyFailed")] })) : (_jsxs(_Fragment, { children: [_jsx(Copy, { className: "size-3" }), " ", t("copy")] })) })] }), _jsx("pre", { tabIndex: 0, className: "max-h-80 overflow-auto px-3 py-2.5 font-mono text-xs leading-relaxed text-foreground outline-none focus-visible:ring-[3px] focus-visible:ring-ring/80 focus-visible:ring-inset", children: code })] }));
|
|
48
48
|
}
|
|
49
49
|
/** The code as token spans. Each token is a text child, so nothing in it is parsed as markup. */
|
|
50
50
|
function Code({ text, language }) {
|
|
@@ -6,6 +6,11 @@ import { cn } from "../lib/utils.js";
|
|
|
6
6
|
import { useStrings } from "../blocks/locale.js";
|
|
7
7
|
import { Payload } from "./payload.js";
|
|
8
8
|
import { Button } from "../ui/button.js";
|
|
9
|
+
import { motion } from "../lib/motion.js";
|
|
10
|
+
/** Three pulses of 1.6 s while the decision waits (4.8 s, then still), shortened from 2.2's 7.2 s. */
|
|
11
|
+
const waitingMotion = motion("permission-prompt", "waiting", "attention", { dur: 1600, amp: "3" });
|
|
12
|
+
/** A press around the prompt instead of on it: one ring, and the refuse shake under expressive. */
|
|
13
|
+
const nudgeMotion = { ...motion("permission-prompt", "nudge", "refuse"), ...motion("permission-prompt", "nudge", "attention") };
|
|
9
14
|
/** PermissionPrompt's fixed words; override them under `permissionPrompt` in LocaleProvider's `strings`. */
|
|
10
15
|
export const permissionPromptStrings = {
|
|
11
16
|
label: "Permission request: {title}",
|
|
@@ -166,7 +171,7 @@ function PromptBody({ request, mode, suggestions, editable, onAllow, onDeny, bus
|
|
|
166
171
|
return;
|
|
167
172
|
onAllow({ ...(edited && changedKeys.length && { updatedInput: edited }), ...(ticked.length && { alwaysAllow: ticked }) });
|
|
168
173
|
};
|
|
169
|
-
return (_jsxs("section", { ref: section, "data-slot": "permission-prompt", "data-waiting": busy ? undefined : "", "data-nudged": nudged ? "" : undefined, "aria-label": t("label", { title: questions ? t("questions") : title }), className: cn("overflow-hidden rounded-xl border border-warning/50 bg-card text-sm shadow-xs [--cd-attention-color:var(--warning)]", nudged && "
|
|
174
|
+
return (_jsxs("section", { ref: section, "data-slot": "permission-prompt", "data-waiting": busy ? undefined : "", "data-nudged": nudged ? "" : undefined, "aria-label": t("label", { title: questions ? t("questions") : title }), style: nudgeMotion, className: cn("overflow-hidden rounded-xl border border-warning/50 bg-card text-sm shadow-xs [--cd-attention-color:var(--warning)]", nudged && "cd-nudge", className), children: [_jsxs("header", { className: "flex items-start gap-2.5 bg-warning-weak/50 px-3.5 py-2.5", children: [_jsx("span", { "aria-hidden": true, style: waitingMotion, className: cn("mt-0.5 flex size-4 shrink-0 items-center justify-center rounded-full text-warning-text", !busy && "cd-attention"), children: _jsx(ShieldQuestion, { className: "size-4" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("h3", { className: "font-semibold leading-snug", children: questions ? t("questions") : title }), request.description && _jsx("p", { className: "mt-0.5 text-muted-foreground", children: request.description })] }), _jsx("code", { className: "shrink-0 font-mono text-2xs text-muted-foreground", children: request.tool })] }), _jsxs("dl", { className: "flex flex-col gap-1.5 px-3.5 pt-2.5 text-sm", children: [request.reason && (_jsxs("div", { className: "flex gap-2", children: [_jsx("dt", { className: "shrink-0 text-muted-foreground", children: t("why") }), _jsx("dd", { className: "min-w-0", children: request.reason })] })), mode && (_jsxs("div", { className: "flex gap-2", children: [_jsx("dt", { className: "shrink-0 text-muted-foreground", children: t("mode") }), _jsx("dd", { "data-slot": "permission-mode", children: modeKeys[mode] ? t(modeKeys[mode]) : _jsx("code", { className: "font-mono text-xs", children: mode }) })] }))] }), (request.blockedPath || request.agentId || request.mcpServer) && (_jsxs("ul", { className: "mt-1.5 flex flex-col gap-0.5 px-3.5 text-xs text-muted-foreground", children: [request.blockedPath && _jsx("li", { children: t("blockedPath", { path: request.blockedPath }) }), request.agentId && _jsx("li", { children: t("subagent") }), request.mcpServer && _jsx("li", { children: t("server", { name: request.mcpServer.name, source: request.mcpServer.source }) })] })), questions ? (_jsx("div", { className: "flex flex-col gap-4 px-3.5 py-3", children: questions.map((q) => {
|
|
170
175
|
const a = answers[q.id];
|
|
171
176
|
return (_jsxs("fieldset", { className: "flex flex-col gap-1.5", children: [_jsxs("legend", { className: "mb-1 font-medium", children: [q.tag && _jsx("span", { className: "me-1.5 rounded-xs bg-muted px-1 text-2xs font-medium text-muted-foreground", children: q.tag }), q.prompt] }), q.options.map((o) => (_jsxs("label", { className: "flex min-h-6 items-start gap-2", children: [_jsx("input", { type: q.multiple ? "checkbox" : "radio", name: `${id}-${q.id}`, checked: a?.choice.includes(o.id) ?? false, onChange: () => choose(q, o.id), className: "mt-0.5 size-4 accent-[var(--primary)]" }), _jsxs("span", { children: [o.label, o.detail && _jsx("span", { className: "block text-xs text-muted-foreground", children: o.detail })] })] }, o.id))), q.other && (_jsxs(_Fragment, { children: [_jsxs("label", { className: "flex min-h-6 items-start gap-2", children: [_jsx("input", { type: q.multiple ? "checkbox" : "radio", name: `${id}-${q.id}`, checked: a?.other ?? false, onChange: () => toggleOther(q), className: "mt-0.5 size-4 accent-[var(--primary)]" }), _jsx("span", { children: t("otherOption") })] }), a?.other && (_jsx("textarea", { "aria-label": t("otherLabel", { question: q.prompt }), rows: 2, value: a.text, onChange: (e) => setAnswers((prev) => ({ ...prev, [q.id]: { ...prev[q.id], text: e.target.value } })), className: "ms-6 block resize-none rounded-md border border-input bg-card px-2.5 py-1.5 text-sm outline-none [field-sizing:content] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/30" }))] }))] }, q.id));
|
|
172
177
|
}) })) : (_jsxs("div", { className: "flex flex-col gap-2 px-3.5 py-3", children: [editing ? (_jsxs(_Fragment, { children: [_jsx("label", { htmlFor: `${id}-edit`, className: "text-xs font-medium text-muted-foreground", children: t("editLabel") }), _jsx("textarea", { id: `${id}-edit`, value: draft, onChange: (e) => setDraft(e.target.value), spellCheck: false, rows: Math.min(12, draft.split("\n").length + 1), "aria-invalid": invalid || undefined, "aria-describedby": `${id}-changes`, className: "block w-full resize-y rounded-md border border-input bg-card px-2.5 py-1.5 font-mono text-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/30 aria-invalid:border-destructive" }), _jsx("div", { id: `${id}-changes`, role: "status", className: "text-xs", children: invalid ? (_jsx("p", { className: "text-destructive-text", children: t("editInvalid") })) : changedKeys.length ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-muted-foreground", children: t("changed") }), _jsx("ul", { className: "flex flex-col gap-1", children: changedKeys.map((k) => (_jsxs("li", { className: "grid gap-0.5 font-mono", children: [_jsx("span", { className: "text-muted-foreground", children: k }), k in proposed && _jsx("span", { className: "rounded-xs bg-destructive-weak/60 px-1 line-through", children: json(proposed[k]) }), k in edited && _jsx("span", { className: "rounded-xs bg-success-weak/70 px-1", children: json(edited[k]) })] }, k))) })] })) : (_jsx("p", { className: "text-muted-foreground", children: t("unchanged") })) }), _jsx(Button, { size: "sm", variant: "ghost", className: "self-start", onClick: () => (setEditing(false), setDraft(original)), children: t("cancelEdit") })] })) : (_jsxs(_Fragment, { children: [_jsx(Payload, { language: "json", title: t("input"), text: original }), editable && proposed && (_jsxs(Button, { size: "sm", variant: "outline", className: "self-start", onClick: () => setEditing(true), children: [_jsx(Pencil, { "aria-hidden": true }), " ", t("edit")] }))] })), suggestions && suggestions.length > 0 && (_jsxs("fieldset", { className: "mt-1 flex flex-col gap-1 rounded-lg border border-dashed px-3 py-2", children: [_jsx("legend", { className: "px-1 text-xs font-medium", children: t("remember") }), _jsxs("p", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(Info, { "aria-hidden": true, className: "size-3" }), " ", t("rememberHint")] }), suggestions.map((s, i) => {
|
|
@@ -4,6 +4,9 @@ import { ArrowUpRight, Sparkles } from "lucide-react";
|
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
5
|
import { Skeleton } from "../ui/skeleton.js";
|
|
6
6
|
import { useStrings } from "../blocks/locale.js";
|
|
7
|
+
import { motion } from "../lib/motion.js";
|
|
8
|
+
/** A suggestion pressed: 2.2's slight scale of 0.99. */
|
|
9
|
+
const pressMotion = motion("prompt-suggestions", "press", "press", { amp: "0.98" });
|
|
7
10
|
/** PromptSuggestions' fixed words; override them under `promptSuggestions` in LocaleProvider's `strings`. */
|
|
8
11
|
export const promptSuggestionsStrings = {
|
|
9
12
|
label: "Try asking",
|
|
@@ -20,6 +23,6 @@ function PromptSuggestions({ suggestions, onChoose, label: labelProp, loading =
|
|
|
20
23
|
return null;
|
|
21
24
|
return (_jsxs("section", { "data-slot": "prompt-suggestions", "aria-label": label, className: cn("flex flex-col gap-2", className), children: [_jsxs("p", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [_jsx(Sparkles, { "aria-hidden": true, className: "size-3" }), " ", label] }), _jsx("div", { className: cn(variant === "cards" ? "grid gap-2 sm:grid-cols-2" : "flex flex-wrap gap-1.5"), children: loading
|
|
22
25
|
? Array.from({ length: variant === "cards" ? 4 : 3 }, (_, i) => (_jsx(Skeleton, { className: variant === "cards" ? "h-16 rounded-xl" : "h-8 w-44 rounded-lg" }, i)))
|
|
23
|
-
: suggestions.map((s) => variant === "cards" ? (_jsxs("button", { type: "button", onClick: () => onChoose(s), className: "group/suggestion flex items-start justify-between gap-3 rounded-xl border bg-card p-3 text-start text-sm
|
|
26
|
+
: suggestions.map((s) => variant === "cards" ? (_jsxs("button", { type: "button", onClick: () => onChoose(s), style: pressMotion, className: "group/suggestion flex items-start justify-between gap-3 rounded-xl border bg-card p-3 text-start text-sm cd-press outline-none hover:border-border-strong hover:shadow-raised focus-visible:ring-[3px] focus-visible:ring-ring/80", children: [_jsx("span", { children: s.text }), _jsx(ArrowUpRight, { "aria-hidden": true, className: "size-3.5 shrink-0 text-faint-foreground cd-tint group-hover/suggestion:text-accent-text" })] }, s.id)) : (_jsx("button", { type: "button", onClick: () => onChoose(s), className: "rounded-lg border bg-card px-3 py-1.5 text-sm text-muted-foreground cd-tint outline-none hover:border-border-strong hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80", children: s.text }, s.id))) })] }));
|
|
24
27
|
}
|
|
25
28
|
export { PromptSuggestions };
|
package/dist/chat/reasoning.js
CHANGED
|
@@ -6,6 +6,9 @@ import { cn } from "../lib/utils.js";
|
|
|
6
6
|
import { ShimmerLabel } from "../blocks/streaming-text.js";
|
|
7
7
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js";
|
|
8
8
|
import { useStrings } from "../blocks/locale.js";
|
|
9
|
+
import { motion, useArrived } from "../lib/motion.js";
|
|
10
|
+
const arriveMotion = motion("reasoning", "arrive", "arrive");
|
|
11
|
+
const turnMotion = motion("reasoning", "open", "reveal");
|
|
9
12
|
/** Reasoning's fixed words; override them under `reasoning` in LocaleProvider's `strings`. */
|
|
10
13
|
export const reasoningStrings = {
|
|
11
14
|
verified: "Verified",
|
|
@@ -31,10 +34,12 @@ const basisTone = {
|
|
|
31
34
|
*/
|
|
32
35
|
function Reasoning({ entries, thinking = false, thoughtFor, className, }) {
|
|
33
36
|
const t = useStrings("reasoning", reasoningStrings);
|
|
37
|
+
// An entry added while the reasoning is on screen rises in; the ones it drew with are still.
|
|
38
|
+
const arrived = useArrived(React.useMemo(() => entries.map((e) => e.id), [entries]));
|
|
34
39
|
const [open, setOpen] = React.useState(thinking);
|
|
35
40
|
React.useEffect(() => setOpen(thinking), [thinking]);
|
|
36
41
|
const assumed = entries.filter((e) => e.basis === "assumed").length;
|
|
37
42
|
const summary = thinking ? null : (_jsxs(_Fragment, { children: [t("reasoning"), thoughtFor && _jsxs(_Fragment, { children: [" \u00B7 ", thoughtFor] }), " \u00B7 ", t("steps", { count: entries.length }), assumed > 0 && _jsxs("span", { className: "text-warning-text", children: [" \u00B7 ", t("assumedCount", { count: assumed })] })] }));
|
|
38
|
-
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "reasoning", className: cn("group/reasoning", className), children: [_jsxs(CollapsibleTrigger, { className: "relative after:absolute after:inset-x-0 after:-inset-y-0.5 inline-flex items-center gap-1.5 rounded-md py-0.5 pe-1.5 text-xs text-muted-foreground outline-none
|
|
43
|
+
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "reasoning", className: cn("group/reasoning", className), children: [_jsxs(CollapsibleTrigger, { className: "relative after:absolute after:inset-x-0 after:-inset-y-0.5 inline-flex items-center gap-1.5 rounded-md py-0.5 pe-1.5 text-xs text-muted-foreground outline-none cd-tint hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80", children: [_jsx(Brain, { "aria-hidden": true, className: cn("size-3.5", thinking && "text-accent-text") }), thinking ? _jsx(ShimmerLabel, { className: "text-xs", children: t("thinking") }) : summary, _jsx(ChevronRight, { "aria-hidden": true, style: turnMotion, className: "rtl:-scale-x-100 size-3 cd-reveal-turn group-data-[state=open]/reasoning:[transform:rotate(90deg)]" })] }), _jsx(CollapsibleContent, { style: turnMotion, children: _jsx("ol", { className: "mt-2 ms-1.5 flex flex-col gap-3 border-s ps-4", children: entries.map((e) => (_jsxs("li", { style: arriveMotion, className: cn("relative", arrived(e.id) && "cd-arrive"), children: [_jsx("span", { "aria-hidden": true, className: "absolute top-1.5 -start-[1.2rem] size-1.5 rounded-full bg-border-strong" }), _jsxs("div", { className: "flex flex-wrap items-baseline gap-x-2 gap-y-1", children: [_jsx("p", { className: "text-sm font-medium", children: e.title }), _jsx("span", { className: cn("rounded-xs px-1 text-2xs font-medium", basisTone[e.basis]), children: t(e.basis) })] }), e.body && _jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: e.body }), _jsx("p", { className: "mt-1 text-xs text-faint-foreground", children: e.basis === "verified" ? _jsxs(_Fragment, { children: [t("evidence"), " ", e.evidence] }) : _jsxs(_Fragment, { children: [t("assumption"), " ", e.assumption] }) })] }, e.id))) }) })] }));
|
|
39
44
|
}
|
|
40
45
|
export { Reasoning };
|
package/dist/chat/thread.js
CHANGED
|
@@ -5,6 +5,13 @@ import { ArrowDown } from "lucide-react";
|
|
|
5
5
|
import { cn } from "../lib/utils.js";
|
|
6
6
|
import { useStrings } from "../blocks/locale.js";
|
|
7
7
|
import { useVirtualList } from "../lib/use-virtual-list.js";
|
|
8
|
+
import { motion, useMotionAllowed } from "../lib/motion.js";
|
|
9
|
+
/** The smooth scroll to the latest message. */
|
|
10
|
+
const scrollMotion = motion("thread", "scroll", "move");
|
|
11
|
+
/** Jump to latest appearing while the reader is scrolled up: 2.2's 150 ms and 4 px rise. */
|
|
12
|
+
const jumpMotion = motion("thread", "jump", "arrive", { dur: 150 });
|
|
13
|
+
/** Its ring when a message arrives while the reader is scrolled up. */
|
|
14
|
+
const arrivalMotion = motion("thread", "arrival", "attention");
|
|
8
15
|
/** Thread's fixed words; override them under `thread` in LocaleProvider's `strings`. */
|
|
9
16
|
export const threadStrings = {
|
|
10
17
|
messages: "Messages",
|
|
@@ -37,14 +44,15 @@ function Thread({ children, empty, footer, className, contentClassName, virtuali
|
|
|
37
44
|
const pinnedRef = React.useRef(!anchoring);
|
|
38
45
|
const [pinned, setPinned] = React.useState(!anchoring);
|
|
39
46
|
React.useImperativeHandle(scrollerRef, () => scroller.current, []);
|
|
47
|
+
// A smooth jump only when nothing asks for stillness: the OS setting (the calm level), data-motion="calm" or "none"
|
|
48
|
+
// around the thread, or a brand that turned the scroll off.
|
|
49
|
+
const smooth = useMotionAllowed(scroller, scrollMotion);
|
|
40
50
|
const toBottom = React.useCallback((behavior = "auto") => {
|
|
41
51
|
const el = scroller.current;
|
|
42
52
|
if (!el)
|
|
43
53
|
return;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
el.scrollTo({ top: el.scrollHeight, behavior: still ? "auto" : behavior });
|
|
47
|
-
}, []);
|
|
54
|
+
el.scrollTo({ top: el.scrollHeight, behavior: smooth() ? behavior : "auto" });
|
|
55
|
+
}, [smooth]);
|
|
48
56
|
// A new message while the reader is scrolled up: the Jump to latest control pulses once for each arrival, even when
|
|
49
57
|
// the messages sit inside a wrapper component or a fragment. A second arrival while it still pulses restarts it.
|
|
50
58
|
const [arrived, setArrived] = React.useState(false);
|
|
@@ -98,8 +106,11 @@ function Thread({ children, empty, footer, className, contentClassName, virtuali
|
|
|
98
106
|
const had = new Set(before);
|
|
99
107
|
const survivors = before.filter((node) => node.isConnected);
|
|
100
108
|
const added = known.filter((node) => !had.has(node));
|
|
101
|
-
const
|
|
102
|
-
|
|
109
|
+
const arrivals = added.filter((node) => survivors.every((old) => old.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_FOLLOWING));
|
|
110
|
+
// A message that arrived after the thread first drew rises in (Message reads data-arrived); loaded history is still.
|
|
111
|
+
for (const node of arrivals)
|
|
112
|
+
node.setAttribute("data-arrived", "");
|
|
113
|
+
if (arrivals.length)
|
|
103
114
|
arrive();
|
|
104
115
|
});
|
|
105
116
|
observer.observe(el, { childList: true, subtree: true });
|
|
@@ -177,10 +188,10 @@ function Thread({ children, empty, footer, className, contentClassName, virtuali
|
|
|
177
188
|
}
|
|
178
189
|
: undefined, onBlur: virtualize ? (e) => !e.currentTarget.contains(e.relatedTarget) && setFocused(null) : undefined, className: "h-full scroll-pb-14 overflow-y-auto overscroll-contain", children: _jsx("div", { ref: content, role: "log", "aria-label": t("messages"), className: cn("mx-auto flex w-full max-w-(--cd-thread-width) flex-col gap-7 px-4 py-6", contentClassName), children: !hasMessages ? (empty) : virtualize ? (_jsx(_Fragment, { children: list.rows.map((row) => "spacer" in row ? (_jsx("div", { "aria-hidden": true, style: { height: row.spacer } }, row.key)) : (_jsx("div", { ref: list.measure, "data-virtual-key": String(keyOf(row.index)), className: "flex flex-col", children: items[row.index] }, keyOf(row.index)))) })) : (children) }) }), !pinned && hasMessages && (
|
|
179
190
|
// The entrance is the wrapper's, so the pulse on the button never replays it.
|
|
180
|
-
_jsx("span", { className: "absolute bottom-3 left-1/2 z-10 -translate-x-1/2
|
|
191
|
+
_jsx("span", { style: jumpMotion, className: "absolute bottom-3 left-1/2 z-10 -translate-x-1/2 cd-arrive", children: _jsxs("button", { type: "button", "data-slot": "thread-jump", "data-arrived": arrived ? "" : undefined, onClick: () => {
|
|
181
192
|
pinnedRef.current = true;
|
|
182
193
|
setPinned(true);
|
|
183
194
|
toBottom("smooth");
|
|
184
|
-
}, className: cn("inline-flex items-center gap-1.5 rounded-full border bg-popover px-3 py-1.5 text-xs font-medium shadow-float [--cd-attention-color:var(--primary)]", arrived && "
|
|
195
|
+
}, style: arrivalMotion, className: cn("inline-flex items-center gap-1.5 rounded-full border bg-popover px-3 py-1.5 text-xs font-medium shadow-float [--cd-attention-color:var(--primary)]", arrived && "cd-attention"), children: [_jsx(ArrowDown, { "aria-hidden": true, className: "size-3" }), " ", t("jumpToLatest")] }) })), fade && _jsx("div", { "aria-hidden": true, "data-slot": "thread-fade", className: "pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-background to-transparent" })] }), footer && _jsx("div", { className: "mx-auto w-full max-w-(--cd-thread-width) px-4 pb-4", children: footer })] }));
|
|
185
196
|
}
|
|
186
197
|
export { Thread };
|
package/dist/chat/tool-call.js
CHANGED
|
@@ -9,6 +9,10 @@ import { useStatusLabel } from "../lib/status.js";
|
|
|
9
9
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js";
|
|
10
10
|
import { Payload } from "./payload.js";
|
|
11
11
|
import { useStrings } from "../blocks/locale.js";
|
|
12
|
+
import { motion } from "../lib/motion.js";
|
|
13
|
+
/** A call's detail opening, and a group of calls. */
|
|
14
|
+
const openMotion = motion("tool-call", "open", "reveal");
|
|
15
|
+
const groupMotion = motion("tool-call", "group-open", "reveal");
|
|
12
16
|
/** ToolCall's fixed words; override them under `toolCall` in LocaleProvider's `strings`. */
|
|
13
17
|
export const toolCallStrings = {
|
|
14
18
|
input: "Input",
|
|
@@ -35,7 +39,7 @@ function ToolCall({ call, className, }) {
|
|
|
35
39
|
const failed = call.status === "failed";
|
|
36
40
|
const [open, setOpen] = React.useState(failed);
|
|
37
41
|
const hasDetail = Boolean(call.input || call.output || call.error || call.approval?.note || call.approval?.originalInput);
|
|
38
|
-
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call", "data-status": call.status, className: cn("group/tool", className), children: [_jsxs(CollapsibleTrigger, { disabled: !hasDetail, className: "flex w-full items-center gap-2.5 rounded-md px-1.5 py-1 text-start outline-none
|
|
42
|
+
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call", "data-status": call.status, className: cn("group/tool", className), children: [_jsxs(CollapsibleTrigger, { disabled: !hasDetail, className: "flex w-full items-center gap-2.5 rounded-md px-1.5 py-1 text-start outline-none cd-tint enabled:hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/80", children: [_jsx(StepMark, { status: call.status }), _jsxs("span", { className: "min-w-0 flex-1 truncate text-sm", children: [call.status === "running" ? _jsx(ShimmerLabel, { className: "font-normal", children: call.title }) : call.title, _jsxs("span", { className: "sr-only", children: [" \u2014 ", statusLabel(call.status)] })] }), call.approval?.originalInput !== undefined && (_jsx("span", { "data-slot": "tool-call-edited", className: "shrink-0 rounded-xs bg-warning-weak px-1 text-2xs text-warning-text", children: t("editedBeforeRun") })), call.approval?.by === "automatic" && (_jsx("span", { "data-slot": "tool-call-approval", className: "shrink-0 rounded-xs bg-muted px-1 text-2xs text-muted-foreground", children: t("approvedAutomatically") })), _jsx("code", { className: "hidden shrink-0 font-mono text-2xs text-faint-foreground sm:inline", children: call.tool }), call.duration && _jsx("span", { className: "shrink-0 text-xs tabular-nums text-faint-foreground", children: call.duration }), hasDetail && (_jsx(ChevronRight, { "aria-hidden": true, style: openMotion, className: "rtl:-scale-x-100 size-3.5 shrink-0 text-faint-foreground cd-reveal-turn group-data-[state=open]/tool:[transform:rotate(90deg)]" }))] }), _jsx(CollapsibleContent, { style: openMotion, children: _jsxs("div", { className: "mt-1 mb-2 ms-[1.375rem] flex flex-col gap-2 border-s ps-3.5", children: [call.error && (_jsx("p", { className: "rounded-md bg-destructive-weak px-2.5 py-1.5 text-xs text-destructive-text", children: call.error })), call.approval?.note && (_jsxs("p", { className: "text-xs text-muted-foreground", children: [call.approval.by === "automatic" ? t("approvedAutomatically") : t("approvedByPerson"), ": ", call.approval.note] })), call.approval?.originalInput !== undefined && _jsx("p", { className: "text-xs text-muted-foreground", children: t("editedNote") }), call.input && _jsx(Payload, { language: "json", title: t("input"), text: call.input }), call.approval?.originalInput !== undefined && _jsx(Payload, { language: "json", title: t("proposed"), text: call.approval.originalInput }), call.output && _jsx(Payload, { language: call.outputLanguage ?? "text", title: t("output"), text: call.output })] }) })] }));
|
|
39
43
|
}
|
|
40
44
|
/** A run of tool calls, with a count when it's long enough to fold. */
|
|
41
45
|
function ToolCallGroup({ calls, className, }) {
|
|
@@ -47,6 +51,6 @@ function ToolCallGroup({ calls, className, }) {
|
|
|
47
51
|
if (calls.length <= 2) {
|
|
48
52
|
return (_jsx("div", { "data-slot": "tool-call-group", className: cn("flex flex-col", className), children: calls.map((c) => (_jsx(ToolCall, { call: c }, c.id))) }));
|
|
49
53
|
}
|
|
50
|
-
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call-group", className: cn("group/tools rounded-lg border bg-card", className), children: [_jsxs(CollapsibleTrigger, { className: "flex w-full items-center gap-2 px-3 py-2 text-start text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80 rounded-lg", children: [_jsx(Wrench, { "aria-hidden": true, className: "size-3.5" }), _jsxs("span", { className: "flex-1", children: [t("calls", { count: calls.length }), done > 0 && ` · ${t("done", { count: done })}`, failed > 0 && _jsxs("span", { className: "text-destructive-text", children: [" \u00B7 ", t("failed", { count: failed })] }), running && ` · ${t("working")}`] }), _jsx(ChevronRight, { "aria-hidden": true, className: "rtl:-scale-x-100 size-3.5
|
|
54
|
+
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "tool-call-group", className: cn("group/tools rounded-lg border bg-card", className), children: [_jsxs(CollapsibleTrigger, { className: "flex w-full items-center gap-2 px-3 py-2 text-start text-xs text-muted-foreground outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/80 rounded-lg", children: [_jsx(Wrench, { "aria-hidden": true, className: "size-3.5" }), _jsxs("span", { className: "flex-1", children: [t("calls", { count: calls.length }), done > 0 && ` · ${t("done", { count: done })}`, failed > 0 && _jsxs("span", { className: "text-destructive-text", children: [" \u00B7 ", t("failed", { count: failed })] }), running && ` · ${t("working")}`] }), _jsx(ChevronRight, { "aria-hidden": true, style: groupMotion, className: "rtl:-scale-x-100 size-3.5 cd-reveal-turn group-data-[state=open]/tools:[transform:rotate(90deg)]" })] }), _jsx(CollapsibleContent, { style: groupMotion, children: _jsx("div", { className: "flex flex-col border-t px-1.5 py-1.5", children: calls.map((c) => (_jsx(ToolCall, { call: c }, c.id))) }) })] }));
|
|
51
55
|
}
|
|
52
56
|
export { ToolCall, ToolCallGroup };
|
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,14 @@ export * from "./lib/render-prop.js";
|
|
|
43
43
|
export * from "./chat/ai-disclosure.js";
|
|
44
44
|
export * from "./chat/permission-prompt.js";
|
|
45
45
|
export * from "./lib/mcp-input.js";
|
|
46
|
+
export * from "./lib/motion.js";
|
|
47
|
+
export * from "./ui/label.js";
|
|
48
|
+
export * from "./ui/checkbox.js";
|
|
49
|
+
export * from "./ui/switch.js";
|
|
50
|
+
export * from "./ui/slider.js";
|
|
51
|
+
export * from "./ui/progress.js";
|
|
52
|
+
export * from "./ui/alert.js";
|
|
53
|
+
export * from "./ui/accordion.js";
|
|
46
54
|
export * from "./lib/utils.js";
|
|
47
55
|
export { ToolCall } from "./chat/tool-call.js";
|
|
48
56
|
export type { ToolCall as ToolCallData } from "./lib/chat.js";
|
package/dist/index.js
CHANGED
|
@@ -43,6 +43,14 @@ export * from "./lib/render-prop.js";
|
|
|
43
43
|
export * from "./chat/ai-disclosure.js";
|
|
44
44
|
export * from "./chat/permission-prompt.js";
|
|
45
45
|
export * from "./lib/mcp-input.js";
|
|
46
|
+
export * from "./lib/motion.js";
|
|
47
|
+
export * from "./ui/label.js";
|
|
48
|
+
export * from "./ui/checkbox.js";
|
|
49
|
+
export * from "./ui/switch.js";
|
|
50
|
+
export * from "./ui/slider.js";
|
|
51
|
+
export * from "./ui/progress.js";
|
|
52
|
+
export * from "./ui/alert.js";
|
|
53
|
+
export * from "./ui/accordion.js";
|
|
46
54
|
export * from "./lib/utils.js";
|
|
47
55
|
export { ToolCall } from "./chat/tool-call.js";
|
|
48
56
|
export { Message } from "./chat/message.js";
|