@kitn.ai/ui 0.15.1 → 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 +37 -13
- package/bin/mcp.js +22 -0
- package/dist/bash-InADTalH.js +6 -1
- package/dist/components/artifact.d.ts +58 -0
- package/dist/components/attachments.d.ts +67 -0
- package/dist/components/card-fallback.d.ts +10 -0
- package/dist/components/card-renderer.d.ts +13 -0
- package/dist/components/card.d.ts +28 -0
- package/dist/components/chain-of-thought.d.ts +29 -0
- package/dist/components/chat-container.d.ts +19 -0
- package/dist/components/chat-scope-picker.d.ts +9 -0
- package/dist/components/chat-thread.d.ts +91 -0
- package/dist/components/checkpoint.d.ts +20 -0
- package/dist/components/choice-card.d.ts +78 -0
- package/dist/components/code-block.d.ts +16 -0
- package/dist/components/confirm-card.d.ts +57 -0
- package/dist/components/context.d.ts +65 -0
- package/dist/components/conversation-item.d.ts +8 -0
- package/dist/components/conversation-list.d.ts +11 -0
- package/dist/components/dismissed-stub.d.ts +22 -0
- package/dist/components/embed.d.ts +21 -0
- package/dist/components/empty.d.ts +43 -0
- package/dist/components/feedback-bar.d.ts +45 -0
- package/dist/components/file-tree.d.ts +57 -0
- package/dist/components/file-upload.d.ts +16 -0
- package/dist/components/form-widgets.d.ts +31 -0
- package/dist/components/form.d.ts +103 -0
- package/dist/components/image.d.ts +12 -0
- package/dist/components/link-preview.d.ts +23 -0
- package/dist/components/loader.d.ts +60 -0
- package/dist/components/markdown.d.ts +8 -0
- package/dist/components/message-skills.d.ts +15 -0
- package/dist/components/message.d.ts +111 -0
- package/dist/components/model-switcher.d.ts +8 -0
- package/dist/components/prompt-input.d.ts +38 -0
- package/dist/components/prompt-suggestion.d.ts +13 -0
- package/dist/components/reasoning.d.ts +20 -0
- package/dist/components/response-compare-types.d.ts +66 -0
- package/dist/components/response-compare.d.ts +51 -0
- package/dist/components/response-stream.d.ts +14 -0
- package/dist/components/scroll-button.d.ts +7 -0
- package/dist/components/slash-command.d.ts +15 -0
- package/dist/components/source.d.ts +23 -0
- package/dist/components/tasks-card.d.ts +72 -0
- package/dist/components/text-shimmer.d.ts +9 -0
- package/dist/components/thinking-bar.d.ts +10 -0
- package/dist/components/toast.d.ts +48 -0
- package/dist/components/tool.d.ts +16 -0
- package/dist/components/use-card-resolution.d.ts +16 -0
- package/dist/components/voice-input.d.ts +7 -0
- package/dist/core-AYMC6_lb.js +5874 -12
- package/dist/css-M7EaDHN_.js +6 -1
- package/dist/custom-elements.json +260 -11
- package/dist/elements/artifact.d.ts +1 -0
- package/dist/elements/artifact.js +1 -0
- package/dist/elements/attachments.d.ts +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.d.ts +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 +1 -0
- package/dist/elements/chain-of-thought.js +1 -0
- package/dist/elements/chat-scope-picker.d.ts +1 -0
- package/dist/elements/chat-scope-picker.js +1 -0
- package/dist/elements/chat-workspace.d.ts +1 -0
- package/dist/elements/chat-workspace.js +1 -0
- package/dist/elements/chat.d.ts +1 -0
- package/dist/elements/chat.js +1 -0
- package/dist/elements/checkpoint.d.ts +1 -0
- package/dist/elements/checkpoint.js +1 -0
- package/dist/elements/choice.d.ts +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.d.ts +1 -0
- package/dist/elements/code-block.js +1 -0
- package/dist/elements/compare.d.ts +1 -0
- package/dist/elements/compare.js +1 -0
- package/dist/elements/confirm-card.d.ts +1 -0
- package/dist/elements/confirm-card.js +1 -0
- package/dist/elements/context-meter.d.ts +1 -0
- package/dist/elements/context-meter.js +1 -0
- package/dist/elements/conversation-list.d.ts +1 -0
- package/dist/elements/conversation-list.js +1 -0
- package/dist/elements/embed.d.ts +1 -0
- package/dist/elements/embed.js +1 -0
- package/dist/elements/empty.d.ts +1 -0
- package/dist/elements/empty.js +1 -0
- package/dist/elements/feedback-bar.d.ts +1 -0
- package/dist/elements/feedback-bar.js +1 -0
- package/dist/elements/file-tree.d.ts +1 -0
- package/dist/elements/file-tree.js +1 -0
- package/dist/elements/file-upload.d.ts +1 -0
- package/dist/elements/file-upload.js +1 -0
- package/dist/elements/form.d.ts +1 -0
- package/dist/elements/form.js +1 -0
- package/dist/elements/image.d.ts +1 -0
- package/dist/elements/image.js +1 -0
- package/dist/elements/link-preview.d.ts +1 -0
- package/dist/elements/link-preview.js +1 -0
- package/dist/elements/loader.d.ts +1 -0
- package/dist/elements/loader.js +1 -0
- package/dist/elements/markdown.d.ts +1 -0
- package/dist/elements/markdown.js +1 -0
- package/dist/elements/message-skills.d.ts +1 -0
- package/dist/elements/message-skills.js +1 -0
- package/dist/elements/message.d.ts +1 -0
- package/dist/elements/message.js +1 -0
- package/dist/elements/model-switcher.d.ts +1 -0
- package/dist/elements/model-switcher.js +1 -0
- package/dist/elements/popover.d.ts +1 -0
- package/dist/elements/popover.js +1 -0
- package/dist/elements/prompt-input.d.ts +1 -0
- package/dist/elements/prompt-input.js +1 -0
- package/dist/elements/prompt-suggestions.d.ts +1 -0
- package/dist/elements/prompt-suggestions.js +1 -0
- package/dist/elements/reasoning.d.ts +1 -0
- package/dist/elements/reasoning.js +1 -0
- package/dist/elements/resizable.d.ts +1 -0
- package/dist/elements/resizable.js +1 -0
- package/dist/elements/response-stream.d.ts +1 -0
- package/dist/elements/response-stream.js +14 -0
- package/dist/elements/scroll-button.d.ts +1 -0
- package/dist/elements/scroll-button.js +1 -0
- package/dist/elements/source.d.ts +1 -0
- package/dist/elements/source.js +1 -0
- package/dist/elements/switch.d.ts +1 -0
- package/dist/elements/switch.js +1 -0
- package/dist/elements/tasks.d.ts +1 -0
- package/dist/elements/tasks.js +1 -0
- package/dist/elements/text-shimmer.d.ts +1 -0
- package/dist/elements/text-shimmer.js +1 -0
- package/dist/elements/thinking-bar.d.ts +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.d.ts +1 -0
- package/dist/elements/tool.js +1 -0
- package/dist/elements/voice-input.d.ts +1 -0
- package/dist/elements/voice-input.js +1 -0
- package/dist/elements.d.ts +768 -0
- package/dist/engine-javascript-vq0WuIJl.js +2516 -14
- package/dist/github-dark-dimmed-DUshB20C.js +4 -1
- package/dist/github-light-JYsPkUQd.js +4 -1
- package/dist/html-CPZ3oZQ7.js +10 -1
- package/dist/index.d.ts +124 -2
- package/dist/index.js +15593 -0
- package/dist/javascript-C25yR2R2.js +6 -1
- package/dist/json-DxJze_jm.js +6 -1
- package/dist/kitn-chat.es.js +1 -77
- package/dist/llms/llms-full.txt +57 -12
- package/dist/llms/llms.txt +3 -3
- package/dist/mcp.es.js +2296 -0
- package/dist/primitives/card-contract.d.ts +119 -0
- package/dist/primitives/card-host.d.ts +12 -0
- package/dist/primitives/card-recovery.d.ts +55 -0
- package/dist/primitives/card-registry.d.ts +17 -0
- package/dist/primitives/card-resolution.d.ts +8 -0
- package/dist/primitives/card-routing.d.ts +12 -0
- package/dist/primitives/card-validate.d.ts +25 -0
- package/dist/primitives/chat-config.d.ts +31 -0
- package/dist/primitives/controllable.d.ts +13 -0
- package/dist/primitives/embed-providers.d.ts +65 -0
- package/dist/primitives/highlighter.d.ts +23 -0
- package/dist/primitives/link-preview.d.ts +47 -0
- package/dist/primitives/message-feedback.d.ts +35 -0
- package/dist/primitives/pdf-preview.d.ts +50 -0
- package/dist/primitives/toast-store.d.ts +88 -0
- package/dist/primitives/use-auto-resize.d.ts +8 -0
- package/dist/primitives/use-resize-observer.d.ts +1 -0
- package/dist/primitives/use-stick-to-bottom.d.ts +5 -0
- package/dist/primitives/use-text-stream.d.ts +19 -0
- package/dist/primitives/use-voice-recorder.d.ts +9 -0
- package/dist/react/index.d.ts +1198 -0
- package/dist/react/runtime.d.ts +15 -0
- package/dist/react.js +275 -0
- package/dist/register-JyTglR6m.js +1 -0
- package/dist/register-impl-C6wlSsts.js +77 -0
- package/dist/remote/host-embed.d.ts +33 -0
- package/dist/remote/index.d.ts +2 -0
- package/dist/remote/origin.d.ts +5 -0
- package/dist/remote/provider-runtime.d.ts +27 -0
- package/dist/remote/provider.d.ts +2 -0
- package/dist/remote/validate.d.ts +4 -0
- package/dist/remote/version.d.ts +4 -0
- package/dist/remote/wire.d.ts +53 -0
- package/dist/schemas/choice.schema.json +5 -0
- package/dist/schemas/tasks.schema.json +5 -0
- package/dist/svelte--5p79yCD.js +15 -1
- package/dist/tsx-B8rCNbgL.js +6 -1
- package/dist/types.d.ts +42 -0
- package/dist/typescript-RycA9KXf.js +6 -1
- package/dist/ui/action-icons.d.ts +11 -0
- package/dist/ui/avatar.d.ts +8 -0
- package/dist/ui/badge.d.ts +9 -0
- package/dist/ui/button.d.ts +10 -0
- package/dist/ui/collapsible.d.ts +27 -0
- package/dist/ui/dropdown.d.ts +20 -0
- package/dist/ui/hover-card.d.ts +28 -0
- package/dist/ui/overlay.d.ts +67 -0
- package/dist/ui/popover.d.ts +32 -0
- package/dist/ui/resizable.d.ts +76 -0
- package/dist/ui/scroll-area.d.ts +5 -0
- package/dist/ui/separator.d.ts +5 -0
- package/dist/ui/skeleton.d.ts +5 -0
- package/dist/ui/switch.d.ts +19 -0
- package/dist/ui/textarea.d.ts +6 -0
- package/dist/ui/tooltip.d.ts +9 -0
- package/dist/utils/cn.d.ts +2 -0
- package/dist/vue-BmIZj4XD.js +33 -1
- package/frameworks/react/index.tsx +67 -11
- package/frameworks/react/runtime.tsx +16 -2
- package/llms-full.txt +57 -12
- package/llms.txt +3 -3
- package/package.json +36 -14
- package/src/agent-tooling/archetypes.ts +46 -0
- package/src/agent-tooling/integrations/cloudflare.ts +88 -0
- package/src/agent-tooling/integrations/langgraph.ts +60 -0
- package/src/agent-tooling/integrations/mastra.ts +27 -0
- package/src/agent-tooling/integrations/mock.ts +34 -0
- package/src/agent-tooling/integrations/ollama.ts +47 -0
- package/src/agent-tooling/integrations/openrouter.ts +26 -0
- package/src/agent-tooling/integrations/pi.ts +40 -0
- package/src/agent-tooling/integrations/pydantic-ai.ts +52 -0
- package/src/agent-tooling/integrations/vercel-ai-sdk.ts +44 -0
- package/src/agent-tooling/mcp/manifest.ts +128 -0
- package/src/agent-tooling/mcp/server.ts +59 -0
- package/src/agent-tooling/mcp/stdio.ts +18 -0
- package/src/agent-tooling/mcp/tools/debug.ts +489 -0
- package/src/agent-tooling/mcp/tools/reference.ts +132 -0
- package/src/agent-tooling/mcp/tools/scaffold.ts +1759 -0
- package/src/agent-tooling/mcp/tools/theme.ts +340 -0
- package/src/agent-tooling/mcp/tools/types.ts +14 -0
- package/src/agent-tooling/registry.ts +41 -0
- package/src/agent-tooling/types.ts +30 -0
- 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/define.tsx +7 -1
- package/src/elements/element-manifest.json +182 -0
- package/src/elements/element-meta.json +191 -68
- package/src/elements/element-types.d.ts +43 -6
- package/src/elements/framework-usage.json +25 -0
- package/src/elements/message.tsx +27 -45
- package/src/elements/register-impl.ts +58 -0
- package/src/elements/register.ts +26 -51
- 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/theme.css +4 -2
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Tool } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* theme — produce a `--kai-*` CSS token override block from a brand color or
|
|
6
|
+
* description. Tokens are sourced exclusively from the real names in theme.css.
|
|
7
|
+
*
|
|
8
|
+
* Real --kai-color-* names (verified against repo-root theme.css):
|
|
9
|
+
* --kai-color-primary → brand color
|
|
10
|
+
* --kai-color-primary-foreground → AA-contrast fg (white or black)
|
|
11
|
+
* --kai-color-ring → focus ring (brand color)
|
|
12
|
+
* --kai-color-accent → tinted accent surface
|
|
13
|
+
* --kai-color-accent-foreground → text on accent surface
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ─── hex color utilities (no deps) ──────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
/** Parse #rgb or #rrggbb → { r, g, b } 0-255. Returns null on failure. */
|
|
19
|
+
function parseHex(hex: string): { r: number; g: number; b: number } | null {
|
|
20
|
+
const s = hex.trim().replace(/^#/, '');
|
|
21
|
+
if (s.length === 3) {
|
|
22
|
+
const r = parseInt(s[0] + s[0], 16);
|
|
23
|
+
const g = parseInt(s[1] + s[1], 16);
|
|
24
|
+
const b = parseInt(s[2] + s[2], 16);
|
|
25
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
|
|
26
|
+
return { r, g, b };
|
|
27
|
+
}
|
|
28
|
+
if (s.length === 6) {
|
|
29
|
+
const r = parseInt(s.slice(0, 2), 16);
|
|
30
|
+
const g = parseInt(s.slice(2, 4), 16);
|
|
31
|
+
const b = parseInt(s.slice(4, 6), 16);
|
|
32
|
+
if (isNaN(r) || isNaN(g) || isNaN(b)) return null;
|
|
33
|
+
return { r, g, b };
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function toHex(r: number, g: number, b: number): string {
|
|
39
|
+
return (
|
|
40
|
+
'#' +
|
|
41
|
+
[r, g, b]
|
|
42
|
+
.map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0'))
|
|
43
|
+
.join('')
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Relative luminance (WCAG 2.1) — used for AA contrast check.
|
|
49
|
+
* Returns 0 (black) to 1 (white).
|
|
50
|
+
*/
|
|
51
|
+
function relativeLuminance(r: number, g: number, b: number): number {
|
|
52
|
+
const lin = (c: number) => {
|
|
53
|
+
const s = c / 255;
|
|
54
|
+
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
55
|
+
};
|
|
56
|
+
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** White or black for maximum contrast against the brand color. */
|
|
60
|
+
function foreground(r: number, g: number, b: number): '#ffffff' | '#000000' {
|
|
61
|
+
const lum = relativeLuminance(r, g, b);
|
|
62
|
+
// Contrast against white = (1 + 0.05) / (lum + 0.05)
|
|
63
|
+
// Contrast against black = (lum + 0.05) / (0 + 0.05)
|
|
64
|
+
const contrastWhite = 1.05 / (lum + 0.05);
|
|
65
|
+
const contrastBlack = (lum + 0.05) / 0.05;
|
|
66
|
+
return contrastWhite >= contrastBlack ? '#ffffff' : '#000000';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Lighten a color by a ratio 0-1 (moves toward white). */
|
|
70
|
+
function lighten(r: number, g: number, b: number, ratio: number): string {
|
|
71
|
+
return toHex(
|
|
72
|
+
r + (255 - r) * ratio,
|
|
73
|
+
g + (255 - g) * ratio,
|
|
74
|
+
b + (255 - b) * ratio,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Darken a color by a ratio 0-1 (moves toward black). */
|
|
79
|
+
function darken(r: number, g: number, b: number, ratio: number): string {
|
|
80
|
+
return toHex(r * (1 - ratio), g * (1 - ratio), b * (1 - ratio));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Produce a tinted accent surface (very light tint of the brand color). */
|
|
84
|
+
function accentSurface(r: number, g: number, b: number): string {
|
|
85
|
+
return lighten(r, g, b, 0.88);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Dark-mode variant: lighten the brand so it reads well on dark backgrounds. */
|
|
89
|
+
function darkModeBrand(r: number, g: number, b: number): string {
|
|
90
|
+
const lum = relativeLuminance(r, g, b);
|
|
91
|
+
// If brand is already light enough for dark bg, keep it; otherwise lighten.
|
|
92
|
+
if (lum > 0.25) return toHex(r, g, b);
|
|
93
|
+
return lighten(r, g, b, 0.35);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Dark-mode accent surface: subtle dark tint of brand. */
|
|
97
|
+
function darkAccentSurface(r: number, g: number, b: number): string {
|
|
98
|
+
return darken(r, g, b, 0.75);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── keyword → hex fallback map ──────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
const KEYWORD_COLORS: Record<string, string> = {
|
|
104
|
+
blue: '#3b82f6',
|
|
105
|
+
navy: '#1e3a8a',
|
|
106
|
+
sky: '#0ea5e9',
|
|
107
|
+
cyan: '#06b6d4',
|
|
108
|
+
teal: '#14b8a6',
|
|
109
|
+
green: '#22c55e',
|
|
110
|
+
emerald: '#10b981',
|
|
111
|
+
lime: '#84cc16',
|
|
112
|
+
yellow: '#eab308',
|
|
113
|
+
amber: '#f59e0b',
|
|
114
|
+
orange: '#f97316',
|
|
115
|
+
red: '#ef4444',
|
|
116
|
+
rose: '#f43f5e',
|
|
117
|
+
pink: '#ec4899',
|
|
118
|
+
purple: '#a855f7',
|
|
119
|
+
violet: '#7c3aed',
|
|
120
|
+
indigo: '#6366f1',
|
|
121
|
+
slate: '#64748b',
|
|
122
|
+
gray: '#6b7280',
|
|
123
|
+
zinc: '#71717a',
|
|
124
|
+
neutral: '#737373',
|
|
125
|
+
stone: '#78716c',
|
|
126
|
+
white: '#f8fafc',
|
|
127
|
+
black: '#0f172a',
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const DEFAULT_BRAND = '#6366f1'; // indigo — kit's own brand feel
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Resolve a brand hex from input.
|
|
134
|
+
* Returns { hex, note } where note explains the assumption if we guessed.
|
|
135
|
+
*/
|
|
136
|
+
function resolveBrand(
|
|
137
|
+
brand?: string,
|
|
138
|
+
description?: string,
|
|
139
|
+
): { hex: string; r: number; g: number; b: number; note: string } {
|
|
140
|
+
// 1. Explicit hex
|
|
141
|
+
if (brand) {
|
|
142
|
+
const parsed = parseHex(brand);
|
|
143
|
+
if (parsed) {
|
|
144
|
+
return { hex: brand.startsWith('#') ? brand : `#${brand}`, ...parsed, note: '' };
|
|
145
|
+
}
|
|
146
|
+
// brand provided but unparseable — fall through with a note
|
|
147
|
+
const fallback = parseHex(DEFAULT_BRAND)!;
|
|
148
|
+
return {
|
|
149
|
+
hex: DEFAULT_BRAND,
|
|
150
|
+
...fallback,
|
|
151
|
+
note: `Could not parse \`${brand}\` as a hex color — defaulted to **${DEFAULT_BRAND}** (indigo). Pass a valid \`#rgb\` or \`#rrggbb\` value to use your color.`,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 2. Description → keyword match
|
|
156
|
+
if (description) {
|
|
157
|
+
const lower = description.toLowerCase();
|
|
158
|
+
for (const [keyword, hex] of Object.entries(KEYWORD_COLORS)) {
|
|
159
|
+
if (lower.includes(keyword)) {
|
|
160
|
+
const parsed = parseHex(hex)!;
|
|
161
|
+
return {
|
|
162
|
+
hex,
|
|
163
|
+
...parsed,
|
|
164
|
+
note: `No brand color provided — inferred **${hex}** (${keyword}) from description. Override with \`brand: '#rrggbb'\` to use an exact color.`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Description present but no keyword matched → tasteful default
|
|
169
|
+
const parsed = parseHex(DEFAULT_BRAND)!;
|
|
170
|
+
return {
|
|
171
|
+
hex: DEFAULT_BRAND,
|
|
172
|
+
...parsed,
|
|
173
|
+
note: `No recognizable color keyword in description — defaulted to **${DEFAULT_BRAND}** (indigo). Override with \`brand: '#rrggbb'\` for an exact color.`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 3. Nothing provided → kit brand default
|
|
178
|
+
const parsed = parseHex(DEFAULT_BRAND)!;
|
|
179
|
+
return {
|
|
180
|
+
hex: DEFAULT_BRAND,
|
|
181
|
+
...parsed,
|
|
182
|
+
note: `No brand color or description provided — defaulted to **${DEFAULT_BRAND}** (indigo, the kit's own brand). Pass \`brand: '#rrggbb'\` to use your color.`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ─── CSS block builders ───────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
interface TokenSet {
|
|
189
|
+
primary: string;
|
|
190
|
+
primaryFg: string;
|
|
191
|
+
ring: string;
|
|
192
|
+
accent: string;
|
|
193
|
+
accentFg: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function buildTokenSet(r: number, g: number, b: number): TokenSet {
|
|
197
|
+
const primary = toHex(r, g, b);
|
|
198
|
+
const primaryFg = foreground(r, g, b);
|
|
199
|
+
const accent = accentSurface(r, g, b);
|
|
200
|
+
// foreground on the very-light accent surface
|
|
201
|
+
const accentFgColor = foreground(
|
|
202
|
+
...([accent]
|
|
203
|
+
.map((h) => parseHex(h)!)
|
|
204
|
+
.flatMap((c) => [c.r, c.g, c.b]) as [number, number, number]),
|
|
205
|
+
);
|
|
206
|
+
return {
|
|
207
|
+
primary,
|
|
208
|
+
primaryFg,
|
|
209
|
+
ring: primary,
|
|
210
|
+
accent,
|
|
211
|
+
accentFg: accentFgColor,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function buildDarkTokenSet(r: number, g: number, b: number): TokenSet {
|
|
216
|
+
const darkBrand = darkModeBrand(r, g, b);
|
|
217
|
+
const parsed = parseHex(darkBrand)!;
|
|
218
|
+
const primary = darkBrand;
|
|
219
|
+
const primaryFg = foreground(parsed.r, parsed.g, parsed.b);
|
|
220
|
+
const accent = darkAccentSurface(r, g, b);
|
|
221
|
+
const accentFgParsed = parseHex(accent)!;
|
|
222
|
+
return {
|
|
223
|
+
primary,
|
|
224
|
+
primaryFg,
|
|
225
|
+
ring: darkBrand,
|
|
226
|
+
accent,
|
|
227
|
+
accentFg: foreground(accentFgParsed.r, accentFgParsed.g, accentFgParsed.b),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function cssBlock(selector: string, tokens: TokenSet): string {
|
|
232
|
+
return [
|
|
233
|
+
`${selector} {`,
|
|
234
|
+
` --kai-color-primary: ${tokens.primary};`,
|
|
235
|
+
` --kai-color-primary-foreground: ${tokens.primaryFg};`,
|
|
236
|
+
` --kai-color-ring: ${tokens.ring};`,
|
|
237
|
+
` --kai-color-accent: ${tokens.accent};`,
|
|
238
|
+
` --kai-color-accent-foreground: ${tokens.accentFg};`,
|
|
239
|
+
`}`,
|
|
240
|
+
].join('\n');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ─── Tool definition ─────────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
export const theme: Tool = {
|
|
246
|
+
name: 'theme',
|
|
247
|
+
description:
|
|
248
|
+
'Generate a --kai-* CSS token override block from a brand color or description. ' +
|
|
249
|
+
'Paste the block into your global stylesheet to brand every kai-* element in one shot.',
|
|
250
|
+
|
|
251
|
+
inputSchema: z.object({
|
|
252
|
+
brand: z
|
|
253
|
+
.string()
|
|
254
|
+
.optional()
|
|
255
|
+
.describe('Brand color as #rgb or #rrggbb hex (e.g. "#7c3aed")'),
|
|
256
|
+
description: z
|
|
257
|
+
.string()
|
|
258
|
+
.optional()
|
|
259
|
+
.describe(
|
|
260
|
+
'Natural-language description of your product (used to infer a color when `brand` is omitted)',
|
|
261
|
+
),
|
|
262
|
+
mode: z
|
|
263
|
+
.enum(['light', 'dark', 'both'])
|
|
264
|
+
.optional()
|
|
265
|
+
.describe(
|
|
266
|
+
'"light" → :root only (default); "dark" → .dark only; "both" → :root + .dark',
|
|
267
|
+
),
|
|
268
|
+
}),
|
|
269
|
+
|
|
270
|
+
handler: async (args) => {
|
|
271
|
+
const {
|
|
272
|
+
brand,
|
|
273
|
+
description,
|
|
274
|
+
mode = 'light',
|
|
275
|
+
} = args as { brand?: string; description?: string; mode?: 'light' | 'dark' | 'both' };
|
|
276
|
+
|
|
277
|
+
const { hex, r, g, b, note } = resolveBrand(brand, description);
|
|
278
|
+
|
|
279
|
+
const lightTokens = buildTokenSet(r, g, b);
|
|
280
|
+
const darkTokens = buildDarkTokenSet(r, g, b);
|
|
281
|
+
|
|
282
|
+
// Which blocks to emit
|
|
283
|
+
const blocks: string[] = [];
|
|
284
|
+
if (mode === 'light' || mode === 'both') {
|
|
285
|
+
blocks.push(cssBlock(':root', lightTokens));
|
|
286
|
+
}
|
|
287
|
+
if (mode === 'dark' || mode === 'both') {
|
|
288
|
+
blocks.push(cssBlock('.dark', darkTokens));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const cssOutput = blocks.join('\n\n');
|
|
292
|
+
|
|
293
|
+
const noteLabel = note.startsWith('Could not parse') ? 'Note' : 'Assumption';
|
|
294
|
+
const noteSection = note ? `\n> **${noteLabel}:** ${note}\n` : '';
|
|
295
|
+
|
|
296
|
+
const text = `\
|
|
297
|
+
## AI/UI theme override — \`${hex}\`
|
|
298
|
+
${noteSection}
|
|
299
|
+
Paste this block into your **global stylesheet** (before the \`@import\` of \`@kitn.ai/ui\` or after — order doesn't matter since \`--kai-*\` tokens are resolved at runtime via \`var()\` fallbacks). For \`mode:'dark'\` overrides, the \`.dark\` class must be on a parent element (e.g. \`<html class="dark">\`) for them to take effect.
|
|
300
|
+
|
|
301
|
+
\`\`\`css
|
|
302
|
+
${cssOutput}
|
|
303
|
+
\`\`\`
|
|
304
|
+
|
|
305
|
+
### How it works
|
|
306
|
+
|
|
307
|
+
The \`--kai-color-*\` tokens pierce the Shadow DOM via CSS custom-property inheritance. Every \`kai-*\` element reads them through a \`var(--kai-color-primary, <default>)\` fallback chain defined in \`theme.css\`. Setting them on \`:root\` (or a wrapper element) is enough to rebrand everything globally.
|
|
308
|
+
|
|
309
|
+
**Tokens emitted** (verified names from \`theme.css\`):
|
|
310
|
+
- \`--kai-color-primary\` — primary brand color
|
|
311
|
+
- \`--kai-color-primary-foreground\` — AA-contrast foreground on primary (auto-selected black/white)
|
|
312
|
+
- \`--kai-color-ring\` — keyboard focus ring
|
|
313
|
+
- \`--kai-color-accent\` — tinted accent surface
|
|
314
|
+
- \`--kai-color-accent-foreground\` — text on accent surface
|
|
315
|
+
|
|
316
|
+
### Apply to a subtree only
|
|
317
|
+
|
|
318
|
+
\`\`\`css
|
|
319
|
+
/* scope to a specific container instead of :root */
|
|
320
|
+
.my-chat-widget {
|
|
321
|
+
${blocks[0]
|
|
322
|
+
?.split('\n')
|
|
323
|
+
.slice(1, -1)
|
|
324
|
+
.map((l) => ' ' + l)
|
|
325
|
+
.join('\n')}
|
|
326
|
+
}
|
|
327
|
+
\`\`\`
|
|
328
|
+
|
|
329
|
+
### Fine-tune the full palette
|
|
330
|
+
|
|
331
|
+
The theme editor lets you tweak every token interactively and copy the result:
|
|
332
|
+
|
|
333
|
+
**→ [Open Theme Editor](https://ui.kitn.ai/theme/editor/)**
|
|
334
|
+
|
|
335
|
+
For the complete token list see the [theming guide](https://ui.kitn.ai/guides/theming/) and the source \`theme.css\` in the package root.
|
|
336
|
+
`;
|
|
337
|
+
|
|
338
|
+
return { content: [{ type: 'text', text }] };
|
|
339
|
+
},
|
|
340
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The shape every AI/UI MCP tool module exports. The server reads `inputSchema`
|
|
6
|
+
* (a zod object) to advertise the tool over the protocol — converting it to JSON
|
|
7
|
+
* Schema — and calls `handler` when the tool is invoked.
|
|
8
|
+
*/
|
|
9
|
+
export interface Tool {
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
inputSchema: z.ZodObject<z.ZodRawShape>;
|
|
13
|
+
handler: (args: Record<string, unknown>) => Promise<CallToolResult>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Integration, Archetype } from './types';
|
|
2
|
+
import openrouter from './integrations/openrouter';
|
|
3
|
+
import vercelAiSdk from './integrations/vercel-ai-sdk';
|
|
4
|
+
import langgraph from './integrations/langgraph';
|
|
5
|
+
import cloudflare from './integrations/cloudflare';
|
|
6
|
+
import ollama from './integrations/ollama';
|
|
7
|
+
import mastra from './integrations/mastra';
|
|
8
|
+
import pi from './integrations/pi';
|
|
9
|
+
import pydanticAi from './integrations/pydantic-ai';
|
|
10
|
+
import mock from './integrations/mock';
|
|
11
|
+
import { archetypes as _archetypes } from './archetypes';
|
|
12
|
+
|
|
13
|
+
export const integrations: Integration[] = [
|
|
14
|
+
openrouter,
|
|
15
|
+
vercelAiSdk,
|
|
16
|
+
langgraph,
|
|
17
|
+
cloudflare,
|
|
18
|
+
ollama,
|
|
19
|
+
mastra,
|
|
20
|
+
pi,
|
|
21
|
+
pydanticAi,
|
|
22
|
+
mock,
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export const archetypes: Archetype[] = _archetypes;
|
|
26
|
+
|
|
27
|
+
export function getIntegration(id: string): Integration | undefined {
|
|
28
|
+
return integrations.find((i) => i.id === id);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getArchetype(id: string): Archetype | undefined {
|
|
32
|
+
return archetypes.find((a) => a.id === id);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function listIntegrations(): Integration[] {
|
|
36
|
+
return integrations;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function listArchetypes(): Archetype[] {
|
|
40
|
+
return archetypes;
|
|
41
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const Category = z.enum(['provider', 'gateway', 'framework', 'harness', 'mock']);
|
|
4
|
+
export const Language = z.enum(['ts', 'python']);
|
|
5
|
+
export const StreamFormat = z.enum(['openai-sse', 'ai-sdk', 'native']);
|
|
6
|
+
export const Framework = z.enum(['html', 'react', 'next', 'vue', 'svelte', 'fastapi', 'express', 'worker', 'tanstack-start']);
|
|
7
|
+
export const Placement = z.enum(['side', 'full-page', 'docked-widget', 'inline']);
|
|
8
|
+
|
|
9
|
+
export const IntegrationSchema = z.object({
|
|
10
|
+
id: z.string(),
|
|
11
|
+
title: z.string(),
|
|
12
|
+
category: Category,
|
|
13
|
+
language: Language,
|
|
14
|
+
streamFormat: StreamFormat,
|
|
15
|
+
envVars: z.array(z.string()).default([]),
|
|
16
|
+
routeTemplates: z.record(z.string(), z.string()), // keyed by Framework value → code string
|
|
17
|
+
streamMapping: z.string(), // prose: how the stream maps to messages
|
|
18
|
+
runNote: z.string(),
|
|
19
|
+
docsSlug: z.string(),
|
|
20
|
+
});
|
|
21
|
+
export type Integration = z.infer<typeof IntegrationSchema>;
|
|
22
|
+
|
|
23
|
+
export const ArchetypeSchema = z.object({
|
|
24
|
+
id: z.string(),
|
|
25
|
+
title: z.string(),
|
|
26
|
+
components: z.array(z.string()), // kai-* tags, e.g. ['kai-chat', 'kai-sources']
|
|
27
|
+
defaultPlacement: Placement,
|
|
28
|
+
docsSlug: z.string(),
|
|
29
|
+
});
|
|
30
|
+
export type Archetype = z.infer<typeof ArchetypeSchema>;
|
|
@@ -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
|
);
|