@kitn.ai/ui 0.14.1 → 0.15.1
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/dist/kitn-chat.es.js +1 -1
- package/package.json +6 -1
- package/src/components/component-meta.json +10 -10
- package/src/elements/compiled.css +1 -1
- package/src/remote/host-embed.ts +1 -1
- package/src/components/artifact.stories.tsx +0 -138
- package/src/components/attachments.stories.tsx +0 -303
- package/src/components/attachments.test.tsx +0 -64
- package/src/components/chain-of-thought.stories.tsx +0 -211
- package/src/components/chat-container.stories.tsx +0 -187
- package/src/components/chat-thread.test.tsx +0 -58
- package/src/components/checkpoint.stories.tsx +0 -102
- package/src/components/code-block.stories.tsx +0 -150
- package/src/components/confirm-card.stories.tsx +0 -74
- package/src/components/context.stories.tsx +0 -229
- package/src/components/context.test.tsx +0 -133
- package/src/components/conversation-item.stories.tsx +0 -125
- package/src/components/conversation-list.stories.tsx +0 -133
- package/src/components/default-input.test.tsx +0 -111
- package/src/components/empty.stories.tsx +0 -434
- package/src/components/feedback-bar.stories.tsx +0 -138
- package/src/components/feedback-bar.test.tsx +0 -103
- package/src/components/file-tree.stories.tsx +0 -73
- package/src/components/file-upload.stories.tsx +0 -156
- package/src/components/image.stories.tsx +0 -89
- package/src/components/loader.stories.tsx +0 -181
- package/src/components/markdown.stories.tsx +0 -180
- package/src/components/message-action-bar.test.tsx +0 -128
- package/src/components/message-narrow.stories.tsx +0 -329
- package/src/components/message-skills.stories.tsx +0 -211
- package/src/components/message.stories.tsx +0 -281
- package/src/components/model-switcher.stories.tsx +0 -123
- package/src/components/model-switcher.test.tsx +0 -65
- package/src/components/prompt-input.stories.tsx +0 -222
- package/src/components/prompt-suggestion.stories.tsx +0 -142
- package/src/components/reasoning.stories.tsx +0 -140
- package/src/components/resizable.test.tsx +0 -226
- package/src/components/scroll-button.stories.tsx +0 -100
- package/src/components/slash-command.stories.tsx +0 -163
- package/src/components/source-list.test.tsx +0 -152
- package/src/components/source.stories.tsx +0 -124
- package/src/components/tasks-card.stories.tsx +0 -78
- package/src/components/text-shimmer.stories.tsx +0 -87
- package/src/components/thinking-bar.stories.tsx +0 -87
- package/src/components/tool.stories.tsx +0 -153
- package/src/components/voice-input.stories.tsx +0 -83
- package/src/elements/artifact.stories.tsx +0 -506
- package/src/elements/attachments.stories.tsx +0 -190
- package/src/elements/card.stories.tsx +0 -113
- package/src/elements/cards.stories.tsx +0 -59
- package/src/elements/catalog.stories.tsx +0 -491
- package/src/elements/chain-of-thought.declarative.test.tsx +0 -153
- package/src/elements/chain-of-thought.stories.tsx +0 -113
- package/src/elements/chat-scope-picker.stories.tsx +0 -73
- package/src/elements/chat-workspace.stories.tsx +0 -288
- package/src/elements/chat.stories.tsx +0 -151
- package/src/elements/checkpoint.stories.tsx +0 -72
- package/src/elements/choice.stories.tsx +0 -240
- package/src/elements/code-block.stories.tsx +0 -83
- package/src/elements/composed-shell.stories.tsx +0 -316
- package/src/elements/confirm-card.stories.tsx +0 -227
- package/src/elements/context-meter.stories.tsx +0 -151
- package/src/elements/conversation-list.declarative.test.tsx +0 -137
- package/src/elements/conversation-list.stories.tsx +0 -243
- package/src/elements/embed.stories.tsx +0 -213
- package/src/elements/empty.stories.tsx +0 -111
- package/src/elements/feedback-bar.stories.tsx +0 -114
- package/src/elements/file-tree.stories.tsx +0 -134
- package/src/elements/file-upload.stories.tsx +0 -82
- package/src/elements/form.stories.tsx +0 -248
- package/src/elements/image.stories.tsx +0 -71
- package/src/elements/link-preview.stories.tsx +0 -209
- package/src/elements/loader.stories.tsx +0 -88
- package/src/elements/markdown.stories.tsx +0 -76
- package/src/elements/message-skills.declarative.test.tsx +0 -104
- package/src/elements/message-skills.stories.tsx +0 -114
- package/src/elements/message.stories.tsx +0 -218
- package/src/elements/model-switcher.declarative.test.tsx +0 -130
- package/src/elements/model-switcher.stories.tsx +0 -198
- package/src/elements/popover.stories.tsx +0 -80
- package/src/elements/prompt-input-slash-command.test.tsx +0 -128
- package/src/elements/prompt-input-stoppable.test.tsx +0 -88
- package/src/elements/prompt-input-toolbar-actions.test.tsx +0 -120
- package/src/elements/prompt-input.stories.tsx +0 -331
- package/src/elements/prompt-suggestions.declarative.test.tsx +0 -164
- package/src/elements/prompt-suggestions.stories.tsx +0 -213
- package/src/elements/reasoning.stories.tsx +0 -77
- package/src/elements/remote.stories.tsx +0 -314
- package/src/elements/resizable.stories.tsx +0 -426
- package/src/elements/response-stream.stories.tsx +0 -79
- package/src/elements/scroll-button.stories.tsx +0 -202
- package/src/elements/scroll-button.test.tsx +0 -148
- package/src/elements/source-list.stories.tsx +0 -143
- package/src/elements/source.stories.tsx +0 -88
- package/src/elements/switch.stories.tsx +0 -56
- package/src/elements/tasks.stories.tsx +0 -237
- package/src/elements/text-shimmer.stories.tsx +0 -64
- package/src/elements/thinking-bar.stories.tsx +0 -72
- package/src/elements/tool.stories.tsx +0 -89
- package/src/elements/voice-input.stories.tsx +0 -88
- package/src/primitives/controllable.test.ts +0 -47
- package/src/stories/chat-panel-layout.stories.tsx +0 -145
- package/src/stories/chat-scene.tsx +0 -571
- package/src/stories/checkpoint-restore.stories.tsx +0 -256
- package/src/stories/context-usage.stories.tsx +0 -212
- package/src/stories/conversation-with-reasoning.stories.tsx +0 -182
- package/src/stories/conversation-with-sources.stories.tsx +0 -471
- package/src/stories/docs/Accessibility.mdx +0 -119
- package/src/stories/docs/ChildElements.mdx +0 -269
- package/src/stories/docs/ForAIAgents.mdx +0 -162
- package/src/stories/docs/GettingStarted.mdx +0 -65
- package/src/stories/docs/Installation.mdx +0 -83
- package/src/stories/docs/Introduction.mdx +0 -50
- package/src/stories/docs/SolidJsAdvanced.mdx +0 -17
- package/src/stories/docs/Theming.mdx +0 -85
- package/src/stories/docs/element-controls.ts +0 -121
- package/src/stories/docs/frameworks/Angular.mdx +0 -245
- package/src/stories/docs/frameworks/Html.mdx +0 -227
- package/src/stories/docs/frameworks/Overview.mdx +0 -47
- package/src/stories/docs/frameworks/React.mdx +0 -227
- package/src/stories/docs/frameworks/Solid.mdx +0 -173
- package/src/stories/docs/frameworks/Svelte.mdx +0 -194
- package/src/stories/docs/frameworks/Vue.mdx +0 -238
- package/src/stories/docs/generative-ui-overview.mdx +0 -186
- package/src/stories/docs/recipes/SpeechToText.mdx +0 -7
- package/src/stories/docs/recipes/Streaming.mdx +0 -68
- package/src/stories/docs/recipes/TextToSpeech.mdx +0 -35
- package/src/stories/docs/theme-editor/canvas.tsx +0 -32
- package/src/stories/docs/theme-editor/inspector.tsx +0 -66
- package/src/stories/docs/theme-editor/presets.test.ts +0 -32
- package/src/stories/docs/theme-editor/presets.ts +0 -64
- package/src/stories/docs/theme-editor/theme-css.test.ts +0 -19
- package/src/stories/docs/theme-editor/theme-css.ts +0 -15
- package/src/stories/docs/theme-editor/theme-editor.tsx +0 -146
- package/src/stories/docs/theme-tokens.tsx +0 -174
- package/src/stories/examples/ChoosingComponents.mdx +0 -106
- package/src/stories/examples/sample-data.ts +0 -79
- package/src/stories/examples/usage/checkpoint-restore.ts +0 -156
- package/src/stories/examples/usage/context-usage.ts +0 -591
- package/src/stories/examples/usage/conversation-with-reasoning.ts +0 -224
- package/src/stories/examples/usage/conversation-with-sources.ts +0 -589
- package/src/stories/examples/usage/empty-state.ts +0 -144
- package/src/stories/examples/usage/full-chat-app.ts +0 -277
- package/src/stories/examples/usage/index.ts +0 -63
- package/src/stories/examples/usage/message-actions.ts +0 -704
- package/src/stories/examples/usage/pattern-centered-conversation.ts +0 -446
- package/src/stories/examples/usage/pattern-chat-panel-layout.ts +0 -442
- package/src/stories/examples/usage/pattern-docked-widget.ts +0 -479
- package/src/stories/examples/usage/prompt-input-variants.ts +0 -1356
- package/src/stories/examples/usage/streaming-response.ts +0 -687
- package/src/stories/examples/usage/types.ts +0 -33
- package/src/stories/full-chat.stories.tsx +0 -18
- package/src/stories/message-actions.stories.tsx +0 -230
- package/src/stories/pattern-centered-conversation.stories.tsx +0 -93
- package/src/stories/pattern-docked-widget.stories.tsx +0 -93
- package/src/stories/pattern-empty-state.stories.tsx +0 -76
- package/src/stories/prompt-input-variants.stories.tsx +0 -456
- package/src/stories/streaming-response.stories.tsx +0 -438
- package/src/stories/theme-editor.stories.tsx +0 -16
- package/src/stories/token-reference.stories.tsx +0 -18
- package/src/stories/typography.stories.tsx +0 -78
- package/src/ui/avatar.stories.tsx +0 -103
- package/src/ui/badge.stories.tsx +0 -86
- package/src/ui/button.stories.tsx +0 -145
- package/src/ui/collapsible.stories.tsx +0 -69
- package/src/ui/dropdown.stories.tsx +0 -59
- package/src/ui/hover-card.stories.tsx +0 -77
- package/src/ui/hover-card.test.tsx +0 -30
- package/src/ui/overlay.stories.tsx +0 -115
- package/src/ui/popover.stories.tsx +0 -81
- package/src/ui/popover.test.tsx +0 -99
- package/src/ui/resizable.stories.tsx +0 -236
- package/src/ui/scroll-area.stories.tsx +0 -50
- package/src/ui/separator.stories.tsx +0 -81
- package/src/ui/skeleton.stories.tsx +0 -337
- package/src/ui/switch.stories.tsx +0 -51
- package/src/ui/switch.test.tsx +0 -59
- package/src/ui/textarea.stories.tsx +0 -76
- package/src/ui/tooltip.stories.tsx +0 -74
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
-
import { createSignal, onMount, type JSX } from 'solid-js';
|
|
3
|
-
import './tasks';
|
|
4
|
-
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
5
|
-
import type { TasksCardData } from '../components/tasks-card';
|
|
6
|
-
import type { CardEvent } from '../primitives/card-contract';
|
|
7
|
-
|
|
8
|
-
declare module 'solid-js' {
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
10
|
-
namespace JSX {
|
|
11
|
-
interface IntrinsicElements {
|
|
12
|
-
'kai-tasks': JSX.HTMLAttributes<HTMLElement> & {
|
|
13
|
-
heading?: string;
|
|
14
|
-
'card-id'?: string;
|
|
15
|
-
ref?: (el: HTMLElement) => void;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type TasksEl = HTMLElement & { data?: TasksCardData; resolution?: Record<string, unknown> };
|
|
22
|
-
|
|
23
|
-
function Frame(props: { children: JSX.Element }) {
|
|
24
|
-
return <div style={{ 'max-width': '460px' }}>{props.children}</div>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Mounts a <kai-tasks>, sets `.data`, logs the emitted CardEvent under the render. */
|
|
28
|
-
function TasksDemo(props: { def: TasksCardData; cardId: string; heading?: string }) {
|
|
29
|
-
const [log, setLog] = createSignal<CardEvent[]>([]);
|
|
30
|
-
let el: TasksEl | undefined;
|
|
31
|
-
onMount(() => {
|
|
32
|
-
if (!el) return;
|
|
33
|
-
el.data = props.def;
|
|
34
|
-
el.addEventListener('kai-card', (e) => {
|
|
35
|
-
const detail = (e as CustomEvent<CardEvent>).detail;
|
|
36
|
-
setLog((prev) => [...prev, detail]);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
return (
|
|
40
|
-
<Frame>
|
|
41
|
-
<div style={{ display: 'flex', 'flex-direction': 'column', gap: '12px' }}>
|
|
42
|
-
<kai-tasks ref={(e) => (el = e as TasksEl)} card-id={props.cardId} heading={props.heading} />
|
|
43
|
-
<pre
|
|
44
|
-
style={{
|
|
45
|
-
margin: 0,
|
|
46
|
-
'max-height': '180px',
|
|
47
|
-
overflow: 'auto',
|
|
48
|
-
background: 'var(--color-muted, #f4f4f5)',
|
|
49
|
-
'border-radius': '8px',
|
|
50
|
-
padding: '8px',
|
|
51
|
-
'font-size': '12px',
|
|
52
|
-
}}
|
|
53
|
-
>
|
|
54
|
-
{log().length === 0 ? '// emitted CardEvents appear here' : JSON.stringify(log(), null, 2)}
|
|
55
|
-
</pre>
|
|
56
|
-
</div>
|
|
57
|
-
</Frame>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const PLAN: TasksCardData = {
|
|
62
|
-
mode: 'select',
|
|
63
|
-
selectAll: true,
|
|
64
|
-
confirmLabel: 'Run selected',
|
|
65
|
-
tasks: [
|
|
66
|
-
{ id: 'lint', label: 'Run linter', checked: true },
|
|
67
|
-
{ id: 'test', label: 'Run unit tests', checked: true },
|
|
68
|
-
{ id: 'build', label: 'Build production bundle' },
|
|
69
|
-
{ id: 'deploy', label: 'Deploy to staging', description: 'Reversible; staging only' },
|
|
70
|
-
],
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const REQUIRE_ONE: TasksCardData = {
|
|
74
|
-
confirmLabel: 'Apply',
|
|
75
|
-
allowEmpty: false,
|
|
76
|
-
tasks: [
|
|
77
|
-
{ id: 'cache', label: 'Clear the CDN cache' },
|
|
78
|
-
{ id: 'reindex', label: 'Rebuild the search index' },
|
|
79
|
-
{ id: 'restart', label: 'Restart the workers' },
|
|
80
|
-
],
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const BOUNDED: TasksCardData = {
|
|
84
|
-
heading: 'Pick up to 2 reviewers',
|
|
85
|
-
confirmLabel: 'Request review',
|
|
86
|
-
min: 1,
|
|
87
|
-
max: 2,
|
|
88
|
-
tasks: [
|
|
89
|
-
{ id: 'ana', label: 'Ana' },
|
|
90
|
-
{ id: 'ben', label: 'Ben' },
|
|
91
|
-
{ id: 'cat', label: 'Cat' },
|
|
92
|
-
{ id: 'dan', label: 'Dan' },
|
|
93
|
-
],
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const WITH_DESCRIPTIONS: TasksCardData = {
|
|
97
|
-
selectAll: true,
|
|
98
|
-
confirmLabel: 'Run cleanup',
|
|
99
|
-
tasks: [
|
|
100
|
-
{ id: 'tmp', label: 'Delete temp files', description: 'Frees ~2.1 GB; safe to remove' },
|
|
101
|
-
{ id: 'logs', label: 'Rotate logs', description: 'Archives logs older than 30 days' },
|
|
102
|
-
{ id: 'orphans', label: 'Prune orphaned blobs', description: 'Unreferenced uploads only' },
|
|
103
|
-
],
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const HEADING_MAP: Record<string, string | undefined> = {
|
|
107
|
-
'card-plan': 'Approve the plan steps',
|
|
108
|
-
'card-require': 'Choose maintenance steps',
|
|
109
|
-
'card-bounded': undefined,
|
|
110
|
-
'card-desc': 'Storage cleanup',
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const HTML_SNIPPET = (def: TasksCardData, cardId: string) => {
|
|
114
|
-
const heading = HEADING_MAP[cardId];
|
|
115
|
-
return `<kai-tasks${heading ? ` heading="${heading}"` : ''}></kai-tasks>
|
|
116
|
-
<script type="module">
|
|
117
|
-
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
118
|
-
|
|
119
|
-
const el = document.querySelector('kai-tasks');
|
|
120
|
-
// \`data\` is the CardEnvelope.data (set as a property).
|
|
121
|
-
el.data = ${JSON.stringify(def, null, 2)};
|
|
122
|
-
|
|
123
|
-
// Toggling rows is local; only CONFIRM emits — a single bubbling \`kai-card\` event.
|
|
124
|
-
el.addEventListener('kai-card', (e) => {
|
|
125
|
-
const ev = e.detail; // { kind:'submit', cardId, data:{ selected } } | ...
|
|
126
|
-
if (ev.kind === 'submit') console.log('selected', ev.data.selected);
|
|
127
|
-
});
|
|
128
|
-
</script>`;
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const meta = {
|
|
132
|
-
title: 'Generative UI/Cards/kai-tasks',
|
|
133
|
-
tags: ['autodocs'],
|
|
134
|
-
argTypes: argTypesFor('kai-tasks'),
|
|
135
|
-
parameters: {
|
|
136
|
-
layout: 'padded',
|
|
137
|
-
docs: {
|
|
138
|
-
description: specDescription('kai-tasks', [
|
|
139
|
-
"`<kai-tasks>` is a **selectable** task/plan list (set via the `data` **property**): checkbox rows + an optional select-all + a confirm button. The user picks a subset, confirms, and the card emits the Card contract's **`submit`** verb up a bubbling **`kai-card`** CustomEvent of `{ kind: 'submit', cardId, data: { selected } }` — the checked ids in **input order**. Toggling rows is local UI state; **only the final confirm emits** (the wire stays quiet, the result atomic).",
|
|
140
|
-
'**Anatomy:** `<kai-card>` chrome (optional heading) → **select-all row** (shown when `selectAll:true` and ≥2 toggleable tasks; indeterminate state when partially checked) → **task rows** (one per `tasks[]` item: checkbox + label + optional description; disabled when `max` reached or `task.disabled`) → **card footer** (selected-count label + confirm `<Button>`, disabled until gating passes; replaced by a read-only resolved summary after submission).',
|
|
141
|
-
'**Gating:** confirm is enabled when `selectedCount >= (min ?? (allowEmpty ? 0 : 1))` and `<= (max ?? ∞)`. Select-all checks every toggleable (non-`disabled`) row and shows an **indeterminate** (`aria-checked="mixed"`) state when only some are checked. When `max` is reached, unchecked rows become non-toggleable. v1 is **select/approve only** (a future `mode:\'progress\'` is reserved in the schema).',
|
|
142
|
-
'**Events** (all frozen Card-contract verbs): `ready` on mount, `submit` on confirm, `error` for a malformed definition (renders the inline `kai-card` error). It **never invents events**. The same shapes flow over the remote iframe transport unchanged.',
|
|
143
|
-
]),
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
} satisfies Meta;
|
|
147
|
-
|
|
148
|
-
export default meta;
|
|
149
|
-
type Story = StoryObj;
|
|
150
|
-
|
|
151
|
-
/** Select a plan (select-all on, a few pre-checked). */
|
|
152
|
-
export const SelectAPlan: Story = {
|
|
153
|
-
render: () => <TasksDemo def={PLAN} cardId="card-plan" heading="Approve the plan steps" />,
|
|
154
|
-
parameters: { docs: { source: { code: HTML_SNIPPET(PLAN, 'card-plan'), language: 'html' } } },
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
/** Require at least one (`allowEmpty:false`, the default) — confirm stays disabled until a row is checked. */
|
|
158
|
-
export const RequireAtLeastOne: Story = {
|
|
159
|
-
render: () => <TasksDemo def={REQUIRE_ONE} cardId="card-require" heading="Choose maintenance steps" />,
|
|
160
|
-
parameters: { docs: { source: { code: HTML_SNIPPET(REQUIRE_ONE, 'card-require'), language: 'html' } } },
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
/** Bounded (`min`/`max`) — confirm gating + max-reached row disabling. */
|
|
164
|
-
export const Bounded: Story = {
|
|
165
|
-
render: () => <TasksDemo def={BOUNDED} cardId="card-bounded" />,
|
|
166
|
-
parameters: { docs: { source: { code: HTML_SNIPPET(BOUNDED, 'card-bounded'), language: 'html' } } },
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
/** Rows with secondary descriptions (linked via aria-describedby). */
|
|
170
|
-
export const WithDescriptions: Story = {
|
|
171
|
-
render: () => <TasksDemo def={WITH_DESCRIPTIONS} cardId="card-desc" heading="Storage cleanup" />,
|
|
172
|
-
parameters: { docs: { source: { code: HTML_SNIPPET(WITH_DESCRIPTIONS, 'card-desc'), language: 'html' } } },
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
const RESOLVED_TASKS: TasksCardData = {
|
|
176
|
-
confirmLabel: 'Export',
|
|
177
|
-
tasks: [
|
|
178
|
-
{ id: 'sum', label: 'Executive summary' },
|
|
179
|
-
{ id: 'data', label: 'Raw data' },
|
|
180
|
-
{ id: 'charts', label: 'Charts' },
|
|
181
|
-
],
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
/** The card after the user confirmed their selection — shows "Selected 2 of 3", no checkboxes or button. */
|
|
185
|
-
export const Resolved: Story = {
|
|
186
|
-
name: 'Resolved (read-only)',
|
|
187
|
-
render: () => {
|
|
188
|
-
let el: TasksEl | undefined;
|
|
189
|
-
onMount(() => {
|
|
190
|
-
if (!el) return;
|
|
191
|
-
el.data = RESOLVED_TASKS;
|
|
192
|
-
el.resolution = { kind: 'submit', data: { selected: ['sum', 'charts'] } };
|
|
193
|
-
});
|
|
194
|
-
return (
|
|
195
|
-
<Frame>
|
|
196
|
-
<kai-tasks ref={(e) => (el = e as TasksEl)} card-id="card-resolved-tasks" heading="Export report" />
|
|
197
|
-
</Frame>
|
|
198
|
-
);
|
|
199
|
-
},
|
|
200
|
-
parameters: {
|
|
201
|
-
docs: {
|
|
202
|
-
source: {
|
|
203
|
-
code: `<kai-tasks heading="Export report"></kai-tasks>
|
|
204
|
-
<script type="module">
|
|
205
|
-
import '@kitn.ai/ui/elements';
|
|
206
|
-
const el = document.querySelector('kai-tasks');
|
|
207
|
-
el.data = ${JSON.stringify(RESOLVED_TASKS, null, 2)};
|
|
208
|
-
// Setting .resolution renders the chromed read-only view — no checkboxes or confirm button.
|
|
209
|
-
el.resolution = { kind: 'submit', data: { selected: ['sum', 'charts'] } };
|
|
210
|
-
</script>`,
|
|
211
|
-
language: 'html',
|
|
212
|
-
},
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
/** A malformed `data` (empty `tasks`) → the inline error state + an `error` event. */
|
|
218
|
-
export const ErrorState: Story = {
|
|
219
|
-
render: () => <TasksDemo def={{ tasks: [] } as unknown as TasksCardData} cardId="card-bad" />,
|
|
220
|
-
parameters: {
|
|
221
|
-
docs: {
|
|
222
|
-
source: {
|
|
223
|
-
code: `<kai-tasks></kai-tasks>
|
|
224
|
-
<script type="module">
|
|
225
|
-
import '@kitn.ai/ui/elements';
|
|
226
|
-
const el = document.querySelector('kai-tasks');
|
|
227
|
-
// No tasks → inline error state + an \`error\` event.
|
|
228
|
-
el.data = { tasks: [] };
|
|
229
|
-
el.addEventListener('kai-card', (e) => {
|
|
230
|
-
if (e.detail.kind === 'error') console.warn('tasks error:', e.detail.message);
|
|
231
|
-
});
|
|
232
|
-
</script>`,
|
|
233
|
-
language: 'html',
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
};
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
-
import './register'; // side effect: registers the custom elements
|
|
3
|
-
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
4
|
-
|
|
5
|
-
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
6
|
-
declare module 'solid-js' {
|
|
7
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
8
|
-
namespace JSX {
|
|
9
|
-
interface IntrinsicElements {
|
|
10
|
-
'kai-text-shimmer': JSX.HTMLAttributes<HTMLElement> & {
|
|
11
|
-
text?: string;
|
|
12
|
-
duration?: number;
|
|
13
|
-
spread?: number;
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
20
|
-
<kai-text-shimmer text="Thinking…" duration="3" spread="20"></kai-text-shimmer>
|
|
21
|
-
|
|
22
|
-
<script type="module">
|
|
23
|
-
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
24
|
-
</script>`;
|
|
25
|
-
|
|
26
|
-
const meta = {
|
|
27
|
-
title: 'Components/TextShimmer',
|
|
28
|
-
tags: ['autodocs'],
|
|
29
|
-
argTypes: argTypesFor('kai-text-shimmer'),
|
|
30
|
-
parameters: {
|
|
31
|
-
layout: 'fullscreen',
|
|
32
|
-
docs: {
|
|
33
|
-
description: specDescription('kai-text-shimmer', [
|
|
34
|
-
'`<kai-text-shimmer>` is the framework-agnostic **web component** for animated shimmering text — a gradient sweep across a label, isolated in **Shadow DOM**.',
|
|
35
|
-
'**When to use:** signalling a quiet, in-progress state ("Thinking…", "Generating…") inline. In SolidJS, use the `TextShimmer` primitive.',
|
|
36
|
-
'**Placement:** inline within a status row, toolbar label, or above a streaming content area — it renders as an inline `span` so it flows naturally alongside other text or icons.',
|
|
37
|
-
"**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the `text` attribute, and tune `duration` (seconds) and `spread` (gradient width, 5–45).",
|
|
38
|
-
'See the **Code** tab for HTML usage.',
|
|
39
|
-
]),
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
} satisfies Meta;
|
|
43
|
-
|
|
44
|
-
export default meta;
|
|
45
|
-
type Story = StoryObj;
|
|
46
|
-
|
|
47
|
-
/** Default shimmer. */
|
|
48
|
-
export const Default: Story = {
|
|
49
|
-
render: () => (
|
|
50
|
-
<div style={{ padding: '24px', 'font-size': '18px' }}>
|
|
51
|
-
<kai-text-shimmer text="Thinking…" />
|
|
52
|
-
</div>
|
|
53
|
-
),
|
|
54
|
-
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
/** Faster sweep with a wider gradient spread. */
|
|
58
|
-
export const Tuned: Story = {
|
|
59
|
-
render: () => (
|
|
60
|
-
<div style={{ padding: '24px', 'font-size': '18px' }}>
|
|
61
|
-
<kai-text-shimmer text="Generating response…" duration={2} spread={35} />
|
|
62
|
-
</div>
|
|
63
|
-
),
|
|
64
|
-
};
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
-
import './register'; // side effect: registers the custom elements
|
|
3
|
-
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
4
|
-
|
|
5
|
-
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
6
|
-
declare module 'solid-js' {
|
|
7
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
8
|
-
namespace JSX {
|
|
9
|
-
interface IntrinsicElements {
|
|
10
|
-
'kai-thinking-bar': JSX.HTMLAttributes<HTMLElement> & {
|
|
11
|
-
text?: string;
|
|
12
|
-
stoppable?: boolean | string;
|
|
13
|
-
'stop-label'?: string;
|
|
14
|
-
'on:kai-stop'?: (e: CustomEvent) => void;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
21
|
-
<kai-thinking-bar text="Thinking" stoppable stop-label="Answer now"></kai-thinking-bar>
|
|
22
|
-
|
|
23
|
-
<script type="module">
|
|
24
|
-
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
25
|
-
|
|
26
|
-
document.querySelector('kai-thinking-bar')
|
|
27
|
-
.addEventListener('kai-stop', () => console.log('user asked to stop'));
|
|
28
|
-
</script>`;
|
|
29
|
-
|
|
30
|
-
const meta = {
|
|
31
|
-
title: 'Components/ThinkingBar',
|
|
32
|
-
tags: ['autodocs'],
|
|
33
|
-
argTypes: argTypesFor('kai-thinking-bar'),
|
|
34
|
-
parameters: {
|
|
35
|
-
layout: 'fullscreen',
|
|
36
|
-
docs: {
|
|
37
|
-
description: specDescription('kai-thinking-bar', [
|
|
38
|
-
'`<kai-thinking-bar>` is the framework-agnostic **web component** for an animated "thinking" indicator with an optional stop affordance — a pure leaf element isolated in **Shadow DOM**. (`<kai-chat>` does not surface this; compose it yourself.)',
|
|
39
|
-
'**When to use:** showing that the assistant is reasoning, optionally letting the user interrupt with "Answer now". In SolidJS, use the `ThinkingBar` primitive.',
|
|
40
|
-
"**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the `text`/`stop-label` attributes, add the `stoppable` flag to show the stop button, and listen for the `kai-stop` **CustomEvent**.",
|
|
41
|
-
'See the **Code** tab for HTML usage.',
|
|
42
|
-
]),
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
} satisfies Meta;
|
|
46
|
-
|
|
47
|
-
export default meta;
|
|
48
|
-
type Story = StoryObj;
|
|
49
|
-
|
|
50
|
-
/** A plain thinking indicator. */
|
|
51
|
-
export const Default: Story = {
|
|
52
|
-
render: () => (
|
|
53
|
-
<div style={{ padding: '24px' }}>
|
|
54
|
-
<kai-thinking-bar text="Thinking" />
|
|
55
|
-
</div>
|
|
56
|
-
),
|
|
57
|
-
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/** Stoppable — shows an "Answer now" affordance that fires a `stop` event. */
|
|
61
|
-
export const Stoppable: Story = {
|
|
62
|
-
render: () => (
|
|
63
|
-
<div style={{ padding: '24px' }}>
|
|
64
|
-
<kai-thinking-bar
|
|
65
|
-
text="Reasoning"
|
|
66
|
-
stoppable
|
|
67
|
-
stop-label="Answer now"
|
|
68
|
-
on:kai-stop={() => console.log('stop')}
|
|
69
|
-
/>
|
|
70
|
-
</div>
|
|
71
|
-
),
|
|
72
|
-
};
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
-
import { onMount } from 'solid-js';
|
|
3
|
-
import './register'; // side effect: registers the custom elements
|
|
4
|
-
import type { ToolPart } from '../components/tool';
|
|
5
|
-
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
6
|
-
|
|
7
|
-
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
8
|
-
declare module 'solid-js' {
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
10
|
-
namespace JSX {
|
|
11
|
-
interface IntrinsicElements {
|
|
12
|
-
'kai-tool': JSX.HTMLAttributes<HTMLElement>;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const completedTool: ToolPart = {
|
|
18
|
-
type: 'database_query',
|
|
19
|
-
state: 'output-available',
|
|
20
|
-
input: { table: 'users', limit: 10 },
|
|
21
|
-
output: { rows: 10, ms: 42 },
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const runningTool: ToolPart = {
|
|
25
|
-
type: 'search',
|
|
26
|
-
state: 'input-available',
|
|
27
|
-
input: { query: 'kitn docs' },
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
/** Render the actual `<kai-tool>` custom element with a `tool` property. */
|
|
31
|
-
function ToolElement(props: { tool: ToolPart; open?: boolean }) {
|
|
32
|
-
let el: (HTMLElement & { tool?: ToolPart; open?: boolean }) | undefined;
|
|
33
|
-
onMount(() => {
|
|
34
|
-
if (el) {
|
|
35
|
-
el.tool = props.tool;
|
|
36
|
-
if (props.open) el.open = true;
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
return (
|
|
40
|
-
<kai-tool ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px', 'max-width': '720px' }} />
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
45
|
-
<kai-tool id="tool" open></kai-tool>
|
|
46
|
-
|
|
47
|
-
<script type="module">
|
|
48
|
-
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
49
|
-
|
|
50
|
-
const tool = document.getElementById('tool');
|
|
51
|
-
tool.tool = {
|
|
52
|
-
type: 'database_query',
|
|
53
|
-
state: 'output-available',
|
|
54
|
-
input: { table: 'users', limit: 10 },
|
|
55
|
-
output: { rows: 10, ms: 42 },
|
|
56
|
-
};
|
|
57
|
-
</script>`;
|
|
58
|
-
|
|
59
|
-
const meta = {
|
|
60
|
-
title: 'Components/Tool',
|
|
61
|
-
tags: ['autodocs'],
|
|
62
|
-
argTypes: argTypesFor('kai-tool'),
|
|
63
|
-
parameters: {
|
|
64
|
-
layout: 'fullscreen',
|
|
65
|
-
docs: {
|
|
66
|
-
description: specDescription('kai-tool', [
|
|
67
|
-
'`<kai-tool>` is the framework-agnostic **web component** for a single tool-call panel — a collapsible input/output inspector with a state badge — isolated in **Shadow DOM**.',
|
|
68
|
-
'**When to use:** rendering an agent/tool-call trace in a non-Solid app. In SolidJS, use the `Tool` primitive directly.',
|
|
69
|
-
'**Placement:** inline inside an assistant message row, one element per tool call, below the text content; renders as a block sized to its content.',
|
|
70
|
-
"**How to use:** register once with `import '@kitn.ai/ui/elements'`, set the call via the `tool` **property** (`el.tool = {...}`), and add the `open` flag to start it expanded.",
|
|
71
|
-
'See the **Code** tab for HTML usage.',
|
|
72
|
-
]),
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
} satisfies Meta;
|
|
76
|
-
|
|
77
|
-
export default meta;
|
|
78
|
-
type Story = StoryObj;
|
|
79
|
-
|
|
80
|
-
/** A completed call with input and output, started expanded. */
|
|
81
|
-
export const Completed: Story = {
|
|
82
|
-
render: () => <ToolElement tool={completedTool} open />,
|
|
83
|
-
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
/** A call still awaiting output (collapsed). */
|
|
87
|
-
export const Running: Story = {
|
|
88
|
-
render: () => <ToolElement tool={runningTool} />,
|
|
89
|
-
};
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
-
import { onMount } from 'solid-js';
|
|
3
|
-
import './register'; // side effect: registers the custom elements
|
|
4
|
-
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
5
|
-
|
|
6
|
-
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
7
|
-
declare module 'solid-js' {
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
9
|
-
namespace JSX {
|
|
10
|
-
interface IntrinsicElements {
|
|
11
|
-
'kai-voice-input': JSX.HTMLAttributes<HTMLElement> & {
|
|
12
|
-
disabled?: boolean | string;
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Render `<kai-voice-input>` with a stub `transcribe` function-property. */
|
|
19
|
-
function VoiceElement(props: { disabled?: boolean }) {
|
|
20
|
-
let el: (HTMLElement & { transcribe?: (audio: Blob) => Promise<string> }) | undefined;
|
|
21
|
-
onMount(() => {
|
|
22
|
-
if (!el) return;
|
|
23
|
-
// transcribe MUST be set as a JS property — a value-returning callback
|
|
24
|
-
// can't be modelled as an attribute.
|
|
25
|
-
el.transcribe = async () => {
|
|
26
|
-
await new Promise((r) => setTimeout(r, 400));
|
|
27
|
-
return 'transcribed text';
|
|
28
|
-
};
|
|
29
|
-
el.addEventListener('kai-transcription', (e) => {
|
|
30
|
-
const ev = e as CustomEvent<{ text: string }>;
|
|
31
|
-
console.log('kai-transcription', ev.detail.text);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
return (
|
|
35
|
-
<kai-voice-input
|
|
36
|
-
ref={(e) => (el = e as HTMLElement)}
|
|
37
|
-
disabled={props.disabled ? true : undefined}
|
|
38
|
-
style={{ display: 'inline-block', padding: '40px' }}
|
|
39
|
-
/>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
44
|
-
<kai-voice-input id="voice"></kai-voice-input>
|
|
45
|
-
|
|
46
|
-
<script type="module">
|
|
47
|
-
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
48
|
-
|
|
49
|
-
const voice = document.getElementById('voice');
|
|
50
|
-
// transcribe is a FUNCTION property — your async transcriber
|
|
51
|
-
voice.transcribe = async (blob) => {
|
|
52
|
-
const text = await myTranscriptionApi(blob);
|
|
53
|
-
return text;
|
|
54
|
-
};
|
|
55
|
-
voice.addEventListener('kai-transcription', (e) => console.log(e.detail.text));
|
|
56
|
-
</script>`;
|
|
57
|
-
|
|
58
|
-
const meta = {
|
|
59
|
-
title: 'Components/VoiceInput',
|
|
60
|
-
tags: ['autodocs'],
|
|
61
|
-
argTypes: argTypesFor('kai-voice-input'),
|
|
62
|
-
parameters: {
|
|
63
|
-
layout: 'fullscreen',
|
|
64
|
-
docs: {
|
|
65
|
-
description: specDescription('kai-voice-input', [
|
|
66
|
-
'`<kai-voice-input>` is the framework-agnostic **web component** for a mic button that records and transcribes audio — isolated in **Shadow DOM**. It is the canonical **function-property** element.',
|
|
67
|
-
'**When to use:** adding voice dictation to an input in a non-Solid app. In SolidJS, use the `VoiceInput` primitive.',
|
|
68
|
-
'**Placement:** inline beside the prompt input field or as an icon button in the composer toolbar; renders as an inline-block sized to the mic button.',
|
|
69
|
-
"**How to use:** register once with `import '@kitn.ai/ui/elements'`, then set the `transcribe` **function property** (`el.transcribe = async blob => '...'`) — a value-returning callback can't be modelled as an event. It also emits `kai-audio-captured` (raw blob) and `kai-transcription` (text) **CustomEvents**.",
|
|
70
|
-
'See the **Code** tab for HTML usage.',
|
|
71
|
-
]),
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
} satisfies Meta;
|
|
75
|
-
|
|
76
|
-
export default meta;
|
|
77
|
-
type Story = StoryObj;
|
|
78
|
-
|
|
79
|
-
/** A working mic button wired to a stub transcriber. */
|
|
80
|
-
export const Default: Story = {
|
|
81
|
-
render: () => <VoiceElement />,
|
|
82
|
-
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
/** A disabled mic button (non-interactive). */
|
|
86
|
-
export const Disabled: Story = {
|
|
87
|
-
render: () => <VoiceElement disabled />,
|
|
88
|
-
};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { createRoot, createSignal } from 'solid-js';
|
|
3
|
-
import { createControllableSignal } from './controllable';
|
|
4
|
-
|
|
5
|
-
describe('createControllableSignal', () => {
|
|
6
|
-
it('uses the initial value when uncontrolled (controlled accessor is undefined)', () => {
|
|
7
|
-
createRoot((dispose) => {
|
|
8
|
-
const [value] = createControllableSignal(() => undefined, true);
|
|
9
|
-
expect(value()).toBe(true);
|
|
10
|
-
dispose();
|
|
11
|
-
});
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('updates via the setter when uncontrolled', () => {
|
|
15
|
-
createRoot((dispose) => {
|
|
16
|
-
const [value, setValue] = createControllableSignal(() => undefined, false);
|
|
17
|
-
setValue(true);
|
|
18
|
-
expect(value()).toBe(true);
|
|
19
|
-
dispose();
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('reflects the controlled value and ignores the internal setter while controlled', () => {
|
|
24
|
-
createRoot((dispose) => {
|
|
25
|
-
const [controlled, setControlled] = createSignal<boolean | undefined>(false);
|
|
26
|
-
const [value, setValue] = createControllableSignal(controlled, false);
|
|
27
|
-
expect(value()).toBe(false);
|
|
28
|
-
setValue(true); // masked while controlled
|
|
29
|
-
expect(value()).toBe(false);
|
|
30
|
-
setControlled(true); // the controlling value wins
|
|
31
|
-
expect(value()).toBe(true);
|
|
32
|
-
dispose();
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('falls back to the internal value when control is released (becomes undefined)', () => {
|
|
37
|
-
createRoot((dispose) => {
|
|
38
|
-
const [controlled, setControlled] = createSignal<boolean | undefined>(true);
|
|
39
|
-
const [value, setValue] = createControllableSignal(controlled, false);
|
|
40
|
-
expect(value()).toBe(true);
|
|
41
|
-
setValue(true); // sets internal (masked now)
|
|
42
|
-
setControlled(undefined); // uncontrolled → internal shows
|
|
43
|
-
expect(value()).toBe(true);
|
|
44
|
-
dispose();
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
});
|