@candor-ui/react 2.2.0 → 2.4.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/README.md +3 -3
- 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 +23 -2
- package/dist/blocks/relative-time.js +1 -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.d.ts +6 -1
- package/dist/chat/attachment-preview.js +9 -3
- package/dist/chat/card-registry.js +6 -4
- package/dist/chat/citation.js +3 -3
- package/dist/chat/composer.js +19 -53
- package/dist/chat/context-meter.d.ts +5 -0
- package/dist/chat/context-meter.js +15 -1
- package/dist/chat/entity-chip.d.ts +3 -1
- package/dist/chat/entity-chip.js +2 -2
- 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.d.ts +5 -0
- package/dist/chat/permission-prompt.js +36 -2
- package/dist/chat/prompt-suggestions.js +4 -1
- package/dist/chat/reasoning.js +6 -1
- package/dist/chat/thread.js +37 -9
- package/dist/chat/tool-call.js +6 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/lib/motion.d.ts +466 -0
- package/dist/lib/motion.js +380 -0
- package/dist/lib/sound.d.ts +44 -0
- package/dist/lib/sound.js +63 -0
- package/dist/styles.css +562 -45
- package/dist/ui/accordion.d.ts +23 -0
- package/dist/ui/accordion.js +45 -0
- package/dist/ui/alert.d.ts +22 -0
- package/dist/ui/alert.js +59 -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 +2 -3
package/dist/chat/composer.js
CHANGED
|
@@ -7,6 +7,14 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
|
|
|
7
7
|
import { EntityChip, useEntityKinds } from "./entity-chip.js";
|
|
8
8
|
import { AttachmentChip } from "./attachment-preview.js";
|
|
9
9
|
import { useStrings } from "../blocks/locale.js";
|
|
10
|
+
import { motion, useArrived, useLeaving } from "../lib/motion.js";
|
|
11
|
+
/** A chip added after the first draw pops in; a removed one pops out as a ghost. */
|
|
12
|
+
const chipMotion = motion("composer", "chip", "pop");
|
|
13
|
+
/** The mention and command menu: 2.2's fade and 4 px rise. */
|
|
14
|
+
const menuMotion = motion("composer", "menu", "overlay", { variant: "slide" });
|
|
15
|
+
/** Send and Stop acknowledge the press: 2.2's scale of 0.95. */
|
|
16
|
+
const sendMotion = motion("composer", "send", "press", { amp: "0.9" });
|
|
17
|
+
const sendingMotion = motion("composer", "sending", "live", { dur: 1000 });
|
|
10
18
|
/** Composer's fixed words; override them under `composer` in LocaleProvider's `strings`. */
|
|
11
19
|
export const composerStrings = {
|
|
12
20
|
placeholder: "Ask a question, or describe a change",
|
|
@@ -42,55 +50,9 @@ const groupOf = (item, commandsLabel = "Commands") => (item.type === "command" ?
|
|
|
42
50
|
const hasFiles = (e) => Array.from(e.dataTransfer?.types ?? []).includes("Files");
|
|
43
51
|
const referenceKey = (r) => `${r.kind}:${r.name}`;
|
|
44
52
|
const attachmentKey = (a) => a.id;
|
|
45
|
-
/**
|
|
46
|
-
* Which chips were added after the composer first drew (they pop in) and which were just removed (they pop out as
|
|
47
|
-
* inert ghosts, then go). Chips present at first render are drawn still: nothing moves for a state the reader
|
|
48
|
-
* already had. The ghosts are cleared by their animation's end, or after 400ms when nothing animates.
|
|
49
|
-
*/
|
|
50
|
-
function useChipMotion(items, keyOf) {
|
|
51
|
-
const [entered, setEntered] = React.useState(() => new Set());
|
|
52
|
-
const [leaving, setLeaving] = React.useState([]);
|
|
53
|
-
const previous = React.useRef(null);
|
|
54
|
-
React.useLayoutEffect(() => {
|
|
55
|
-
const before = previous.current;
|
|
56
|
-
previous.current = items;
|
|
57
|
-
if (!before)
|
|
58
|
-
return;
|
|
59
|
-
const now = new Set(items.map(keyOf));
|
|
60
|
-
const was = new Set(before.map(keyOf));
|
|
61
|
-
const added = items.filter((item) => !was.has(keyOf(item))).map(keyOf);
|
|
62
|
-
const gone = before.filter((item) => !now.has(keyOf(item)));
|
|
63
|
-
if (added.length || gone.length) {
|
|
64
|
-
setEntered((set) => {
|
|
65
|
-
const next = new Set(set);
|
|
66
|
-
for (const item of gone)
|
|
67
|
-
next.delete(keyOf(item));
|
|
68
|
-
for (const key of added)
|
|
69
|
-
next.add(key);
|
|
70
|
-
return next;
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
// A chip added again while its ghost is still leaving takes the ghost's place: the ghost goes at once.
|
|
74
|
-
if (gone.length || added.length) {
|
|
75
|
-
setLeaving((list) => {
|
|
76
|
-
const kept = list.filter((item) => !now.has(keyOf(item)));
|
|
77
|
-
return kept.length === list.length && gone.length === 0 ? list : [...kept, ...gone];
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}, [items, keyOf]);
|
|
81
|
-
React.useEffect(() => {
|
|
82
|
-
if (leaving.length === 0)
|
|
83
|
-
return;
|
|
84
|
-
const timer = window.setTimeout(() => setLeaving([]), 400);
|
|
85
|
-
return () => window.clearTimeout(timer);
|
|
86
|
-
}, [leaving]);
|
|
87
|
-
const entering = (item) => entered.has(keyOf(item));
|
|
88
|
-
const left = (item) => setLeaving((list) => list.filter((x) => x !== item));
|
|
89
|
-
return { entering, leaving, left };
|
|
90
|
-
}
|
|
91
53
|
/** A removed chip, kept for the length of its pop-out and hidden from assistive tech. */
|
|
92
54
|
function LeavingChip({ onDone, children }) {
|
|
93
|
-
return (_jsx("span", { "aria-hidden": true, "data-leaving": true, className: "pointer-events-none inline-flex max-w-full
|
|
55
|
+
return (_jsx("span", { "aria-hidden": true, "data-leaving": true, style: chipMotion, className: "pointer-events-none inline-flex max-w-full cd-pop-out", onAnimationEnd: onDone, children: children }));
|
|
94
56
|
}
|
|
95
57
|
/**
|
|
96
58
|
* Where a person asks. Mentions (@) attach things in the user's system as chips the agent
|
|
@@ -118,8 +80,12 @@ function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, o
|
|
|
118
80
|
const fileInput = React.useRef(null);
|
|
119
81
|
const [trigger, setTrigger] = React.useState(null);
|
|
120
82
|
const [active, setActive] = React.useState(0);
|
|
121
|
-
|
|
122
|
-
|
|
83
|
+
// Chips added after the composer first drew pop in; chips just removed pop out as inert ghosts, then go. Chips there
|
|
84
|
+
// at first render are drawn still: nothing moves for a state the reader already had.
|
|
85
|
+
const referenceArrived = useArrived(React.useMemo(() => references.map(referenceKey), [references]));
|
|
86
|
+
const attachmentArrived = useArrived(React.useMemo(() => attachments.map(attachmentKey), [attachments]));
|
|
87
|
+
const referenceMotion = useLeaving(references, referenceKey);
|
|
88
|
+
const attachmentMotion = useLeaving(attachments, attachmentKey);
|
|
123
89
|
const held = typeof state === "object" && state.kind === "held" ? state : null;
|
|
124
90
|
const locked = typeof state === "object" && state.kind !== "held" ? state : null;
|
|
125
91
|
const canAttach = Boolean(onAttach) && !locked;
|
|
@@ -243,7 +209,7 @@ function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, o
|
|
|
243
209
|
const files = Array.from(e.dataTransfer.files);
|
|
244
210
|
if (files.length)
|
|
245
211
|
onAttach(files);
|
|
246
|
-
}, children: [dragDepth > 0 && (_jsx("div", { "aria-hidden": true, className: "pointer-events-none absolute inset-0 z-30 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary bg-card/90 text-sm font-medium text-accent-text", children: t("dropFiles") })), menuOpen && (_jsx("div", { id: listId, role: "listbox", "aria-label": trigger?.char === "/" ? t("commands") : t("mentions"), className: "absolute inset-x-0 bottom-full z-20 mb-2 max-h-72 overflow-y-auto rounded-xl border bg-popover p-1 shadow-float
|
|
212
|
+
}, children: [dragDepth > 0 && (_jsx("div", { "aria-hidden": true, className: "pointer-events-none absolute inset-0 z-30 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary bg-card/90 text-sm font-medium text-accent-text", children: t("dropFiles") })), menuOpen && (_jsx("div", { id: listId, role: "listbox", "aria-label": trigger?.char === "/" ? t("commands") : t("mentions"), style: menuMotion, className: "absolute inset-x-0 bottom-full z-20 mb-2 max-h-72 overflow-y-auto rounded-xl border bg-popover p-1 shadow-float [--cd-overlay-distance:0.25rem] [--cd-side-y:1] cd-overlay", children: items.map((item, i) => {
|
|
247
213
|
const group = groupOf(item, t("commands"));
|
|
248
214
|
const prev = items[i - 1];
|
|
249
215
|
const header = !prev || groupOf(prev, t("commands")) !== group ? group : null;
|
|
@@ -252,7 +218,7 @@ function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, o
|
|
|
252
218
|
e.preventDefault();
|
|
253
219
|
choose(item);
|
|
254
220
|
}, onMouseMove: () => setActive(i), className: "flex cursor-pointer items-center gap-2.5 rounded-md px-2 py-1.5 text-sm aria-selected:bg-accent", children: item.type === "command" ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "font-mono text-xs text-accent-text", children: ["/", item.command.name] }), item.command.args && _jsx("span", { className: "font-mono text-xs text-faint-foreground", children: item.command.args }), _jsx("span", { className: "ms-auto truncate text-xs text-muted-foreground", children: item.command.description })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "flex size-6 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground", children: Icon ? _jsx(Icon, { className: "size-3.5" }) : _jsx("span", { className: "text-xs", children: "@" }) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate", children: item.option.label }), (item.option.description ?? item.option.entity?.name) !== item.option.label && (item.option.description ?? item.option.entity?.name) && (_jsx("span", { className: "block truncate font-mono text-2xs text-faint-foreground", children: item.option.description ?? item.option.entity?.name }))] })] })) })] }, item.type === "command" ? item.command.id : item.option.id));
|
|
255
|
-
}) })), _jsxs("div", { className: cn("rounded-2xl border bg-card shadow-raised
|
|
221
|
+
}) })), _jsxs("div", { className: cn("rounded-2xl border bg-card shadow-raised cd-tint focus-within:border-border-strong focus-within:shadow-float", locked && "bg-muted shadow-none"), children: [(references.length > 0 || attachments.length > 0 || referenceMotion.leaving.length > 0 || attachmentMotion.leaving.length > 0) && (_jsxs("div", { className: "flex flex-wrap gap-1.5 px-3 pt-3", children: [references.map((r) => (_jsx(EntityChip, { entity: r, onRemove: (e) => onReferencesChange?.(references.filter((x) => x !== e)), style: chipMotion, className: cn(referenceArrived(referenceKey(r)) && "cd-pop") }, referenceKey(r)))), referenceMotion.leaving.map((r) => (_jsx(LeavingChip, { onDone: () => referenceMotion.left(r), children: _jsx(EntityChip, { entity: r }) }, `leaving:${referenceKey(r)}`))), attachments.map((a) => (_jsx(AttachmentChip, { attachment: a, onRemove: onRemoveAttachment, style: chipMotion, className: cn(attachmentArrived(a.id) && "cd-pop") }, a.id))), attachmentMotion.leaving.map((a) => (_jsx(LeavingChip, { onDone: () => attachmentMotion.left(a), children: _jsx(AttachmentChip, { attachment: a }) }, `leaving:${a.id}`)))] })), _jsx("label", { htmlFor: id, className: "sr-only", children: label }), _jsx("textarea", { ref: textarea, id: id, rows: 1, value: value, disabled: Boolean(locked), placeholder: locked ? undefined : placeholder, onChange: (e) => {
|
|
256
222
|
onChange(e.target.value);
|
|
257
223
|
requestAnimationFrame(syncTrigger);
|
|
258
224
|
}, onKeyDown: onKeyDown, onKeyUp: (e) => {
|
|
@@ -271,8 +237,8 @@ function Composer({ value, onChange, onSubmit, onStop, state = "ready", scope, o
|
|
|
271
237
|
if (files.length)
|
|
272
238
|
onAttach(files);
|
|
273
239
|
e.target.value = "";
|
|
274
|
-
} }), _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
|
|
240
|
+
} }), _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 cd-tint 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 cd-tint 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"))] })] })), hints && (
|
|
275
241
|
// Only what this composer can do: @ and / are named only when there's something for them to offer.
|
|
276
|
-
_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"), mentions.length > 0 && (_jsxs(_Fragment, { children: [" · ", _jsx("kbd", { className: "font-sans", children: "@" }), " ", t("hintMention")] })), commands.length > 0 && (_jsxs(_Fragment, { children: [" · ", _jsx("kbd", { className: "font-sans", children: "/" }), " ", t("hintCommand")] }))] })), 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
|
|
242
|
+
_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"), mentions.length > 0 && (_jsxs(_Fragment, { children: [" · ", _jsx("kbd", { className: "font-sans", children: "@" }), " ", t("hintMention")] })), commands.length > 0 && (_jsxs(_Fragment, { children: [" · ", _jsx("kbd", { className: "font-sans", children: "/" }), " ", t("hintCommand")] }))] })), state === "streaming" && onStop ? (_jsx("button", { type: "button", "aria-label": t("stop"), onClick: onStop, style: sendMotion, className: cn("ms-auto flex size-8 items-center justify-center bg-foreground text-background cd-press", hints && "@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", style: sendMotion, className: cn("ms-auto flex size-8 items-center justify-center bg-primary text-primary-foreground cd-press enabled:hover:bg-primary-hover disabled:opacity-30", hints && "@lg:ms-1", sendShape === "round" ? "rounded-full" : "rounded-lg"), children: state === "sending" ? _jsx(LoaderCircle, { style: sendingMotion, className: "size-4 cd-live-spin" }) : _jsx(ArrowUp, { className: "size-4", strokeWidth: 2.5 }) }))] })] })] }));
|
|
277
243
|
}
|
|
278
244
|
export { Composer };
|
|
@@ -15,6 +15,11 @@ export declare const contextMeterStrings: {
|
|
|
15
15
|
* Counts are the service's own token counts; the ring warns past 80%. When a
|
|
16
16
|
* new count arrives the ring sweeps to it and changes colour at the warning
|
|
17
17
|
* line; it draws its first value in place, with no sweep from zero.
|
|
18
|
+
*
|
|
19
|
+
* @motion fill:fill The ring moves from the old measured value to the new one when the count changes while the meter is
|
|
20
|
+
* on screen, and changes colour at the warning line. The first value is drawn in place, never swept from empty.
|
|
21
|
+
* @motion value:value The percentage beside the ring crossfades to the new figure in the quick duration when the count
|
|
22
|
+
* changes while the meter is on screen; it never counts through values that were not measured.
|
|
18
23
|
*/
|
|
19
24
|
declare function ContextMeter({ segments, limit, className, }: {
|
|
20
25
|
/** What fills the context window, by kind, in tokens. */
|
|
@@ -4,6 +4,13 @@ import * as React from "react";
|
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
5
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
|
|
6
6
|
import { useFormat, useStrings } from "../blocks/locale.js";
|
|
7
|
+
import { motion, useSettled } from "../lib/motion.js";
|
|
8
|
+
/** The ring moving to a new measured value, after the first one was drawn in place. */
|
|
9
|
+
const fillMotion = motion("context-meter", "fill", "fill");
|
|
10
|
+
/** The percentage changing in place. */
|
|
11
|
+
const valueMotion = motion("context-meter", "value", "value");
|
|
12
|
+
/** Every new figure counts as a change worth showing. */
|
|
13
|
+
const changed = { settled: () => true, over: () => false };
|
|
7
14
|
/** ContextMeter's fixed words; override them under `contextMeter` in LocaleProvider's `strings`. */
|
|
8
15
|
export const contextMeterStrings = {
|
|
9
16
|
/** A token count in thousands; `{n}` is already formatted. */
|
|
@@ -33,6 +40,11 @@ function k(n, format, thousands) {
|
|
|
33
40
|
* Counts are the service's own token counts; the ring warns past 80%. When a
|
|
34
41
|
* new count arrives the ring sweeps to it and changes colour at the warning
|
|
35
42
|
* line; it draws its first value in place, with no sweep from zero.
|
|
43
|
+
*
|
|
44
|
+
* @motion fill:fill The ring moves from the old measured value to the new one when the count changes while the meter is
|
|
45
|
+
* on screen, and changes colour at the warning line. The first value is drawn in place, never swept from empty.
|
|
46
|
+
* @motion value:value The percentage beside the ring crossfades to the new figure in the quick duration when the count
|
|
47
|
+
* changes while the meter is on screen; it never counts through values that were not measured.
|
|
36
48
|
*/
|
|
37
49
|
function ContextMeter({ segments, limit, className, }) {
|
|
38
50
|
const t = useStrings("contextMeter", contextMeterStrings);
|
|
@@ -46,7 +58,9 @@ function ContextMeter({ segments, limit, className, }) {
|
|
|
46
58
|
// The sweep is a transition, so it runs only for a value that changed after the first one was drawn.
|
|
47
59
|
const [drawn, setDrawn] = React.useState(false);
|
|
48
60
|
React.useEffect(() => setDrawn(true), []);
|
|
49
|
-
|
|
61
|
+
const percent = fmt.number(Math.round(pct * 100) / 100, { style: "percent" });
|
|
62
|
+
const percentChanges = useSettled(percent, changed);
|
|
63
|
+
return (_jsxs(Popover, { children: [_jsxs(PopoverTrigger, { "data-slot": "context-meter", "aria-label": t("label", { used: tokens(used), limit: tokens(limit) }), "data-warn": warn ? "" : undefined, 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", { "data-slot": "context-meter-ring", cx: "9", cy: "9", r: r, fill: "none", stroke: warn ? "var(--warning)" : "var(--primary)", strokeWidth: "2.5", strokeDasharray: `${pct * c} ${c}`, strokeLinecap: "round", style: fillMotion, className: cn(drawn && "cd-fill") })] }), percentChanges > 0 ? (_jsx("span", { style: valueMotion, className: "cd-value", children: percent }, percentChanges)) : (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]
|
|
50
64
|
.sort((a, b) => b.tokens - a.tokens)
|
|
51
65
|
.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") })] })] }));
|
|
52
66
|
}
|
|
@@ -44,7 +44,7 @@ declare function useEntityKinds(): (kind: string) => EntityKindInfo;
|
|
|
44
44
|
* identifier is the label unless a friendlier one is given; then the
|
|
45
45
|
* identifier is on hover and for screen readers.
|
|
46
46
|
*/
|
|
47
|
-
declare function EntityChip({ entity, onOpen, onRemove, className, }: {
|
|
47
|
+
declare function EntityChip({ entity, onOpen, onRemove, className, style, }: {
|
|
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. */
|
|
@@ -52,5 +52,7 @@ declare function EntityChip({ entity, onOpen, onRemove, className, }: {
|
|
|
52
52
|
/** Adds a remove button, e.g. in the composer. */
|
|
53
53
|
onRemove?: ((entity: EntityRef) => void) | undefined;
|
|
54
54
|
className?: string | undefined;
|
|
55
|
+
/** Inline style for the chip's outer element, such as a motion chain from `motion()`. */
|
|
56
|
+
style?: React.CSSProperties | undefined;
|
|
55
57
|
}): React.JSX.Element;
|
|
56
58
|
export { EntityChip, EntityKindsProvider, useEntityKinds };
|
package/dist/chat/entity-chip.js
CHANGED
|
@@ -72,7 +72,7 @@ function useEntityKinds() {
|
|
|
72
72
|
* identifier is the label unless a friendlier one is given; then the
|
|
73
73
|
* identifier is on hover and for screen readers.
|
|
74
74
|
*/
|
|
75
|
-
function EntityChip({ entity, onOpen, onRemove, className, }) {
|
|
75
|
+
function EntityChip({ entity, onOpen, onRemove, className, style, }) {
|
|
76
76
|
const { Icon, noun } = useEntityKinds()(entity.kind);
|
|
77
77
|
const t = useStrings("entityChip", entityChipStrings);
|
|
78
78
|
const shown = entity.label ?? entity.name;
|
|
@@ -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
|
|
84
|
+
return (_jsxs("span", { "data-slot": "entity-chip", "data-kind": entity.kind, title: title, style: style, 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 cd-tint 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 };
|
|
@@ -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 }) {
|
|
@@ -113,6 +113,11 @@ export type PermissionAnswer = {
|
|
|
113
113
|
* nothing else is ever counted as a denial. While the decision waits, its shield pulses slowly; a press on inert
|
|
114
114
|
* space outside it (the gesture that would dismiss a dialog) nudges it instead, so the person sees it is still
|
|
115
115
|
* waiting. Both stop once the answer is being sent.
|
|
116
|
+
*
|
|
117
|
+
* @motion attention:waiting While the decision waits, a ring pulses out from the shield three times and then is still.
|
|
118
|
+
* It stops once the answer is being sent, and under calm it is one ring that fades.
|
|
119
|
+
* @motion attention:nudge A press on inert space outside the prompt rings it once, so the person sees it is still waiting.
|
|
120
|
+
* @motion refuse:nudge Under expressive the same press also shakes the prompt a little; nothing is dismissed.
|
|
116
121
|
*/
|
|
117
122
|
declare function PermissionPrompt({ request, mode, suggestions, editable, onAllow, onDeny, busy, className, }: {
|
|
118
123
|
/** The waiting request. */
|
|
@@ -6,6 +6,21 @@ 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
|
+
/** How long the animations running on `el` take in all, in milliseconds (the longest of duration × iteration count, capped at 6 s); undefined when it can't be read. */
|
|
11
|
+
function animationMs(el) {
|
|
12
|
+
if (!el || typeof getComputedStyle === "undefined")
|
|
13
|
+
return undefined;
|
|
14
|
+
const style = getComputedStyle(el);
|
|
15
|
+
const durations = style.animationDuration.split(",").map((t) => (t.trim().endsWith("ms") ? Number.parseFloat(t) : Number.parseFloat(t) * 1000));
|
|
16
|
+
const counts = style.animationIterationCount.split(",").map((c) => (c.trim() === "infinite" ? Number.POSITIVE_INFINITY : Number.parseFloat(c)));
|
|
17
|
+
const totals = durations.map((d, i) => d * (counts[i % counts.length] || 1)).filter((n) => Number.isFinite(n) || n === Number.POSITIVE_INFINITY);
|
|
18
|
+
return totals.length ? Math.min(6000, Math.max(...totals)) : undefined;
|
|
19
|
+
}
|
|
20
|
+
/** Three pulses of 1.6 s while the decision waits (4.8 s, then still), shortened from 2.2's 7.2 s. */
|
|
21
|
+
const waitingMotion = motion("permission-prompt", "waiting", "attention", { dur: 1600, amp: "3" });
|
|
22
|
+
/** A press around the prompt instead of on it: one ring, and the refuse shake under expressive. */
|
|
23
|
+
const nudgeMotion = { ...motion("permission-prompt", "nudge", "refuse"), ...motion("permission-prompt", "nudge", "attention") };
|
|
9
24
|
/** PermissionPrompt's fixed words; override them under `permissionPrompt` in LocaleProvider's `strings`. */
|
|
10
25
|
export const permissionPromptStrings = {
|
|
11
26
|
label: "Permission request: {title}",
|
|
@@ -83,6 +98,11 @@ function parseObject(text) {
|
|
|
83
98
|
* nothing else is ever counted as a denial. While the decision waits, its shield pulses slowly; a press on inert
|
|
84
99
|
* space outside it (the gesture that would dismiss a dialog) nudges it instead, so the person sees it is still
|
|
85
100
|
* waiting. Both stop once the answer is being sent.
|
|
101
|
+
*
|
|
102
|
+
* @motion attention:waiting While the decision waits, a ring pulses out from the shield three times and then is still.
|
|
103
|
+
* It stops once the answer is being sent, and under calm it is one ring that fades.
|
|
104
|
+
* @motion attention:nudge A press on inert space outside the prompt rings it once, so the person sees it is still waiting.
|
|
105
|
+
* @motion refuse:nudge Under expressive the same press also shakes the prompt a little; nothing is dismissed.
|
|
86
106
|
*/
|
|
87
107
|
function PermissionPrompt({ request, mode, suggestions, editable = false, onAllow, onDeny, busy = false, className, }) {
|
|
88
108
|
// Keyed by the request, so an edit, a ticked rule or an answer never carries over to the next request.
|
|
@@ -111,6 +131,7 @@ function PromptBody({ request, mode, suggestions, editable, onAllow, onDeny, bus
|
|
|
111
131
|
// A press on inert space outside every prompt, while it waits, nudges it rather than dismissing anything.
|
|
112
132
|
const section = React.useRef(null);
|
|
113
133
|
const [nudged, setNudged] = React.useState(false);
|
|
134
|
+
const ended = React.useRef(new Set());
|
|
114
135
|
React.useEffect(() => {
|
|
115
136
|
if (busy)
|
|
116
137
|
return;
|
|
@@ -131,7 +152,11 @@ function PromptBody({ request, mode, suggestions, editable, onAllow, onDeny, bus
|
|
|
131
152
|
React.useEffect(() => {
|
|
132
153
|
if (!nudged)
|
|
133
154
|
return;
|
|
134
|
-
|
|
155
|
+
// The nudge ends when its animations do (onAnimationEnd on the section). This timer is only the fallback for when
|
|
156
|
+
// none runs (motion off, or no Candor CSS), set from the duration and count the page resolved, so a brand's longer
|
|
157
|
+
// ring or shake isn't cut short.
|
|
158
|
+
ended.current = new Set();
|
|
159
|
+
const timer = window.setTimeout(() => setNudged(false), (animationMs(section.current) ?? 1200) + 100);
|
|
135
160
|
return () => window.clearTimeout(timer);
|
|
136
161
|
}, [nudged]);
|
|
137
162
|
const word = (s) => {
|
|
@@ -166,7 +191,16 @@ function PromptBody({ request, mode, suggestions, editable, onAllow, onDeny, bus
|
|
|
166
191
|
return;
|
|
167
192
|
onAllow({ ...(edited && changedKeys.length && { updatedInput: edited }), ...(ticked.length && { alwaysAllow: ticked }) });
|
|
168
193
|
};
|
|
169
|
-
return (_jsxs("section", { ref: section, "data-slot": "permission-prompt", "data-waiting": busy ? undefined : "", "data-nudged": nudged ? "" : undefined,
|
|
194
|
+
return (_jsxs("section", { ref: section, "data-slot": "permission-prompt", "data-waiting": busy ? undefined : "", "data-nudged": nudged ? "" : undefined, onAnimationEnd: (e) => {
|
|
195
|
+
// The nudge runs two animations (the shake and the ring) and ends with the last of them. A child's own
|
|
196
|
+
// animation, or one that isn't Candor's, doesn't end it.
|
|
197
|
+
if (e.target !== e.currentTarget || !e.animationName.startsWith("cd-"))
|
|
198
|
+
return;
|
|
199
|
+
ended.current.add(e.animationName);
|
|
200
|
+
const running = getComputedStyle(e.currentTarget).animationName.split(",").map((n) => n.trim()).filter((n) => n.startsWith("cd-"));
|
|
201
|
+
if (running.every((n) => ended.current.has(n)))
|
|
202
|
+
setNudged(false);
|
|
203
|
+
}, "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
204
|
const a = answers[q.id];
|
|
171
205
|
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
206
|
}) })) : (_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 };
|