@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
|
@@ -223,6 +223,53 @@ const RULES: Rule[] = [
|
|
|
223
223
|
"// Use `integration: 'mock'` in the scaffold tool\n" +
|
|
224
224
|
'```',
|
|
225
225
|
},
|
|
226
|
+
{
|
|
227
|
+
// Rule 9 — reduce bundle size / footprint / "how much does @kitn.ai/ui add"
|
|
228
|
+
// Source: dist/elements/<file>.js per-element exports; dist/autoloader.js
|
|
229
|
+
id: 'bundle-footprint',
|
|
230
|
+
test: (t) =>
|
|
231
|
+
/bundle\s*size|footprint|tree.?shak|how\s+much.*does.*@kitn|reduce.*import|import.*only.*element|per.?element\s+import|autoload|cdn.*no.?build|no.?build.*cdn/i.test(
|
|
232
|
+
t,
|
|
233
|
+
),
|
|
234
|
+
title: 'Reducing bundle footprint — three load modes',
|
|
235
|
+
cause:
|
|
236
|
+
'The default `import \'@kitn.ai/ui/elements\'` registers every `kai-*` element. ' +
|
|
237
|
+
'If your page uses only one or two elements, that pulls in the full ~119 KB gz bundle. ' +
|
|
238
|
+
'Two opt-in modes let you load only what you need.',
|
|
239
|
+
fix:
|
|
240
|
+
'**Mode 1 — register-all (default, SSR-safe):**\n' +
|
|
241
|
+
'Best for multi-element apps or any SSR/meta-framework. ' +
|
|
242
|
+
'Load once and every `kai-*` element is available.\n\n' +
|
|
243
|
+
'```js\n' +
|
|
244
|
+
"import '@kitn.ai/ui/elements'; // ~119 KB gz — registers everything\n" +
|
|
245
|
+
'```\n\n' +
|
|
246
|
+
'**Mode 2 — per-element import (tree-shaking, bundler apps):**\n' +
|
|
247
|
+
'Use `import \'@kitn.ai/ui/elements/<file>\'` to register only one element. ' +
|
|
248
|
+
'A bundler (Vite, webpack, Rollup) will tree-shake to just its chunks (~73 KB gz for `kai-chat` alone). ' +
|
|
249
|
+
'Client-only — do not use in SSR entry points.\n\n' +
|
|
250
|
+
'```js\n' +
|
|
251
|
+
"// Registers only <kai-chat> (~73 KB gz vs ~119 KB gz register-all)\n" +
|
|
252
|
+
"import '@kitn.ai/ui/elements/chat';\n\n" +
|
|
253
|
+
"// Other examples:\n" +
|
|
254
|
+
"import '@kitn.ai/ui/elements/code-block'; // <kai-code-block>\n" +
|
|
255
|
+
"import '@kitn.ai/ui/elements/confirm-card'; // <kai-confirm>\n" +
|
|
256
|
+
'```\n\n' +
|
|
257
|
+
'The file name is the element\'s source basename from `element-manifest.json` ' +
|
|
258
|
+
'(e.g. `kai-chat` → `chat`, `kai-confirm` → `confirm-card`).\n\n' +
|
|
259
|
+
'**Mode 3 — autoloader (no-build / CDN pages only):**\n' +
|
|
260
|
+
'Watches the DOM and dynamically imports each `kai-*` element\'s module on demand. ' +
|
|
261
|
+
'A page that uses only `<kai-chat>` never downloads the other elements. ' +
|
|
262
|
+
'It is a CDN / static-file tool — load it from a `<script type="module">` tag. ' +
|
|
263
|
+
'It is NOT importable through a bundler: Vite/webpack relocate it and the on-demand imports 404. ' +
|
|
264
|
+
'Client-only.\n\n' +
|
|
265
|
+
'```html\n' +
|
|
266
|
+
'<script type="module" src="https://cdn.jsdelivr.net/npm/@kitn.ai/ui@<version>/dist/elements/autoloader.js"></script>\n' +
|
|
267
|
+
'```\n\n' +
|
|
268
|
+
'In a BUNDLED app (Vite/webpack/Next) use Mode 1 or Mode 2 instead — not the autoloader.\n\n' +
|
|
269
|
+
'**SSR note:** use Mode 1 (register-all) in SSR apps — ' +
|
|
270
|
+
'per-element imports and the autoloader are client-only (they call DOM APIs at module eval). ' +
|
|
271
|
+
'Modes 1 & 2 are side-effect imports; keep them even if your linter flags them as "unused".',
|
|
272
|
+
},
|
|
226
273
|
{
|
|
227
274
|
// Rule 5 — SSR / server component / document is not defined
|
|
228
275
|
// Source: for-ai-agents.mdx (client-only import); context7.json rule 2 (property rule requires DOM)
|
|
@@ -256,6 +303,143 @@ const RULES: Rule[] = [
|
|
|
256
303
|
"import { Chat } from '@kitn.ai/ui/react';\n" +
|
|
257
304
|
'```',
|
|
258
305
|
},
|
|
306
|
+
{
|
|
307
|
+
// Rule 10 — toast() is the imperative API; there is no <kai-toast> to place
|
|
308
|
+
// Source: src/primitives/toast-store.ts (the `toast` fn + auto-mounted region)
|
|
309
|
+
id: 'toast-imperative',
|
|
310
|
+
test: (t) => {
|
|
311
|
+
// Placing a toast element by hand (the region auto-mounts — you never write it).
|
|
312
|
+
if (/<kai-toast(-region)?\b/.test(t)) return true;
|
|
313
|
+
// Asking how to show / trigger a toast or notification with kai context.
|
|
314
|
+
if (
|
|
315
|
+
/\btoast(s)?\b|notification|snackbar/i.test(t) &&
|
|
316
|
+
/how.*(show|raise|trigger|fire|display)|show.*toast|raise.*toast|trigger.*toast|toast.*(not|isn'?t|won'?t).*(show|appear|render)|where.*toast|add.*toast|kai-|@kitn/i.test(t)
|
|
317
|
+
)
|
|
318
|
+
return true;
|
|
319
|
+
return false;
|
|
320
|
+
},
|
|
321
|
+
title: 'Toast is an imperative call — `toast(\'…\')`, not a `<kai-toast>` you place',
|
|
322
|
+
cause:
|
|
323
|
+
'Toasts are raised IMPERATIVELY by calling `toast(message)` — there is no ' +
|
|
324
|
+
'`<kai-toast>` element you add to your markup. The first call lazily mounts ONE ' +
|
|
325
|
+
'`<kai-toast-region>` on `document.body` (a real, kit-styled, viewport-positioned ' +
|
|
326
|
+
'element) and every later toast feeds that same region. Trying to place a toast ' +
|
|
327
|
+
'element by hand, or looking for a `messages`/`toasts` prop to push into, is the ' +
|
|
328
|
+
'wrong model.',
|
|
329
|
+
fix:
|
|
330
|
+
'Import `toast` and call it. It is exported from BOTH the root `@kitn.ai/ui` and ' +
|
|
331
|
+
'the `@kitn.ai/ui/elements` bundle, so the web-components-only consumer gets it too. ' +
|
|
332
|
+
'It is SSR-safe (no DOM is touched until the first call on the client).\n\n' +
|
|
333
|
+
'```js\n' +
|
|
334
|
+
"import { toast } from '@kitn.ai/ui/elements'; // or '@kitn.ai/ui'\n\n" +
|
|
335
|
+
"// ✅ Fire-and-forget\n" +
|
|
336
|
+
"toast('Copied to clipboard');\n" +
|
|
337
|
+
"toast.success('Saved');\n\n" +
|
|
338
|
+
"// ✅ With an Undo action + an imperative handle\n" +
|
|
339
|
+
"const t = toast('Item deleted', {\n" +
|
|
340
|
+
" action: { label: 'Undo', onAction: () => restore() },\n" +
|
|
341
|
+
'});\n' +
|
|
342
|
+
"t.update({ message: 'Restored', variant: 'success' });\n" +
|
|
343
|
+
't.dismiss();\n' +
|
|
344
|
+
'```\n\n' +
|
|
345
|
+
'The auto-mounted `<kai-toast-region>` carries its own shadow root + kit styles — ' +
|
|
346
|
+
'do NOT add a `<kai-toast-region>` tag yourself unless you deliberately want a ' +
|
|
347
|
+
'second, declaratively-controlled region.',
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
// Rule 11 — dismissed cards are DEFERRED (reopenable stub), not deleted
|
|
351
|
+
// Source: src/primitives/card-recovery.ts (dismissRecovery) + the dismissed stub
|
|
352
|
+
id: 'card-dismiss-deferred',
|
|
353
|
+
test: (t) => {
|
|
354
|
+
const cardCtx = /\bcard(s)?\b|envelope|kai-card|kai-cards|kai-confirm|kai-choice|kai-tasks|kai-form|generative.?ui|resolution|dismissRecovery/i;
|
|
355
|
+
if (!cardCtx.test(t)) return false;
|
|
356
|
+
// dismiss / reopen / undo / disappear / filter-out signals in a card context.
|
|
357
|
+
return /dismiss|reopen|re-?open|\bundo\b|disappear|remove.*card|card.*(gone|remove|delete|vanish)|filter.*out|stub/i.test(t);
|
|
358
|
+
},
|
|
359
|
+
title: 'Dismissed cards are DEFERRED (a reopenable stub), not deleted',
|
|
360
|
+
cause:
|
|
361
|
+
'Dismissing a generative-UI card does NOT delete its envelope from history. The ' +
|
|
362
|
+
'card stamps a `{ kind: \'dismissed\' }` resolution onto its envelope and collapses ' +
|
|
363
|
+
'to a small reopenable stub ("Proposed: <title> — dismissed · Reopen"). If you ' +
|
|
364
|
+
'filter `dismissed` envelopes out of your cards array, the stub vanishes and the ' +
|
|
365
|
+
'user can never reopen it — and you lose the audit trail of what was proposed.',
|
|
366
|
+
fix:
|
|
367
|
+
'Keep dismissed envelopes in the array. Wire dismiss/reopen with `dismissRecovery()` ' +
|
|
368
|
+
'(from `@kitn.ai/ui`), which builds the `onDismiss`/`onReopen` half of a `CardPolicy` ' +
|
|
369
|
+
'over your store and can show a "Dismissed · Undo" toast via an injected adapter.\n\n' +
|
|
370
|
+
'```ts\n' +
|
|
371
|
+
"import { dismissRecovery } from '@kitn.ai/ui';\n" +
|
|
372
|
+
"import { toast } from '@kitn.ai/ui/elements';\n\n" +
|
|
373
|
+
'// Adapter: map dismissRecovery\'s toast shape onto the imperative toast().\n' +
|
|
374
|
+
'const toastAdapter = {\n' +
|
|
375
|
+
' show: ({ message, action, durationMs }) => {\n' +
|
|
376
|
+
" const handle = toast(message, {\n" +
|
|
377
|
+
' duration: durationMs,\n' +
|
|
378
|
+
' action: action && { label: action.label, onAction: action.onClick },\n' +
|
|
379
|
+
' });\n' +
|
|
380
|
+
' return { dismiss: handle.dismiss };\n' +
|
|
381
|
+
' },\n' +
|
|
382
|
+
'};\n\n' +
|
|
383
|
+
'const { onDismiss, onReopen } = dismissRecovery({\n' +
|
|
384
|
+
' get: () => cards, // your current envelopes\n' +
|
|
385
|
+
' set: (next) => setCards(next), // NEW array reference (never mutate in place)\n' +
|
|
386
|
+
' toast: toastAdapter,\n' +
|
|
387
|
+
'});\n' +
|
|
388
|
+
'// Pass these on the CardPolicy you hand to <kai-cards> / <kai-remote>.\n' +
|
|
389
|
+
'```\n\n' +
|
|
390
|
+
'`onDismiss` writes `dismissed` immutably (Undo restores the prior resolution); ' +
|
|
391
|
+
'`onReopen` clears it back to live (or stamps `expired` when the host says the card ' +
|
|
392
|
+
'is no longer reopenable). Never mutate the array in place — re-render needs a new ref.',
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
// Rule 12 — kai-compare contract: two candidates, JS data prop, stream both, terminal pick
|
|
396
|
+
// Source: src/elements/compare.tsx + src/components/response-compare-types.ts
|
|
397
|
+
id: 'compare-contract',
|
|
398
|
+
test: (t) => {
|
|
399
|
+
if (/<kai-compare\b|kai-compare-select|ResponseCompareData|response.?compare/i.test(t)) return true;
|
|
400
|
+
// "compare two responses / candidates / A vs B" with a kai/UI context.
|
|
401
|
+
if (
|
|
402
|
+
/compar(e|ing|ison)|side.by.side|a\/b|two\s+(responses|candidates|answers|completions)|dual.?response/i.test(t) &&
|
|
403
|
+
/kai-|@kitn|candidate|prefer(ence)?|chosen|reject/i.test(t)
|
|
404
|
+
)
|
|
405
|
+
return true;
|
|
406
|
+
return false;
|
|
407
|
+
},
|
|
408
|
+
title: '`kai-compare` — two candidates, `data` as a JS property, terminal pick',
|
|
409
|
+
cause:
|
|
410
|
+
'`<kai-compare>` shows EXACTLY two assistant candidates for one prompt and lets the ' +
|
|
411
|
+
'user pick the better one. The `data` value is an array/object, so it must be set as ' +
|
|
412
|
+
'a JS PROPERTY (never an HTML attribute). Both candidates can stream — but, like ' +
|
|
413
|
+
'`kai-chat`, that needs a NEW `data` reference per chunk (mutating in place will not ' +
|
|
414
|
+
're-render). The pick is a COMMIT (not a Submit): it fires once and the card collapses.',
|
|
415
|
+
fix:
|
|
416
|
+
'Set `data` in JS with two candidates, stream by reassigning a fresh `data` object ' +
|
|
417
|
+
'per chunk, and listen for `kai-compare-select` directly on the element.\n\n' +
|
|
418
|
+
'```ts\n' +
|
|
419
|
+
"import { toast } from '@kitn.ai/ui/elements';\n" +
|
|
420
|
+
"import type { ResponseCompareData, CompareSelection } from '@kitn.ai/ui';\n\n" +
|
|
421
|
+
"const el = document.querySelector('kai-compare')!;\n" +
|
|
422
|
+
'// data is a JS PROPERTY — exactly two candidates, each with a unique id.\n' +
|
|
423
|
+
'el.data = {\n' +
|
|
424
|
+
" prompt: 'Summarise the report',\n" +
|
|
425
|
+
' candidates: [\n' +
|
|
426
|
+
" { id: 'a', content: '', streaming: true },\n" +
|
|
427
|
+
" { id: 'b', content: '', streaming: true },\n" +
|
|
428
|
+
' ],\n' +
|
|
429
|
+
'} satisfies ResponseCompareData;\n\n' +
|
|
430
|
+
'// Stream BOTH columns: replace data with a NEW object per chunk.\n' +
|
|
431
|
+
"el.data = { ...el.data, candidates: [{ ...a, content: aText }, { ...b, content: bText }] };\n" +
|
|
432
|
+
'// Clear `streaming` on a candidate when it settles — the pick stays disabled\n' +
|
|
433
|
+
'// until BOTH have settled, then `kai-ready` fires.\n\n' +
|
|
434
|
+
"// Picking is terminal: emits { chosenId, rejectedIds, at } and collapses.\n" +
|
|
435
|
+
"el.addEventListener('kai-compare-select', (e) => {\n" +
|
|
436
|
+
' const { chosenId, rejectedIds } = (e as CustomEvent<CompareSelection>).detail;\n' +
|
|
437
|
+
' recordPreference({ prompt, chosen: chosenId, rejected: rejectedIds });\n' +
|
|
438
|
+
'});\n' +
|
|
439
|
+
'```\n\n' +
|
|
440
|
+
'A malformed definition (not two candidates, missing/duplicate ids) fires `kai-error` ' +
|
|
441
|
+
'instead. The event is non-bubbling — listen on the element, not on `document`.',
|
|
442
|
+
},
|
|
259
443
|
];
|
|
260
444
|
|
|
261
445
|
function buildText(matched: Rule[]): string {
|
|
@@ -1530,7 +1530,139 @@ function compose(
|
|
|
1530
1530
|
envLines,
|
|
1531
1531
|
].join('\n');
|
|
1532
1532
|
|
|
1533
|
-
|
|
1533
|
+
// SCAF-16: loading-options note — inform consumers about the two opt-in load modes
|
|
1534
|
+
// (per-element tree-shaking + autoloader) without changing the default import above.
|
|
1535
|
+
// Leads with "the default is right" rather than a size headline; the debug tool
|
|
1536
|
+
// carries the full KB breakdown for developers who ask for it.
|
|
1537
|
+
const block4 = [
|
|
1538
|
+
`=== LOADING OPTIONS ===`,
|
|
1539
|
+
``,
|
|
1540
|
+
`The scaffold uses \`import '@kitn.ai/ui/elements'\` (register-all) — the right`,
|
|
1541
|
+
`default: it registers every kai-* element and is SSR-safe, so leave it as is.`,
|
|
1542
|
+
`Two opt-in modes load less if a page only ever uses a few elements:`,
|
|
1543
|
+
``,
|
|
1544
|
+
` Per-element (bundler apps): import '@kitn.ai/ui/elements/<file>'`,
|
|
1545
|
+
` Registers just that element; your bundler tree-shakes the rest away.`,
|
|
1546
|
+
` Example: import '@kitn.ai/ui/elements/chat' (client-only — not for SSR)`,
|
|
1547
|
+
``,
|
|
1548
|
+
` Autoloader (no-build / CDN pages): a <script type="module"> tag pointing at`,
|
|
1549
|
+
` dist/elements/autoloader.js — loads each kai-* element on demand as it`,
|
|
1550
|
+
` appears in the DOM. A CDN/static-file tool; not importable through a bundler.`,
|
|
1551
|
+
``,
|
|
1552
|
+
`Run the debug tool with "reduce bundle size" for the full breakdown and sizes.`,
|
|
1553
|
+
].join('\n');
|
|
1554
|
+
|
|
1555
|
+
// SCAF-17: interaction patterns — small, copy-pasteable snippets for the
|
|
1556
|
+
// toast / card-recovery / preference-capture features. Appended as a reference
|
|
1557
|
+
// section so the consumer can wire confirmations, undo, and A/B preference
|
|
1558
|
+
// capture without leaving the scaffold. Does not change blocks 1–4.
|
|
1559
|
+
const block5 = interactionPatternsBlock();
|
|
1560
|
+
|
|
1561
|
+
return [header, block1, block2, block3, block4, block5].join('\n\n');
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// ── SCAF-17: reusable interaction-pattern snippets ─────────────────────────────
|
|
1565
|
+
|
|
1566
|
+
/**
|
|
1567
|
+
* The three interaction patterns from the chat-interactions feature set, emitted
|
|
1568
|
+
* as a labeled reference section the scaffolder always appends:
|
|
1569
|
+
* 1. `toast()` confirmation + Undo.
|
|
1570
|
+
* 2. `dismissRecovery()` card-policy wiring (with a toast adapter).
|
|
1571
|
+
* 3. `kai-compare` preference capture (two streams → kai-compare-select →
|
|
1572
|
+
* recordPreference({ prompt, chosen, rejected })).
|
|
1573
|
+
*
|
|
1574
|
+
* Framework-agnostic (plain TS / DOM) so it drops into any of the front-end
|
|
1575
|
+
* targets; the imports are valid from `@kitn.ai/ui` (and `toast` also from
|
|
1576
|
+
* `@kitn.ai/ui/elements`).
|
|
1577
|
+
*/
|
|
1578
|
+
function interactionPatternsBlock(): string {
|
|
1579
|
+
const toastPattern = [
|
|
1580
|
+
`--- Pattern: toast() — confirmation + Undo ---`,
|
|
1581
|
+
`// toast is IMPERATIVE — call it; there is no <kai-toast> to place. The first`,
|
|
1582
|
+
`// call auto-mounts one <kai-toast-region> on document.body. Exported from`,
|
|
1583
|
+
`// both '@kitn.ai/ui' and '@kitn.ai/ui/elements'.`,
|
|
1584
|
+
`import { toast } from '@kitn.ai/ui/elements';`,
|
|
1585
|
+
``,
|
|
1586
|
+
`toast('Copied to clipboard'); // neutral, auto-dismisses`,
|
|
1587
|
+
`toast.success('Saved'); // emerald success variant`,
|
|
1588
|
+
``,
|
|
1589
|
+
`// Undo affordance: an action floors the duration so there's time to act.`,
|
|
1590
|
+
`const t = toast('Item deleted', {`,
|
|
1591
|
+
` action: { label: 'Undo', onAction: () => restoreItem() },`,
|
|
1592
|
+
`});`,
|
|
1593
|
+
`// t.update({ message: 'Restored', variant: 'success' }); t.dismiss();`,
|
|
1594
|
+
``,
|
|
1595
|
+
`// Collapsed (Sonner-style) stacking — toasts pile + expand on hover/focus.`,
|
|
1596
|
+
`// Opt in once at startup, or per-region via <kai-toast-region stack="collapsed">.`,
|
|
1597
|
+
`import { configureToasts } from '@kitn.ai/ui/elements';`,
|
|
1598
|
+
`configureToasts({ stack: 'collapsed' });`,
|
|
1599
|
+
].join('\n');
|
|
1600
|
+
|
|
1601
|
+
const recoveryPattern = [
|
|
1602
|
+
`--- Pattern: dismissRecovery() — card dismiss + Undo (DEFERRED, not deleted) ---`,
|
|
1603
|
+
`// Dismissing a generative-UI card does NOT delete its envelope — it stamps a`,
|
|
1604
|
+
`// 'dismissed' resolution and collapses to a reopenable stub. Keep dismissed`,
|
|
1605
|
+
`// envelopes in your array; wire the policy with dismissRecovery().`,
|
|
1606
|
+
`import { dismissRecovery } from '@kitn.ai/ui';`,
|
|
1607
|
+
`import { toast } from '@kitn.ai/ui/elements';`,
|
|
1608
|
+
``,
|
|
1609
|
+
`// Adapter: map dismissRecovery's toast shape onto the imperative toast().`,
|
|
1610
|
+
`const toastAdapter = {`,
|
|
1611
|
+
` show: ({ message, action, durationMs }) => {`,
|
|
1612
|
+
` const h = toast(message, {`,
|
|
1613
|
+
` duration: durationMs,`,
|
|
1614
|
+
` action: action && { label: action.label, onAction: action.onClick },`,
|
|
1615
|
+
` });`,
|
|
1616
|
+
` return { dismiss: h.dismiss };`,
|
|
1617
|
+
` },`,
|
|
1618
|
+
`};`,
|
|
1619
|
+
``,
|
|
1620
|
+
`const { onDismiss, onReopen } = dismissRecovery({`,
|
|
1621
|
+
` get: () => cards, // your current envelopes`,
|
|
1622
|
+
` set: (next) => setCards(next), // a NEW array reference (never mutate in place)`,
|
|
1623
|
+
` toast: toastAdapter,`,
|
|
1624
|
+
`});`,
|
|
1625
|
+
`// Hand { onDismiss, onReopen } to the CardPolicy on <kai-cards> / <kai-remote>.`,
|
|
1626
|
+
].join('\n');
|
|
1627
|
+
|
|
1628
|
+
const preferencePattern = [
|
|
1629
|
+
`--- Pattern: kai-compare — capture an A/B preference pair ---`,
|
|
1630
|
+
`// <kai-compare> shows EXACTLY two candidates for one prompt. data is a JS`,
|
|
1631
|
+
`// PROPERTY; stream both columns with a fresh data ref per chunk; picking is`,
|
|
1632
|
+
`// terminal and fires kai-compare-select { chosenId, rejectedIds }.`,
|
|
1633
|
+
`import type { ResponseCompareData, CompareSelection } from '@kitn.ai/ui';`,
|
|
1634
|
+
``,
|
|
1635
|
+
`const el = document.querySelector('kai-compare')!;`,
|
|
1636
|
+
`el.data = {`,
|
|
1637
|
+
` prompt,`,
|
|
1638
|
+
` candidates: [`,
|
|
1639
|
+
` { id: 'a', content: '', streaming: true },`,
|
|
1640
|
+
` { id: 'b', content: '', streaming: true },`,
|
|
1641
|
+
` ],`,
|
|
1642
|
+
`} satisfies ResponseCompareData;`,
|
|
1643
|
+
``,
|
|
1644
|
+
`// Stream BOTH: replace data with a NEW object per chunk; clear streaming when`,
|
|
1645
|
+
`// a candidate settles. The pick unlocks once both have settled (kai-ready).`,
|
|
1646
|
+
`// el.data = { ...el.data, candidates: [{ ...a, content: aText }, { ...b, content: bText }] };`,
|
|
1647
|
+
``,
|
|
1648
|
+
`el.addEventListener('kai-compare-select', (e) => {`,
|
|
1649
|
+
` const { chosenId, rejectedIds } = (e as CustomEvent<CompareSelection>).detail;`,
|
|
1650
|
+
` recordPreference({ prompt, chosen: chosenId, rejected: rejectedIds });`,
|
|
1651
|
+
`});`,
|
|
1652
|
+
].join('\n');
|
|
1653
|
+
|
|
1654
|
+
return [
|
|
1655
|
+
`=== INTERACTION PATTERNS ===`,
|
|
1656
|
+
``,
|
|
1657
|
+
`Optional snippets for confirmations, card recovery, and preference capture.`,
|
|
1658
|
+
`Drop in the one(s) you need; all imports resolve from @kitn.ai/ui.`,
|
|
1659
|
+
``,
|
|
1660
|
+
toastPattern,
|
|
1661
|
+
``,
|
|
1662
|
+
recoveryPattern,
|
|
1663
|
+
``,
|
|
1664
|
+
preferencePattern,
|
|
1665
|
+
].join('\n');
|
|
1534
1666
|
}
|
|
1535
1667
|
|
|
1536
1668
|
// ── error text ────────────────────────────────────────────────────────────────
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { createSignal, For, Show } from 'solid-js';
|
|
2
2
|
import { ChatConfig, useChatConfig } from '../primitives/chat-config';
|
|
3
3
|
import { ChatContainer, ChatContainerContent, ChatContainerScrollAnchor } from './chat-container';
|
|
4
|
-
import { Message, MessageAvatar,
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { Attachments, Attachment, AttachmentPreview, AttachmentInfo, type AttachmentData } from './attachments';
|
|
4
|
+
import { Message, MessageAvatar, MessageBody } from './message';
|
|
5
|
+
import { type AttachmentData } from './attachments';
|
|
6
|
+
import { createMessageFeedback, type MessageActionDetail } from '../primitives/message-feedback';
|
|
8
7
|
import { ModelSwitcher } from './model-switcher';
|
|
9
8
|
import { ScrollButton } from './scroll-button';
|
|
10
9
|
import {
|
|
@@ -95,7 +94,7 @@ export interface ChatThreadProps {
|
|
|
95
94
|
onSubmit?: (detail: { value: string; attachments: AttachmentData[] }) => void;
|
|
96
95
|
onSuggestionClick?: (value: string) => void;
|
|
97
96
|
onModelChange?: (modelId: string) => void;
|
|
98
|
-
onMessageAction?: (detail:
|
|
97
|
+
onMessageAction?: (detail: MessageActionDetail) => void;
|
|
99
98
|
onSearch?: () => void;
|
|
100
99
|
onVoice?: () => void;
|
|
101
100
|
onSlashSelect?: (command: SlashCommandItem) => void;
|
|
@@ -104,6 +103,15 @@ export interface ChatThreadProps {
|
|
|
104
103
|
export function ChatThread(props: ChatThreadProps) {
|
|
105
104
|
const outer = useChatConfig();
|
|
106
105
|
const reveal = () => (props.actionsReveal === 'hover' ? 'hover' : 'always');
|
|
106
|
+
// Feedback (copy + vote) state lives ABOVE the per-message <For>, so streaming
|
|
107
|
+
// re-renders (a fresh `messages` array ref per chunk) don't wipe it.
|
|
108
|
+
// The copy/feedback toasts scope to the chat (this thread's root) so they appear
|
|
109
|
+
// in-chat rather than at the page top.
|
|
110
|
+
let rootEl: HTMLElement | undefined;
|
|
111
|
+
const feedback = createMessageFeedback({
|
|
112
|
+
emit: (detail) => props.onMessageAction?.(detail),
|
|
113
|
+
target: () => rootEl,
|
|
114
|
+
});
|
|
107
115
|
const [internal, setInternal] = createSignal(props.value ?? '');
|
|
108
116
|
const [attachments, setAttachments] = createSignal<AttachmentData[]>([]);
|
|
109
117
|
const current = () => props.value ?? internal();
|
|
@@ -122,7 +130,7 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
122
130
|
|
|
123
131
|
return (
|
|
124
132
|
<ChatConfig proseSize={props.proseSize} codeTheme={props.codeTheme} codeHighlight={props.codeHighlight !== false} portalMount={outer.portalMount()}>
|
|
125
|
-
<div class={`flex h-full flex-col bg-background ${props.class ?? ''}`}>
|
|
133
|
+
<div ref={(e) => (rootEl = e as HTMLElement)} class={`flex h-full flex-col bg-background ${props.class ?? ''}`}>
|
|
126
134
|
<Show when={showHeader()}>
|
|
127
135
|
<header class="flex h-14 shrink-0 items-center justify-between border-b border-border px-5">
|
|
128
136
|
<div class="flex items-center gap-2">
|
|
@@ -168,35 +176,19 @@ export function ChatThread(props: ChatThreadProps) {
|
|
|
168
176
|
<For each={props.messages}>
|
|
169
177
|
{(m) => {
|
|
170
178
|
const body = (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
</Attachments>
|
|
185
|
-
</Show>
|
|
186
|
-
<MessageContent
|
|
187
|
-
markdown={m.role === 'assistant'}
|
|
188
|
-
class={m.role === 'user' ? 'bg-muted text-primary max-w-[85%] rounded-2xl px-4 py-2' : 'bg-transparent p-0'}
|
|
189
|
-
>
|
|
190
|
-
{m.content}
|
|
191
|
-
</MessageContent>
|
|
192
|
-
<Show when={m.actions?.length}>
|
|
193
|
-
<MessageActionBar
|
|
194
|
-
actions={m.actions!}
|
|
195
|
-
reveal={reveal()}
|
|
196
|
-
onAction={(action) => props.onMessageAction?.({ messageId: m.id, action })}
|
|
197
|
-
/>
|
|
198
|
-
</Show>
|
|
199
|
-
</>
|
|
179
|
+
<MessageBody
|
|
180
|
+
content={m.content}
|
|
181
|
+
reasoning={m.reasoning}
|
|
182
|
+
tools={m.tools}
|
|
183
|
+
attachments={m.attachments}
|
|
184
|
+
isUser={m.role === 'user'}
|
|
185
|
+
markdown={m.role === 'assistant'}
|
|
186
|
+
actions={m.actions}
|
|
187
|
+
actionsReveal={reveal()}
|
|
188
|
+
activeFeedback={feedback.resolveFeedback(m)}
|
|
189
|
+
copied={feedback.isCopied(m.id)}
|
|
190
|
+
onAction={(action) => feedback.handleAction(m, action)}
|
|
191
|
+
/>
|
|
200
192
|
);
|
|
201
193
|
const rowGroup = reveal() === 'hover' ? 'group ' : '';
|
|
202
194
|
return (
|
|
@@ -15,11 +15,12 @@ import { cn } from '../utils/cn';
|
|
|
15
15
|
import { Button } from '../ui/button';
|
|
16
16
|
import { HoverCard } from '../ui/hover-card';
|
|
17
17
|
import { Card } from './card';
|
|
18
|
+
import { DismissedStub } from './dismissed-stub';
|
|
18
19
|
import type { CardEnvelope, CardEvent, CardHost, CardResolution } from '../primitives/card-contract';
|
|
19
20
|
import { useCardResolution } from './use-card-resolution';
|
|
20
21
|
import { emitCardEvent } from '../primitives/card-routing';
|
|
21
22
|
import { useCardHost } from '../primitives/card-host';
|
|
22
|
-
import { Check } from 'lucide-solid';
|
|
23
|
+
import { Check, X } from 'lucide-solid';
|
|
23
24
|
|
|
24
25
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
26
|
// Types (choice.schema.json) — see src/primitives/card-schemas/choice.schema.json
|
|
@@ -49,6 +50,7 @@ export interface ChoiceCardData {
|
|
|
49
50
|
options: ChoiceOption[]; // 1..N
|
|
50
51
|
allowOther?: ChoiceAllowOther; // free-text escape
|
|
51
52
|
submitLabel?: string; // label for the Submit button (default 'Submit')
|
|
53
|
+
dismissible?: boolean; // show a close affordance that emits `dismiss`
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
export type ChoiceCardEnvelope = CardEnvelope<'choice', ChoiceCardData>;
|
|
@@ -310,6 +312,15 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
|
|
|
310
312
|
});
|
|
311
313
|
};
|
|
312
314
|
|
|
315
|
+
// Dismiss: emit `dismiss` AND optimistically flip to a `dismissed` resolution so
|
|
316
|
+
// the card collapses to its re-openable stub immediately.
|
|
317
|
+
const onDismiss = (): void => {
|
|
318
|
+
if (res.isResolved()) return;
|
|
319
|
+
emit({ kind: 'dismiss', cardId: local.cardId });
|
|
320
|
+
res.setLocal({ kind: 'dismissed' });
|
|
321
|
+
};
|
|
322
|
+
const onReopen = (): void => emit({ kind: 'reopen', cardId: local.cardId });
|
|
323
|
+
|
|
313
324
|
const focusRadio = (index: number): void => {
|
|
314
325
|
const radios = groupRef?.querySelectorAll<HTMLElement>('[role="radio"]');
|
|
315
326
|
radios?.[index]?.focus();
|
|
@@ -353,6 +364,10 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
|
|
|
353
364
|
return <Card heading={local.heading} errorMessage="The card failed to render." />;
|
|
354
365
|
}}
|
|
355
366
|
>
|
|
367
|
+
<Show
|
|
368
|
+
when={!res.isDeferred()}
|
|
369
|
+
fallback={<DismissedStub type={CHOICE_CARD_TYPE} title={local.heading} onReopen={onReopen} />}
|
|
370
|
+
>
|
|
356
371
|
<Card heading={local.heading}>
|
|
357
372
|
<div class={cn('flex flex-col gap-3', local.class)}>
|
|
358
373
|
<Show when={local.data?.prompt}>
|
|
@@ -421,12 +436,26 @@ export function ChoiceCard(props: ChoiceCardProps): JSX.Element {
|
|
|
421
436
|
</div>
|
|
422
437
|
</Show>
|
|
423
438
|
|
|
424
|
-
<
|
|
425
|
-
{
|
|
426
|
-
|
|
439
|
+
<div class="flex w-full flex-wrap items-center justify-between gap-2">
|
|
440
|
+
<Show when={local.data?.dismissible === true}>
|
|
441
|
+
<Button
|
|
442
|
+
type="button"
|
|
443
|
+
variant="ghost"
|
|
444
|
+
size="icon-sm"
|
|
445
|
+
aria-label="Dismiss"
|
|
446
|
+
onClick={onDismiss}
|
|
447
|
+
>
|
|
448
|
+
<X size={16} aria-hidden="true" />
|
|
449
|
+
</Button>
|
|
450
|
+
</Show>
|
|
451
|
+
<Button type="button" class="ml-auto" disabled={!canSubmit()} onClick={submit}>
|
|
452
|
+
{submitLabel()}
|
|
453
|
+
</Button>
|
|
454
|
+
</div>
|
|
427
455
|
</Show>
|
|
428
456
|
</div>
|
|
429
457
|
</Card>
|
|
458
|
+
</Show>
|
|
430
459
|
</ErrorBoundary>
|
|
431
460
|
</Show>
|
|
432
461
|
);
|