@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.
837 lines (782 loc) • 84.1 kB
TypeScript
// AUTO-GENERATED by scripts/gen-element-api.mjs — do not edit by hand.
// Typed custom-element interfaces + HTMLElementTagNameMap augmentation, so
// `document.querySelector('kai-message')` is typed and gets prop autocomplete.
// Re-exports for `import { … } from '@kitn.ai/ui/elements'`.
export type { ChatMessage, ChatMessageAction } from './chat-types';
export type { CodeHighlightingOptions } from '../primitives/highlighter';
export declare function configureCodeHighlighting(options: CodeHighlightingOptions): void;
export declare function isCodeHighlightingEnabled(): boolean;
/** Resolves once the kai-* elements are registered (browser); inert on the server. */
export declare const elementsReady: Promise<unknown>;
// --- Inlined from src/primitives/toast-store.ts (kept self-contained: no source imports) ---
export type ToastVariant = 'neutral' | 'success' | 'warning' | 'error' | 'info';
export interface ToastConfig {
stack?: 'expanded' | 'collapsed';
position?: 'top-center' | 'top-right' | 'top-left' | 'bottom-center' | 'bottom-right' | 'bottom-left';
max?: number;
/** Default appearance for imperatively-raised toasts. Defaults to `'pill'`. */
appearance?: 'pill' | 'card';
/** Default high-contrast inverse treatment. Defaults to `false`. */
inverse?: boolean;
}
/** An action button rendered inside the toast. Returning `false` from `onAction`
* keeps the toast open; any other return value dismisses it. */
export interface ToastAction {
label: string;
onAction: () => void | false;
}
export interface ToastItem {
id: string;
message: string;
variant?: ToastVariant;
/** Visual treatment: `'pill'` (default) or `'card'`. */
appearance?: 'pill' | 'card';
/** High-contrast inverse surface. Defaults to `false`. */
inverse?: boolean;
/** Secondary line shown below the message in the `'card'` appearance. */
description?: string;
action?: ToastAction;
/** Auto-dismiss delay in ms. `0` = sticky. */
duration?: number;
/** Whether the close affordance is shown. Defaults to `true`. */
dismissible?: boolean;
/** Container to scope this toast within instead of the viewport. */
target?: HTMLElement;
}
/** Options accepted by `toast()` — everything but the message. */
export interface ToastOptions {
id?: string;
variant?: ToastVariant;
appearance?: 'pill' | 'card';
inverse?: boolean;
description?: string;
action?: ToastAction;
duration?: number;
dismissible?: boolean;
target?: HTMLElement;
}
/** Handle returned from `toast()` for imperative control. */
export interface ToastHandle {
id: string;
dismiss: () => void;
update: (patch: Partial<Omit<ToastItem, 'id'>>) => void;
}
// Runtime values live in the compiled `default` (dist/kai.es.js); we only
// DECLARE their signatures here so the .d.ts pulls no source.
/** Raise a transient toast. `toast('Saved')`, `toast.success('Copied')`,
* `toast.dismiss(id)`. Returns a `{ id, dismiss, update }` handle. */
export declare const toast: {
(message: string, opts?: ToastOptions): ToastHandle;
/** Raise a success (green check) toast. */
success: (message: string, opts?: ToastOptions) => ToastHandle;
/** Raise a warning (amber) toast. */
warning: (message: string, opts?: ToastOptions) => ToastHandle;
/** Raise an error (destructive/red) toast. */
error: (message: string, opts?: ToastOptions) => ToastHandle;
/** Raise an info (blue) toast. */
info: (message: string, opts?: ToastOptions) => ToastHandle;
/** Dismiss a toast by id. */
dismiss: (id: string) => void;
/** Dismiss every active toast. */
clear: () => void;
};
/** Configure the imperative `toast()` singleton — call once at app start. */
export declare function configureToasts(config: ToastConfig): void;
export interface KaiAgentCardElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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 };
}
export interface KaiArtifactElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiAttachmentsElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiAvatarElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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 interface KaiBadgeElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** `default` (muted pill) · `count` (compact number badge) · `citation` (filled primary, for inline citation markers). Defaults to `default`. */
variant?: "default" | "count" | "citation";
}
export interface KaiButtonElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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";
}
export interface KaiCardElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiCardsElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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" };
}
export interface KaiChainOfThoughtElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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[];
}
export interface KaiChatElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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";
}
export interface KaiCheckpointElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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";
}
export interface KaiChoiceElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiCoachmarkElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiCodeBlockElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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 interface KaiCommandElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiCompareElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiComposerElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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>;
}
export interface KaiConfirmElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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 interface KaiContextElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiConversationsElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiDialogElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiEditableLabelElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiEmbedElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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 interface KaiEmptyElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** Title text. Attribute: `empty-title` (`title` is a global HTML attribute). */
emptyTitle?: string;
/** Description text. */
description?: string;
}
export interface KaiFeedbackBarElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiFileTreeElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiFileUploadElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiFormElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiHoverCardElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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;
}
export interface KaiIconElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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 interface KaiImageElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** 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 interface KaiInputElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** Native input type: `text` (default) · `email` · `url` · `search` · `tel` · `password` · `number`. Single-line only. */
type?: string;
/** Controlled value — settable and reflected to the `value` attribute. `el.value = 'hi'` drives it (no event); typing updates it and fires `kai-input`. Read `el.value` for live state. */
value?: string;
/** Placeholder shown when empty. */
placeholder?: string;
/** Field label, linked to the input. */
label?: string;
/** Helper text below the control. */
hint?: string;
/** Error text; flips the field invalid (`aria-invalid` + destructive border). */
error?: string;
/** Control density: `sm` or `md`. Defaults to `md`. */
size?: "sm" | "md";
/** Disable interaction. */
disabled?: boolean;
/** Make the input read-only. */
readonly?: boolean;
/** Mark the input required. */
required?: boolean;
/** Force the invalid state without an `error` string. */
invalid?: boolean;
/** Form-control name. */
name?: string;
/** Autofill hint forwarded to the inner input (e.g. `email`, `current-password`). */
autocomplete?: string;
/** Virtual-keyboard hint forwarded to the inner input (e.g. `numeric`, `email`). */
inputmode?: string;
}
export interface KaiKbdElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** Shortcut spec — tokens joined by `+` (e.g. `Mod+Shift+K`). Omit it to show default-slot content instead. Display only; the element does not bind keys. */
keys?: string;
/** `mac` uses ⌘/⌥, `other` uses Ctrl. `auto` (default) sniffs the OS. */
platform?: "other" | "auto" | "mac";
/** Cap size: `sm` or `md`. Defaults to `md`. */
size?: "sm" | "md";
}
export interface KaiLinkPreviewElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** Stable card id correlating every emitted event. Set as an attribute or property. */
cardId?: string;
/** The link payload (OG metadata). Set as a JS **property** (object). */
data?: { url: string; title?: string; description?: string; image?: string; imageAlt?: string; favicon?: string; domain?: string; siteName?: string };
}
export interface KaiLoaderElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** The animation style: `'circular' | 'classic' | 'pulse' | 'pulse-dot' | 'dots' | 'typing' | 'wave' | 'bars' | 'terminal' | 'text-blink' | 'text-shimmer' | 'loading-dots'`. Defaults to `'circular'`. */
variant?: "circular" | "classic" | "pulse" | "pulse-dot" | "dots" | "typing" | "wave" | "bars" | "terminal" | "text-blink" | "text-shimmer" | "loading-dots";
/** Loader size: `'sm' | 'md' | 'lg'`. Defaults to `'md'`. */
size?: "sm" | "md" | "lg";
/** Label for the text-based variants. */
text?: string;
}
export interface KaiMarkdownElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** The markdown source to render. */
content: string;
/** Text/markdown sizing. */
proseSize?: "sm" | "lg" | "xs" | "base";
/** Shiki theme for fenced code blocks. */
codeTheme?: string;
/** Disable syntax highlighting (no Shiki loads). */
codeHighlight?: boolean;
}
export interface KaiMenuElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** Tree of menu items. Set as a JS property — not an HTML attribute. */
items?: { id?: string; label?: string; icon?: string; shortcut?: string; checked?: boolean; radioGroup?: string; disabled?: boolean; separator?: boolean; heading?: boolean; items?: Record<string, unknown>[] }[];
/** Optional placement hint (unused by the underlying Dropdown which always positions bottom-start, kept for future extension). */
placement?: string;
/** Built-in trigger: leading icon (a named icon like `"plus"`, an image URL/data-URI, or text). Use this instead of slotting `slot="trigger"` for the common case — a slotted trigger overrides it. */
triggerIcon?: string;
/** Built-in trigger: a text label (e.g. `"High"`). */
triggerLabel?: string;
/** Built-in trigger: a trailing icon (e.g. `"chevron-down"` for a select look). */
triggerIconTrailing?: string;
/** Accessible name for an icon-only trigger (no visible label). */
label?: string;
/** Drive/observe open state (Shoelace-style: settable + reflected to the `open` attribute, the menu still self-manages on click/keyboard). Set `el.open = true`, or `<kai-menu open>`; listen for `kai-open-change`. */
open?: boolean;
/** Initial open state on mount (uncontrolled seed). */
defaultOpen?: boolean;
/** Disable the trigger — click/keyboard and `show()` no longer open the menu. */
disabled?: boolean;
}
export interface KaiMessageElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** The full message object. Set as a JS property. */
message?: { id: string; role: "user" | "assistant"; content: string; reasoning?: { text: string; label?: string }; tools?: { type: string; state: "input-streaming" | "input-available" | "output-available" | "output-error"; input?: Record<string, unknown>; output?: Record<string, unknown>; toolCallId?: string; errorText?: string }[]; attachments?: { id: string; type: "file" | "source-document"; filename?: string; mediaType?: string; url?: string; title?: string }[]; actions?: ("copy" | "like" | "dislike" | "regenerate" | "edit" | { id: string; label: string; icon?: string; tooltip?: string })[]; avatar?: { src?: string; fallback?: string; alt?: string }; feedback?: "like" | "dislike" };
/** Convenience for simple cases when not passing a `message` object. */
role?: "user" | "assistant";
/** Convenience content (used when `message` is not set). */
content?: string;
/** Force markdown on/off. Defaults to on for assistant, off for user. */
markdown?: boolean;
/** Text/markdown sizing for the message body. */
proseSize?: "sm" | "lg" | "xs" | "base";
/** Shiki theme name used for fenced code blocks in the content. */
codeTheme?: string;
/** Disable syntax highlighting for code blocks (no Shiki loads). */
codeHighlight?: boolean;
/** Whether the action bar is always visible (`'always'`, default) or only revealed on hover of the message row (`'hover'`). */
actionsReveal?: "always" | "hover";
/** Convenience avatar image URL (used when `message.avatar` is not set). */
avatarSrc?: string;
/** Convenience avatar fallback text (used when `message.avatar` is not set). */
avatarFallback?: string;
/** Avatar rail mode. `'none'` omits the avatar rail entirely so the body spans the full row (predictable layout when you never show avatars). Any other value keeps the default behaviour: the built-in avatar when one resolves, or your `slot="avatar"` content when projected (which REPLACES the built-in). */
avatar?: string;
}
export interface KaiModelSwitcherElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** The selectable models. Set as a JS property (array). */
models: { id: string; name: string; provider?: undefined | string; description?: undefined | string; group?: undefined | string }[];
/** The currently-selected model id. Defaults to the first model. */
currentModel?: string;
/** Drive/observe the dropdown's open state (Shoelace-style: settable + reflected to the `open` attribute, the dropdown still self-manages on click/keyboard). Set `el.open = true`, or `<kai-model-switcher open>`; listen for `kai-open-change`. */
open?: boolean;
/** Initial open state on mount (uncontrolled seed). */
defaultOpen?: boolean;
/** Disable the trigger — click/keyboard and `show()` no longer open the dropdown. */
disabled?: boolean;
}
export interface KaiNavElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** The nav items. Set as a JS property (array, not an attribute). Each item may carry `children` (a collapsible group), a `status` dot, and trailing `meta` text. */
items?: { id: string; label?: string; icon?: string; badge?: string; trailing?: string; disabled?: boolean; children?: Record<string, unknown>[]; status?: { tone: "error" | "primary" | "info" | "success" | "warning" | "neutral"; label?: string; pulse?: boolean }; meta?: string; action?: { icon: string; label: string }; closable?: boolean }[];
/** Active item id (controlled). */
value?: string;
/** Initial active id when uncontrolled. */
defaultValue?: string;
/** Ids of group items collapsed on first render (groups default to expanded). Set as a JS property (array). */
defaultCollapsed?: string[];
}
export interface KaiNoticeElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** `neutral` (default) · `info` · `warning` · `error` · `success`. Drives the leading icon's color and the a11y role (`alert` for errors, else `status`). */
severity?: "error" | "info" | "success" | "warning" | "neutral";
/** Leading icon: omit for the severity default, `"none"` to hide it, or a named icon to override. */
icon?: string;
/** Show a dismiss (×) that hides the notice and emits `kai-dismiss`. */
dismissible?: boolean;
}
export interface KaiPaneElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** The pane title (the agent / window name). Named `headline` because `title` collides with the global `HTMLElement.title` attribute (it throws at registration). Attribute: `headline`. */
headline?: string;
/** A role / label shown under the title (e.g. "Reviewer", "claude-sonnet"). Attribute: `subtitle`. */
subtitle?: string;
/** Show the restore glyph instead of maximize, and signal the maximized view-state. Drive it yourself in response to `kai-maximize`. Attribute: `maximized`. */
maximized?: boolean;
/** Highlight the frame with a ring/border to mark the ACTIVE pane. Attribute: `focused`. */
focused?: boolean;
/** Show a split-pane window control that fires `kai-split`. Off by default. Attribute: `show-split`. */
showSplit?: boolean;
/** Show a dock-to-side window control that fires `kai-dock`. Off by default. Attribute: `show-dock`. */
showDock?: boolean;
/** A tone-colored status dot (+ optional label) in the header. An object `{ tone, label?, pulse? }` set as a JS PROPERTY (not an attribute). */
status?: { tone: "working" | "idle" | "done" | "error" | "blocked"; label?: string; pulse?: boolean };
}
export interface KaiPaneGroupElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** The tabs to render. An array of `{ id, name, status?, needsAttention?, number? }` set as a JS PROPERTY (not an HTML attribute). */
tabs?: { id: string; name: string; status?: { tone: "working" | "idle" | "done" | "error" | "blocked"; label?: string; pulse?: boolean }; needsAttention?: boolean; number?: number }[];
/** The active tab id (controlled, and reflected to the `active` ATTRIBUTE so `::part`/`[active]` selectors and the per-tab named slot follow it). Set it as the `active` attribute or drive it from `kai-tab-change`; omit for uncontrolled (the first tab). */
active?: string;
/** Highlight the frame as the ACTIVE group in a multi-group layout. Attribute: `focused`. */
focused?: boolean;
}
export interface KaiPopoverElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** Floating placement relative to the trigger (floating-ui placement). */
placement?: "top" | "right" | "bottom" | "left" | "top-start" | "top-end" | "right-start" | "right-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end";
/** Gap in px between the trigger and the panel. */
gutter?: number;
/** Drive/observe open state (Shoelace-style: settable + reflected to the `open` attribute, the element still self-manages on click). Set `el.open = true`, or `<kai-popover open>`; listen for `kai-open-change`. */
open?: boolean;
/** Initial open state on mount (uncontrolled seed). */
defaultOpen?: boolean;
/** Turn the popover off while keeping the trigger mounted (clicks and `show()` no longer open it). */
disabled?: boolean;
}
export interface KaiProgressBarElement extends HTMLElement {
/** Color mode (`auto` follows prefers-color-scheme). */
theme?: 'light' | 'dark' | 'auto';
/** Current progress value (0..max). Attribute: `value`. */
value?: number;
/**