@kitn.ai/ui 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -13
- package/dist/components/chat-thread.d.ts +2 -4
- package/dist/components/choice-card.d.ts +1 -0
- package/dist/components/dismissed-stub.d.ts +22 -0
- package/dist/components/message.d.ts +59 -2
- package/dist/components/response-compare-types.d.ts +66 -0
- package/dist/components/response-compare.d.ts +51 -0
- package/dist/components/tasks-card.d.ts +1 -0
- package/dist/components/toast.d.ts +48 -0
- package/dist/components/use-card-resolution.d.ts +10 -6
- package/dist/custom-elements.json +260 -11
- package/dist/elements/artifact.js +1 -0
- package/dist/elements/attachments.js +1 -0
- package/dist/elements/autoloader.d.ts +2 -0
- package/dist/elements/autoloader.js +1 -0
- package/dist/elements/card.js +1 -0
- package/dist/elements/cards.d.ts +1 -0
- package/dist/elements/cards.js +1 -0
- package/dist/elements/chain-of-thought.d.ts +0 -12
- package/dist/elements/chain-of-thought.js +1 -0
- package/dist/elements/chat-scope-picker.js +1 -0
- package/dist/elements/chat-workspace.js +1 -0
- package/dist/elements/chat.js +1 -0
- package/dist/elements/checkpoint.js +1 -0
- package/dist/elements/choice.js +1 -0
- package/dist/elements/chunks/Icon-D_Zmwmwh.js +1 -0
- package/dist/elements/chunks/action-icons-PVfIvcuG.js +1 -0
- package/dist/elements/chunks/attachments-Dp6vqtrc.js +1 -0
- package/dist/elements/chunks/bash-InADTalH.js +1 -0
- package/dist/elements/chunks/button-CKyYD9G4.js +1 -0
- package/dist/elements/chunks/card-Cjho-PYk.js +1 -0
- package/dist/elements/chunks/card-routing-C986KuUA.js +1 -0
- package/dist/elements/chunks/chat-thread-RT808iPf.js +1 -0
- package/dist/elements/chunks/check-CDYaMYrM.js +1 -0
- package/dist/elements/chunks/chevron-down-3SBiq8HF.js +1 -0
- package/dist/elements/chunks/chevron-right-k2UFlIVK.js +1 -0
- package/dist/elements/chunks/cn-Dogu6PbV.js +1 -0
- package/dist/elements/chunks/code-block-BYUmfFSd.js +1 -0
- package/dist/elements/chunks/collapsible-BiNwdOn6.js +1 -0
- package/dist/elements/chunks/context-18g6oi4J.js +1 -0
- package/dist/elements/chunks/conversation-list-D9Y0NVdo.js +1 -0
- package/dist/elements/chunks/core-AYMC6_lb.js +12 -0
- package/dist/elements/chunks/css-M7EaDHN_.js +1 -0
- package/dist/elements/chunks/default-input-CSAoYYMd.js +1 -0
- package/dist/elements/chunks/download-D8K0Je8r.js +1 -0
- package/dist/elements/chunks/dropdown-bUs2iTia.js +1 -0
- package/dist/elements/chunks/engine-javascript-vq0WuIJl.js +141 -0
- package/dist/elements/chunks/external-link-6mA0wxab.js +1 -0
- package/dist/elements/chunks/file-text-DnaYk3Dm.js +1 -0
- package/dist/elements/chunks/file-tree-CgtRLlxK.js +1 -0
- package/dist/elements/chunks/github-dark-dimmed-DUshB20C.js +1 -0
- package/dist/elements/chunks/github-light-JYsPkUQd.js +1 -0
- package/dist/elements/chunks/hover-card-CIGw62gP.js +1 -0
- package/dist/elements/chunks/html-CPZ3oZQ7.js +1 -0
- package/dist/elements/chunks/index-CTBEJeo6.js +1 -0
- package/dist/elements/chunks/javascript-C25yR2R2.js +1 -0
- package/dist/elements/chunks/json-DxJze_jm.js +1 -0
- package/dist/elements/chunks/link-BlGSeoPy.js +1 -0
- package/dist/elements/chunks/loader-DvhR4qa0.js +1 -0
- package/dist/elements/chunks/markdown-CMTGx9r8.js +64 -0
- package/dist/elements/chunks/message-CDZwXjhL.js +1 -0
- package/dist/elements/chunks/message-feedback-CYx81QiA.js +1 -0
- package/dist/elements/chunks/model-switcher-O_tUhoOT.js +1 -0
- package/dist/elements/chunks/overlay-DydXYMQW.js +1 -0
- package/dist/elements/chunks/prompt-suggestion-ldlWV5kd.js +1 -0
- package/dist/elements/chunks/reasoning-2PUnHlmr.js +1 -0
- package/dist/elements/chunks/resizable-B5WNbSeb.js +1 -0
- package/dist/elements/chunks/star-t9tI-P0q.js +1 -0
- package/dist/elements/chunks/store-C6rIooZz.js +1 -0
- package/dist/elements/chunks/svelte--5p79yCD.js +1 -0
- package/dist/elements/chunks/text-shimmer-uh9V_KUh.js +1 -0
- package/dist/elements/chunks/textarea-3nm6Ia9U.js +1 -0
- package/dist/elements/chunks/thumbs-up-BBtguGld.js +1 -0
- package/dist/elements/chunks/toast-store-D8cL3fWl.js +1 -0
- package/dist/elements/chunks/tool-GJ3vwp1U.js +1 -0
- package/dist/elements/chunks/tooltip-DwKAz4XR.js +1 -0
- package/dist/elements/chunks/triangle-alert-Ct1zt2JL.js +1 -0
- package/dist/elements/chunks/tsx-B8rCNbgL.js +1 -0
- package/dist/elements/chunks/typescript-RycA9KXf.js +1 -0
- package/dist/elements/chunks/use-card-resolution-D-_0Alzy.js +1 -0
- package/dist/elements/chunks/video-mVYiM99-.js +1 -0
- package/dist/elements/chunks/vue-BmIZj4XD.js +1 -0
- package/dist/elements/chunks/x-CzxL27Ch.js +1 -0
- package/dist/elements/code-block.js +1 -0
- package/dist/elements/compare.js +1 -0
- package/dist/elements/confirm-card.js +1 -0
- package/dist/elements/context-meter.js +1 -0
- package/dist/elements/conversation-list.d.ts +1 -11
- package/dist/elements/conversation-list.js +1 -0
- package/dist/elements/embed.js +1 -0
- package/dist/elements/empty.js +1 -0
- package/dist/elements/feedback-bar.js +1 -0
- package/dist/elements/file-tree.js +1 -0
- package/dist/elements/file-upload.js +1 -0
- package/dist/elements/form.js +1 -0
- package/dist/elements/image.js +1 -0
- package/dist/elements/link-preview.js +1 -0
- package/dist/elements/loader.js +1 -0
- package/dist/elements/markdown.js +1 -0
- package/dist/elements/message-skills.d.ts +0 -16
- package/dist/elements/message-skills.js +1 -0
- package/dist/elements/message.js +1 -0
- package/dist/elements/model-switcher.d.ts +1 -11
- package/dist/elements/model-switcher.js +1 -0
- package/dist/elements/popover.js +1 -0
- package/dist/elements/prompt-input.d.ts +1 -9
- package/dist/elements/prompt-input.js +1 -0
- package/dist/elements/prompt-suggestions.d.ts +0 -6
- package/dist/elements/prompt-suggestions.js +1 -0
- package/dist/elements/reasoning.js +1 -0
- package/dist/elements/resizable.d.ts +1 -17
- package/dist/elements/resizable.js +1 -0
- package/dist/elements/response-stream.js +14 -0
- package/dist/elements/scroll-button.js +1 -0
- package/dist/elements/source.d.ts +0 -17
- package/dist/elements/source.js +1 -0
- package/dist/elements/switch.js +1 -0
- package/dist/elements/tasks.js +1 -0
- package/dist/elements/text-shimmer.js +1 -0
- package/dist/elements/thinking-bar.js +1 -0
- package/dist/elements/toast.d.ts +1 -0
- package/dist/elements/toast.js +1 -0
- package/dist/elements/tool.js +1 -0
- package/dist/elements/voice-input.js +1 -0
- package/dist/elements.d.ts +41 -5
- package/dist/index.d.ts +12 -0
- package/dist/index.js +11832 -9790
- package/dist/kitn-chat.es.js +1 -1
- package/dist/llms/llms-full.txt +57 -12
- package/dist/llms/llms.txt +3 -3
- package/dist/mcp.es.js +157 -1
- package/dist/primitives/card-contract.d.ts +20 -4
- package/dist/primitives/card-recovery.d.ts +55 -0
- package/dist/primitives/message-feedback.d.ts +35 -0
- package/dist/primitives/toast-store.d.ts +88 -0
- package/dist/react/index.d.ts +83 -3
- package/dist/react.js +90 -80
- package/dist/register-JyTglR6m.js +1 -0
- package/dist/register-impl-C6wlSsts.js +77 -0
- package/dist/schemas/choice.schema.json +5 -0
- package/dist/schemas/tasks.schema.json +5 -0
- package/dist/ui/tooltip.d.ts +1 -0
- package/frameworks/react/index.tsx +63 -11
- package/llms-full.txt +57 -12
- package/llms.txt +3 -3
- package/package.json +15 -6
- package/src/agent-tooling/mcp/tools/debug.ts +184 -0
- package/src/agent-tooling/mcp/tools/scaffold.ts +133 -1
- package/src/components/chat-thread.tsx +27 -35
- package/src/components/choice-card.tsx +33 -4
- package/src/components/component-meta.json +250 -26
- package/src/components/confirm-card.tsx +15 -1
- package/src/components/dismissed-stub.tsx +80 -0
- package/src/components/form.tsx +21 -3
- package/src/components/message.tsx +151 -10
- package/src/components/response-compare-types.ts +127 -0
- package/src/components/response-compare.tsx +431 -0
- package/src/components/tasks-card.tsx +33 -3
- package/src/components/toast.tsx +398 -0
- package/src/components/use-card-resolution.ts +24 -8
- package/src/elements/autoloader.ts +89 -0
- package/src/elements/chat-types.ts +7 -0
- package/src/elements/chat-workspace.tsx +4 -2
- package/src/elements/chat.tsx +4 -2
- package/src/elements/compare.tsx +88 -0
- package/src/elements/compiled.css +1 -1
- package/src/elements/element-manifest.json +182 -0
- package/src/elements/element-meta.json +191 -68
- package/src/elements/element-types.d.ts +41 -5
- package/src/elements/framework-usage.json +25 -0
- package/src/elements/message.tsx +27 -45
- package/src/elements/register-impl.ts +3 -0
- package/src/elements/register.ts +9 -0
- package/src/elements/remote.tsx +4 -0
- package/src/elements/styles.css +19 -0
- package/src/elements/toast.tsx +74 -0
- package/src/index.ts +30 -0
- package/src/primitives/card-contract.ts +14 -5
- package/src/primitives/card-recovery.ts +149 -0
- package/src/primitives/card-routing.ts +3 -0
- package/src/primitives/card-schemas/choice.schema.json +5 -0
- package/src/primitives/card-schemas/tasks.schema.json +5 -0
- package/src/primitives/message-feedback.ts +123 -0
- package/src/primitives/toast-store.ts +235 -0
- package/src/ui/tooltip.tsx +8 -2
- package/dist/elements/chat-types.d.ts +0 -41
- package/dist/elements/css.d.ts +0 -1
- package/dist/elements/default-input.d.ts +0 -40
- package/dist/elements/define.d.ts +0 -42
- package/dist/elements/register-impl.d.ts +0 -0
- package/dist/elements/register.d.ts +0 -3
- package/dist/register-impl-wCsFPoh9.js +0 -77
- package/dist/register-ujhr1nBS.js +0 -1
- /package/dist/elements/{remote.d.ts → compare.d.ts} +0 -0
|
@@ -80,11 +80,11 @@ export interface KaiCardsElement extends HTMLElement {
|
|
|
80
80
|
/** Color mode (`auto` follows prefers-color-scheme). */
|
|
81
81
|
theme?: 'light' | 'dark' | 'auto';
|
|
82
82
|
/** The stream of card envelopes to render. Set as a JS PROPERTY: `el.cards = [...]`. */
|
|
83
|
-
cards?: { type: string; id: string; data: unknown; title?: string; resolution?: { kind: "action"; action: string; payload?: unknown; at?: string } | { kind: "submit"; data: unknown; at?: string } }[];
|
|
83
|
+
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 } }[];
|
|
84
84
|
/** 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. */
|
|
85
85
|
types?: Record<string, string>;
|
|
86
86
|
/** Optional CardPolicy handling child events. Property: `el.policy`. */
|
|
87
|
-
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; onError?: (cardId: string, message: string) => void; maxSendPromptMode?: "compose" | "send" };
|
|
87
|
+
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" };
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
export interface KaiChainOfThoughtElement extends HTMLElement {
|
|
@@ -98,7 +98,7 @@ export interface KaiChatElement extends HTMLElement {
|
|
|
98
98
|
/** Color mode (`auto` follows prefers-color-scheme). */
|
|
99
99
|
theme?: 'light' | 'dark' | 'auto';
|
|
100
100
|
/** 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 = [...]`). */
|
|
101
|
-
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 } }[];
|
|
101
|
+
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" }[];
|
|
102
102
|
/** Controlled value of the input. When set, the host owns the input text and must update it on `kai-value-change`; leave unset for uncontrolled behavior. */
|
|
103
103
|
value?: string;
|
|
104
104
|
/** Placeholder text shown in the empty input. */
|
|
@@ -186,6 +186,25 @@ export interface KaiCodeBlockElement extends HTMLElement {
|
|
|
186
186
|
proseSize?: "xs" | "sm" | "base" | "lg";
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
export interface KaiCompareElement extends HTMLElement {
|
|
190
|
+
/** Color mode (`auto` follows prefers-color-scheme). */
|
|
191
|
+
theme?: 'light' | 'dark' | 'auto';
|
|
192
|
+
/** 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. */
|
|
193
|
+
data?: Record<string, unknown>;
|
|
194
|
+
/** Stable id correlating every emitted event. Attribute: `compare-id`. */
|
|
195
|
+
compareId?: string;
|
|
196
|
+
/** Re-hydrate / control the user's pick. Set as a JS PROPERTY: `el.selection = { chosenId, rejectedIds }`. Renders the collapsed winner. */
|
|
197
|
+
selection?: Record<string, unknown>;
|
|
198
|
+
/** Layout: `'auto'` (default — columns when wide, tabs when narrow, by CONTAINER width) | `'columns'` (side-by-side) | `'tabs'` (pills to switch). Attribute: `layout`. */
|
|
199
|
+
layout?: "auto" | "columns" | "tabs";
|
|
200
|
+
/** Prose/text size for the rendered candidates. Attribute: `prose-size`. */
|
|
201
|
+
proseSize?: "xs" | "sm" | "base" | "lg";
|
|
202
|
+
/** Shiki theme for code blocks in the candidates. Attribute: `code-theme`. */
|
|
203
|
+
codeTheme?: string;
|
|
204
|
+
/** Whether code blocks are syntax-highlighted. Attribute: `code-highlight`. */
|
|
205
|
+
codeHighlight?: boolean;
|
|
206
|
+
}
|
|
207
|
+
|
|
189
208
|
export interface KaiConfirmElement extends HTMLElement {
|
|
190
209
|
/** Color mode (`auto` follows prefers-color-scheme). */
|
|
191
210
|
theme?: 'light' | 'dark' | 'auto';
|
|
@@ -347,7 +366,7 @@ export interface KaiMessageElement extends HTMLElement {
|
|
|
347
366
|
/** Color mode (`auto` follows prefers-color-scheme). */
|
|
348
367
|
theme?: 'light' | 'dark' | 'auto';
|
|
349
368
|
/** The full message object. Set as a JS property. */
|
|
350
|
-
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 } };
|
|
369
|
+
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" };
|
|
351
370
|
/** Convenience for simple cases when not passing a `message` object. */
|
|
352
371
|
role?: "user" | "assistant";
|
|
353
372
|
/** Convenience content (used when `message` is not set). */
|
|
@@ -602,6 +621,21 @@ export interface KaiThinkingBarElement extends HTMLElement {
|
|
|
602
621
|
stopLabel?: string;
|
|
603
622
|
}
|
|
604
623
|
|
|
624
|
+
export interface KaiToastRegionElement extends HTMLElement {
|
|
625
|
+
/** Color mode (`auto` follows prefers-color-scheme). */
|
|
626
|
+
theme?: 'light' | 'dark' | 'auto';
|
|
627
|
+
/** The toasts to render. Newest is shown on top. Set as a JS property (array); pass a new array reference to update. */
|
|
628
|
+
toasts: { id: string; message: string; variant?: undefined | "neutral" | "success"; action?: undefined | { label: string; onAction: () => void | false }; duration?: undefined | number; dismissible?: undefined | boolean; target?: undefined | HTMLElement }[];
|
|
629
|
+
/** Stack anchor: `'top-center'` (default), `'top-right'`, `'bottom-center'`, … */
|
|
630
|
+
position?: "top-center" | "top-right" | "top-left" | "bottom-center" | "bottom-right" | "bottom-left";
|
|
631
|
+
/** Max simultaneously-visible toasts; the rest queue. Defaults to `3`. */
|
|
632
|
+
max?: number;
|
|
633
|
+
/** Stacking: 'expanded' (default, full column) | 'collapsed' (Sonner-style pile that expands on hover/focus). Attribute: stack. */
|
|
634
|
+
stack?: "expanded" | "collapsed";
|
|
635
|
+
/** Container element to anchor this region to (JS property). Set by the store for a scoped region; unset = the global viewport region. */
|
|
636
|
+
target?: HTMLElement;
|
|
637
|
+
}
|
|
638
|
+
|
|
605
639
|
export interface KaiToolElement extends HTMLElement {
|
|
606
640
|
/** Color mode (`auto` follows prefers-color-scheme). */
|
|
607
641
|
theme?: 'light' | 'dark' | 'auto';
|
|
@@ -630,7 +664,7 @@ export interface KaiWorkspaceElement extends HTMLElement {
|
|
|
630
664
|
/** Id of the open conversation, highlighted in the sidebar. */
|
|
631
665
|
activeId?: string;
|
|
632
666
|
/** The active conversation's message thread, newest last. Set as a JS property. */
|
|
633
|
-
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 } }[];
|
|
667
|
+
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" }[];
|
|
634
668
|
value?: string;
|
|
635
669
|
placeholder?: string;
|
|
636
670
|
loading?: boolean;
|
|
@@ -672,6 +706,7 @@ declare global {
|
|
|
672
706
|
'kai-checkpoint': KaiCheckpointElement;
|
|
673
707
|
'kai-choice': KaiChoiceElement;
|
|
674
708
|
'kai-code-block': KaiCodeBlockElement;
|
|
709
|
+
'kai-compare': KaiCompareElement;
|
|
675
710
|
'kai-confirm': KaiConfirmElement;
|
|
676
711
|
'kai-context': KaiContextElement;
|
|
677
712
|
'kai-conversations': KaiConversationsElement;
|
|
@@ -704,6 +739,7 @@ declare global {
|
|
|
704
739
|
'kai-tasks': KaiTasksElement;
|
|
705
740
|
'kai-text-shimmer': KaiTextShimmerElement;
|
|
706
741
|
'kai-thinking-bar': KaiThinkingBarElement;
|
|
742
|
+
'kai-toast-region': KaiToastRegionElement;
|
|
707
743
|
'kai-tool': KaiToolElement;
|
|
708
744
|
'kai-voice-input': KaiVoiceInputElement;
|
|
709
745
|
'kai-workspace': KaiWorkspaceElement;
|
|
@@ -113,6 +113,18 @@
|
|
|
113
113
|
"solid": "import { CodeBlock } from '@kitn.ai/ui';\n\n<CodeBlock\n code={code}\n/>"
|
|
114
114
|
}
|
|
115
115
|
},
|
|
116
|
+
{
|
|
117
|
+
"tag": "kai-compare",
|
|
118
|
+
"displayName": "Compare",
|
|
119
|
+
"hasSolid": false,
|
|
120
|
+
"snippets": {
|
|
121
|
+
"html": "<script type=\"module\" src=\"https://cdn.jsdelivr.net/npm/@kitn.ai/ui/dist/kitn-chat.es.js\"></script>\n\n<kai-compare></kai-compare>\n<script type=\"module\">\n const el = document.querySelector('kai-compare');\n el.addEventListener('kai-compare-select', (e) => console.log(e.detail));\n el.addEventListener('kai-error', (e) => console.log(e.detail));\n el.addEventListener('kai-ready', (e) => console.log(e.detail));\n</script>",
|
|
122
|
+
"react": "import { Compare } from '@kitn.ai/ui/react';\n\n<Compare\n onCompareSelect={(e) => console.log(e.detail)}\n onError={(e) => console.log(e.detail)}\n onReady={(e) => console.log(e.detail)}\n/>",
|
|
123
|
+
"vue": "<kai-compare\n @kai-compare-select=\"onCompareSelect\"\n @kai-error=\"onError\"\n @kai-ready=\"onReady\"\n/>",
|
|
124
|
+
"svelte": "<script>\n import '@kitn.ai/ui/elements';\n</script>\n\n<kai-compare\n on:kai-compare-select={onCompareSelect}\n on:kai-error={onError}\n on:kai-ready={onReady}\n></kai-compare>",
|
|
125
|
+
"angular": "<kai-compare\n (kai-compare-select)=\"onCompareSelect($event)\"\n (kai-error)=\"onError($event)\"\n (kai-ready)=\"onReady($event)\"\n></kai-compare>"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
116
128
|
{
|
|
117
129
|
"tag": "kai-confirm",
|
|
118
130
|
"displayName": "Confirm",
|
|
@@ -522,6 +534,19 @@
|
|
|
522
534
|
"solid": "import { ThinkingBar } from '@kitn.ai/ui';\n\n<ThinkingBar\n onStop={(e) => console.log(e.detail)}\n/>"
|
|
523
535
|
}
|
|
524
536
|
},
|
|
537
|
+
{
|
|
538
|
+
"tag": "kai-toast-region",
|
|
539
|
+
"displayName": "ToastRegion",
|
|
540
|
+
"hasSolid": true,
|
|
541
|
+
"snippets": {
|
|
542
|
+
"html": "<script type=\"module\" src=\"https://cdn.jsdelivr.net/npm/@kitn.ai/ui/dist/kitn-chat.es.js\"></script>\n\n<kai-toast-region></kai-toast-region>\n<script type=\"module\">\n const el = document.querySelector('kai-toast-region');\n el.toasts = /* … */;\n el.addEventListener('kai-action', (e) => console.log(e.detail));\n el.addEventListener('kai-dismiss', (e) => console.log(e.detail));\n</script>",
|
|
543
|
+
"react": "import { ToastRegion } from '@kitn.ai/ui/react';\n\n<ToastRegion\n toasts={toasts}\n onAction={(e) => console.log(e.detail)}\n onDismiss={(e) => console.log(e.detail)}\n/>",
|
|
544
|
+
"vue": "<kai-toast-region\n :toasts.prop=\"toasts\"\n @kai-action=\"onAction\"\n @kai-dismiss=\"onDismiss\"\n/>",
|
|
545
|
+
"svelte": "<script>\n import '@kitn.ai/ui/elements';\n</script>\n\n<kai-toast-region\n {toasts}\n on:kai-action={onAction}\n on:kai-dismiss={onDismiss}\n></kai-toast-region>",
|
|
546
|
+
"angular": "<kai-toast-region\n [toasts]=\"toasts\"\n (kai-action)=\"onAction($event)\"\n (kai-dismiss)=\"onDismiss($event)\"\n></kai-toast-region>",
|
|
547
|
+
"solid": "import { ToastRegion } from '@kitn.ai/ui';\n\n<ToastRegion\n toasts={toasts}\n onAction={(e) => console.log(e.detail)}\n onDismiss={(e) => console.log(e.detail)}\n/>"
|
|
548
|
+
}
|
|
549
|
+
},
|
|
525
550
|
{
|
|
526
551
|
"tag": "kai-tool",
|
|
527
552
|
"displayName": "Tool",
|
package/src/elements/message.tsx
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Show, createSignal, onMount, onCleanup } from 'solid-js';
|
|
2
2
|
import { defineWebComponent } from './define';
|
|
3
3
|
import { ChatConfig, useChatConfig, type ProseSize } from '../primitives/chat-config';
|
|
4
|
-
import { Message, MessageAvatar,
|
|
5
|
-
import {
|
|
6
|
-
import { Tool } from '../components/tool';
|
|
7
|
-
import { Attachments, Attachment, AttachmentPreview, AttachmentInfo } from '../components/attachments';
|
|
4
|
+
import { Message, MessageAvatar, MessageBody } from '../components/message';
|
|
5
|
+
import { createMessageFeedback } from '../primitives/message-feedback';
|
|
8
6
|
import type { ChatMessage } from './chat-types';
|
|
9
7
|
|
|
10
8
|
interface Props extends Record<string, unknown> {
|
|
@@ -33,8 +31,10 @@ interface Props extends Record<string, unknown> {
|
|
|
33
31
|
|
|
34
32
|
/** Events fired by `<kai-message>`. */
|
|
35
33
|
interface Events {
|
|
36
|
-
/** An action button was clicked. `action` is the built-in name or custom id.
|
|
37
|
-
|
|
34
|
+
/** An action button was clicked. `action` is the built-in name or custom id.
|
|
35
|
+
* `state` is present only for the toggleable feedback votes: `'on'` when a
|
|
36
|
+
* like/dislike is set, `'off'` when re-tapped to clear. */
|
|
37
|
+
'kai-message-action': { messageId: string; action: string; state?: 'on' | 'off' };
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -58,6 +58,12 @@ defineWebComponent<Props, Events>('kai-message', {
|
|
|
58
58
|
const outer = useChatConfig();
|
|
59
59
|
const msg = (): ChatMessage =>
|
|
60
60
|
props.message ?? { id: 'message', role: props.role ?? 'assistant', content: props.content ?? '' };
|
|
61
|
+
// Copy + vote state lives here (above the rendered body) so it survives a
|
|
62
|
+
// re-render when the host swaps in a fresh `message` object during streaming.
|
|
63
|
+
const feedback = createMessageFeedback({
|
|
64
|
+
emit: (detail) => dispatch('kai-message-action', detail),
|
|
65
|
+
target: () => element,
|
|
66
|
+
});
|
|
61
67
|
|
|
62
68
|
// Read declarative <kai-action> children from light DOM.
|
|
63
69
|
// Shadow DOM with no <slot> suppresses them visually — they're invisible data carriers.
|
|
@@ -89,45 +95,21 @@ defineWebComponent<Props, Events>('kai-message', {
|
|
|
89
95
|
element.hasAttribute('markdown') || props.markdown === true || props.markdown === false;
|
|
90
96
|
const useMarkdown = () => (markdownExplicit() ? flag('markdown') : !isUser());
|
|
91
97
|
|
|
98
|
+
const mergedActions = () => [...(msg().actions ?? []), ...slottedActions()];
|
|
92
99
|
const body = () => (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
{(att) => (
|
|
107
|
-
<Attachment data={att}>
|
|
108
|
-
<AttachmentPreview />
|
|
109
|
-
<AttachmentInfo />
|
|
110
|
-
</Attachment>
|
|
111
|
-
)}
|
|
112
|
-
</For>
|
|
113
|
-
</Attachments>
|
|
114
|
-
</Show>
|
|
115
|
-
<MessageContent
|
|
116
|
-
markdown={useMarkdown()}
|
|
117
|
-
class={isUser()
|
|
118
|
-
? 'bg-muted text-primary max-w-[85%] rounded-2xl px-4 py-2'
|
|
119
|
-
: 'bg-transparent p-0'}
|
|
120
|
-
>
|
|
121
|
-
{msg().content}
|
|
122
|
-
</MessageContent>
|
|
123
|
-
<Show when={(msg().actions?.length ?? 0) > 0 || slottedActions().length > 0}>
|
|
124
|
-
<MessageActionBar
|
|
125
|
-
actions={[...(msg().actions ?? []), ...slottedActions()]}
|
|
126
|
-
reveal={reveal()}
|
|
127
|
-
onAction={(action) => dispatch('kai-message-action', { messageId: msg().id, action })}
|
|
128
|
-
/>
|
|
129
|
-
</Show>
|
|
130
|
-
</>
|
|
100
|
+
<MessageBody
|
|
101
|
+
content={msg().content}
|
|
102
|
+
reasoning={msg().reasoning}
|
|
103
|
+
tools={msg().tools}
|
|
104
|
+
attachments={msg().attachments}
|
|
105
|
+
isUser={isUser()}
|
|
106
|
+
markdown={useMarkdown()}
|
|
107
|
+
actions={mergedActions()}
|
|
108
|
+
actionsReveal={reveal()}
|
|
109
|
+
activeFeedback={feedback.resolveFeedback(msg())}
|
|
110
|
+
copied={feedback.isCopied(msg().id)}
|
|
111
|
+
onAction={(action) => feedback.handleAction(msg(), action)}
|
|
112
|
+
/>
|
|
131
113
|
);
|
|
132
114
|
|
|
133
115
|
// Row carries `group` so a hover-revealed action bar fades in on row hover.
|
|
@@ -44,6 +44,7 @@ import './artifact';
|
|
|
44
44
|
import './scroll-button';
|
|
45
45
|
import './popover';
|
|
46
46
|
import './switch';
|
|
47
|
+
import './toast';
|
|
47
48
|
// Generative-UI cards (Card Contract)
|
|
48
49
|
import './card';
|
|
49
50
|
import './form';
|
|
@@ -53,3 +54,5 @@ import './confirm-card';
|
|
|
53
54
|
import './tasks';
|
|
54
55
|
import './choice';
|
|
55
56
|
import './cards';
|
|
57
|
+
// Dual-response comparison (preference capture)
|
|
58
|
+
import './compare';
|
package/src/elements/register.ts
CHANGED
|
@@ -20,3 +20,12 @@ if (typeof window !== 'undefined' && typeof customElements !== 'undefined') {
|
|
|
20
20
|
export type { ChatMessage, ChatMessageAction } from './chat-types';
|
|
21
21
|
export { configureCodeHighlighting, isCodeHighlightingEnabled } from '../primitives/highlighter';
|
|
22
22
|
export type { CodeHighlightingOptions } from '../primitives/highlighter';
|
|
23
|
+
|
|
24
|
+
// Imperative toast API — usable directly from the elements entry so consumers
|
|
25
|
+
// who only import the web-components bundle still get `toast()`. The store is
|
|
26
|
+
// SSR-safe (no DOM touched until the first toast is raised on the client).
|
|
27
|
+
export { toast, configureToasts } from '../primitives/toast-store';
|
|
28
|
+
export type {
|
|
29
|
+
ToastConfig,
|
|
30
|
+
ToastItem, ToastVariant, ToastAction, ToastOptions, ToastHandle,
|
|
31
|
+
} from '../primitives/toast-store';
|
package/src/elements/remote.tsx
CHANGED
|
@@ -81,6 +81,10 @@ function buildWrappedPolicy(element: HTMLElement, userPolicy: CardPolicy | undef
|
|
|
81
81
|
emit({ kind: 'dismiss', cardId });
|
|
82
82
|
userPolicy?.onDismiss?.(cardId);
|
|
83
83
|
},
|
|
84
|
+
onReopen(cardId) {
|
|
85
|
+
emit({ kind: 'reopen', cardId });
|
|
86
|
+
userPolicy?.onReopen?.(cardId);
|
|
87
|
+
},
|
|
84
88
|
onError(cardId, message) {
|
|
85
89
|
emit({ kind: 'error', cardId, message });
|
|
86
90
|
userPolicy?.onError?.(cardId, message);
|
package/src/elements/styles.css
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/* Element-bundle stylesheet. Compiled to compiled.css and injected into each
|
|
2
2
|
custom element's shadow root. Mirrors .storybook/styles.css but scans src/. */
|
|
3
3
|
@import "tailwindcss";
|
|
4
|
+
/* tw-animate-css supplies the animate-in/animate-out + slide/fade/zoom utilities
|
|
5
|
+
the kit uses for every overlay enter/exit (toast, popover, tooltip, dropdown,
|
|
6
|
+
hover-card, the message action-row hide). Without this @import those classes
|
|
7
|
+
compile to nothing and all the animations are silently dead. Compiled into
|
|
8
|
+
compiled.css at build time, so it's a build-time dep only (no consumer import). */
|
|
9
|
+
@import "tw-animate-css";
|
|
4
10
|
@import "../../theme.css";
|
|
5
11
|
@plugin "@tailwindcss/typography";
|
|
6
12
|
|
|
@@ -65,6 +71,19 @@
|
|
|
65
71
|
}
|
|
66
72
|
*::-webkit-scrollbar-thumb:hover { background-color: var(--color-scrollbar-thumb-hover); }
|
|
67
73
|
*::-webkit-scrollbar-corner { background: transparent; }
|
|
74
|
+
|
|
75
|
+
/* Respect a user's reduced-motion preference: disable the tw-animate-css
|
|
76
|
+
enter/exit keyframe animations (toasts, overlays, cards) so they appear and
|
|
77
|
+
disappear instantly instead of sliding/fading. Targets the utility classes
|
|
78
|
+
tw-animate-css emits (.animate-in / .animate-out) plus any arbitrary
|
|
79
|
+
`animate-[…]` keyframe utility. */
|
|
80
|
+
@media (prefers-reduced-motion: reduce) {
|
|
81
|
+
.animate-in,
|
|
82
|
+
.animate-out,
|
|
83
|
+
[class*="animate-["] {
|
|
84
|
+
animation: none !important;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
68
87
|
}
|
|
69
88
|
|
|
70
89
|
@layer components {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { defineWebComponent } from './define';
|
|
2
|
+
import { ToastRegion, type ToastDismissReason, type ToastPosition } from '../components/toast';
|
|
3
|
+
import { toast as toastStore, type ToastItem } from '../primitives/toast-store';
|
|
4
|
+
|
|
5
|
+
interface Props extends Record<string, unknown> {
|
|
6
|
+
/** The toasts to render. Newest is shown on top. Set as a JS property (array);
|
|
7
|
+
* pass a new array reference to update. */
|
|
8
|
+
toasts: ToastItem[];
|
|
9
|
+
/** Stack anchor: `'top-center'` (default), `'top-right'`, `'bottom-center'`, … */
|
|
10
|
+
position?: ToastPosition;
|
|
11
|
+
/** Max simultaneously-visible toasts; the rest queue. Defaults to `3`. */
|
|
12
|
+
max?: number;
|
|
13
|
+
/** Stacking: 'expanded' (default, full column) | 'collapsed' (Sonner-style
|
|
14
|
+
* pile that expands on hover/focus). Attribute: stack. */
|
|
15
|
+
stack?: 'expanded' | 'collapsed';
|
|
16
|
+
/** Container element to anchor this region to (JS property). Set by the store
|
|
17
|
+
* for a scoped region; unset = the global viewport region. */
|
|
18
|
+
target?: HTMLElement;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface Events {
|
|
22
|
+
/** A toast left the stack. `reason` is `'timeout' | 'close' | 'action'`. */
|
|
23
|
+
'kai-dismiss': { id: string; reason: ToastDismissReason };
|
|
24
|
+
/** A toast's action button was pressed. */
|
|
25
|
+
'kai-action': { id: string; label: string };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `<kai-toast-region>` — the viewport overlay that renders the toast stack.
|
|
30
|
+
*
|
|
31
|
+
* It is the substrate behind the imperative `toast()` API: the store lazily
|
|
32
|
+
* mounts ONE of these on `document.body` and binds the list to `toasts`. It is
|
|
33
|
+
* also usable declaratively — set `el.toasts = [...]` (a JS property, never an
|
|
34
|
+
* attribute) and listen for `kai-dismiss` / `kai-action`.
|
|
35
|
+
*
|
|
36
|
+
* Because it is a real `kai-*` element it carries its own shadow root + the
|
|
37
|
+
* shared kit stylesheet, so it is viewport-positioned AND kit-styled.
|
|
38
|
+
*/
|
|
39
|
+
defineWebComponent<Props, Events>('kai-toast-region', {
|
|
40
|
+
toasts: [],
|
|
41
|
+
position: 'top-center',
|
|
42
|
+
max: 3,
|
|
43
|
+
stack: 'expanded',
|
|
44
|
+
target: undefined,
|
|
45
|
+
}, (props, { dispatch }) => {
|
|
46
|
+
// `max` may arrive as a string attribute (`<kai-toast-region max="2">`).
|
|
47
|
+
const max = () => {
|
|
48
|
+
const raw = props.max as unknown;
|
|
49
|
+
const n = typeof raw === 'string' ? parseInt(raw, 10) : raw;
|
|
50
|
+
return typeof n === 'number' && !Number.isNaN(n) ? n : 3;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const remove = (id: string) => {
|
|
54
|
+
// Drop from the shared store (imperative path). For purely-declarative
|
|
55
|
+
// consumers the store simply won't contain the id — the dispatched event
|
|
56
|
+
// is the signal for them to update their own `toasts` array.
|
|
57
|
+
toastStore.dismiss(id);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<ToastRegion
|
|
62
|
+
toasts={props.toasts ?? []}
|
|
63
|
+
position={props.position}
|
|
64
|
+
max={max()}
|
|
65
|
+
stack={props.stack as 'expanded' | 'collapsed' | undefined}
|
|
66
|
+
target={props.target as HTMLElement | undefined}
|
|
67
|
+
onDismiss={(id, reason) => {
|
|
68
|
+
remove(id);
|
|
69
|
+
dispatch('kai-dismiss', { id, reason });
|
|
70
|
+
}}
|
|
71
|
+
onAction={(id, label) => dispatch('kai-action', { id, label })}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,16 @@ export type { CodeHighlightingOptions } from './primitives/highlighter';
|
|
|
18
18
|
export { configurePdfPreview, isPdfPreviewEnabled } from './primitives/pdf-preview';
|
|
19
19
|
export type { PdfPreviewOptions } from './primitives/pdf-preview';
|
|
20
20
|
|
|
21
|
+
// Toasts: imperative `toast()` API + the reactive store behind <kai-toast-region>
|
|
22
|
+
export { toast, configureToasts, ensureMounted as ensureToastRegion, getToasts } from './primitives/toast-store';
|
|
23
|
+
export type {
|
|
24
|
+
ToastItem, ToastVariant, ToastAction, ToastOptions, ToastHandle, ToastFn, ToastConfig,
|
|
25
|
+
} from './primitives/toast-store';
|
|
26
|
+
export { Toast, ToastRegion } from './components/toast';
|
|
27
|
+
export type {
|
|
28
|
+
ToastRegionProps, ToastDismissReason, ToastPosition,
|
|
29
|
+
} from './components/toast';
|
|
30
|
+
|
|
21
31
|
// Card Contract (generative-UI foundation)
|
|
22
32
|
export { CARD_CONTRACT_VERSION } from './primitives/card-contract';
|
|
23
33
|
export type {
|
|
@@ -27,6 +37,10 @@ export { applyResolution, resolutionFromEvent } from './primitives/card-resoluti
|
|
|
27
37
|
export { CardProvider, useCardHost } from './primitives/card-host';
|
|
28
38
|
export type { CardProviderProps } from './primitives/card-host';
|
|
29
39
|
export { CARD_EVENT_NAME, emitCardEvent, routeCardEvent, listenForCardEvents } from './primitives/card-routing';
|
|
40
|
+
export { dismissRecovery, defaultIsReopenable } from './primitives/card-recovery';
|
|
41
|
+
export type {
|
|
42
|
+
RecoveryToast, ReopenEnv, DismissRecoveryOptions,
|
|
43
|
+
} from './primitives/card-recovery';
|
|
30
44
|
export { validateAgainstSchema } from './primitives/card-validate';
|
|
31
45
|
export type { JsonSchema, ValidationResult } from './primitives/card-validate';
|
|
32
46
|
|
|
@@ -47,6 +61,8 @@ export type { CardComponent, CardComponentMap, CardTagMap } from './primitives/c
|
|
|
47
61
|
// Card: kai-card (base shell) + kai-form (JSON-Schema form renderer)
|
|
48
62
|
export { Card } from './components/card';
|
|
49
63
|
export type { CardProps } from './components/card';
|
|
64
|
+
export { DismissedStub, stubIntent } from './components/dismissed-stub';
|
|
65
|
+
export type { DismissedStubProps, DismissedCardType } from './components/dismissed-stub';
|
|
50
66
|
export { Form, validateForm, buildResult, widgetFor, orderedKeys, coerceValue } from './components/form';
|
|
51
67
|
export type {
|
|
52
68
|
FormProps,
|
|
@@ -168,6 +184,20 @@ export {
|
|
|
168
184
|
} from './components/chat-container';
|
|
169
185
|
export { Message, MessageAvatar, MessageContent, MessageActions, MessageAction, MessageCopyButton } from './components/message';
|
|
170
186
|
export type { MessageCopyButtonProps } from './components/message';
|
|
187
|
+
export { ResponseCompare, useResolved } from './components/response-compare';
|
|
188
|
+
export type { ResponseCompareProps, CompareLayout, ResolvedController } from './components/response-compare';
|
|
189
|
+
export {
|
|
190
|
+
normalizeCandidates,
|
|
191
|
+
buildSelection,
|
|
192
|
+
isAnyStreaming,
|
|
193
|
+
} from './components/response-compare';
|
|
194
|
+
export type {
|
|
195
|
+
CompareCandidate,
|
|
196
|
+
ComparePair,
|
|
197
|
+
CompareCollapse,
|
|
198
|
+
ResponseCompareData,
|
|
199
|
+
CompareSelection,
|
|
200
|
+
} from './components/response-compare';
|
|
171
201
|
export { MessageSkills } from './components/message-skills';
|
|
172
202
|
export type { Skill as MessageSkill } from './components/message-skills';
|
|
173
203
|
export {
|
|
@@ -38,18 +38,24 @@ export type CardEvent =
|
|
|
38
38
|
| { kind: 'resize'; cardId: string; height: number }
|
|
39
39
|
| { kind: 'state'; cardId: string; patch: unknown }
|
|
40
40
|
| { kind: 'dismiss'; cardId: string }
|
|
41
|
+
| { kind: 'reopen'; cardId: string }
|
|
41
42
|
| { kind: 'error'; cardId: string; message: string };
|
|
42
43
|
|
|
43
44
|
export type CardEventKind = CardEvent['kind'];
|
|
44
45
|
|
|
45
46
|
/** How a card was resolved by the user — the re-hydration channel for the chromed
|
|
46
|
-
* read-only state.
|
|
47
|
-
* resolution is just the event that resolved the
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* read-only state. The terminal kinds (`action`/`submit`) mirror the two terminal
|
|
48
|
+
* CardEvents (minus `cardId`): the resolution is just the event that resolved the
|
|
49
|
+
* card. `dismissed` is a DEFERRED (non-terminal) state — the user set the card
|
|
50
|
+
* aside; it can be re-opened. `expired` is terminal — a dismissed card the host
|
|
51
|
+
* decided can no longer be re-opened (e.g. the agent proceeded, or it went stale).
|
|
52
|
+
* `at` is optional ISO-8601 provenance (data only; never rendered). Additive —
|
|
53
|
+
* does not bump the contract version. */
|
|
50
54
|
export type CardResolution =
|
|
51
55
|
| { kind: 'action'; action: string; payload?: unknown; at?: string }
|
|
52
|
-
| { kind: 'submit'; data: unknown; at?: string }
|
|
56
|
+
| { kind: 'submit'; data: unknown; at?: string }
|
|
57
|
+
| { kind: 'dismissed'; at?: string }
|
|
58
|
+
| { kind: 'expired'; reason?: string; at?: string };
|
|
53
59
|
|
|
54
60
|
/** What every card is handed (via native context or the iframe bridge). */
|
|
55
61
|
export interface CardHost {
|
|
@@ -65,6 +71,9 @@ export interface CardPolicy {
|
|
|
65
71
|
onOpen?: (url: string, target: 'tab' | 'artifact') => void;
|
|
66
72
|
onState?: (cardId: string, patch: unknown) => void;
|
|
67
73
|
onDismiss?: (cardId: string) => void;
|
|
74
|
+
/** A dismissed card asked to be re-opened. The host decides whether it can come
|
|
75
|
+
* back (clear the resolution → live again) or has expired (stamp `expired`). */
|
|
76
|
+
onReopen?: (cardId: string) => void;
|
|
68
77
|
onError?: (cardId: string, message: string) => void;
|
|
69
78
|
/** Cap on send-prompt: 'compose' (default) forbids silent sends. 'send' to allow. */
|
|
70
79
|
maxSendPromptMode?: 'compose' | 'send';
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// src/primitives/card-recovery.ts
|
|
2
|
+
// Recovery glue for the card dismiss/reopen flow. `dismissRecovery` builds the
|
|
3
|
+
// `{ onDismiss, onReopen }` half of a CardPolicy for a host that keeps its cards in
|
|
4
|
+
// some store: it writes a `dismissed` resolution onto the matching envelope (a NEW
|
|
5
|
+
// array reference, never an in-place mutation, so a Solid/React host re-renders),
|
|
6
|
+
// optionally shows an injected "Dismissed · Undo" toast (Undo restores the prior
|
|
7
|
+
// resolution), and on reopen decides — via `isReopenable` — whether the card comes
|
|
8
|
+
// back live (resolution cleared) or `expired`.
|
|
9
|
+
//
|
|
10
|
+
// The toast is an INJECTED ADAPTER, never an import — cards (and this helper) stay
|
|
11
|
+
// decoupled from the toast module. The re-appear rule is the host's: "the agent
|
|
12
|
+
// already proceeded" is a host fact, surfaced through `isReopenable`.
|
|
13
|
+
import type { CardEnvelope, CardPolicy, CardResolution } from './card-contract';
|
|
14
|
+
|
|
15
|
+
/** The minimal toast surface this helper needs. Inject a real adapter (e.g. one
|
|
16
|
+
* backed by `kai-toast-region`) — this module never imports the toast itself. */
|
|
17
|
+
export interface RecoveryToast {
|
|
18
|
+
show(opts: {
|
|
19
|
+
message: string;
|
|
20
|
+
action?: { label: string; onClick: () => void };
|
|
21
|
+
durationMs?: number;
|
|
22
|
+
}): { dismiss(): void };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Context handed to a custom `isReopenable` predicate. */
|
|
26
|
+
export interface ReopenEnv {
|
|
27
|
+
cardId: string;
|
|
28
|
+
/** The card's current resolution at reopen time (should be `dismissed`). */
|
|
29
|
+
resolution: CardResolution | undefined;
|
|
30
|
+
/** `Date.now()` at reopen time (injected for testability). */
|
|
31
|
+
now: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DismissRecoveryOptions {
|
|
35
|
+
/** Read the current cards array. */
|
|
36
|
+
get: () => CardEnvelope[];
|
|
37
|
+
/** Write the next cards array (a NEW reference). */
|
|
38
|
+
set: (next: CardEnvelope[]) => void;
|
|
39
|
+
/** Optional injected toast adapter for the "Dismissed · Undo" affordance. */
|
|
40
|
+
toast?: RecoveryToast;
|
|
41
|
+
/** Override the default re-openable rule. Return false to expire instead. */
|
|
42
|
+
isReopenable?: (env: ReopenEnv) => boolean;
|
|
43
|
+
/** A dismissed card older than this (ms since `dismissed.at`) is no longer
|
|
44
|
+
* re-openable (→ expired). Omit/Infinity = never stale. */
|
|
45
|
+
staleAfterMs?: number;
|
|
46
|
+
/** Auto-dismiss the Undo toast after this long. Default 6000ms. */
|
|
47
|
+
undoMs?: number;
|
|
48
|
+
/** Injected clock (defaults to Date.now); keeps the helper testable. */
|
|
49
|
+
now?: () => number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The terminal resolution kinds that block a reopen (a finished card can't come back). */
|
|
53
|
+
const TERMINAL_KINDS = new Set<CardResolution['kind']>(['action', 'submit', 'expired']);
|
|
54
|
+
|
|
55
|
+
/** Default re-openable rule: a card can come back unless it carries a terminal
|
|
56
|
+
* resolution OR it has gone stale (`staleAfterMs` elapsed since `dismissed.at`).
|
|
57
|
+
* A `dismissed` card with no/invalid `at` is always re-openable. */
|
|
58
|
+
export function defaultIsReopenable(env: ReopenEnv, staleAfterMs?: number): boolean {
|
|
59
|
+
const r = env.resolution;
|
|
60
|
+
if (r && TERMINAL_KINDS.has(r.kind)) return false;
|
|
61
|
+
if (
|
|
62
|
+
staleAfterMs !== undefined &&
|
|
63
|
+
Number.isFinite(staleAfterMs) &&
|
|
64
|
+
r?.kind === 'dismissed' &&
|
|
65
|
+
typeof r.at === 'string'
|
|
66
|
+
) {
|
|
67
|
+
const at = Date.parse(r.at);
|
|
68
|
+
if (!Number.isNaN(at) && env.now - at >= staleAfterMs) return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Return a new cards array with `cardId`'s envelope stamped with `resolution`
|
|
74
|
+
* (or cleared when `resolution` is undefined). Same reference if nothing changed. */
|
|
75
|
+
function withResolution(
|
|
76
|
+
cards: CardEnvelope[],
|
|
77
|
+
cardId: string,
|
|
78
|
+
resolution: CardResolution | undefined,
|
|
79
|
+
): CardEnvelope[] {
|
|
80
|
+
let changed = false;
|
|
81
|
+
const next = cards.map((c) => {
|
|
82
|
+
if (c.id !== cardId) return c;
|
|
83
|
+
changed = true;
|
|
84
|
+
if (resolution === undefined) {
|
|
85
|
+
if (c.resolution === undefined) return c;
|
|
86
|
+
const { resolution: _drop, ...rest } = c;
|
|
87
|
+
return rest;
|
|
88
|
+
}
|
|
89
|
+
return { ...c, resolution };
|
|
90
|
+
});
|
|
91
|
+
return changed ? next : cards;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The current resolution on `cardId`, if any. */
|
|
95
|
+
function resolutionOf(cards: CardEnvelope[], cardId: string): CardResolution | undefined {
|
|
96
|
+
return cards.find((c) => c.id === cardId)?.resolution;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Build the `{ onDismiss, onReopen }` policy handlers for the card dismiss/recovery
|
|
101
|
+
* flow over a host store (`get`/`set`).
|
|
102
|
+
*
|
|
103
|
+
* - `onDismiss(cardId)` — stamps `{ kind:'dismissed', at }` immutably and, when a
|
|
104
|
+
* `toast` is injected, shows "Dismissed" with an Undo that restores the card's
|
|
105
|
+
* prior resolution (live again when there was none).
|
|
106
|
+
* - `onReopen(cardId)` — clears the resolution (live) when `isReopenable`, else
|
|
107
|
+
* stamps `{ kind:'expired', at }`.
|
|
108
|
+
*/
|
|
109
|
+
export function dismissRecovery(
|
|
110
|
+
opts: DismissRecoveryOptions,
|
|
111
|
+
): Pick<CardPolicy, 'onDismiss' | 'onReopen'> {
|
|
112
|
+
const now = opts.now ?? (() => Date.now());
|
|
113
|
+
const reopenable = opts.isReopenable ?? ((env: ReopenEnv) => defaultIsReopenable(env, opts.staleAfterMs));
|
|
114
|
+
const undoMs = opts.undoMs ?? 6000;
|
|
115
|
+
|
|
116
|
+
const onDismiss = (cardId: string): void => {
|
|
117
|
+
const before = opts.get();
|
|
118
|
+
const prior = resolutionOf(before, cardId);
|
|
119
|
+
const dismissed: CardResolution = { kind: 'dismissed', at: new Date(now()).toISOString() };
|
|
120
|
+
opts.set(withResolution(before, cardId, dismissed));
|
|
121
|
+
|
|
122
|
+
opts.toast?.show({
|
|
123
|
+
message: 'Dismissed',
|
|
124
|
+
durationMs: undoMs,
|
|
125
|
+
action: {
|
|
126
|
+
label: 'Undo',
|
|
127
|
+
onClick: () => {
|
|
128
|
+
// Restore the prior resolution (undefined → live again). Read fresh in
|
|
129
|
+
// case the store moved on; only touch this card.
|
|
130
|
+
opts.set(withResolution(opts.get(), cardId, prior));
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const onReopen = (cardId: string): void => {
|
|
137
|
+
const before = opts.get();
|
|
138
|
+
const current = resolutionOf(before, cardId);
|
|
139
|
+
const env: ReopenEnv = { cardId, resolution: current, now: now() };
|
|
140
|
+
if (reopenable(env)) {
|
|
141
|
+
opts.set(withResolution(before, cardId, undefined));
|
|
142
|
+
} else {
|
|
143
|
+
const expired: CardResolution = { kind: 'expired', at: new Date(now()).toISOString() };
|
|
144
|
+
opts.set(withResolution(before, cardId, expired));
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return { onDismiss, onReopen };
|
|
149
|
+
}
|