@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.
124 lines (123 loc) • 5.97 kB
TypeScript
import { JSX } from 'solid-js';
import { ChatMessageAction, CustomAction, FeedbackVote } from '../elements/chat-types';
import { ToolPart } from './tool';
import { AttachmentData } from './attachments';
export interface MessageProps extends JSX.HTMLAttributes<HTMLDivElement> {
children: JSX.Element;
}
declare function Message(props: MessageProps): JSX.Element;
export interface MessageAvatarProps {
src: string;
alt: string;
fallback?: string;
class?: string;
}
declare function MessageAvatar(props: MessageAvatarProps): JSX.Element;
export interface MessageContentProps extends JSX.HTMLAttributes<HTMLDivElement> {
children: JSX.Element | string;
markdown?: boolean;
/** `::part` name(s) exposed on the content node. The body passes
* `"bubble content"` so consumers can target either the rounded bubble or the
* text region from outside the shadow boundary. */
part?: string;
}
declare function MessageContent(props: MessageContentProps): JSX.Element;
export interface MessageActionsProps extends JSX.HTMLAttributes<HTMLDivElement> {
children: JSX.Element;
}
declare function MessageActions(props: MessageActionsProps): JSX.Element;
export interface MessageActionBarProps {
/** Built-in action names and/or custom action descriptors, in order. */
actions: (ChatMessageAction | CustomAction)[];
/** `'always'` (default) keeps the bar visible; `'hover'` reveals it on
* parent `.group` hover. */
reveal?: 'always' | 'hover';
/** Fired with the built-in name or the custom action id when a button is clicked. */
onAction: (id: string) => void;
/** The active feedback vote. When `'like'`/`'dislike'` is set, that button is
* marked pressed (filled) and the OTHER vote button animates out. `undefined`
* shows both. Pure/prop-driven so the bar survives re-renders. */
activeFeedback?: FeedbackVote;
/** When true, the `copy` button shows its emerald check icon instead of the
* copy glyph (cleared by the owner after ~2s). */
copied?: boolean;
class?: string;
}
/**
* The shared message action toolbar. Renders one ghost icon button per entry —
* built-in names pull their label+icon from the curated registry; custom
* descriptors use their `label` plus `actionIcon(icon)` (label-only when the
* icon is unknown or absent). `reveal="hover"` makes the bar fade in on the
* parent `.group`'s hover.
*
* Pure/prop-driven: feedback (`activeFeedback`) and copy (`copied`) state are
* owned by the parent facade and passed in — the bar holds no internal signals,
* so it survives the new-array-per-chunk re-renders of a streaming thread. With
* a vote active, the chosen `like`/`dislike` button is marked `aria-pressed` +
* filled and the other vote button collapses its width (sliding the active thumb
* into its place) via a 0fr↔1fr grid transition. The
* `copy` button swaps to an emerald check while `copied`.
*/
declare function MessageActionBar(props: MessageActionBarProps): JSX.Element;
export interface MessageBodyProps {
/** The message text/markdown. */
content: string;
/** Optional collapsible reasoning block, rendered above the content. */
reasoning?: {
text: string;
label?: string;
};
/** Tool-call parts, rendered above the content. */
tools?: ToolPart[];
/** Inline attachment previews, rendered above the content. */
attachments?: AttachmentData[];
/** Whether this is a user message (right-aligned bubble) vs an assistant
* message (full-width transparent). */
isUser: boolean;
/** Whether the content renders as markdown. */
markdown: boolean;
/** Action-bar entries — built-in names and/or custom descriptors. When empty
* the bar is not rendered. */
actions?: (ChatMessageAction | CustomAction)[];
/** `'always'` (default) keeps the bar visible; `'hover'` reveals it on parent
* `.group` hover. */
actionsReveal?: 'always' | 'hover';
/** Fired with the built-in name or custom id when an action is clicked. */
onAction?: (id: string) => void;
/** The currently-active feedback vote, so the bar can mark like/dislike and
* hide the other. */
activeFeedback?: FeedbackVote;
/** When true, the copy button shows its "copied" check icon. */
copied?: boolean;
/** INJECT slot projected at the TOP of the body — above the reasoning / tools /
* content. A per-message header: a model-name label, a role + timestamp line.
* In the `<kai-message>` shadow this is `<slot name="before-body" />`. */
beforeBody?: JSX.Element;
/** INJECT slot projected at the BOTTOM of the body — below the action bar. A
* citation / sources row, a token-cost / latency line. In the `<kai-message>`
* shadow this is `<slot name="after-body" />`. */
afterBody?: JSX.Element;
}
/**
* The shared message body: an optional reasoning block, tool calls, inline
* attachments, the content bubble, and the action bar — in that order. This is
* the single source of truth for how a message renders, consumed by `ChatThread`
* (the `<For>` over `messages`), the standalone `<kai-message>` facade, and (in
* future) `kai-compare` for each candidate. Pure/prop-driven: all interaction
* state (copied, feedback vote) is owned above and passed in.
*/
declare function MessageBody(props: MessageBodyProps): JSX.Element;
export interface MessageActionProps {
tooltip: string;
children: JSX.Element;
side?: "top" | "bottom" | "left" | "right";
class?: string;
}
declare function MessageAction(props: MessageActionProps): JSX.Element;
export interface MessageCopyButtonProps {
content: string;
size?: number;
class?: string;
}
declare function MessageCopyButton(props: MessageCopyButtonProps): JSX.Element;
export { Message, MessageAvatar, MessageContent, MessageActions, MessageActionBar, MessageBody, MessageAction, MessageCopyButton };