@kitnai/chat 0.7.0 → 0.8.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.
Files changed (214) hide show
  1. package/README.md +9 -9
  2. package/dist/custom-elements.json +1626 -883
  3. package/dist/kitn-chat.es.js +36 -36
  4. package/dist/llms/llms-full.txt +303 -142
  5. package/dist/llms/llms.txt +18 -18
  6. package/dist/schemas/card-envelope.schema.json +14 -0
  7. package/dist/schemas/card-event.schema.json +12 -0
  8. package/dist/schemas/confirm.schema.json +65 -0
  9. package/dist/schemas/embed.schema.json +65 -0
  10. package/dist/schemas/form.result.schema.json +7 -0
  11. package/dist/schemas/form.schema.json +33 -0
  12. package/dist/schemas/link.schema.json +56 -0
  13. package/dist/schemas/task-list.result.schema.json +16 -0
  14. package/dist/schemas/task-list.schema.json +78 -0
  15. package/dist/theme.tokens.css +65 -65
  16. package/dist/tsx-B8rCNbgL.js +1 -0
  17. package/dist/typescript-RycA9KXf.js +1 -0
  18. package/frameworks/react/index.tsx +356 -189
  19. package/frameworks/react/runtime.tsx +2 -2
  20. package/llms-full.txt +303 -142
  21. package/llms.txt +18 -18
  22. package/package.json +5 -2
  23. package/src/components/artifact.stories.tsx +138 -0
  24. package/src/components/artifact.tsx +581 -0
  25. package/src/components/attachments.stories.tsx +7 -8
  26. package/src/components/attachments.tsx +2 -2
  27. package/src/components/card.tsx +110 -0
  28. package/src/components/chain-of-thought.stories.tsx +7 -8
  29. package/src/components/chat-container.stories.tsx +7 -8
  30. package/src/components/chat-container.tsx +4 -0
  31. package/src/components/checkpoint.stories.tsx +7 -8
  32. package/src/components/checkpoint.tsx +3 -0
  33. package/src/components/code-block.stories.tsx +8 -9
  34. package/src/components/code-block.tsx +5 -2
  35. package/src/components/component-meta.json +3419 -0
  36. package/src/components/confirm-card.stories.tsx +74 -0
  37. package/src/components/confirm-card.tsx +299 -0
  38. package/src/components/context.stories.tsx +7 -8
  39. package/src/components/conversation-item.stories.tsx +7 -8
  40. package/src/components/conversation-item.tsx +2 -2
  41. package/src/components/conversation-list.stories.tsx +7 -8
  42. package/src/components/conversation-list.tsx +1 -1
  43. package/src/components/embed.tsx +196 -0
  44. package/src/components/empty.stories.tsx +8 -9
  45. package/src/components/feedback-bar.stories.tsx +7 -8
  46. package/src/components/file-tree.stories.tsx +73 -0
  47. package/src/components/file-tree.tsx +383 -0
  48. package/src/components/file-upload.stories.tsx +7 -8
  49. package/src/components/form-widgets.tsx +461 -0
  50. package/src/components/form.tsx +796 -0
  51. package/src/components/image.stories.tsx +7 -8
  52. package/src/components/link-card.tsx +194 -0
  53. package/src/components/loader.stories.tsx +7 -8
  54. package/src/components/markdown.stories.tsx +7 -8
  55. package/src/components/message-narrow.stories.tsx +12 -13
  56. package/src/components/message-skills.stories.tsx +16 -17
  57. package/src/components/message.stories.tsx +17 -18
  58. package/src/components/model-switcher.stories.tsx +7 -8
  59. package/src/components/prompt-input.stories.tsx +8 -9
  60. package/src/components/prompt-suggestion.stories.tsx +7 -8
  61. package/src/components/prompt-suggestion.tsx +3 -3
  62. package/src/components/reasoning.stories.tsx +7 -8
  63. package/src/components/scroll-button.stories.tsx +7 -8
  64. package/src/components/slash-command.stories.tsx +8 -9
  65. package/src/components/slash-command.tsx +2 -2
  66. package/src/components/source.stories.tsx +7 -8
  67. package/src/components/source.tsx +1 -1
  68. package/src/components/task-list-card.stories.tsx +78 -0
  69. package/src/components/task-list-card.tsx +388 -0
  70. package/src/components/text-shimmer.stories.tsx +7 -8
  71. package/src/components/thinking-bar.stories.tsx +7 -8
  72. package/src/components/tool.stories.tsx +7 -8
  73. package/src/components/tool.tsx +2 -2
  74. package/src/components/voice-input.stories.tsx +7 -8
  75. package/src/elements/artifact.stories.tsx +291 -0
  76. package/src/elements/artifact.tsx +72 -0
  77. package/src/elements/{kitn-attachments.stories.tsx → attachments.stories.tsx} +11 -20
  78. package/src/elements/attachments.tsx +4 -4
  79. package/src/elements/card.stories.tsx +118 -0
  80. package/src/elements/card.tsx +40 -0
  81. package/src/elements/catalog.stories.tsx +491 -0
  82. package/src/elements/{kitn-chain-of-thought.stories.tsx → chain-of-thought.stories.tsx} +13 -22
  83. package/src/elements/chain-of-thought.tsx +3 -3
  84. package/src/elements/{kitn-chat-scope-picker.stories.tsx → chat-scope-picker.stories.tsx} +10 -19
  85. package/src/elements/chat-scope-picker.tsx +4 -4
  86. package/src/elements/{kitn-chat-workspace.stories.tsx → chat-workspace.stories.tsx} +15 -23
  87. package/src/elements/chat-workspace.tsx +2 -2
  88. package/src/elements/{kitn-chat.stories.tsx → chat.stories.tsx} +12 -20
  89. package/src/elements/chat.tsx +2 -2
  90. package/src/elements/{kitn-checkpoint.stories.tsx → checkpoint.stories.tsx} +11 -20
  91. package/src/elements/checkpoint.tsx +8 -4
  92. package/src/elements/{kitn-code-block.stories.tsx → code-block.stories.tsx} +10 -19
  93. package/src/elements/code-block.tsx +3 -3
  94. package/src/elements/compiled.css +1 -1
  95. package/src/elements/composed-shell.stories.tsx +316 -0
  96. package/src/elements/confirm-card.stories.tsx +186 -0
  97. package/src/elements/confirm-card.tsx +45 -0
  98. package/src/elements/{kitn-context-meter.stories.tsx → context-meter.stories.tsx} +10 -19
  99. package/src/elements/context-meter.tsx +3 -3
  100. package/src/elements/{kitn-conversation-list.stories.tsx → conversation-list.stories.tsx} +12 -20
  101. package/src/elements/conversation-list.tsx +2 -2
  102. package/src/elements/css.ts +1 -1
  103. package/src/elements/define.tsx +10 -10
  104. package/src/elements/element-meta.json +1379 -733
  105. package/src/elements/element-types.d.ts +251 -125
  106. package/src/elements/embed.stories.tsx +197 -0
  107. package/src/elements/embed.tsx +35 -0
  108. package/src/elements/{kitn-empty.stories.tsx → empty.stories.tsx} +12 -21
  109. package/src/elements/empty.tsx +3 -3
  110. package/src/elements/{kitn-feedback-bar.stories.tsx → feedback-bar.stories.tsx} +11 -20
  111. package/src/elements/feedback-bar.tsx +4 -4
  112. package/src/elements/file-tree.stories.tsx +133 -0
  113. package/src/elements/file-tree.tsx +52 -0
  114. package/src/elements/{kitn-file-upload.stories.tsx → file-upload.stories.tsx} +12 -21
  115. package/src/elements/file-upload.tsx +4 -4
  116. package/src/elements/form.stories.tsx +204 -0
  117. package/src/elements/form.tsx +37 -0
  118. package/src/elements/{kitn-image.stories.tsx → image.stories.tsx} +10 -19
  119. package/src/elements/image.tsx +3 -3
  120. package/src/elements/link-card.stories.tsx +193 -0
  121. package/src/elements/link-card.tsx +34 -0
  122. package/src/elements/{kitn-loader.stories.tsx → loader.stories.tsx} +11 -20
  123. package/src/elements/loader.tsx +3 -3
  124. package/src/elements/{kitn-markdown.stories.tsx → markdown.stories.tsx} +10 -19
  125. package/src/elements/markdown.tsx +3 -3
  126. package/src/elements/{kitn-message-skills.stories.tsx → message-skills.stories.tsx} +10 -19
  127. package/src/elements/message-skills.tsx +3 -3
  128. package/src/elements/{kitn-message.stories.tsx → message.stories.tsx} +12 -21
  129. package/src/elements/message.tsx +5 -5
  130. package/src/elements/{kitn-model-switcher.stories.tsx → model-switcher.stories.tsx} +10 -19
  131. package/src/elements/model-switcher.tsx +5 -5
  132. package/src/elements/{kitn-prompt-input.stories.tsx → prompt-input.stories.tsx} +14 -22
  133. package/src/elements/prompt-input.tsx +3 -3
  134. package/src/elements/{kitn-prompt-suggestions.stories.tsx → prompt-suggestions.stories.tsx} +13 -22
  135. package/src/elements/prompt-suggestions.tsx +4 -4
  136. package/src/elements/{kitn-reasoning.stories.tsx → reasoning.stories.tsx} +10 -19
  137. package/src/elements/reasoning.tsx +4 -4
  138. package/src/elements/register.ts +11 -1
  139. package/src/elements/resizable.stories.tsx +200 -0
  140. package/src/elements/resizable.tsx +264 -0
  141. package/src/elements/{kitn-response-stream.stories.tsx → response-stream.stories.tsx} +10 -19
  142. package/src/elements/response-stream.tsx +4 -4
  143. package/src/elements/{kitn-source-list.stories.tsx → source-list.stories.tsx} +11 -20
  144. package/src/elements/{kitn-source.stories.tsx → source.stories.tsx} +12 -21
  145. package/src/elements/source.tsx +5 -5
  146. package/src/elements/styles.css +140 -1
  147. package/src/elements/task-list-card.stories.tsx +194 -0
  148. package/src/elements/task-list-card.tsx +40 -0
  149. package/src/elements/{kitn-text-shimmer.stories.tsx → text-shimmer.stories.tsx} +10 -19
  150. package/src/elements/text-shimmer.tsx +3 -3
  151. package/src/elements/{kitn-thinking-bar.stories.tsx → thinking-bar.stories.tsx} +11 -20
  152. package/src/elements/thinking-bar.tsx +5 -5
  153. package/src/elements/{kitn-tool.stories.tsx → tool.stories.tsx} +10 -19
  154. package/src/elements/tool.tsx +3 -3
  155. package/src/elements/{kitn-voice-input.stories.tsx → voice-input.stories.tsx} +10 -19
  156. package/src/elements/voice-input.tsx +4 -4
  157. package/src/index.ts +94 -2
  158. package/src/primitives/card-contract.ts +60 -0
  159. package/src/primitives/card-host.tsx +35 -0
  160. package/src/primitives/card-routing.ts +79 -0
  161. package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
  162. package/src/primitives/card-schemas/card-event.schema.json +12 -0
  163. package/src/primitives/card-schemas/confirm.schema.json +65 -0
  164. package/src/primitives/card-schemas/embed.schema.json +65 -0
  165. package/src/primitives/card-schemas/form.result.schema.json +7 -0
  166. package/src/primitives/card-schemas/form.schema.json +33 -0
  167. package/src/primitives/card-schemas/link.schema.json +56 -0
  168. package/src/primitives/card-schemas/task-list.result.schema.json +16 -0
  169. package/src/primitives/card-schemas/task-list.schema.json +78 -0
  170. package/src/primitives/card-validate.ts +95 -0
  171. package/src/primitives/embed-providers.ts +254 -0
  172. package/src/primitives/highlighter.ts +4 -0
  173. package/src/primitives/link-preview.ts +87 -0
  174. package/src/primitives/pdf-preview.ts +121 -0
  175. package/src/stories/chat-panel-layout.stories.tsx +2 -1
  176. package/src/stories/chat-scene.tsx +22 -21
  177. package/src/stories/checkpoint-restore.stories.tsx +10 -10
  178. package/src/stories/conversation-with-reasoning.stories.tsx +4 -4
  179. package/src/stories/conversation-with-sources.stories.tsx +7 -7
  180. package/src/stories/docs/Accessibility.mdx +2 -2
  181. package/src/stories/docs/ForAIAgents.mdx +3 -3
  182. package/src/stories/docs/GettingStarted.mdx +2 -2
  183. package/src/stories/docs/Installation.mdx +2 -2
  184. package/src/stories/docs/Integrations.mdx +29 -29
  185. package/src/stories/docs/Introduction.mdx +3 -3
  186. package/src/stories/docs/Theming.mdx +2 -2
  187. package/src/stories/docs/element-controls.ts +32 -0
  188. package/src/stories/docs/theme-editor/theme-editor.tsx +1 -0
  189. package/src/stories/examples/ChoosingComponents.mdx +94 -0
  190. package/src/stories/examples/sample-data.ts +79 -0
  191. package/src/stories/message-actions.stories.tsx +13 -13
  192. package/src/stories/pattern-centered-conversation.stories.tsx +3 -3
  193. package/src/stories/pattern-docked-widget.stories.tsx +1 -1
  194. package/src/stories/pattern-empty-state.stories.tsx +3 -3
  195. package/src/stories/prompt-input-variants.stories.tsx +13 -13
  196. package/src/stories/streaming-response.stories.tsx +3 -3
  197. package/src/stories/typography.stories.tsx +4 -4
  198. package/src/ui/avatar.stories.tsx +7 -8
  199. package/src/ui/badge.stories.tsx +7 -8
  200. package/src/ui/button.stories.tsx +8 -9
  201. package/src/ui/button.tsx +1 -0
  202. package/src/ui/collapsible.stories.tsx +6 -7
  203. package/src/ui/dropdown.stories.tsx +6 -7
  204. package/src/ui/hover-card.stories.tsx +6 -7
  205. package/src/ui/resizable.stories.tsx +74 -9
  206. package/src/ui/resizable.tsx +351 -71
  207. package/src/ui/scroll-area.stories.tsx +6 -7
  208. package/src/ui/scroll-area.tsx +3 -1
  209. package/src/ui/separator.stories.tsx +7 -8
  210. package/src/ui/skeleton.stories.tsx +7 -8
  211. package/src/ui/textarea.stories.tsx +6 -7
  212. package/src/ui/tooltip.stories.tsx +8 -9
  213. package/theme.css +65 -65
  214. package/src/stories/docs/element-spec.tsx +0 -86
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { Image } from './image';
3
+ import { componentDescription } from '../stories/docs/element-controls';
3
4
 
4
5
  // Compact SVG chat typing icon as base64
5
6
  const chatIconBase64 =
@@ -13,14 +14,12 @@ const meta = {
13
14
  layout: 'padded',
14
15
  docs: {
15
16
  controls: { exclude: ['use:eventListener'] },
16
- description: {
17
- component: [
18
- 'Renders an image from a `base64` string or a `uint8Array`, building a data URL or object URL automatically; shows a pulsing placeholder until a source is available.',
19
- '**When to use:** to display model-generated or attached images supplied as raw bytes / base64 (the `GeneratedImageLike` shape) rather than a remote URL.',
20
- '**How to use:** pass `base64` + `mediaType` (or `uint8Array` + `mediaType`) and an `alt` description; size and style via `class`.',
21
- '**Placement:** inside assistant messages, attachment previews, or anywhere a generated image needs to be shown.',
22
- ].join('\n\n'),
23
- },
17
+ description: componentDescription([
18
+ 'Renders an image from a `base64` string or a `uint8Array`, building a data URL or object URL automatically; shows a pulsing placeholder until a source is available.',
19
+ '**When to use:** to display model-generated or attached images supplied as raw bytes / base64 (the `GeneratedImageLike` shape) rather than a remote URL.',
20
+ '**How to use:** pass `base64` + `mediaType` (or `uint8Array` + `mediaType`) and an `alt` description; size and style via `class`.',
21
+ '**Placement:** inside assistant messages, attachment previews, or anywhere a generated image needs to be shown.',
22
+ ]),
24
23
  },
25
24
  },
26
25
  argTypes: {
@@ -0,0 +1,194 @@
1
+ import {
2
+ type JSX,
3
+ splitProps,
4
+ createSignal,
5
+ createEffect,
6
+ createMemo,
7
+ on,
8
+ onMount,
9
+ onCleanup,
10
+ Show,
11
+ } from 'solid-js';
12
+ import { cn } from '../utils/cn';
13
+ import { Link as LinkIcon } from 'lucide-solid';
14
+ import type { CardEvent } from '../primitives/card-contract';
15
+ import {
16
+ type LinkCardData,
17
+ deriveDomain,
18
+ isRenderableLink,
19
+ hasLinkPreviewFetcher,
20
+ resolveLinkMetadata,
21
+ } from '../primitives/link-preview';
22
+
23
+ export interface LinkCardProps {
24
+ /** The card id correlating every emitted event. */
25
+ cardId: string;
26
+ /** The link payload (data-down). */
27
+ data: LinkCardData;
28
+ /** Emit a contract CardEvent up (host routes it). */
29
+ onEmit?: (event: CardEvent) => void;
30
+ /** Extra classes for the card root. */
31
+ class?: string;
32
+ }
33
+
34
+ /** True when the payload already carries renderable metadata (the pure path). */
35
+ function hasMetadata(data: LinkCardData): boolean {
36
+ return Boolean(data.title || data.description || data.image);
37
+ }
38
+
39
+ /**
40
+ * `LinkCard` — a pure, themed, accessible rich link / OG preview. Renders from the
41
+ * supplied metadata; it never fetches the network itself. When the payload is a
42
+ * bare `{ url }` and an app has registered a `configureLinkPreview` fetcher, it
43
+ * shows a skeleton, calls the hook, merges the result, and renders. Activating the
44
+ * card (click / Enter / Space) emits the contract `open` verb (`target:'tab'`); the
45
+ * host policy performs the navigation so it can veto/redirect.
46
+ */
47
+ export function LinkCard(props: LinkCardProps): JSX.Element {
48
+ const [local] = splitProps(props, ['cardId', 'data', 'onEmit', 'class']);
49
+
50
+ const emit = (event: CardEvent) => local.onEmit?.(event);
51
+
52
+ // Bare-URL resolution state (only used when the payload lacks metadata + a fetcher exists).
53
+ const [fetched, setFetched] = createSignal<Partial<LinkCardData> | undefined>();
54
+ const [loading, setLoading] = createSignal(false);
55
+ const [imageBroken, setImageBroken] = createSignal(false);
56
+
57
+ const url = () => local.data.url;
58
+ const valid = createMemo(() => isRenderableLink(url()));
59
+
60
+ // The effective payload = supplied data merged with any fetched metadata.
61
+ const effective = createMemo<LinkCardData>(() => ({ ...local.data, ...fetched() }));
62
+
63
+ // Lifecycle `ready` once on mount.
64
+ onMount(() => emit({ kind: 'ready', cardId: local.cardId }));
65
+
66
+ // Invalid URL → emit a single `error` (belt-and-suspenders; host also rejects bad schemes on open).
67
+ createEffect(
68
+ on(valid, (ok) => {
69
+ if (!ok) emit({ kind: 'error', cardId: local.cardId, message: `Invalid link url: ${url()}` });
70
+ }),
71
+ );
72
+
73
+ // Bare-URL path: when there's no metadata AND a fetcher is configured, resolve once.
74
+ createEffect(
75
+ on(
76
+ () => [local.data, valid()] as const,
77
+ ([data, ok]) => {
78
+ setImageBroken(false);
79
+ if (!ok || hasMetadata(data) || !hasLinkPreviewFetcher()) return;
80
+ let cancelled = false;
81
+ // Cancel a stale in-flight fetch if `data` changes before it resolves.
82
+ onCleanup(() => {
83
+ cancelled = true;
84
+ });
85
+ setLoading(true);
86
+ resolveLinkMetadata(data.url)
87
+ .then((meta) => {
88
+ if (!cancelled) setFetched(meta);
89
+ })
90
+ .catch(() => {
91
+ // Reject → fall back to the bare link chip; the URL itself is still usable.
92
+ if (!cancelled) setFetched({});
93
+ })
94
+ .finally(() => {
95
+ if (!cancelled) setLoading(false);
96
+ });
97
+ },
98
+ ),
99
+ );
100
+
101
+ const domain = createMemo(() => effective().domain ?? deriveDomain(url()) ?? url());
102
+ const heading = createMemo(() => effective().siteName ?? domain());
103
+ const titleText = createMemo(() => effective().title ?? domain());
104
+ const showImage = createMemo(() => Boolean(effective().image) && !imageBroken());
105
+
106
+ const activate = () => {
107
+ if (!valid()) return;
108
+ emit({ kind: 'open', cardId: local.cardId, url: url(), target: 'tab' });
109
+ };
110
+
111
+ const onClick = (e: MouseEvent) => {
112
+ // Intercept the anchor's default navigation so it routes through host policy.
113
+ // (Middle-click / cmd-click still open the real href in a new tab — acceptable + safe.)
114
+ e.preventDefault();
115
+ activate();
116
+ };
117
+ const onKeyDown = (e: KeyboardEvent) => {
118
+ if (e.key === 'Enter' || e.key === ' ') {
119
+ e.preventDefault();
120
+ activate();
121
+ }
122
+ };
123
+
124
+ // --- Invalid-link error chip ------------------------------------------
125
+ return (
126
+ <Show
127
+ when={valid()}
128
+ fallback={
129
+ <div
130
+ role="img"
131
+ aria-label="Invalid link"
132
+ class={cn(
133
+ 'flex items-center gap-2 rounded-xl border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive dark:border-red-400/50 dark:bg-destructive/20 dark:text-red-400',
134
+ local.class,
135
+ )}
136
+ >
137
+ <LinkIcon size={16} aria-hidden="true" />
138
+ <span class="truncate">Invalid link</span>
139
+ </div>
140
+ }
141
+ >
142
+ <a
143
+ href={url()}
144
+ rel="noopener noreferrer"
145
+ aria-label={`Open ${titleText()} on ${domain()}`}
146
+ class={cn(
147
+ 'group block overflow-hidden rounded-xl border border-border bg-card text-card-foreground no-underline outline-none transition-shadow',
148
+ 'cursor-pointer hover:shadow-md focus-visible:ring-2 focus-visible:ring-ring',
149
+ local.class,
150
+ )}
151
+ onClick={onClick}
152
+ onKeyDown={onKeyDown}
153
+ >
154
+ <Show when={loading()}>
155
+ {/* Skeleton while the bare-URL fetcher resolves. */}
156
+ <div class="aspect-[16/9] w-full animate-pulse bg-muted" />
157
+ <div class="space-y-2 p-3">
158
+ <div class="h-3 w-1/3 animate-pulse rounded bg-muted" />
159
+ <div class="h-4 w-2/3 animate-pulse rounded bg-muted" />
160
+ <div class="h-3 w-full animate-pulse rounded bg-muted" />
161
+ </div>
162
+ </Show>
163
+ <Show when={!loading()}>
164
+ <Show when={showImage()}>
165
+ <img
166
+ src={effective().image}
167
+ alt={effective().imageAlt ?? ''}
168
+ role="img"
169
+ class="aspect-[16/9] w-full object-cover"
170
+ onError={() => setImageBroken(true)}
171
+ />
172
+ </Show>
173
+ <div class="space-y-1 p-3">
174
+ <div class="flex items-center gap-1.5 text-xs text-muted-foreground">
175
+ <Show
176
+ when={effective().favicon}
177
+ fallback={<LinkIcon size={12} class="shrink-0 opacity-70" aria-hidden="true" />}
178
+ >
179
+ <img src={effective().favicon} alt="" class="h-3.5 w-3.5 shrink-0 rounded-sm" />
180
+ </Show>
181
+ <span class="truncate">{heading()}</span>
182
+ </div>
183
+ <div class="line-clamp-2 text-sm font-semibold text-foreground">{titleText()}</div>
184
+ <Show when={effective().description}>
185
+ <div class="line-clamp-3 text-xs text-muted-foreground">{effective().description}</div>
186
+ </Show>
187
+ </div>
188
+ </Show>
189
+ </a>
190
+ </Show>
191
+ );
192
+ }
193
+
194
+ export type { LinkCardData } from '../primitives/link-preview';
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { For } from 'solid-js';
3
3
  import { Loader, type LoaderVariant, type LoaderSize } from './loader';
4
+ import { componentDescription } from '../stories/docs/element-controls';
4
5
 
5
6
  const meta = {
6
7
  title: 'Components/Loader',
@@ -10,14 +11,12 @@ const meta = {
10
11
  layout: 'padded',
11
12
  docs: {
12
13
  controls: { exclude: ['use:eventListener'] },
13
- description: {
14
- component: [
15
- 'A loading indicator with twelve animated **variants** (spinners, dots, bars, terminal, and text-based) in three **sizes**.',
16
- '**When to use:** while waiting on an async operation a streaming response, a tool call, or any pending state.',
17
- '**How to use:** pick a `variant` and `size`. Text variants (`text-blink`, `text-shimmer`, `loading-dots`) display the `text` prop (defaults to "Thinking").',
18
- '**Placement:** inside assistant message bubbles, send-button states, empty states, or anywhere an in-progress signal is needed.',
19
- ].join('\n\n'),
20
- },
14
+ description: componentDescription([
15
+ 'A loading indicator with twelve animated **variants** (spinners, dots, bars, terminal, and text-based) in three **sizes**.',
16
+ '**When to use:** while waiting on an async operation — a streaming response, a tool call, or any pending state.',
17
+ '**How to use:** pick a `variant` and `size`. Text variants (`text-blink`, `text-shimmer`, `loading-dots`) display the `text` prop (defaults to "Thinking").',
18
+ '**Placement:** inside assistant message bubbles, send-button states, empty states, or anywhere an in-progress signal is needed.',
19
+ ]),
21
20
  },
22
21
  },
23
22
  argTypes: {
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { Markdown } from './markdown';
3
+ import { componentDescription } from '../stories/docs/element-controls';
3
4
 
4
5
  const meta = {
5
6
  title: 'Components/Markdown',
@@ -9,14 +10,12 @@ const meta = {
9
10
  layout: 'padded',
10
11
  docs: {
11
12
  controls: { exclude: ['use:eventListener'] },
12
- description: {
13
- component: [
14
- 'Renders a Markdown string to styled HTML (GFM enabled — tables, lists, blockquotes), splitting fenced code into highlighted `CodeBlock`s.',
15
- '**When to use:** to display assistant message content, documentation, or any rich text authored in Markdown.',
16
- '**How to use:** pass the Markdown string as `content`; optionally set `codeTheme` for code fences and `class` for the prose container. Prose sizing follows the surrounding `ChatConfig`.',
17
- '**Placement:** inside `MessageContent`, response panels, or anywhere formatted text is shown.',
18
- ].join('\n\n'),
19
- },
13
+ description: componentDescription([
14
+ 'Renders a Markdown string to styled HTML (GFM enabled — tables, lists, blockquotes), splitting fenced code into highlighted `CodeBlock`s.',
15
+ '**When to use:** to display assistant message content, documentation, or any rich text authored in Markdown.',
16
+ '**How to use:** pass the Markdown string as `content`; optionally set `codeTheme` for code fences and `class` for the prose container. Prose sizing follows the surrounding `ChatConfig`.',
17
+ '**Placement:** inside `MessageContent`, response panels, or anywhere formatted text is shown.',
18
+ ]),
20
19
  },
21
20
  },
22
21
  argTypes: {
@@ -3,6 +3,7 @@ import { Message, MessageAvatar, MessageContent } from './message';
3
3
  import { ChatContainer } from './chat-container';
4
4
  import { ChatConfig } from '../primitives/chat-config';
5
5
  import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '../ui/resizable';
6
+ import { componentDescription } from '../stories/docs/element-controls';
6
7
 
7
8
  const meta = {
8
9
  title: 'Components/Message/Narrow Panel',
@@ -12,14 +13,12 @@ const meta = {
12
13
  layout: 'centered',
13
14
  docs: {
14
15
  controls: { exclude: ['use:eventListener'] },
15
- description: {
16
- component: [
17
- 'Layout stress-tests for `Message` inside narrow chat panels (300–380px) and resizable side panels, verifying text wraps and the 32px avatar stays fixed.',
18
- '**When to use:** as a reference when embedding the chat in a constrained column — a browser-extension side panel, a docked drawer, or a resizable split view.',
19
- '**How to use:** wrap messages in a fixed/min-width container (and usually `ChatConfig proseSize="sm"`), keeping `min-w-0` on flex children so long text wraps instead of overflowing.',
20
- '**Placement:** these are full-composition showcases, not control-driven; use them to copy the surrounding panel structure.',
21
- ].join('\n\n'),
22
- },
16
+ description: componentDescription([
17
+ 'Layout stress-tests for `Message` inside narrow chat panels (300–380px) and resizable side panels, verifying text wraps and the 32px avatar stays fixed.',
18
+ '**When to use:** as a reference when embedding the chat in a constrained column — a browser-extension side panel, a docked drawer, or a resizable split view.',
19
+ '**How to use:** wrap messages in a fixed/min-width container (and usually `ChatConfig proseSize="sm"`), keeping `min-w-0` on flex children so long text wraps instead of overflowing.',
20
+ '**Placement:** these are full-composition showcases, not control-driven; use them to copy the surrounding panel structure.',
21
+ ]),
23
22
  },
24
23
  },
25
24
  argTypes: {
@@ -191,11 +190,11 @@ export const FullExtensionLayout: Story = {
191
190
  <ChatConfig proseSize="sm">
192
191
  <div class="flex h-screen bg-background relative" style={{ height: '600px' }}>
193
192
  <div class="flex-shrink-0 w-[300px] bg-[#161618] p-4 overflow-y-auto">
194
- <div class="text-sm text-muted-foreground">Left Nav</div>
193
+ <div class="text-sm text-white/70">Left Nav</div>
195
194
  <div class="mt-2 space-y-1">
196
195
  <div class="text-sky-400 text-sm px-2 py-1 bg-sky-400/10 rounded">Content</div>
197
- <div class="text-muted-foreground text-sm px-2 py-1">Summary</div>
198
- <div class="text-muted-foreground text-sm px-2 py-1">Key Points</div>
196
+ <div class="text-white/70 text-sm px-2 py-1">Summary</div>
197
+ <div class="text-white/70 text-sm px-2 py-1">Key Points</div>
199
198
  </div>
200
199
  </div>
201
200
 
@@ -239,7 +238,7 @@ export const FullExtensionLayout: Story = {
239
238
  </div>
240
239
  </ChatContainer>
241
240
  <div class="px-3 pb-3 pt-1 flex-shrink-0">
242
- <div class="bg-muted/40 rounded-lg px-3 py-2.5 text-sm text-muted-foreground/40">
241
+ <div class="bg-muted/40 rounded-lg px-3 py-2.5 text-sm text-foreground/75">
243
242
  Ask about this page...
244
243
  </div>
245
244
  </div>
@@ -304,7 +303,7 @@ export const InsideResizablePanel: Story = {
304
303
  </ChatContainer>
305
304
 
306
305
  <div class="px-3 pb-3 pt-1 flex-shrink-0">
307
- <div class="bg-muted/40 rounded-lg px-3 py-2.5 text-sm text-muted-foreground/40">
306
+ <div class="bg-muted/40 rounded-lg px-3 py-2.5 text-sm text-foreground/75">
308
307
  Ask about this page...
309
308
  </div>
310
309
  </div>
@@ -3,6 +3,7 @@ import { MessageSkills } from "./message-skills";
3
3
  import { Message, MessageContent, MessageActions } from "./message";
4
4
  import { ChatConfig } from "../primitives/chat-config";
5
5
  import { Copy, ThumbsUp, ThumbsDown } from "lucide-solid";
6
+ import { componentDescription } from '../stories/docs/element-controls';
6
7
 
7
8
  const meta = {
8
9
  title: "Components/MessageSkills",
@@ -12,14 +13,12 @@ const meta = {
12
13
  layout: "padded",
13
14
  docs: {
14
15
  controls: { exclude: ["use:eventListener"] },
15
- description: {
16
- component: [
17
- "A row of small badges that label which **skills** were active when a message was generated.",
18
- "**When to use:** above an assistant message whose response was shaped by one or more skills (e.g. `Concise`, `ELI5`). Renders nothing when the `skills` array is empty.",
19
- "**How to use:** pass a `skills` array of `{ id, name }`; each `name` is shown as a badge. Add `class` (e.g. `mb-1`) to space it from the message body.",
20
- "**Placement:** directly above `MessageContent` inside an assistant `Message`.",
21
- ].join("\n\n"),
22
- },
16
+ description: componentDescription([
17
+ "A row of small badges that label which **skills** were active when a message was generated.",
18
+ "**When to use:** above an assistant message whose response was shaped by one or more skills (e.g. `Concise`, `ELI5`). Renders nothing when the `skills` array is empty.",
19
+ "**How to use:** pass a `skills` array of `{ id, name }`; each `name` is shown as a badge. Add `class` (e.g. `mb-1`) to space it from the message body.",
20
+ "**Placement:** directly above `MessageContent` inside an assistant `Message`.",
21
+ ]),
23
22
  },
24
23
  },
25
24
  argTypes: {
@@ -94,13 +93,13 @@ export const InAssistantMessage: Story = {
94
93
  {"Bug in auth middleware. Token expiry check use `<` not `<=`. Fix: update comparison operator in `validateToken()`."}
95
94
  </MessageContent>
96
95
  <MessageActions class="[&>button]:p-1 [&>button]:rounded [&>button]:text-foreground/60 [&>button]:hover:text-foreground [&>button]:transition-colors">
97
- <button>
96
+ <button aria-label="Copy message">
98
97
  <Copy size={14} />
99
98
  </button>
100
- <button>
99
+ <button aria-label="Good response">
101
100
  <ThumbsUp size={14} />
102
101
  </button>
103
- <button>
102
+ <button aria-label="Bad response">
104
103
  <ThumbsDown size={14} />
105
104
  </button>
106
105
  </MessageActions>
@@ -113,13 +112,13 @@ export const InAssistantMessage: Story = {
113
112
  causes tokens to be accepted even when they've just expired.
114
113
  </MessageContent>
115
114
  <MessageActions class="[&>button]:p-1 [&>button]:rounded [&>button]:text-foreground/60 [&>button]:hover:text-foreground [&>button]:transition-colors">
116
- <button>
115
+ <button aria-label="Copy message">
117
116
  <Copy size={14} />
118
117
  </button>
119
- <button>
118
+ <button aria-label="Good response">
120
119
  <ThumbsUp size={14} />
121
120
  </button>
122
- <button>
121
+ <button aria-label="Bad response">
123
122
  <ThumbsDown size={14} />
124
123
  </button>
125
124
  </MessageActions>
@@ -153,7 +152,7 @@ export const InConversation: Story = {
153
152
  components, render props, and custom hooks for state management.
154
153
  </MessageContent>
155
154
  <MessageActions class="[&>button]:p-1 [&>button]:rounded [&>button]:text-foreground/60 [&>button]:hover:text-foreground [&>button]:transition-colors">
156
- <button><Copy size={14} /></button>
155
+ <button aria-label="Copy message"><Copy size={14} /></button>
157
156
  </MessageActions>
158
157
  </Message>
159
158
 
@@ -171,7 +170,7 @@ export const InConversation: Story = {
171
170
  it easier to share pieces between different builds.
172
171
  </MessageContent>
173
172
  <MessageActions class="[&>button]:p-1 [&>button]:rounded [&>button]:text-foreground/60 [&>button]:hover:text-foreground [&>button]:transition-colors">
174
- <button><Copy size={14} /></button>
173
+ <button aria-label="Copy message"><Copy size={14} /></button>
175
174
  </MessageActions>
176
175
  </Message>
177
176
 
@@ -197,7 +196,7 @@ export const InConversation: Story = {
197
196
  **Custom Hooks**: Extract stateful logic into reusable functions prefixed with \`use\`.`}
198
197
  </MessageContent>
199
198
  <MessageActions class="[&>button]:p-1 [&>button]:rounded [&>button]:text-foreground/60 [&>button]:hover:text-foreground [&>button]:transition-colors">
200
- <button><Copy size={14} /></button>
199
+ <button aria-label="Copy message"><Copy size={14} /></button>
201
200
  </MessageActions>
202
201
  </Message>
203
202
  </div>
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { Message, MessageAvatar, MessageContent, MessageActions } from './message';
3
3
  import { Button } from '../ui/button';
4
4
  import { Copy, ThumbsUp, ThumbsDown, RefreshCw, Pencil } from 'lucide-solid';
5
+ import { componentDescription } from '../stories/docs/element-controls';
5
6
 
6
7
  const meta = {
7
8
  title: 'Components/Message',
@@ -11,14 +12,12 @@ const meta = {
11
12
  layout: 'padded',
12
13
  docs: {
13
14
  controls: { exclude: ['use:eventListener'] },
14
- description: {
15
- component: [
16
- 'A horizontal message row that composes an optional `MessageAvatar`, a `MessageContent` body (plain or markdown), and an optional `MessageActions` toolbar.',
17
- '**When to use:** rendering any chat turn user or assistant. Use a bubble + right alignment for user turns, and an avatar + transparent content for assistant turns.',
18
- '**How to use:** wrap the parts in `<Message>`. Add `MessageAvatar` for the speaker, `MessageContent` for the text (set `markdown` to render markdown), and `MessageActions` for hover actions. Layout (alignment, bubble) is controlled via `class`.',
19
- '**Placement:** inside a `ChatContainer`/scroll region, stacked vertically as the conversation transcript.',
20
- ].join('\n\n'),
21
- },
15
+ description: componentDescription([
16
+ 'A horizontal message row that composes an optional `MessageAvatar`, a `MessageContent` body (plain or markdown), and an optional `MessageActions` toolbar.',
17
+ '**When to use:** rendering any chat turn user or assistant. Use a bubble + right alignment for user turns, and an avatar + transparent content for assistant turns.',
18
+ '**How to use:** wrap the parts in `<Message>`. Add `MessageAvatar` for the speaker, `MessageContent` for the text (set `markdown` to render markdown), and `MessageActions` for hover actions. Layout (alignment, bubble) is controlled via `class`.',
19
+ '**Placement:** inside a `ChatContainer`/scroll region, stacked vertically as the conversation transcript.',
20
+ ]),
22
21
  },
23
22
  },
24
23
  argTypes: {
@@ -132,10 +131,10 @@ export const UserAlignedRight: Story = {
132
131
  Can you explain how SolidJS reactivity differs from React hooks?
133
132
  </MessageContent>
134
133
  <MessageActions class="flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
135
- <Button variant="ghost" size="icon-sm" class="rounded-full">
134
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Edit message">
136
135
  <Pencil class="size-3.5" />
137
136
  </Button>
138
- <Button variant="ghost" size="icon-sm" class="rounded-full">
137
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
139
138
  <Copy class="size-3.5" />
140
139
  </Button>
141
140
  </MessageActions>
@@ -198,16 +197,16 @@ export const WithActions: Story = {
198
197
  Here is a response with hover actions below it.
199
198
  </MessageContent>
200
199
  <MessageActions class="-ml-2.5 flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
201
- <Button variant="ghost" size="icon-sm" class="rounded-full">
200
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
202
201
  <Copy class="size-3.5" />
203
202
  </Button>
204
- <Button variant="ghost" size="icon-sm" class="rounded-full">
203
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Good response">
205
204
  <ThumbsUp class="size-3.5" />
206
205
  </Button>
207
- <Button variant="ghost" size="icon-sm" class="rounded-full">
206
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Bad response">
208
207
  <ThumbsDown class="size-3.5" />
209
208
  </Button>
210
- <Button variant="ghost" size="icon-sm" class="rounded-full">
209
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Regenerate response">
211
210
  <RefreshCw class="size-3.5" />
212
211
  </Button>
213
212
  </MessageActions>
@@ -251,16 +250,16 @@ Key benefits:
251
250
  - Makes large codebases more maintainable`}
252
251
  </MessageContent>
253
252
  <MessageActions class="-ml-2.5 flex gap-0 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
254
- <Button variant="ghost" size="icon-sm" class="rounded-full">
253
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Copy message">
255
254
  <Copy class="size-3.5" />
256
255
  </Button>
257
- <Button variant="ghost" size="icon-sm" class="rounded-full">
256
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Good response">
258
257
  <ThumbsUp class="size-3.5" />
259
258
  </Button>
260
- <Button variant="ghost" size="icon-sm" class="rounded-full">
259
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Bad response">
261
260
  <ThumbsDown class="size-3.5" />
262
261
  </Button>
263
- <Button variant="ghost" size="icon-sm" class="rounded-full">
262
+ <Button variant="ghost" size="icon-sm" class="rounded-full" aria-label="Regenerate response">
264
263
  <RefreshCw class="size-3.5" />
265
264
  </Button>
266
265
  </MessageActions>
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { fn } from 'storybook/test';
3
3
  import { ModelSwitcher } from './model-switcher';
4
+ import { componentDescription } from '../stories/docs/element-controls';
4
5
 
5
6
  const multipleModels = [
6
7
  { id: 'claude-sonnet', name: 'Claude Sonnet', provider: 'Anthropic' },
@@ -17,14 +18,12 @@ const meta = {
17
18
  layout: 'padded',
18
19
  docs: {
19
20
  controls: { exclude: ['use:eventListener'] },
20
- description: {
21
- component: [
22
- 'A compact dropdown that shows the active model and lets the user switch between the available **models** (grouped by optional `provider` label).',
23
- '**When to use:** when a chat surface offers more than one model. It renders nothing when fewer than two models are provided, so it is safe to mount unconditionally.',
24
- '**How to use:** pass the `models` list and the `currentModelId`, then handle `onModelChange` to update your selected-model state.',
25
- '**Placement:** the prompt input action bar, a chat header, or a settings/toolbar row near the composer.',
26
- ].join('\n\n'),
27
- },
21
+ description: componentDescription([
22
+ 'A compact dropdown that shows the active model and lets the user switch between the available **models** (grouped by optional `provider` label).',
23
+ '**When to use:** when a chat surface offers more than one model. It renders nothing when fewer than two models are provided, so it is safe to mount unconditionally.',
24
+ '**How to use:** pass the `models` list and the `currentModelId`, then handle `onModelChange` to update your selected-model state.',
25
+ '**Placement:** the prompt input action bar, a chat header, or a settings/toolbar row near the composer.',
26
+ ]),
28
27
  },
29
28
  },
30
29
  argTypes: {
@@ -3,6 +3,7 @@ import { fn } from 'storybook/test';
3
3
  import { createSignal } from 'solid-js';
4
4
  import { PromptInput, PromptInputTextarea, PromptInputActions } from './prompt-input';
5
5
  import { Button } from '../ui/button';
6
+ import { componentDescription } from '../stories/docs/element-controls';
6
7
 
7
8
  const meta = {
8
9
  title: 'Components/PromptInput',
@@ -12,14 +13,12 @@ const meta = {
12
13
  layout: 'padded',
13
14
  docs: {
14
15
  controls: { exclude: ['use:eventListener'] },
15
- description: {
16
- component: [
17
- 'A composer shell that hosts an auto-resizing `PromptInputTextarea` and a `PromptInputActions` toolbar, with controlled or uncontrolled value, loading, and disabled states.',
18
- '**When to use:** as the message input at the bottom of any chat surface, wherever the user types and submits a prompt.',
19
- '**How to use:** control text via `value` + `onValueChange` (or leave uncontrolled), wire `onSubmit` (also fired on Enter without Shift), and place your send/stop controls inside `PromptInputActions`. Toggle `isLoading` / `disabled` for in-flight and read-only states.',
20
- '**Placement:** pinned at the bottom of the chat column, below the message transcript.',
21
- ].join('\n\n'),
22
- },
16
+ description: componentDescription([
17
+ 'A composer shell that hosts an auto-resizing `PromptInputTextarea` and a `PromptInputActions` toolbar, with controlled or uncontrolled value, loading, and disabled states.',
18
+ '**When to use:** as the message input at the bottom of any chat surface, wherever the user types and submits a prompt.',
19
+ '**How to use:** control text via `value` + `onValueChange` (or leave uncontrolled), wire `onSubmit` (also fired on Enter without Shift), and place your send/stop controls inside `PromptInputActions`. Toggle `isLoading` / `disabled` for in-flight and read-only states.',
20
+ '**Placement:** pinned at the bottom of the chat column, below the message transcript.',
21
+ ]),
23
22
  },
24
23
  },
25
24
  argTypes: {
@@ -199,7 +198,7 @@ export const WithMultipleActions: Story = {
199
198
  <PromptInputTextarea placeholder="Ask anything..." />
200
199
  <PromptInputActions class="justify-between w-full px-2 pb-1">
201
200
  <div class="flex items-center gap-1">
202
- <Button variant="ghost" size="icon-sm">
201
+ <Button variant="ghost" size="icon-sm" aria-label="Attach file">
203
202
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
204
203
  <path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48" />
205
204
  </svg>
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from 'storybook-solidjs-vite';
2
2
  import { fn } from 'storybook/test';
3
3
  import { PromptSuggestion } from './prompt-suggestion';
4
+ import { componentDescription } from '../stories/docs/element-controls';
4
5
 
5
6
  const meta = {
6
7
  title: 'Components/PromptSuggestion',
@@ -9,14 +10,12 @@ const meta = {
9
10
  parameters: {
10
11
  layout: 'padded',
11
12
  docs: {
12
- description: {
13
- component: [
14
- 'A clickable suggestion chip built on `Button` renders as a rounded pill, a full-width list row (`block`), or a search-style row that highlights a matching substring (`highlight`).',
15
- '**When to use:** offer the user ready-made prompts to send empty-state starter questions, follow-up suggestions, or a filtered list of matching prompts as they type.',
16
- '**How to use:** pass the prompt text as children and wire `onClick` to submit it. Use `block` for stacked, left-aligned list rows; pass `highlight` to emphasize a matched substring (forces list-row layout). Override `variant`/`size` to restyle.',
17
- '**Placement:** chat empty states, below the prompt input as follow-ups, or in a suggestion dropdown.',
18
- ].join('\n\n'),
19
- },
13
+ description: componentDescription([
14
+ 'A clickable suggestion chip built on `Button` — renders as a rounded pill, a full-width list row (`block`), or a search-style row that highlights a matching substring (`highlight`).',
15
+ '**When to use:** offer the user ready-made prompts to send empty-state starter questions, follow-up suggestions, or a filtered list of matching prompts as they type.',
16
+ '**How to use:** pass the prompt text as children and wire `onClick` to submit it. Use `block` for stacked, left-aligned list rows; pass `highlight` to emphasize a matched substring (forces list-row layout). Override `variant`/`size` to restyle.',
17
+ '**Placement:** chat empty states, below the prompt input as follow-ups, or in a suggestion dropdown.',
18
+ ]),
20
19
  controls: { exclude: ['use:eventListener'] },
21
20
  },
22
21
  },