@kitn.ai/ui
Version:
Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS.
981 lines • 113 kB
TypeScript
import { registerAll, WebComponentProps } from './runtime';
export { registerAll };
export { useKaiChat } from './use-kai-chat';
export type { UseKaiChatOptions, KaiChatController, ChatMessage } from './use-kai-chat';
export interface AgentCardProps extends WebComponentProps {
/** The agent's name — the primary label. Attribute: `name`. */
name?: string;
/** Selected / focused state: highlighted border + surface. Attribute: `active`. */
active?: boolean;
/** Raise a prominent "Needs you" pill plus a glowing amber edge — the attention-routing signal that pulls focus to this agent. Attribute: `needs-attention`. */
needsAttention?: boolean;
/** Run status — a JS PROPERTY (object), not an attribute. Shape: `{ tone, label?, pulse? }`, where `tone` is one of `working` | `idle` | `done` | `error` | `blocked` (maps to the kit's tool hues), `label` is an optional short string beside the dot, and `pulse` animates the dot. Set it with `el.status = { tone: 'working', label: 'Working', pulse: true }`. */
status?: {
tone: "working" | "idle" | "done" | "error" | "blocked";
label?: string;
pulse?: boolean;
};
/** The card was activated — clicked, or Enter / Space while focused. Promote this agent back to focus. */
onActivate?: (event: CustomEvent) => void;
/** The trailing "..." kebab was clicked. The consumer opens its own menu; the card only surfaces the affordance (the click does not also activate the card). */
onMenu?: (event: CustomEvent) => void;
}
export declare const AgentCard: import('react').ForwardRefExoticComponent<AgentCardProps & import('react').RefAttributes<HTMLElement>>;
export interface ArtifactProps extends WebComponentProps {
/** URL the preview iframe frames. Consumer-controlled. */
src?: string;
/** Files for the Code tab tree + each file's preview `url`. Set as a JS property (array). */
files: {
path: string;
url?: undefined | string;
code?: undefined | string;
language?: undefined | string;
type?: undefined | "html" | "pdf" | "image" | "other";
additions?: undefined | number;
deletions?: undefined | number;
status?: undefined | "added" | "modified" | "deleted" | "renamed" | "untracked";
}[];
/** Controlled active tab: `preview` or `code`. When set, the artifact follows it (re-asserted on change). Leave unset for an uncontrolled tab (see `defaultTab`). */
tab?: "preview" | "code";
/** Uncontrolled INITIAL tab (used only when `tab` is unset). Default `preview`. Seeds the starting tab; the user can then switch freely without the consumer re-asserting a controlled `tab`. */
defaultTab?: "preview" | "code";
/** Selected file path — syncs the tree highlight, Code source, and preview. */
activeFile?: string;
/** iframe `sandbox` override. Secure default `allow-scripts allow-forms` (NOT `allow-same-origin`). */
sandbox?: string;
/** Accessible title for the preview iframe. */
iframeTitle?: string;
/** Reflects the artifact's own maximized view-state (usually driven by the protocol). */
maximized?: boolean;
/** Show the expand-to-fill button (OPT-IN). */
expandable?: boolean;
/** Show the open-in-new-tab button (OPT-IN). */
openInTab?: boolean;
/** Hide back/forward. */
noNav?: boolean;
/** Hide reload. */
noReload?: boolean;
/** Hide home. */
noHome?: boolean;
/** Hide the address field. */
noPathField?: boolean;
/** Hide the Preview|Code toggle. */
noTabs?: boolean;
/** Standalone chrome: rounded corners + border (else square, borderless in-panel). */
standalone?: boolean;
/** Show the address but make it read-only (visible, nav-tracking, non-editable). */
readonlyPath?: boolean;
/** Friendly address shown in the path field instead of the real current url (read-only, non-navigable). Use when the framed url is not consumer-facing (e.g. a `data:` blob) so a clean address shows instead of leaking it. Scalar string: set as the `display-url` attribute or the `displayUrl` property. */
displayUrl?: string;
/** Fired when a file is selected. `detail.path`. */
onFileSelect?: (event: CustomEvent<{
path: string;
}>) => void;
/** Artifact's own maximize button toggled (consumer-observable; non-bubbling). */
onMaximizeChange?: (event: CustomEvent<{
maximized: boolean;
}>) => void;
/** Fired when the preview navigates. `detail.url` = the new location. */
onNavigate?: (event: CustomEvent<{
url: string;
}>) => void;
/** Fired when the Preview|Code tab changes. `detail.tab`. */
onTabChange?: (event: CustomEvent<{
tab: "preview" | "code";
}>) => void;
}
export declare const Artifact: import('react').ForwardRefExoticComponent<ArtifactProps & import('react').RefAttributes<HTMLElement>>;
export interface AttachmentsProps extends WebComponentProps {
/** The attachments to render. Set as a JS property (array). */
items: {
id: string;
type: "file" | "source-document";
filename?: undefined | string;
mediaType?: undefined | string;
url?: undefined | string;
title?: undefined | string;
}[];
/** Layout: `grid` = visual tiles, `inline` = icon + label chips, `list` = rows. */
variant?: "grid" | "inline" | "list";
/** Wrap each item in a hover card that previews its details. */
hoverCard?: boolean;
/** Show a remove button per item; clicking it fires a `kai-remove` event. */
removable?: boolean;
/** Also show the media type beneath the filename (non-grid variants). */
showMediaType?: boolean;
/** Text shown when `items` is empty. */
emptyText?: string;
/** A remove button was clicked. */
onRemove?: (event: CustomEvent<{
id: string;
}>) => void;
}
export declare const Attachments: import('react').ForwardRefExoticComponent<AttachmentsProps & import('react').RefAttributes<HTMLElement>>;
export interface AvatarProps extends WebComponentProps {
/** Image URL/data-URI. When absent, the `fallback` initials show instead. */
src?: string;
/** Alt text for the image. Defaults to `fallback`. */
alt?: string;
/** Short text shown when there's no image — usually initials (e.g. "JD", "AI"). */
fallback?: string;
/** Size token: `sm` | `md` (default) | `lg`. */
size?: "sm" | "md" | "lg";
}
export declare const Avatar: import('react').ForwardRefExoticComponent<AvatarProps & import('react').RefAttributes<HTMLElement>>;
export interface BadgeProps extends WebComponentProps {
/** `default` (muted pill) · `count` (compact number badge) · `citation` (filled primary, for inline citation markers). Defaults to `default`. */
variant?: "default" | "count" | "citation";
}
export declare const Badge: import('react').ForwardRefExoticComponent<BadgeProps & import('react').RefAttributes<HTMLElement>>;
export interface ButtonProps extends WebComponentProps {
/** Visual style. `default` (filled), `subtle` (muted text, hover tint — the toolbar icon look), `ghost` (transparent, hover fill), `outline`, or `destructive`. Defaults to `default`. */
variant?: "default" | "subtle" | "ghost" | "outline" | "destructive";
/** Size token. `icon` / `icon-sm` are square (for icon-only buttons); `sm` / `md` / `lg` size text buttons. Defaults to `md`. */
size?: "sm" | "md" | "lg" | "icon" | "icon-sm";
/** Leading icon: a named icon (e.g. `"mic"`, `"plus"`), an image URL/data-URI, or plain text. Renders before any slotted label. */
icon?: string;
/** Trailing icon, after the label (e.g. `"chevron-down"` for a menu affordance). */
iconTrailing?: string;
/** Accessible name. REQUIRED for icon-only buttons (no visible text); ignored when you slot visible text, which already names the button. */
label?: string;
/** Disable the button (non-interactive, dimmed). */
disabled?: boolean;
/** Stretch the button to the full width of its container (a block button) — e.g. a card CTA or a stacked action. Attribute: `full`. */
full?: boolean;
/** Justify the button's content: `start`, `center` (default), or `end`. Combine with `full` for a full-width, left-aligned button. */
align?: "start" | "center" | "end";
/** Native button `type`. Defaults to `button` (so it never submits a form). */
type?: "button" | "submit" | "reset";
/** The button was activated (pointer or keyboard). Carries no detail. The native `click` also bubbles (composed) for consumers who prefer it. */
onClick?: (event: CustomEvent) => void;
}
export declare const Button: import('react').ForwardRefExoticComponent<ButtonProps & import('react').RefAttributes<HTMLElement>>;
export interface CardProps extends WebComponentProps {
/** Surface treatment: `outlined` (default) | `filled` | `plain` | `accent`. Attribute: `appearance`. */
appearance?: "outlined" | "filled" | "plain" | "accent";
/** `vertical` (default, media on top) | `horizontal` (media at the start) | `responsive` (horizontal when the card's container is wide enough, else vertical — a container query on the card's own width). Attribute: `orientation`. */
orientation?: "vertical" | "horizontal" | "responsive";
/** The card width below which a `responsive` card collapses to vertical and the footer actions stack. A CSS length; default `28rem`. Attribute: `collapse`. */
collapse?: string;
/** Tighter spacing for dense lists. Attribute: `dense`. */
dense?: boolean;
/** Show a close (×) that hides the card and emits `kai-dismiss`. Attribute: `dismissible`. Off by default. */
dismissible?: boolean;
/** Render the whole card as a link. Attribute: `href`. Wins over `clickable`. */
href?: string;
/** `target` for the `href` anchor. Attribute: `target`. */
target?: string;
/** `rel` for the `href` anchor. Attribute: `rel`. */
rel?: string;
/** Make the whole card a button (`role="button"`, Enter/Space, hover affordance) that emits `kai-card-click`. Attribute: `clickable`. Ignored when `href` is set. */
clickable?: boolean;
/** A `clickable`/`href` card was activated (click, or Enter/Space). */
onCardClick?: (event: CustomEvent) => void;
/** The card was dismissed via its × (it also hides itself). */
onDismiss?: (event: CustomEvent) => void;
}
export declare const Card: import('react').ForwardRefExoticComponent<CardProps & import('react').RefAttributes<HTMLElement>>;
export interface CardsProps extends WebComponentProps {
/** The stream of card envelopes to render. Set as a JS PROPERTY: `el.cards = [...]`. */
cards?: {
type: string;
id: string;
data: unknown;
title?: string;
resolution?: {
kind: "action";
action: string;
payload?: unknown;
at?: string;
} | {
kind: "submit";
data: unknown;
at?: string;
} | {
kind: "dismissed";
at?: string;
} | {
kind: "expired";
reason?: string;
at?: string;
};
}[];
/** Optional type→tag overrides/additions (merged over the built-ins). Property: `el.types`. Typed as a plain string map (not the `CardTagMap` alias) so the generated React wrapper inlines it instead of emitting an unresolved named type. */
types?: Record<string, string>;
/** Optional CardPolicy handling child events. Property: `el.policy`. */
policy?: {
onSubmit?: (cardId: string, data: unknown) => void;
onAction?: (cardId: string, action: string, payload?: unknown) => void;
onSendPrompt?: (text: string, opts: {
mode: "compose" | "send";
context?: unknown;
}) => void;
onOpen?: (url: string, target: "tab" | "artifact") => void;
onState?: (cardId: string, patch: unknown) => void;
onDismiss?: (cardId: string) => void;
onReopen?: (cardId: string) => void;
onError?: (cardId: string, message: string) => void;
maxSendPromptMode?: "compose" | "send";
};
/** A child card transitioned to a resolved/deferred state (an action was chosen, a form/tasks submission landed, or it was dismissed) — re-emitted off the host as a non-bubbling convenience event so a consumer can observe resolution centrally without diffing the cards array. `detail` = `{ cardId, resolution }`. (A `reopen` un-resolves a card and has no `CardResolution`, so it does NOT fire this — observe reopen via the underlying bubbling `kai-card` event.) */
onCardResolved?: (event: CustomEvent<{
cardId: string;
resolution: {
kind: "action";
action: string;
payload?: unknown;
at?: undefined | string;
} | {
kind: "submit";
data: unknown;
at?: undefined | string;
} | {
kind: "dismissed";
at?: undefined | string;
} | {
kind: "expired";
reason?: undefined | string;
at?: undefined | string;
};
}>) => void;
}
export declare const Cards: import('react').ForwardRefExoticComponent<CardsProps & import('react').RefAttributes<HTMLElement>>;
export interface ChainOfThoughtProps extends WebComponentProps {
/** The reasoning steps. Set as a JS property. Compound sub-parts collapse to this one data model (Route 1). Each `{ label, content?, id? }`. */
steps: {
label: string;
content?: undefined | string;
id?: undefined | string;
}[];
/** Open mode: `'multiple'` (default — any number of steps open at once) or `'single'` (at most one open; opening a step closes the others). */
type?: "single" | "multiple";
/** Controlled open step key(s). When set, it WINS over user interaction (the consumer owns the open set). String in `single` mode, string[] in `multiple` mode. Set as a JS property. */
value?: string | string[];
/** Uncontrolled INITIAL open step key(s) — seeds which steps render expanded. Ignored once `value` is provided. Set as a JS property. */
defaultValue?: string | string[];
/** The open set changed — by user click OR an expand()/collapse()/toggle() call. `value` is a string in `single` mode, a string[] in `multiple` mode. (Maps Radix Accordion's onValueChange.) */
onValueChange?: (event: CustomEvent<{
value: string | string[];
}>) => void;
}
export declare const ChainOfThought: import('react').ForwardRefExoticComponent<ChainOfThoughtProps & import('react').RefAttributes<HTMLElement>>;
export interface ChatProps extends WebComponentProps {
/** The full message thread to render, newest last. Each entry carries its role, content, and optional reasoning/tools/attachments/actions. Set as a JS property (`el.messages = [...]`). */
messages: {
id: string;
role: "user" | "assistant";
content: string;
reasoning?: undefined | {
text: string;
label?: undefined | string;
};
tools?: undefined | {
type: string;
state: "input-streaming" | "input-available" | "output-available" | "output-error";
input?: undefined | Record<string, unknown>;
output?: undefined | Record<string, unknown>;
toolCallId?: undefined | string;
errorText?: undefined | string;
}[];
attachments?: undefined | {
id: string;
type: "file" | "source-document";
filename?: undefined | string;
mediaType?: undefined | string;
url?: undefined | string;
title?: undefined | string;
}[];
actions?: undefined | ("copy" | "like" | "dislike" | "regenerate" | "edit" | {
id: string;
label: string;
icon?: undefined | string;
tooltip?: undefined | string;
})[];
avatar?: undefined | {
src?: undefined | string;
fallback?: undefined | string;
alt?: undefined | string;
};
feedback?: undefined | "like" | "dislike";
}[];
/** Value of the input. A **string** is controlled (the host owns the text and updates it on `kai-value-change`). A **ComposerDoc** is a one-time seed that pre-populates pills; the user then edits freely. Leave unset for uncontrolled. */
value?: string | ({
type: "text";
text: string;
} | {
type: "entity";
entity: {
kind: string;
id: string;
label: string;
icon?: string;
promptText?: string;
data?: Record<string, unknown>;
};
})[];
/** Placeholder text shown in the empty input. */
placeholder?: string;
/** When true, shows the loading/streaming state and disables submit (use while awaiting the assistant's reply). */
loading?: boolean;
/** Starter prompts shown above the input when the thread is empty. Clicking one follows `suggestionMode`. Set as a JS property. */
suggestions?: string[];
/** What clicking a suggestion does: `'submit'` (default) sends it immediately as if typed and submitted; `'fill'` just places it in the input. */
suggestionMode?: "submit" | "fill";
/** Keep suggestions visible after the conversation starts. By default suggestions are conversation starters and hide once `messages` is non-empty; set this to keep them always shown. Default false. */
persistSuggestions?: boolean;
/** Body/prose font scale for rendered markdown (`'xs' | 'sm' | 'base' | 'lg'`). Defaults to `'sm'`. */
proseSize?: "sm" | "lg" | "xs" | "base";
/** Shiki theme name for syntax-highlighted code blocks (e.g. `'github-dark-dimmed'`). */
codeTheme?: string;
/** Enable Shiki syntax highlighting in code blocks. Turn off to render plain `<pre>` blocks (lighter, no highlighter load). Default true. */
codeHighlight?: boolean;
/** Optional header title shown on the left of the header. */
chatTitle?: string;
/** Optional model list. When set (>1 model) a ModelSwitcher is shown in the header and a `kai-model-change` event fires on selection. */
models?: {
id: string;
name: string;
provider?: string;
description?: string;
group?: string;
}[];
/** The currently selected model id (pairs with `models`). */
currentModel?: string;
/** Optional context-window token usage. When set, a Context token meter is shown in the header. */
context?: {
usedTokens: number;
maxTokens: number;
inputTokens?: number;
outputTokens?: number;
estimatedCost?: number;
};
/** Show the scroll-to-bottom button inside the scroll area. Default true. */
scrollButton?: boolean;
/** Whether the host has `slot="header-start"` content (left of the title) — set by the `<kai-chat>` facade so a custom control forces the header open. */
headerStart?: boolean;
/** Whether the host has `slot="header-end"` content (right of the controls). */
headerEnd?: boolean;
/** REPLACE — full custom header in place of the built-in title/model/context bar. */
headerFull?: boolean;
/** INJECT — left sidebar column (e.g. a conversation list / your own nav). */
sidebar?: boolean;
/** REPLACE — custom zero-state rendered in the message area while the thread is empty (replaces the empty message list only; the composer and its suggestions still render). */
empty?: boolean;
/** REPLACE — full custom composer in place of the built-in prompt input. The projected content wires its own submit (the data-flow boundary). */
composer?: boolean;
/** INJECT — accessory row just above the composer (e.g. extra actions). */
composerActions?: boolean;
/** INJECT — footer row below the composer (disclaimers, token meter, …). */
footer?: boolean;
/** Show a Search (Globe) button in the input toolbar; fires a `search` event. */
search?: boolean;
/** Show a Voice (Mic) button in the input toolbar; fires a `voice` event. */
voice?: boolean;
/** Rich entity triggers — each `{ char, kind, items }` opens a caret-anchored menu that inserts an atomic pill (`/` skills, `@` agents/plugins). Set as a JS property; forwarded to the input. */
triggers?: {
char: string;
kind: string;
items?: {
id: string;
label: string;
icon?: string;
description?: string;
group?: string;
kind?: string;
promptText?: string;
data?: Record<string, unknown>;
}[];
}[];
/** Default icon per entity kind (kind → image src) for pills/menu items. */
kindIcons?: Record<string, string>;
/** Whether each message's action bar is always visible (`'always'`, default) or only revealed on hover of that message row (`'hover'`). */
actionsReveal?: "always" | "hover";
/** The staged attachments changed (file added or removed). Carries the full current list so a consumer can react in real time. */
onAttachmentsChange?: (event: CustomEvent<{
attachments: {
id: string;
type: "file" | "source-document";
filename?: undefined | string;
mediaType?: undefined | string;
url?: undefined | string;
title?: undefined | string;
}[];
}>) => void;
/** An action button on a message was clicked. `action` is the built-in name or custom id. `state` is present only for the toggleable feedback votes: `'on'` when a like/dislike is set, `'off'` when re-tapped to clear. */
onMessageAction?: (event: CustomEvent<{
messageId: string;
action: string;
state?: undefined | "on" | "off";
}>) => void;
/** The header model switcher changed. */
onModelChange?: (event: CustomEvent<{
modelId: string;
}>) => void;
/** The Search button was clicked. */
onSearch?: (event: CustomEvent<Record<string, never>>) => void;
/** User submitted a message. */
onSubmit?: (event: CustomEvent<{
value: string;
attachments: {
id: string;
type: "file" | "source-document";
filename?: undefined | string;
mediaType?: undefined | string;
url?: undefined | string;
title?: undefined | string;
}[];
}>) => void;
/** A suggestion chip was clicked (only in `suggestion-mode="fill"`). */
onSuggestionClick?: (event: CustomEvent<{
value: string;
}>) => void;
/** Fired on every input change. */
onValueChange?: (event: CustomEvent<{
value: string;
}>) => void;
/** The Mic / voice button was clicked. */
onVoice?: (event: CustomEvent<Record<string, never>>) => void;
}
export declare const Chat: import('react').ForwardRefExoticComponent<ChatProps & import('react').RefAttributes<HTMLElement>>;
export interface CheckpointProps extends WebComponentProps {
/** Optional text beside the icon. */
label?: string;
/** Tooltip on hover. */
tooltip?: string;
/** Visual button style. */
variant?: "default" | "ghost" | "outline";
/** Button size (use an `icon*` size for an icon-only checkpoint). */
size?: "sm" | "md" | "lg" | "icon" | "icon-sm";
/** The checkpoint was clicked. */
onSelect?: (event: CustomEvent) => void;
}
export declare const Checkpoint: import('react').ForwardRefExoticComponent<CheckpointProps & import('react').RefAttributes<HTMLElement>>;
export interface ChoiceProps extends WebComponentProps {
/** The choice definition (the CardEnvelope.data). Set as a JS PROPERTY: `el.data = { prompt, options:[…], allowOther?, submitLabel? }`. Import `ChoiceCardData` from `@kitn.ai/ui` for the full shape. */
data?: Record<string, unknown>;
/** Stable card id correlating every emitted CardEvent. Attribute: `card-id`. */
cardId?: string;
/** Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. */
heading?: string;
/** Set when the user resolved this card; renders the read-only view. Property: `el.resolution = { kind:'action', action:'…' }`. */
resolution?: Record<string, unknown>;
/** Controlled selection — the selected option id. When set, the consumer owns the current pick (RadioGroup `value`). Attribute: `value`. */
value?: string;
/** Option id to pre-select on mount (uncontrolled seed). Attribute: `default-value`. */
defaultValue?: string;
/** Disable the whole radiogroup + Submit (e.g. while the agent is busy). Attribute: `disabled`. */
disabled?: boolean;
/** The selection changed BEFORE submit (a row click or the `select()` method). Distinct from the terminal `action` verb on the `kai-card` contract event. */
onValueChange?: (event: CustomEvent<{
value: string;
}>) => void;
}
export declare const Choice: import('react').ForwardRefExoticComponent<ChoiceProps & import('react').RefAttributes<HTMLElement>>;
export interface CoachmarkProps extends WebComponentProps {
/** Drive/observe open state (Shoelace-style: settable + reflected to the `open` attribute; the element still self-manages). Set `el.open = true`, or `<kai-coachmark open>`; listen for `kai-open-change`. */
open?: boolean;
/** Initial open state on mount (uncontrolled seed). */
defaultOpen?: boolean;
/** The bold title. Named `headline` because `title` collides with the global `HTMLElement.title` attribute (it throws at registration). */
headline?: string;
/** A small badge pill beside the headline (e.g. "New"). */
badge?: string;
/** Floating placement relative to the anchor (default `bottom`). */
placement?: string;
/** Color tone: `primary` (default, theme accent), `info` (blue), `success` (green), `warning` (amber), or `error` (red) — reusing the kit's tool hues. */
tone?: "error" | "primary" | "info" | "success" | "warning";
/** Render the arrow that points at the anchor (default `true`). Set `arrow="false"` for a plain bubble with no pointer. */
arrow?: boolean;
/** The × dismiss button was pressed. The consumer records that this hint was seen so it won't show again. */
onDismiss?: (event: CustomEvent<Record<string, never>>) => void;
/** The coachmark opened or closed (a method, the ×, or a driven `open`). */
onOpenChange?: (event: CustomEvent<{
open: boolean;
}>) => void;
}
export declare const Coachmark: import('react').ForwardRefExoticComponent<CoachmarkProps & import('react').RefAttributes<HTMLElement>>;
export interface CodeBlockProps extends WebComponentProps {
/** The source code to render. */
code: string;
/** Language grammar (e.g. `js`, `python`). Defaults to `tsx`. */
language?: string;
/** Shiki theme name. */
codeTheme?: string;
/** Disable syntax highlighting (renders plain text, no Shiki). */
codeHighlight?: boolean;
/** Code text sizing. */
proseSize?: "sm" | "lg" | "xs" | "base";
}
export declare const CodeBlock: import('react').ForwardRefExoticComponent<CodeBlockProps & import('react').RefAttributes<HTMLElement>>;
export interface CommandProps extends WebComponentProps {
/** Flat list of items. Set as a JS property — not an HTML attribute. */
items?: {
id: string;
label: string;
icon?: string;
description?: string;
shortcut?: string;
group?: string;
}[];
/** Placeholder text for the search input. */
placeholder?: string;
/** Label shown when no items match the current query. */
emptyLabel?: string;
/** Fired when the highlighted/active item changes — via Arrow keys or when filtering re-clamps the active row. `id` is the newly active item's id, or `undefined` when no item is active (e.g. the filtered list is empty). Lets a host preview the active item without committing a selection. */
onActiveChange?: (event: CustomEvent<{
id: undefined | string;
}>) => void;
/** Fired on every keystroke in the search input. */
onQueryChange?: (event: CustomEvent<{
value: string;
}>) => void;
/** Fired when the user selects an item (click or Enter). */
onSelect?: (event: CustomEvent<{
id: string;
}>) => void;
}
export declare const Command: import('react').ForwardRefExoticComponent<CommandProps & import('react').RefAttributes<HTMLElement>>;
export interface CompareProps extends WebComponentProps {
/** The compare definition (prompt + the two candidates). Set as a JS PROPERTY: `el.data = { prompt, candidates: [A, B], collapse? }`. Import `ResponseCompareData` from `@kitn.ai/ui` for the full shape. */
data?: Record<string, unknown>;
/** Stable id correlating every emitted event. Attribute: `compare-id`. */
compareId?: string;
/** Re-hydrate / control the user's pick. Set as a JS PROPERTY: `el.selection = { chosenId, rejectedIds }`. Renders the collapsed winner. */
selection?: Record<string, unknown>;
/** Layout: `'auto'` (default — columns when wide, tabs when narrow, by CONTAINER width) | `'columns'` (side-by-side) | `'tabs'` (pills to switch). Attribute: `layout`. */
layout?: "auto" | "columns" | "tabs";
/** Prose/text size for the rendered candidates. Attribute: `prose-size`. */
proseSize?: "sm" | "lg" | "xs" | "base";
/** Shiki theme for code blocks in the candidates. Attribute: `code-theme`. */
codeTheme?: string;
/** Whether code blocks are syntax-highlighted. Attribute: `code-highlight`. */
codeHighlight?: boolean;
/** The user committed a pick. `detail` = `{ chosenId, rejectedIds, at }`. */
onCompareSelect?: (event: CustomEvent<{
chosenId: string;
rejectedIds: string[];
at?: undefined | number;
}>) => void;
/** The definition was unusable. */
onError?: (event: CustomEvent<{
compareId: string;
message: string;
}>) => void;
/** Both candidates have settled and the pick is live. */
onReady?: (event: CustomEvent<{
compareId: string;
}>) => void;
}
export declare const Compare: import('react').ForwardRefExoticComponent<CompareProps & import('react').RefAttributes<HTMLElement>>;
export interface ComposerProps extends WebComponentProps {
/** Controlled value — string or a full ComposerDoc (set as JS property). */
value?: string | ({
type: "text";
text: string;
} | {
type: "entity";
entity: {
kind: string;
id: string;
label: string;
icon?: string;
promptText?: string;
data?: Record<string, unknown>;
};
})[];
/** Placeholder text shown when the composer is empty. */
placeholder?: string;
/** Disable the composer entirely (non-interactive). */
disabled?: boolean;
/** Show a loading/streaming state and block submit. */
loading?: boolean;
/** Maximum height in px before the content scrolls. Default 240. */
maxHeight?: string | number;
/** Whether pressing Enter (without Shift) submits. Default true. */
submitOnEnter?: boolean;
/** Trigger definitions — set as a JS property. */
triggers?: {
char: string;
kind: string;
items?: {
id: string;
label: string;
icon?: string;
description?: string;
group?: string;
kind?: string;
promptText?: string;
data?: Record<string, unknown>;
}[];
}[];
/** Keyword highlight rules — set as a JS property. */
highlights?: (string | {
pattern: string;
flags?: string;
class?: string;
})[];
/** Default icon per entity kind (kind → image URL/data-URI) for items without their own `icon`. Overrides the built-in agent/plugin glyphs. JS property. */
kindIcons?: Record<string, string>;
/** The composer lost focus. */
onBlur?: (event: CustomEvent<{
originalEvent: FocusEvent;
}>) => void;
/** An entity pill was inserted into the composer. */
onEntityAdd?: (event: CustomEvent<{
entity: {
kind: string;
id: string;
label: string;
icon?: undefined | string;
promptText?: undefined | string;
data?: undefined | Record<string, unknown>;
};
}>) => void;
/** An entity pill was deleted from the composer. */
onEntityRemove?: (event: CustomEvent<{
entity: {
kind: string;
id: string;
label: string;
icon?: undefined | string;
promptText?: undefined | string;
data?: undefined | Record<string, unknown>;
};
}>) => void;
/** The composer gained focus. `focus`/`blur` are NOT composed natively, so they don't escape the shadow root — these re-expose them on the host. (For `keydown`/`paste`/`focusin`/`focusout`, listen NATIVELY on `<kai-composer>`: they're composed and already cross the shadow boundary.) */
onFocus?: (event: CustomEvent<{
originalEvent: FocusEvent;
}>) => void;
/** The user submitted the composer (Enter or programmatic submit). */
onSubmit?: (event: CustomEvent<{
doc: ({
type: "text";
text: string;
} | {
type: "entity";
entity: {
kind: string;
id: string;
label: string;
icon?: undefined | string;
promptText?: undefined | string;
data?: undefined | Record<string, unknown>;
};
})[];
text: string;
entities: {
kind: string;
id: string;
label: string;
icon?: undefined | string;
promptText?: undefined | string;
data?: undefined | Record<string, unknown>;
}[];
}>) => void;
/** A trigger character was detected at the caret (e.g. `/` or `@`). */
onTrigger?: (event: CustomEvent<{
char: string;
query: string;
rect: DOMRect;
}>) => void;
/** The active trigger was dismissed (Escape, space, or outside click). */
onTriggerClose?: (event: CustomEvent<Record<string, never>>) => void;
/** The content changed (fires on every input event). */
onValueChange?: (event: CustomEvent<{
doc: ({
type: "text";
text: string;
} | {
type: "entity";
entity: {
kind: string;
id: string;
label: string;
icon?: undefined | string;
promptText?: undefined | string;
data?: undefined | Record<string, unknown>;
};
})[];
text: string;
entities: {
kind: string;
id: string;
label: string;
icon?: undefined | string;
promptText?: undefined | string;
data?: undefined | Record<string, unknown>;
}[];
}>) => void;
}
export declare const Composer: import('react').ForwardRefExoticComponent<ComposerProps & import('react').RefAttributes<HTMLElement>>;
export interface ConfirmProps extends WebComponentProps {
/** The confirm definition (the CardEnvelope.data). Set as a JS PROPERTY: `el.data = { body, tone, actions:[…] }`. Import `ConfirmCardData` from `@kitn.ai/ui` for the full shape. */
data?: Record<string, unknown>;
/** Stable card id correlating every emitted CardEvent. Attribute: `card-id`. */
cardId?: string;
/** Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. */
heading?: string;
/** Focus the default action on mount (off by default — no focus-stealing). Attribute: `autofocus`. */
autofocus?: boolean;
/** Set when the user resolved this card; renders the read-only view. Property: `el.resolution = { kind:'action', action:'…' }`. */
resolution?: Record<string, unknown>;
}
export declare const Confirm: import('react').ForwardRefExoticComponent<ConfirmProps & import('react').RefAttributes<HTMLElement>>;
export interface ContextProps extends WebComponentProps {
/** Token-usage data. Set as a JS property. */
context?: {
usedTokens: number;
maxTokens: number;
inputTokens?: number;
outputTokens?: number;
reasoningTokens?: number;
cacheTokens?: number;
estimatedCost?: number;
};
/** Fraction (0–1) above which the meter turns yellow. Defaults to `0.7` (70%). */
warnThreshold?: number;
/** Fraction (0–1) above which the meter turns red. Defaults to `0.9` (90%). */
dangerThreshold?: number;
/** Fires when the computed severity level changes (ok → warn → danger or back). `detail.level` is `'ok'`, `'warn'`, or `'danger'`. */
onThresholdChange?: (event: CustomEvent<{
level: "ok" | "warn" | "danger";
}>) => void;
}
export declare const Context: import('react').ForwardRefExoticComponent<ContextProps & import('react').RefAttributes<HTMLElement>>;
export interface ConversationsProps extends WebComponentProps {
/** Pre-bucketed conversation groups (e.g. "Today", "Yesterday"), each with its own conversations. Use this when you want to control the grouping/headers yourself; otherwise pass a flat `conversations` array. Set as a JS property. */
groups: {
id: string;
userId?: undefined | string;
teamId?: undefined | string;
name: string;
sortOrder: number;
createdAt: string;
}[];
/** A flat list of conversation summaries; the component buckets them by recency for you. Ignored when `groups` is provided. Set as a JS property. */
conversations: {
id: string;
title: string;
groupId?: undefined | string;
scope: {
type: "document" | "collection";
documentId?: undefined | string;
filters?: undefined | {
tags?: undefined | string[];
authors?: undefined | string[];
contentType?: undefined | "transcript" | "markdown";
dateRange?: undefined | {
from: string;
to: string;
};
};
};
messageCount: number;
lastMessageAt: string;
updatedAt: string;
trailing?: undefined | string;
}[];
/** The id of the currently-open conversation, highlighted in the list. */
activeId?: string;
/** Controlled collapsed state. Set as a JS property (`el.collapsed = true`) to drive the rail from your app, updating it in response to `kai-collapse-toggle`. Omit for uncontrolled (the element manages it). Collapsed shrinks the rail to a floating reopen button. */
collapsed?: boolean;
/** Initial collapsed state when uncontrolled (default false). Use the `default-collapsed` attribute to start collapsed in plain HTML. */
defaultCollapsed?: boolean;
/** The rail was collapsed or expanded (via the toggle, the reopen button, or a `collapse()`/`expand()`/`toggle()` call). */
onCollapseToggle?: (event: CustomEvent<{
collapsed: boolean;
}>) => void;
/** A conversation was selected. */
onConversationSelect?: (event: CustomEvent<{
id: string;
}>) => void;
/** The "New chat" button was clicked. */
onNewChat?: (event: CustomEvent<Record<string, never>>) => void;
/** The built-in search box query changed (typing, or a programmatic `clear()` which fires it with `''`). Lets a consumer mirror or server-side the filter. */
onSearch?: (event: CustomEvent<{
query: string;
}>) => void;
/** The sidebar toggle was clicked. */
onToggleSidebar?: (event: CustomEvent<Record<string, never>>) => void;
}
export declare const Conversations: import('react').ForwardRefExoticComponent<ConversationsProps & import('react').RefAttributes<HTMLElement>>;
export interface DialogProps extends WebComponentProps {
/** Drive/observe open state (Shoelace-style: settable + reflected to the `open` attribute; the element still self-manages on Escape/backdrop). Set `el.open = true`, or `<kai-dialog open>`; listen for `kai-open-change`. */
open?: boolean;
/** Initial open state on mount (uncontrolled seed). */
defaultOpen?: boolean;
/** The dialog opened or closed (Escape, backdrop click, a driven `open`, or a method). */
onOpenChange?: (event: CustomEvent<{
open: boolean;
}>) => void;
}
export declare const Dialog: import('react').ForwardRefExoticComponent<DialogProps & import('react').RefAttributes<HTMLElement>>;
export interface EditableLabelProps extends WebComponentProps {
/** The label text — settable and reflected to the `value` attribute. Read `el.value` for live state. */
value?: string;
/** Controlled edit state. `el.editing = true` opens the field; reflected to the `editing` attribute. */
editing?: boolean;
/** Placeholder shown while editing / when the value is empty. */
placeholder?: string;
/** Disable entering edit mode. */
disabled?: boolean;
/** Edit was cancelled (Esc); the text is restored. */
onCancel?: (event: CustomEvent<Record<string, never>>) => void;
/** Committed a changed value (Enter / blur). */
onRename?: (event: CustomEvent<{
value: string;
}>) => void;
}
export declare const EditableLabel: import('react').ForwardRefExoticComponent<EditableLabelProps & import('react').RefAttributes<HTMLElement>>;
export interface EmbedProps extends WebComponentProps {
/** Stable card id correlating every emitted event. Set as an attribute or property. */
cardId?: string;
/** The embed payload (provider + id/url + options). Set as a JS **property** (object). */
data?: {
provider: "youtube" | "vimeo" | "generic";
id?: string;
url?: string;
title?: string;
poster?: string;
start?: number;
aspectRatio?: "16:9" | "4:3" | "1:1" | "9:16";
};
}
export declare const Embed: import('react').ForwardRefExoticComponent<EmbedProps & import('react').RefAttributes<HTMLElement>>;
export interface EmptyProps extends WebComponentProps {
/** Title text. Attribute: `empty-title` (`title` is a global HTML attribute). */
emptyTitle?: string;
/** Description text. */
description?: string;
}
export declare const Empty: import('react').ForwardRefExoticComponent<EmptyProps & import('react').RefAttributes<HTMLElement>>;
export interface FeedbackBarProps extends WebComponentProps {
/** The banner label (e.g. "Was this helpful?"). Attribute: `bar-title` (`title` is avoided — it's a global HTML attribute). */
barTitle?: string;
/** When set, a not-helpful vote opens an optional detail form before the thank-you confirmation. Attribute: `collect-detail`. */
collectDetail?: boolean;
/** Optional category chips for the detail form. Set as a JS property (array). */
categories?: string[];
/** Heading for the detail form. Attribute: `detail-title`. */
detailTitle?: string;
/** Placeholder for the detail comment box. Attribute: `detail-placeholder`. */
detailPlaceholder?: string;
/** Submit button label in the detail form. Attribute: `submit-label`. */
submitLabel?: string;
/** Confirmation copy shown after a vote/submit. Attribute: `thanks-message`. */
thanksMessage?: string;
/** The user dismissed the banner. */
onClose?: (event: CustomEvent) => void;
/** The user rated the response. `value` is `'helpful'` or `'not-helpful'`. */
onFeedback?: (event: CustomEvent<{
value: "helpful" | "not-helpful";
}>) => void;
/** The user submitted the optional detail form (`collect-detail`). */
onFeedbackDetail?: (event: CustomEvent<{
value: "helpful" | "not-helpful";
category?: undefined | string;
comment?: undefined | string;
}>) => void;
}
export declare const FeedbackBar: import('react').ForwardRefExoticComponent<FeedbackBarProps & import('react').RefAttributes<HTMLElement>>;
export interface FileTreeProps extends WebComponentProps {
/** The files to render. Set as a JS property (array of `{ path, url?, code?, language?, type?, additions?, deletions?, status? }`). */
files: {
path: string;
url?: undefined | string;
code?: undefined | string;
language?: undefined | string;
type?: undefined | "html" | "pdf" | "image" | "other";
additions?: undefined | number;
deletions?: undefined | number;
status?: undefined | "added" | "modified" | "deleted" | "renamed" | "untracked";
}[];
/** Selected file path — highlighted in the tree. */
activeFile?: string;
/** Folder paths expanded initially. Omit to start with all folders open. */
defaultExpanded?: string[];
/** Show a changed-files summary header (file count + summed `+/-` + Collapse-all). Attribute: `summary`. Off by default. */
summary?: boolean;
/** Fired when a file is selected. `detail.path` = the file's path. */
onSelect?: (event: CustomEvent<{
path: string;
}>) => void;
}
export declare const FileTree: import('react').ForwardRefExoticComponent<FileTreeProps & import('react').RefAttributes<HTMLElement>>;
export interface FileUploadProps extends WebComponentProps {
/** Allow selecting multiple files (default true). */
multiple?: boolean;
/** `accept` attribute for the file picker (e.g. `image/*`). */
accept?: string;
/** Disable the dropzone — no clicking, no drag-and-drop. */
disabled?: boolean;
/** Default dropzone label (overridable via the default slot). */
label?: string;
/** Files were picked or dropped. */
onFilesAdded?: (event: CustomEvent<{
files: File[];
}>) => void;
}
export declare const FileUpload: import('react').ForwardRefExoticComponent<FileUploadProps & import('react').RefAttributes<HTMLElement>>;
export interface FormProps extends WebComponentProps {
/** The form definition — a JSON Schema (`type:'object'`) + `x-kai-*` UI hints (the CardEnvelope.data). Set as a JS PROPERTY: `el.data = { type:'object', properties:{…} }`. Import the `FormDefinition` type from `@kitn.ai/ui` for the full shape (it is self-referential, so the element types it loosely). */
data?: Record<string, unknown>;
/** Stable card id correlating every emitted CardEvent. Attribute: `card-id`. */
cardId?: string;
/** Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. */
heading?: string;
/** Set when the user resolved this card; renders the read-only view. Property: `el.resolution = { kind:'submit', data:{…} }`. */
resolution?: Record<string, unknown>;
/** Controlled field values (JS property). When set, it wins over local edits. */
values?: Record<string, unknown>;
/** Initial values overlaying the schema defaults (uncontrolled seed; JS property). */
defaultValues?: Record<string, unknown>;
/** Disable all fields + submit. Attribute: `disabled`. */
disabled?: boolean;
/** The form's values changed on input — current coerced values + validity. */
onValuesChange?: (event: CustomEvent<{
values: Record<string, unknown>;
valid: boolean;
}>) => void;
}
export declare const Form: import('react').ForwardRefExoticComponent<FormProps & import('react').RefAttributes<HTMLElement>>;
export interface HoverCardProps extends WebComponentProps {
/** Delay (ms) before the card opens on hover. Defaults to 0 (focus opens it immediately too). */
openDelay?: number;
/** Delay (ms) before it closes after the pointer leaves. Defaults to 300. */
closeDelay?: number;
/** Preferred placement: `'top' | 'bottom' | 'left' | 'right'` (+ optional `-start`/`-end`). Defaults to `'bottom'`; flips to stay in view. */
placement?: string;
/** Drive/observe open state (Shoelace-style: settable + reflected to the `open` attribute, the element still self-manages on hover). Set `el.open = true`, or `<kai-hover-card open>`; listen for `kai-open-change`. */
open?: boolean;
/** Initial open state on mount (uncontrolled seed). */
defaultOpen?: boolean;
/** Suppress the hover behavior entirely without unmounting. */
disabled?: boolean;
/** The card opened or closed (by hover/focus, outside-click, or a method). */
onOpenChange?: (event: CustomEvent<{
open: boolean;
}>) => void;
}
export declare const HoverCard: import('react').ForwardRefExoticComponent<HoverCardProps & import('react').RefAttributes<HTMLElement>>;
export interface IconProps extends WebComponentProps {
/** A curated icon name (e.g. `"mic"`, `"globe"`), an image URL/data-URI, or plain text. */
name?: string;
/** Size token: `sm` | `md` (default) | `lg`. */
size?: "sm" | "md" | "lg";
}
export declare const Icon: import('react').ForwardRefExoticComponent<IconProps & import('react').RefAttributes<HTMLElement>>;
export interface ImageProps extends WebComponentProps {
/** Base64-encoded image data (pair with `media-type`). */
base64?: string;
/** Raw image bytes (set as a JS property). */
bytes?: Uint8Array;
/** Alt text. */
alt?: string;
/** MIME type (default `image/png`). */
mediaType?: string;
}
export declare const Image: import('react').ForwardRefExoticComponent<ImageProps & import('react').RefAttributes<HTMLElement>>;
export interface InputProps extends WebComponentProps {
/** Native input type: `text` (default) · `email` · `url` · `search` · `tel` · `password` · `number`. Single-line only. */
type?: str