UNPKG

@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.

66 lines (65 loc) 3.66 kB
import { JSX, Accessor } from 'solid-js'; import { CompareSelection, ResponseCompareData } from './response-compare-types'; export type { CompareCandidate, ComparePair, CompareCollapse, ResponseCompareData, CompareSelection, } from './response-compare-types'; export { normalizeCandidates, buildSelection, isAnyStreaming } from './response-compare-types'; export type CompareLayout = 'auto' | 'columns' | 'tabs'; /** Imperative handle exposed via `controllerRef` — surfaces the compare's latent * capabilities (commit a pick by candidate id, focus the roving tab stop) so the * `<kai-compare>` facade can forward them as instance methods (Pattern C). */ export interface ResponseCompareController { /** Commit a pick by candidate id — same path as the "Pick this" button: emits * onSelect + optimistically collapses (single-shot; inert while streaming or * already resolved). No-op for an unknown id. */ select(candidateId: string): void; /** Focus the current roving tab stop (the focused candidate's "Pick this" radio). */ focus(options?: FocusOptions): void; } export interface ResolvedController<T> { value: Accessor<T | undefined>; isResolved: Accessor<boolean>; isOptimistic: Accessor<boolean>; setLocal: (v: T) => void; } export declare function useResolved<T>(opts: { prop: Accessor<T | undefined>; data: Accessor<unknown>; }): ResolvedController<T>; export interface ResponseCompareProps { /** The compare definition (prompt + the two candidates). */ data?: ResponseCompareData; /** Stable id correlating every emitted event. */ compareId?: string; /** Re-hydrate / control the selection. Renders the collapsed winner. Set as a * JS property: `el.selection = { chosenId, rejectedIds }`. */ selection?: CompareSelection; /** Layout. `'columns'` = side-by-side; `'tabs'` = one candidate at a time with * pills to switch; `'auto'` (default) uses a CONTAINER query — columns when the * component is ≥640px wide, tabs when narrower (e.g. on a phone). */ layout?: CompareLayout; class?: string; /** Receive the imperative controller once mounted. The `<kai-compare>` facade * forwards these as element methods (select/focus). */ controllerRef?: (controller: ResponseCompareController) => void; /** Fired when the user commits a pick. */ onSelect?: (sel: CompareSelection) => void; /** Fired once both candidates have settled (stopped streaming) and a valid * definition is shown. */ onReady?: () => void; /** Fired for an unusable definition (inline error state). */ onError?: (message: string) => void; } /** * `ResponseCompare` — a dual-response comparison. Two assistant candidates for the * same prompt render side-by-side (or as tabs), each via `MessageBody` so a * candidate reads exactly like an assistant message (reasoning + tools + * attachments + markdown). The pick is a COMMIT, not a Submit: clicking "Pick this" * (or Enter on the focused column) computes `{ chosenId, rejectedIds:[other], at }`, * optimistically collapses to the chosen candidate's body, and calls `onSelect` for * the consumer to send a `(prompt, chosen, rejected)` preference pair. Single-shot. * * While EITHER candidate is `streaming`, both pick controls are disabled and the * streaming column shows a `TextShimmer`; `onReady` fires once both settle. The two * columns are a WAI-ARIA radiogroup with roving tabindex (Arrow keys move A↔B, * Enter/Space picks). Emits `onError` for a malformed definition. */ export declare function ResponseCompare(props: ResponseCompareProps): JSX.Element;