@candor-ui/react 1.0.0 → 1.1.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/LICENSE +3 -2
- package/dist/adapters/ag-ui.js +3 -3
- package/dist/adapters/ai-sdk.d.ts +2 -2
- package/dist/adapters/ai-sdk.js +7 -6
- package/dist/adapters/claude-agent-sdk.js +2 -2
- package/dist/blocks/copy-field.d.ts +6 -0
- package/dist/blocks/copy-field.js +9 -1
- package/dist/blocks/degraded-panel.d.ts +6 -0
- package/dist/blocks/degraded-panel.js +9 -1
- package/dist/blocks/locale.d.ts +86 -0
- package/dist/blocks/locale.js +146 -0
- package/dist/blocks/relative-time.d.ts +21 -3
- package/dist/blocks/relative-time.js +30 -11
- package/dist/blocks/status-dot.js +4 -2
- package/dist/blocks/step-list.js +4 -2
- package/dist/blocks/streaming-text.js +1 -1
- package/dist/blocks/surface.d.ts +2 -2
- package/dist/chat/agent-avatar.d.ts +14 -0
- package/dist/chat/agent-avatar.js +26 -2
- package/dist/chat/attachment-preview.d.ts +53 -0
- package/dist/chat/attachment-preview.js +82 -0
- package/dist/chat/card-registry.d.ts +7 -0
- package/dist/chat/card-registry.js +14 -4
- package/dist/chat/citation.d.ts +15 -0
- package/dist/chat/citation.js +34 -7
- package/dist/chat/composer.d.ts +27 -4
- package/dist/chat/composer.js +68 -20
- package/dist/chat/context-meter.d.ts +9 -0
- package/dist/chat/context-meter.js +21 -4
- package/dist/chat/entity-chip.d.ts +19 -0
- package/dist/chat/entity-chip.js +37 -3
- package/dist/chat/folded-turns.d.ts +33 -0
- package/dist/chat/folded-turns.js +29 -0
- package/dist/chat/message-actions.d.ts +9 -0
- package/dist/chat/message-actions.js +12 -1
- package/dist/chat/message-content.d.ts +12 -3
- package/dist/chat/message-content.js +37 -6
- package/dist/chat/message.d.ts +81 -4
- package/dist/chat/message.js +50 -9
- package/dist/chat/payload.d.ts +21 -4
- package/dist/chat/payload.js +38 -5
- package/dist/chat/prompt-suggestions.d.ts +6 -2
- package/dist/chat/prompt-suggestions.js +9 -2
- package/dist/chat/reasoning.d.ts +15 -0
- package/dist/chat/reasoning.js +12 -3
- package/dist/chat/thread-divider.d.ts +18 -0
- package/dist/chat/thread-divider.js +39 -0
- package/dist/chat/thread-event.d.ts +19 -0
- package/dist/chat/thread-event.js +15 -0
- package/dist/chat/thread.d.ts +5 -0
- package/dist/chat/thread.js +9 -2
- package/dist/chat/tool-call.d.ts +12 -0
- package/dist/chat/tool-call.js +16 -3
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/lib/chat.d.ts +31 -1
- package/dist/lib/highlight.d.ts +15 -0
- package/dist/lib/highlight.js +224 -0
- package/dist/lib/safe-markdown.d.ts +7 -2
- package/dist/lib/safe-markdown.js +13 -4
- package/dist/lib/status.d.ts +15 -0
- package/dist/lib/status.js +10 -1
- package/dist/styles.css +40 -1
- package/dist/ui/button.d.ts +1 -1
- package/package.json +2 -3
package/dist/chat/payload.d.ts
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
|
+
import * as React from "react";
|
|
1
2
|
import type { PayloadLanguage } from "../lib/chat.js";
|
|
3
|
+
/** Payload's fixed words; override them under `payload` in LocaleProvider's `strings`. */
|
|
4
|
+
export declare const payloadStrings: {
|
|
5
|
+
copy: string;
|
|
6
|
+
copied: string;
|
|
7
|
+
copyFailed: string;
|
|
8
|
+
shell: string;
|
|
9
|
+
text: string;
|
|
10
|
+
};
|
|
2
11
|
/**
|
|
3
12
|
* A block of code or query text from the agent: a query it ran, code it
|
|
4
13
|
* wrote, a config file. Rendered as text in a sunken well, with its language and a copy
|
|
5
|
-
* control that only says "Copied" once the clipboard took it.
|
|
14
|
+
* control that only says "Copied" once the clipboard took it. `bare` drops the frame and
|
|
15
|
+
* the caption, for a card or panel that already has its own.
|
|
16
|
+
*
|
|
17
|
+
* Code is split into tokens (keywords, strings, comments…) rendered as spans of text, never as HTML. They're coloured
|
|
18
|
+
* when `highlight` is set, or anywhere under `data-syntax="highlight"` (set it on `<html>` for the whole app).
|
|
6
19
|
*/
|
|
7
|
-
declare function Payload({ language, text, title, className, }: {
|
|
20
|
+
declare function Payload({ language, text, title, bare, highlight, className, }: {
|
|
8
21
|
/** Shown as the label, e.g. json, sql, python. Never executed or highlighted as HTML. */
|
|
9
22
|
language: PayloadLanguage;
|
|
10
23
|
/** The code or query, shown as text. */
|
|
11
24
|
text: string;
|
|
12
|
-
/** What it is, e.g. "The query I ran". */
|
|
25
|
+
/** What it is, e.g. "The query I ran". Not shown when `bare`. */
|
|
13
26
|
title?: string;
|
|
27
|
+
/** Just the code: no border, language label or copy button, for inside something that has them. */
|
|
28
|
+
bare?: boolean;
|
|
29
|
+
/** Colour the code by its syntax. Left out, it follows the nearest `data-syntax` attribute (off by default). */
|
|
30
|
+
highlight?: boolean;
|
|
14
31
|
className?: string;
|
|
15
|
-
}):
|
|
32
|
+
}): React.JSX.Element;
|
|
16
33
|
export { Payload };
|
package/dist/chat/payload.js
CHANGED
|
@@ -1,18 +1,51 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
3
4
|
import { Check, Copy, X } from "lucide-react";
|
|
4
5
|
import { cn } from "../lib/utils.js";
|
|
5
6
|
import { useCopy } from "../lib/use-copy.js";
|
|
6
|
-
|
|
7
|
+
import { tokenize } from "../lib/highlight.js";
|
|
8
|
+
import { useStrings } from "../blocks/locale.js";
|
|
9
|
+
/** Payload's fixed words; override them under `payload` in LocaleProvider's `strings`. */
|
|
10
|
+
export const payloadStrings = {
|
|
11
|
+
copy: "Copy",
|
|
12
|
+
copied: "Copied",
|
|
13
|
+
copyFailed: "Couldn’t copy",
|
|
14
|
+
shell: "Shell",
|
|
15
|
+
text: "Text",
|
|
16
|
+
};
|
|
17
|
+
// Language names are the same in every language; only the generic ones ("Shell", "Text") come from the strings table.
|
|
18
|
+
const knownLabels = { json: "JSON", xml: "XML", sql: "SQL", yaml: "YAML", html: "HTML", css: "CSS", js: "JavaScript", ts: "TypeScript", tsx: "TSX", python: "Python" };
|
|
19
|
+
const translatedLabels = { bash: "shell", sh: "shell", text: "text" };
|
|
7
20
|
/** "json" → "JSON", "python" → "Python", short unknown ones upper-cased ("hcl" → "HCL"). */
|
|
8
|
-
const languageLabel = (lang) =>
|
|
21
|
+
const languageLabel = (lang, t) => {
|
|
22
|
+
const key = translatedLabels[lang];
|
|
23
|
+
if (key)
|
|
24
|
+
return t(key);
|
|
25
|
+
return knownLabels[lang] ?? (lang.length <= 4 ? lang.toUpperCase() : lang.charAt(0).toUpperCase() + lang.slice(1));
|
|
26
|
+
};
|
|
9
27
|
/**
|
|
10
28
|
* A block of code or query text from the agent: a query it ran, code it
|
|
11
29
|
* wrote, a config file. Rendered as text in a sunken well, with its language and a copy
|
|
12
|
-
* control that only says "Copied" once the clipboard took it.
|
|
30
|
+
* control that only says "Copied" once the clipboard took it. `bare` drops the frame and
|
|
31
|
+
* the caption, for a card or panel that already has its own.
|
|
32
|
+
*
|
|
33
|
+
* Code is split into tokens (keywords, strings, comments…) rendered as spans of text, never as HTML. They're coloured
|
|
34
|
+
* when `highlight` is set, or anywhere under `data-syntax="highlight"` (set it on `<html>` for the whole app).
|
|
13
35
|
*/
|
|
14
|
-
function Payload({ language, text, title, className, }) {
|
|
36
|
+
function Payload({ language, text, title, bare = false, highlight, className, }) {
|
|
15
37
|
const { state, copy } = useCopy();
|
|
16
|
-
|
|
38
|
+
const t = useStrings("payload", payloadStrings);
|
|
39
|
+
const syntax = highlight === undefined ? undefined : highlight ? "highlight" : "off";
|
|
40
|
+
const code = _jsx(Code, { text: text, language: language });
|
|
41
|
+
if (bare) {
|
|
42
|
+
return (_jsx("pre", { "data-slot": "payload", "data-bare": "", "data-language": language, "data-syntax": syntax, className: cn("max-h-80 overflow-auto rounded-md bg-sunken px-3 py-2.5 font-mono text-xs leading-relaxed text-foreground", className), children: code }));
|
|
43
|
+
}
|
|
44
|
+
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: "inline-flex items-center gap-1 rounded-sm px-1.5 py-0.5 text-xs text-muted-foreground transition-colors duration-150 outline-none hover:bg-accent hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40", 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", { className: "max-h-80 overflow-auto px-3 py-2.5 font-mono text-xs leading-relaxed text-foreground", children: code })] }));
|
|
45
|
+
}
|
|
46
|
+
/** The code as token spans. Each token is a text child, so nothing in it is parsed as markup. */
|
|
47
|
+
function Code({ text, language }) {
|
|
48
|
+
const tokens = React.useMemo(() => tokenize(text, language), [text, language]);
|
|
49
|
+
return (_jsx("code", { children: tokens.map((tok, i) => tok.type === "plain" ? (_jsx(React.Fragment, { children: tok.text }, i)) : (_jsx("span", { "data-token": tok.type, children: tok.text }, i))) }));
|
|
17
50
|
}
|
|
18
51
|
export { Payload };
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import type { PromptSuggestion } from "../lib/chat.js";
|
|
2
|
+
/** PromptSuggestions' fixed words; override them under `promptSuggestions` in LocaleProvider's `strings`. */
|
|
3
|
+
export declare const promptSuggestionsStrings: {
|
|
4
|
+
label: string;
|
|
5
|
+
};
|
|
2
6
|
/**
|
|
3
7
|
* Prompts to start from. Choosing one puts it in the composer to edit, rather
|
|
4
8
|
* than sending it, because suggestions are model output too. Loading shows
|
|
5
9
|
* placeholders of the right shape; an empty list renders nothing.
|
|
6
10
|
*/
|
|
7
|
-
declare function PromptSuggestions({ suggestions, onChoose, label, loading, variant, className, }: {
|
|
11
|
+
declare function PromptSuggestions({ suggestions, onChoose, label: labelProp, loading, variant, className, }: {
|
|
8
12
|
/** What to offer. */
|
|
9
13
|
suggestions: PromptSuggestion[];
|
|
10
14
|
/** Called with the chosen one; put it in the composer to edit rather than sending it. */
|
|
11
15
|
onChoose: (suggestion: PromptSuggestion) => void;
|
|
12
|
-
/** The heading above the suggestions. */
|
|
16
|
+
/** The heading above the suggestions; "Try asking" by default. */
|
|
13
17
|
label?: string;
|
|
14
18
|
/** Shows placeholders while suggestions load. */
|
|
15
19
|
loading?: boolean;
|
|
@@ -3,16 +3,23 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { ArrowUpRight, Sparkles } from "lucide-react";
|
|
4
4
|
import { cn } from "../lib/utils.js";
|
|
5
5
|
import { Skeleton } from "../ui/skeleton.js";
|
|
6
|
+
import { useStrings } from "../blocks/locale.js";
|
|
7
|
+
/** PromptSuggestions' fixed words; override them under `promptSuggestions` in LocaleProvider's `strings`. */
|
|
8
|
+
export const promptSuggestionsStrings = {
|
|
9
|
+
label: "Try asking",
|
|
10
|
+
};
|
|
6
11
|
/**
|
|
7
12
|
* Prompts to start from. Choosing one puts it in the composer to edit, rather
|
|
8
13
|
* than sending it, because suggestions are model output too. Loading shows
|
|
9
14
|
* placeholders of the right shape; an empty list renders nothing.
|
|
10
15
|
*/
|
|
11
|
-
function PromptSuggestions({ suggestions, onChoose, label
|
|
16
|
+
function PromptSuggestions({ suggestions, onChoose, label: labelProp, loading = false, variant = "chips", className, }) {
|
|
17
|
+
const t = useStrings("promptSuggestions", promptSuggestionsStrings);
|
|
18
|
+
const label = labelProp ?? t("label");
|
|
12
19
|
if (!loading && suggestions.length === 0)
|
|
13
20
|
return null;
|
|
14
21
|
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
|
|
15
22
|
? Array.from({ length: variant === "cards" ? 4 : 3 }, (_, i) => (_jsx(Skeleton, { className: variant === "cards" ? "h-16 rounded-xl" : "h-8 w-44 rounded-lg" }, i)))
|
|
16
|
-
: 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-
|
|
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 transition-[border-color,box-shadow,transform] duration-150 ease-standard outline-none hover:border-border-strong hover:shadow-raised active:scale-[0.99] focus-visible:ring-[3px] focus-visible:ring-ring/40", children: [_jsx("span", { children: s.text }), _jsx(ArrowUpRight, { "aria-hidden": true, className: "size-3.5 shrink-0 text-faint-foreground transition-colors 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 transition-colors duration-150 outline-none hover:border-border-strong hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40", children: s.text }, s.id))) })] }));
|
|
17
24
|
}
|
|
18
25
|
export { PromptSuggestions };
|
package/dist/chat/reasoning.d.ts
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { ReasoningEntry } from "../lib/chat.js";
|
|
3
|
+
/** Reasoning's fixed words; override them under `reasoning` in LocaleProvider's `strings`. */
|
|
4
|
+
export declare const reasoningStrings: {
|
|
5
|
+
verified: string;
|
|
6
|
+
believed: string;
|
|
7
|
+
assumed: string;
|
|
8
|
+
reasoning: string;
|
|
9
|
+
steps: {
|
|
10
|
+
one: string;
|
|
11
|
+
other: string;
|
|
12
|
+
};
|
|
13
|
+
assumedCount: string;
|
|
14
|
+
thinking: string;
|
|
15
|
+
evidence: string;
|
|
16
|
+
assumption: string;
|
|
17
|
+
};
|
|
3
18
|
/**
|
|
4
19
|
* The agent's stated reasoning above its answer. Each step says what it rests
|
|
5
20
|
* on: verified against evidence, believed, or assumed (with the assumption
|
package/dist/chat/reasoning.js
CHANGED
|
@@ -5,10 +5,18 @@ import { Brain, ChevronRight } from "lucide-react";
|
|
|
5
5
|
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
|
+
/** Reasoning's fixed words; override them under `reasoning` in LocaleProvider's `strings`. */
|
|
10
|
+
export const reasoningStrings = {
|
|
9
11
|
verified: "Verified",
|
|
10
12
|
believed: "Believed",
|
|
11
13
|
assumed: "Assumed",
|
|
14
|
+
reasoning: "Reasoning",
|
|
15
|
+
steps: { one: "{count} step", other: "{count} steps" },
|
|
16
|
+
assumedCount: "{count} assumed",
|
|
17
|
+
thinking: "Thinking",
|
|
18
|
+
evidence: "Evidence:",
|
|
19
|
+
assumption: "Assumption:",
|
|
12
20
|
};
|
|
13
21
|
const basisTone = {
|
|
14
22
|
verified: "bg-success-weak text-success-text",
|
|
@@ -22,10 +30,11 @@ const basisTone = {
|
|
|
22
30
|
* when it answers.
|
|
23
31
|
*/
|
|
24
32
|
function Reasoning({ entries, thinking = false, thoughtFor, className, }) {
|
|
33
|
+
const t = useStrings("reasoning", reasoningStrings);
|
|
25
34
|
const [open, setOpen] = React.useState(thinking);
|
|
26
35
|
React.useEffect(() => setOpen(thinking), [thinking]);
|
|
27
36
|
const assumed = entries.filter((e) => e.basis === "assumed").length;
|
|
28
|
-
const summary = thinking ? null : (_jsxs(_Fragment, { children: ["
|
|
29
|
-
return (_jsxs(Collapsible, { open: open, onOpenChange: setOpen, "data-slot": "reasoning", className: cn("group/reasoning", className), children: [_jsxs(CollapsibleTrigger, { className: "inline-flex items-center gap-1.5 rounded-md py-0.5
|
|
37
|
+
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: "inline-flex items-center gap-1.5 rounded-md py-0.5 pe-1.5 text-xs text-muted-foreground outline-none transition-colors duration-150 hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40", 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, className: "rtl:-scale-x-100 size-3 transition-transform duration-200 ease-standard group-data-[state=open]/reasoning:[transform:rotate(90deg)]" })] }), _jsx(CollapsibleContent, { children: _jsx("ol", { className: "mt-2 ms-1.5 flex flex-col gap-3 border-s ps-4", children: entries.map((e) => (_jsxs("li", { className: "relative animate-rise-in", 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))) }) })] }));
|
|
30
39
|
}
|
|
31
40
|
export { Reasoning };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** ThreadDivider's fixed words; override them under `threadDivider` in LocaleProvider's `strings`. */
|
|
2
|
+
export declare const threadDividerStrings: {
|
|
3
|
+
today: string;
|
|
4
|
+
yesterday: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* A rule across the thread with a label in the middle: a day ("Today", "Yesterday", "5 Nov 2026") or a chapter of
|
|
8
|
+
* the conversation ("Drafting", "Review"). Read by screen readers as a separator with that label. Dates wait for the
|
|
9
|
+
* browser's clock and time zone, like every time in Candor, so server and browser render the same thing.
|
|
10
|
+
*/
|
|
11
|
+
declare function ThreadDivider({ date, label, className, }: {
|
|
12
|
+
/** A day to mark, shown as Today, Yesterday or the date in the locale. With neither this nor `label`, a plain rule. */
|
|
13
|
+
date?: Date | string;
|
|
14
|
+
/** A chapter or any other label; wins over `date`. */
|
|
15
|
+
label?: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
18
|
+
export { ThreadDivider };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { useFormat, useLocale, useStrings } from "../blocks/locale.js";
|
|
5
|
+
import { useNow } from "../blocks/relative-time.js";
|
|
6
|
+
/** ThreadDivider's fixed words; override them under `threadDivider` in LocaleProvider's `strings`. */
|
|
7
|
+
export const threadDividerStrings = {
|
|
8
|
+
today: "Today",
|
|
9
|
+
yesterday: "Yesterday",
|
|
10
|
+
};
|
|
11
|
+
/** The calendar day of a time, as y-m-d in a time zone (the browser's when left out). */
|
|
12
|
+
function dayOf(d, timeZone) {
|
|
13
|
+
return new Intl.DateTimeFormat("en-CA", { year: "numeric", month: "2-digit", day: "2-digit", ...(timeZone && { timeZone }) }).format(d);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A rule across the thread with a label in the middle: a day ("Today", "Yesterday", "5 Nov 2026") or a chapter of
|
|
17
|
+
* the conversation ("Drafting", "Review"). Read by screen readers as a separator with that label. Dates wait for the
|
|
18
|
+
* browser's clock and time zone, like every time in Candor, so server and browser render the same thing.
|
|
19
|
+
*/
|
|
20
|
+
function ThreadDivider({ date, label, className, }) {
|
|
21
|
+
const t = useStrings("threadDivider", threadDividerStrings);
|
|
22
|
+
const fmt = useFormat();
|
|
23
|
+
const now = useNow();
|
|
24
|
+
const { timeZone } = useLocale();
|
|
25
|
+
let text = label ?? "";
|
|
26
|
+
if (!label && date !== undefined) {
|
|
27
|
+
const d = typeof date === "string" ? new Date(date) : date;
|
|
28
|
+
if (now !== null) {
|
|
29
|
+
const day = dayOf(d, timeZone);
|
|
30
|
+
const today = dayOf(new Date(now), timeZone);
|
|
31
|
+
const yesterday = dayOf(new Date(now - 86_400_000), timeZone);
|
|
32
|
+
text = day === today ? t("today") : day === yesterday ? t("yesterday") : fmt.date(d, "date");
|
|
33
|
+
}
|
|
34
|
+
else
|
|
35
|
+
text = fmt.date(d, "date");
|
|
36
|
+
}
|
|
37
|
+
return (_jsxs("div", { "data-slot": "thread-divider", role: "separator", "aria-label": text || undefined, className: cn("flex items-center gap-3 py-1 text-xs font-medium text-muted-foreground", className), children: [_jsx("span", { "aria-hidden": true, className: "h-px flex-1 bg-border" }), _jsx("span", { "aria-hidden": true, children: text }), _jsx("span", { "aria-hidden": true, className: "h-px flex-1 bg-border" })] }));
|
|
38
|
+
}
|
|
39
|
+
export { ThreadDivider };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type LucideIcon } from "lucide-react";
|
|
3
|
+
/**
|
|
4
|
+
* A quiet, centred line in a thread for something that happened rather than something said: "Sam joined", "Switched
|
|
5
|
+
* to the staging environment", "Run stopped by a guardrail". System messages are drawn with it too, so app events and
|
|
6
|
+
* system lines look the same. Text here is the app's own, never model output.
|
|
7
|
+
*/
|
|
8
|
+
declare function ThreadEvent({ icon: Icon, tone, at, children, className, ...props }: {
|
|
9
|
+
/** The glyph before the text; `null` for none. Default: an info mark. */
|
|
10
|
+
icon?: LucideIcon | null;
|
|
11
|
+
/** "quiet" (the default) or "attention", for an event a reader shouldn't miss. */
|
|
12
|
+
tone?: "quiet" | "attention";
|
|
13
|
+
/** When it happened, shown after the text as a relative time. */
|
|
14
|
+
at?: string;
|
|
15
|
+
/** What happened, in a short line. */
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
} & Omit<React.ComponentProps<"div">, "children">): React.JSX.Element;
|
|
19
|
+
export { ThreadEvent };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Info } from "lucide-react";
|
|
5
|
+
import { cn } from "../lib/utils.js";
|
|
6
|
+
import { RelativeTime } from "../blocks/relative-time.js";
|
|
7
|
+
/**
|
|
8
|
+
* A quiet, centred line in a thread for something that happened rather than something said: "Sam joined", "Switched
|
|
9
|
+
* to the staging environment", "Run stopped by a guardrail". System messages are drawn with it too, so app events and
|
|
10
|
+
* system lines look the same. Text here is the app's own, never model output.
|
|
11
|
+
*/
|
|
12
|
+
function ThreadEvent({ icon: Icon = Info, tone = "quiet", at, children, className, ...props }) {
|
|
13
|
+
return (_jsxs("div", { "data-slot": "thread-event", "data-tone": tone, className: cn("flex items-center justify-center gap-1.5 py-1 text-center text-xs", tone === "attention" ? "text-attention-text" : "text-faint-foreground", className), ...props, children: [Icon && _jsx(Icon, { "aria-hidden": true, className: "size-3 shrink-0" }), children, at && (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": true, children: "\u00B7" }), _jsx(RelativeTime, { date: at })] }))] }));
|
|
14
|
+
}
|
|
15
|
+
export { ThreadEvent };
|
package/dist/chat/thread.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
/** Thread's fixed words; override them under `thread` in LocaleProvider's `strings`. */
|
|
3
|
+
export declare const threadStrings: {
|
|
4
|
+
messages: string;
|
|
5
|
+
jumpToLatest: string;
|
|
6
|
+
};
|
|
2
7
|
/**
|
|
3
8
|
* The scrolling message column. It follows new content while you're at the
|
|
4
9
|
* bottom and stops following the moment you scroll up to read, offering a
|
package/dist/chat/thread.js
CHANGED
|
@@ -3,6 +3,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { ArrowDown } from "lucide-react";
|
|
5
5
|
import { cn } from "../lib/utils.js";
|
|
6
|
+
import { useStrings } from "../blocks/locale.js";
|
|
7
|
+
/** Thread's fixed words; override them under `thread` in LocaleProvider's `strings`. */
|
|
8
|
+
export const threadStrings = {
|
|
9
|
+
messages: "Messages",
|
|
10
|
+
jumpToLatest: "Jump to latest",
|
|
11
|
+
};
|
|
6
12
|
/**
|
|
7
13
|
* The scrolling message column. It follows new content while you're at the
|
|
8
14
|
* bottom and stops following the moment you scroll up to read, offering a
|
|
@@ -10,6 +16,7 @@ import { cn } from "../lib/utils.js";
|
|
|
10
16
|
* the composer as `footer`.
|
|
11
17
|
*/
|
|
12
18
|
function Thread({ children, empty, footer, className, contentClassName, }) {
|
|
19
|
+
const t = useStrings("thread", threadStrings);
|
|
13
20
|
const scroller = React.useRef(null);
|
|
14
21
|
const content = React.useRef(null);
|
|
15
22
|
const pinnedRef = React.useRef(true);
|
|
@@ -51,10 +58,10 @@ function Thread({ children, empty, footer, className, contentClassName, }) {
|
|
|
51
58
|
lastTop.current = top;
|
|
52
59
|
};
|
|
53
60
|
const hasMessages = React.Children.count(children) > 0;
|
|
54
|
-
return (_jsxs("div", { "data-slot": "thread", className: cn("flex min-h-0 flex-col", className), children: [_jsxs("div", { className: "relative min-h-0 flex-1", children: [_jsx("div", { ref: scroller, onScroll: onScroll, className: "h-full overflow-y-auto overscroll-contain", children: _jsx("div", { ref: content, role: "log", "aria-label": "
|
|
61
|
+
return (_jsxs("div", { "data-slot": "thread", className: cn("flex min-h-0 flex-col", className), children: [_jsxs("div", { className: "relative min-h-0 flex-1", children: [_jsx("div", { ref: scroller, onScroll: onScroll, className: "h-full 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 ? children : empty }) }), !pinned && hasMessages && (_jsxs("button", { type: "button", onClick: () => {
|
|
55
62
|
pinnedRef.current = true;
|
|
56
63
|
setPinned(true);
|
|
57
64
|
toBottom("smooth");
|
|
58
|
-
}, className: "absolute bottom-3 left-1/2 z-10 inline-flex -translate-x-1/2 items-center gap-1.5 rounded-full border bg-popover px-3 py-1.5 text-xs font-medium shadow-float animate-in fade-in-0 slide-in-from-bottom-1", children: [_jsx(ArrowDown, { className: "size-3" }), "
|
|
65
|
+
}, className: "absolute bottom-3 left-1/2 z-10 inline-flex -translate-x-1/2 items-center gap-1.5 rounded-full border bg-popover px-3 py-1.5 text-xs font-medium shadow-float animate-in fade-in-0 slide-in-from-bottom-1", children: [_jsx(ArrowDown, { "aria-hidden": true, className: "size-3" }), " ", t("jumpToLatest")] })), _jsx("div", { "aria-hidden": true, 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 })] }));
|
|
59
66
|
}
|
|
60
67
|
export { Thread };
|
package/dist/chat/tool-call.d.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { ToolCall as ToolCallData } from "../lib/chat.js";
|
|
3
|
+
/** ToolCall's fixed words; override them under `toolCall` in LocaleProvider's `strings`. */
|
|
4
|
+
export declare const toolCallStrings: {
|
|
5
|
+
input: string;
|
|
6
|
+
output: string;
|
|
7
|
+
calls: {
|
|
8
|
+
one: string;
|
|
9
|
+
other: string;
|
|
10
|
+
};
|
|
11
|
+
done: string;
|
|
12
|
+
failed: string;
|
|
13
|
+
working: string;
|
|
14
|
+
};
|
|
3
15
|
/**
|
|
4
16
|
* One tool call as a compact row: what it did in plain words, its status, and
|
|
5
17
|
* how long it took. Expands to the input and output. A failed call opens by
|
package/dist/chat/tool-call.js
CHANGED
|
@@ -5,22 +5,35 @@ import { ChevronRight, Wrench } from "lucide-react";
|
|
|
5
5
|
import { cn } from "../lib/utils.js";
|
|
6
6
|
import { StepMark } from "../blocks/step-list.js";
|
|
7
7
|
import { ShimmerLabel } from "../blocks/streaming-text.js";
|
|
8
|
-
import {
|
|
8
|
+
import { useStatusLabel } from "../lib/status.js";
|
|
9
9
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../ui/collapsible.js";
|
|
10
10
|
import { Payload } from "./payload.js";
|
|
11
|
+
import { useStrings } from "../blocks/locale.js";
|
|
12
|
+
/** ToolCall's fixed words; override them under `toolCall` in LocaleProvider's `strings`. */
|
|
13
|
+
export const toolCallStrings = {
|
|
14
|
+
input: "Input",
|
|
15
|
+
output: "Output",
|
|
16
|
+
calls: { one: "{count} tool call", other: "{count} tool calls" },
|
|
17
|
+
done: "{count} done",
|
|
18
|
+
failed: "{count} failed",
|
|
19
|
+
working: "working",
|
|
20
|
+
};
|
|
11
21
|
/**
|
|
12
22
|
* One tool call as a compact row: what it did in plain words, its status, and
|
|
13
23
|
* how long it took. Expands to the input and output. A failed call opens by
|
|
14
24
|
* default and leads with its error.
|
|
15
25
|
*/
|
|
16
26
|
function ToolCall({ call, className, }) {
|
|
27
|
+
const t = useStrings("toolCall", toolCallStrings);
|
|
28
|
+
const statusLabel = useStatusLabel();
|
|
17
29
|
const failed = call.status === "failed";
|
|
18
30
|
const [open, setOpen] = React.useState(failed);
|
|
19
31
|
const hasDetail = Boolean(call.input || call.output || call.error);
|
|
20
|
-
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-
|
|
32
|
+
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 transition-colors duration-150 enabled:hover:bg-accent focus-visible:ring-[3px] focus-visible:ring-ring/40", 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)] })] }), _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, className: "rtl:-scale-x-100 size-3.5 shrink-0 text-faint-foreground transition-transform duration-200 ease-standard group-data-[state=open]/tool:[transform:rotate(90deg)]" }))] }), _jsx(CollapsibleContent, { className: "data-[state=open]:animate-in data-[state=open]:fade-in-0", 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.input && _jsx(Payload, { language: "json", title: t("input"), text: call.input }), call.output && _jsx(Payload, { language: call.outputLanguage ?? "text", title: t("output"), text: call.output })] }) })] }));
|
|
21
33
|
}
|
|
22
34
|
/** A run of tool calls, with a count when it's long enough to fold. */
|
|
23
35
|
function ToolCallGroup({ calls, className, }) {
|
|
36
|
+
const t = useStrings("toolCall", toolCallStrings);
|
|
24
37
|
const done = calls.filter((c) => c.status === "succeeded").length;
|
|
25
38
|
const failed = calls.filter((c) => c.status === "failed").length;
|
|
26
39
|
const running = calls.some((c) => c.status === "running");
|
|
@@ -28,6 +41,6 @@ function ToolCallGroup({ calls, className, }) {
|
|
|
28
41
|
if (calls.length <= 2) {
|
|
29
42
|
return (_jsx("div", { "data-slot": "tool-call-group", className: cn("flex flex-col", className), children: calls.map((c) => (_jsx(ToolCall, { call: c }, c.id))) }));
|
|
30
43
|
}
|
|
31
|
-
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-
|
|
44
|
+
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/40 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 transition-transform duration-200 ease-standard group-data-[state=open]/tools:[transform:rotate(90deg)]" })] }), _jsx(CollapsibleContent, { 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))) }) })] }));
|
|
32
45
|
}
|
|
33
46
|
export { ToolCall, ToolCallGroup };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./lib/status.js";
|
|
|
3
3
|
export * from "./lib/use-copy.js";
|
|
4
4
|
export * from "./lib/use-shortcut.js";
|
|
5
5
|
export * from "./blocks/terms.js";
|
|
6
|
+
export * from "./blocks/locale.js";
|
|
6
7
|
export * from "./blocks/locked.js";
|
|
7
8
|
export * from "./blocks/status-dot.js";
|
|
8
9
|
export * from "./blocks/step-list.js";
|
|
@@ -14,6 +15,7 @@ export * from "./blocks/surface.js";
|
|
|
14
15
|
export * from "./chat/agent-avatar.js";
|
|
15
16
|
export * from "./chat/entity-chip.js";
|
|
16
17
|
export * from "./chat/citation.js";
|
|
18
|
+
export * from "./lib/highlight.js";
|
|
17
19
|
export * from "./chat/payload.js";
|
|
18
20
|
export * from "./chat/message-content.js";
|
|
19
21
|
export * from "./chat/message.js";
|
|
@@ -21,7 +23,11 @@ export * from "./chat/message-actions.js";
|
|
|
21
23
|
export * from "./chat/reasoning.js";
|
|
22
24
|
export * from "./chat/tool-call.js";
|
|
23
25
|
export * from "./chat/thread.js";
|
|
26
|
+
export * from "./chat/thread-divider.js";
|
|
27
|
+
export * from "./chat/thread-event.js";
|
|
28
|
+
export * from "./chat/folded-turns.js";
|
|
24
29
|
export * from "./chat/composer.js";
|
|
30
|
+
export * from "./chat/attachment-preview.js";
|
|
25
31
|
export * from "./chat/prompt-suggestions.js";
|
|
26
32
|
export * from "./chat/context-meter.js";
|
|
27
33
|
export * from "./chat/card-registry.js";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./lib/status.js";
|
|
|
3
3
|
export * from "./lib/use-copy.js";
|
|
4
4
|
export * from "./lib/use-shortcut.js";
|
|
5
5
|
export * from "./blocks/terms.js";
|
|
6
|
+
export * from "./blocks/locale.js";
|
|
6
7
|
export * from "./blocks/locked.js";
|
|
7
8
|
export * from "./blocks/status-dot.js";
|
|
8
9
|
export * from "./blocks/step-list.js";
|
|
@@ -14,6 +15,7 @@ export * from "./blocks/surface.js";
|
|
|
14
15
|
export * from "./chat/agent-avatar.js";
|
|
15
16
|
export * from "./chat/entity-chip.js";
|
|
16
17
|
export * from "./chat/citation.js";
|
|
18
|
+
export * from "./lib/highlight.js";
|
|
17
19
|
export * from "./chat/payload.js";
|
|
18
20
|
export * from "./chat/message-content.js";
|
|
19
21
|
export * from "./chat/message.js";
|
|
@@ -21,7 +23,11 @@ export * from "./chat/message-actions.js";
|
|
|
21
23
|
export * from "./chat/reasoning.js";
|
|
22
24
|
export * from "./chat/tool-call.js";
|
|
23
25
|
export * from "./chat/thread.js";
|
|
26
|
+
export * from "./chat/thread-divider.js";
|
|
27
|
+
export * from "./chat/thread-event.js";
|
|
28
|
+
export * from "./chat/folded-turns.js";
|
|
24
29
|
export * from "./chat/composer.js";
|
|
30
|
+
export * from "./chat/attachment-preview.js";
|
|
25
31
|
export * from "./chat/prompt-suggestions.js";
|
|
26
32
|
export * from "./chat/context-meter.js";
|
|
27
33
|
export * from "./chat/card-registry.js";
|
package/dist/lib/chat.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export type MessageAuthor = {
|
|
|
44
44
|
export type EntityKind = string;
|
|
45
45
|
export type EntityRef = {
|
|
46
46
|
kind: EntityKind;
|
|
47
|
-
/** The identifier, e.g. "
|
|
47
|
+
/** The identifier, e.g. "orders.status" or "billing.invoice.due_date". */
|
|
48
48
|
name: string;
|
|
49
49
|
/** A friendlier label when there is one, e.g. "Region". */
|
|
50
50
|
label?: string;
|
|
@@ -65,6 +65,12 @@ export type InlineNode = {
|
|
|
65
65
|
text: string;
|
|
66
66
|
}
|
|
67
67
|
/** A value read from the environment (a count, a setting). Shown with its source on hover. */
|
|
68
|
+
| {
|
|
69
|
+
kind: "measured";
|
|
70
|
+
text: string;
|
|
71
|
+
source?: string;
|
|
72
|
+
}
|
|
73
|
+
/** @deprecated Renamed `measured`; still rendered the same way until 2.0. */
|
|
68
74
|
| {
|
|
69
75
|
kind: "orgData";
|
|
70
76
|
text: string;
|
|
@@ -136,6 +142,20 @@ export type PayloadLanguage = string;
|
|
|
136
142
|
export type MessageBlock = {
|
|
137
143
|
kind: "paragraph";
|
|
138
144
|
nodes: InlineNode[];
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Text exactly as someone typed it: line breaks and spacing kept, nothing parsed. For a person's own words, so
|
|
148
|
+
* `*this*` stays `*this*` and a pasted list keeps its lines.
|
|
149
|
+
*/
|
|
150
|
+
| {
|
|
151
|
+
kind: "plain";
|
|
152
|
+
text: string;
|
|
153
|
+
}
|
|
154
|
+
/** A heading inside a reply. Levels are relative to the message, 1 the largest; the page's own headings sit above. */
|
|
155
|
+
| {
|
|
156
|
+
kind: "heading";
|
|
157
|
+
level: 1 | 2 | 3;
|
|
158
|
+
nodes: InlineNode[];
|
|
139
159
|
} | {
|
|
140
160
|
kind: "list";
|
|
141
161
|
ordered?: boolean;
|
|
@@ -165,6 +185,11 @@ export type MessageBlock = {
|
|
|
165
185
|
card: string;
|
|
166
186
|
props: unknown;
|
|
167
187
|
summary: string;
|
|
188
|
+
}
|
|
189
|
+
/** Files sent with the message, shown as tiles. New in 1.1: a `switch` over kinds needs a `default` branch. */
|
|
190
|
+
| {
|
|
191
|
+
kind: "attachments";
|
|
192
|
+
files: Attachment[];
|
|
168
193
|
};
|
|
169
194
|
export type MessageStatus = "streaming" | "complete" | "interrupted" | "failed";
|
|
170
195
|
export type Message = {
|
|
@@ -209,7 +234,12 @@ export type SlashCommand = {
|
|
|
209
234
|
export type Attachment = {
|
|
210
235
|
id: string;
|
|
211
236
|
name: string;
|
|
237
|
+
/** In bytes. */
|
|
212
238
|
size?: number;
|
|
239
|
+
/** e.g. "image/png", "application/pdf": picks the icon, and allows an image preview. */
|
|
240
|
+
mediaType?: string;
|
|
241
|
+
/** A thumbnail your app serves (or a blob: URL), shown for images. Never a URL from model output. */
|
|
242
|
+
previewUrl?: string;
|
|
213
243
|
/** 0–1 while uploading; absent when done. */
|
|
214
244
|
progress?: number;
|
|
215
245
|
error?: string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small syntax highlighter for code an agent shows: it splits text into typed tokens (keyword, string, comment…),
|
|
3
|
+
* and Payload renders each as a span. The output is text, never HTML, so nothing in the code can run or inject markup.
|
|
4
|
+
* It covers the languages agents show most (JS/TS, Python, SQL, JSON, shell, YAML, CSS, HTML/XML, Go, Rust, Java, C)
|
|
5
|
+
* in a few kilobytes; anything else comes back as one plain token.
|
|
6
|
+
*/
|
|
7
|
+
export type TokenType = "keyword" | "string" | "number" | "comment" | "function" | "property" | "type" | "tag" | "variable" | "operator" | "plain";
|
|
8
|
+
export type Token = {
|
|
9
|
+
type: TokenType;
|
|
10
|
+
text: string;
|
|
11
|
+
};
|
|
12
|
+
/** Whether `language` has a grammar here. */
|
|
13
|
+
export declare function canHighlight(language: string): boolean;
|
|
14
|
+
/** The text split into typed tokens; their texts joined give back the input exactly. */
|
|
15
|
+
export declare function tokenize(text: string, language: string): Token[];
|