@kitnai/chat 0.3.1 → 0.4.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.
Files changed (106) hide show
  1. package/README.md +11 -0
  2. package/dist/custom-elements.json +2494 -0
  3. package/dist/kitn-chat.es.js +52 -39
  4. package/dist/llms/llms-full.txt +667 -0
  5. package/dist/llms/llms.txt +104 -0
  6. package/dist/theme.tokens.css +133 -0
  7. package/frameworks/react/index.tsx +530 -0
  8. package/frameworks/react/runtime.tsx +94 -0
  9. package/llms-full.txt +667 -0
  10. package/llms.txt +104 -0
  11. package/package.json +34 -5
  12. package/src/components/attachments.tsx +4 -2
  13. package/src/components/chain-of-thought.tsx +1 -1
  14. package/src/components/chat-scope-picker.tsx +2 -2
  15. package/src/components/checkpoint.tsx +7 -3
  16. package/src/components/context.tsx +14 -18
  17. package/src/components/conversation-item.tsx +1 -1
  18. package/src/components/conversation-list.tsx +5 -4
  19. package/src/components/message-skills.tsx +1 -1
  20. package/src/components/message.tsx +1 -0
  21. package/src/components/model-switcher.tsx +3 -3
  22. package/src/components/prompt-input.tsx +15 -2
  23. package/src/components/reasoning.tsx +2 -2
  24. package/src/components/scroll-button.tsx +1 -0
  25. package/src/components/slash-command.tsx +17 -8
  26. package/src/components/source.tsx +2 -2
  27. package/src/components/thinking-bar.tsx +2 -2
  28. package/src/components/tool.tsx +17 -6
  29. package/src/components/voice-input.tsx +5 -1
  30. package/src/elements/attachments.tsx +132 -0
  31. package/src/elements/chain-of-thought.tsx +45 -0
  32. package/src/elements/chat-scope-picker.tsx +36 -0
  33. package/src/elements/chat.tsx +51 -7
  34. package/src/elements/checkpoint.tsx +43 -0
  35. package/src/elements/code-block.tsx +42 -0
  36. package/src/elements/compiled.css +1 -1
  37. package/src/elements/context-meter.tsx +71 -0
  38. package/src/elements/conversation-list.tsx +6 -0
  39. package/src/elements/default-input.tsx +22 -1
  40. package/src/elements/define.tsx +97 -11
  41. package/src/elements/element-types.d.ts +404 -0
  42. package/src/elements/empty.tsx +29 -0
  43. package/src/elements/feedback-bar.tsx +33 -0
  44. package/src/elements/file-upload.tsx +44 -0
  45. package/src/elements/image.tsx +32 -0
  46. package/src/elements/kitn-attachments.stories.tsx +181 -0
  47. package/src/elements/kitn-chain-of-thought.stories.tsx +75 -0
  48. package/src/elements/kitn-chat-scope-picker.stories.tsx +72 -0
  49. package/src/elements/kitn-checkpoint.stories.tsx +71 -0
  50. package/src/elements/kitn-code-block.stories.tsx +82 -0
  51. package/src/elements/kitn-context-meter.stories.tsx +85 -0
  52. package/src/elements/kitn-empty.stories.tsx +110 -0
  53. package/src/elements/kitn-feedback-bar.stories.tsx +73 -0
  54. package/src/elements/kitn-file-upload.stories.tsx +81 -0
  55. package/src/elements/kitn-image.stories.tsx +70 -0
  56. package/src/elements/kitn-loader.stories.tsx +87 -0
  57. package/src/elements/kitn-markdown.stories.tsx +75 -0
  58. package/src/elements/kitn-message-skills.stories.tsx +74 -0
  59. package/src/elements/kitn-message.stories.tsx +105 -0
  60. package/src/elements/kitn-model-switcher.stories.tsx +80 -0
  61. package/src/elements/kitn-prompt-input.stories.tsx +74 -16
  62. package/src/elements/kitn-prompt-suggestions.stories.tsx +157 -0
  63. package/src/elements/kitn-reasoning.stories.tsx +76 -0
  64. package/src/elements/kitn-response-stream.stories.tsx +79 -0
  65. package/src/elements/kitn-source-list.stories.tsx +77 -0
  66. package/src/elements/kitn-source.stories.tsx +87 -0
  67. package/src/elements/kitn-text-shimmer.stories.tsx +63 -0
  68. package/src/elements/kitn-thinking-bar.stories.tsx +72 -0
  69. package/src/elements/kitn-tool.stories.tsx +88 -0
  70. package/src/elements/kitn-voice-input.stories.tsx +87 -0
  71. package/src/elements/loader.tsx +25 -0
  72. package/src/elements/markdown.tsx +38 -0
  73. package/src/elements/message-skills.tsx +22 -0
  74. package/src/elements/message.tsx +125 -0
  75. package/src/elements/model-switcher.tsx +35 -0
  76. package/src/elements/prompt-input.tsx +83 -7
  77. package/src/elements/prompt-suggestions.tsx +58 -0
  78. package/src/elements/reasoning.tsx +50 -0
  79. package/src/elements/register.ts +31 -0
  80. package/src/elements/response-stream.tsx +40 -0
  81. package/src/elements/source.tsx +67 -0
  82. package/src/elements/text-shimmer.tsx +28 -0
  83. package/src/elements/thinking-bar.tsx +34 -0
  84. package/src/elements/tool.tsx +23 -0
  85. package/src/elements/voice-input.tsx +41 -0
  86. package/src/index.ts +0 -1
  87. package/src/primitives/chat-config.tsx +2 -2
  88. package/src/stories/docs/Accessibility.mdx +119 -0
  89. package/src/stories/docs/ForAIAgents.mdx +93 -0
  90. package/src/stories/docs/GettingStarted.mdx +2 -2
  91. package/src/stories/docs/Installation.mdx +2 -2
  92. package/src/stories/docs/Integrations.mdx +415 -15
  93. package/src/stories/docs/Introduction.mdx +5 -5
  94. package/src/stories/docs/Theming.mdx +1 -1
  95. package/src/stories/typography.stories.tsx +78 -0
  96. package/src/ui/button.tsx +1 -1
  97. package/src/ui/collapsible.tsx +119 -8
  98. package/src/ui/dropdown.tsx +177 -12
  99. package/src/ui/hover-card.tsx +147 -26
  100. package/src/ui/overlay.tsx +151 -0
  101. package/src/ui/textarea.tsx +1 -1
  102. package/src/ui/tooltip.stories.tsx +1 -1
  103. package/src/ui/tooltip.tsx +59 -13
  104. package/src/utils/cn.ts +19 -1
  105. package/theme.css +72 -43
  106. package/src/ui/dialog.tsx +0 -21
@@ -0,0 +1,530 @@
1
+ // AUTO-GENERATED by scripts/gen-element-api.mjs — do not edit by hand.
2
+ // Typed React wrappers for every kitn custom element. Usage:
3
+ // import { KitnMessage } from '@kitnai/chat/react';
4
+ // <KitnMessage message={msg} onMessageaction={(e) => …} />
5
+ import { createKitnComponent, type KitnBaseProps } from './runtime';
6
+
7
+
8
+ export interface KitnAttachmentsProps extends KitnBaseProps {
9
+ /** The attachments to render. Set as a JS property (array). */
10
+ items: { id: string; type: "file" | "source-document"; filename?: undefined | string; mediaType?: undefined | string; url?: undefined | string; title?: undefined | string }[];
11
+ /** Layout: `grid` = visual tiles, `inline` = icon + label chips, `list` = rows. */
12
+ variant?: "grid" | "inline" | "list";
13
+ /** Wrap each item in a hover card that previews its details. */
14
+ hoverCard?: boolean;
15
+ /** Show a remove button per item; clicking it fires a `remove` event. */
16
+ removable?: boolean;
17
+ /** Also show the media type beneath the filename (non-grid variants). */
18
+ showMediaType?: boolean;
19
+ /** Text shown when `items` is empty. */
20
+ emptyText?: string;
21
+ /** A remove button was clicked. */
22
+ onRemove?: (event: CustomEvent<{ id: string }>) => void;
23
+ }
24
+
25
+ export const KitnAttachments = createKitnComponent<KitnAttachmentsProps>(
26
+ 'kitn-attachments',
27
+ ["theme","items","variant","hoverCard","removable","showMediaType","emptyText"],
28
+ { onRemove: 'remove' },
29
+ );
30
+
31
+ export interface KitnChainOfThoughtProps extends KitnBaseProps {
32
+ /** The reasoning steps. Set as a JS property. Compound sub-parts collapse to this one data model (Route 1). */
33
+ steps: { label: string; content?: undefined | string }[];
34
+ }
35
+
36
+ export const KitnChainOfThought = createKitnComponent<KitnChainOfThoughtProps>(
37
+ 'kitn-chain-of-thought',
38
+ ["theme","steps"],
39
+ { },
40
+ );
41
+
42
+ export interface KitnChatProps extends KitnBaseProps {
43
+ /** The full message thread to render, newest last. Each entry carries its role, content, and optional reasoning/tools/attachments/actions. Set as a JS property (`el.messages = [...]`). */
44
+ messages: { id: string; role: "user" | "assistant"; content: string; reasoning?: undefined | { text: string; label?: undefined | string }; tools?: undefined | { type: string; state: "input-streaming" | "input-available" | "output-available" | "output-error"; input?: undefined | Record<string, unknown>; output?: undefined | Record<string, unknown>; toolCallId?: undefined | string; errorText?: undefined | string }[]; attachments?: undefined | { id: string; type: "file" | "source-document"; filename?: undefined | string; mediaType?: undefined | string; url?: undefined | string; title?: undefined | string }[]; actions?: undefined | ("copy" | "like" | "dislike" | "regenerate" | "edit")[] }[];
45
+ /** Controlled value of the input. When set, the host owns the input text and must update it on `valuechange`; leave unset for uncontrolled behavior. */
46
+ value?: string;
47
+ /** Placeholder text shown in the empty input. */
48
+ placeholder?: string;
49
+ /** When true, shows the loading/streaming state and disables submit (use while awaiting the assistant's reply). */
50
+ loading?: boolean;
51
+ /** Starter prompts shown above the input when the thread is empty. Clicking one follows `suggestionMode`. Set as a JS property. */
52
+ suggestions?: string[];
53
+ /** What clicking a suggestion does: `'submit'` (default) sends it immediately as if typed and submitted; `'fill'` just places it in the input. */
54
+ suggestionMode?: "submit" | "fill";
55
+ /** Body/prose font scale for rendered markdown (`'xs' | 'sm' | 'base' | 'lg'`). Defaults to `'sm'`. */
56
+ proseSize?: "xs" | "sm" | "base" | "lg";
57
+ /** Shiki theme name for syntax-highlighted code blocks (e.g. `'github-dark-dimmed'`). */
58
+ codeTheme?: string;
59
+ /** Enable Shiki syntax highlighting in code blocks. Turn off to render plain `<pre>` blocks (lighter, no highlighter load). Default true. */
60
+ codeHighlight?: boolean;
61
+ /** Optional header title shown on the left of the header. */
62
+ chatTitle?: string;
63
+ /** Optional model list. When set (>1 model) a ModelSwitcher is shown in the header and a `modelchange` event fires on selection. */
64
+ models?: { id: string; name: string; provider?: string }[];
65
+ /** The currently selected model id (pairs with `models`). */
66
+ currentModel?: string;
67
+ /** Optional context-window token usage. When set, a Context token meter is shown in the header. */
68
+ context?: { usedTokens: number; maxTokens: number; inputTokens?: number; outputTokens?: number; estimatedCost?: number };
69
+ /** Show the scroll-to-bottom button inside the scroll area. Default true. */
70
+ scrollButton?: boolean;
71
+ /** Show a Search (Globe) button in the input toolbar; fires a `search` event. */
72
+ search?: boolean;
73
+ /** Show a Voice (Mic) button in the input toolbar; fires a `voice` event. */
74
+ voice?: boolean;
75
+ /** Slash commands — when set, typing `/` in the input opens the command palette and fires `slashselect`. Set as a JS property. */
76
+ slashCommands?: { id: string; label: string; description?: string; category?: string }[];
77
+ /** Command ids to highlight as active in the palette. */
78
+ slashActiveIds?: string[];
79
+ /** Single-line palette rows. */
80
+ slashCompact?: boolean;
81
+ onMessageaction?: (event: CustomEvent<unknown>) => void;
82
+ onModelchange?: (event: CustomEvent<unknown>) => void;
83
+ onSearch?: (event: CustomEvent<unknown>) => void;
84
+ onSlashselect?: (event: CustomEvent<unknown>) => void;
85
+ onSubmit?: (event: CustomEvent<unknown>) => void;
86
+ onSuggestionclick?: (event: CustomEvent<unknown>) => void;
87
+ onValuechange?: (event: CustomEvent<unknown>) => void;
88
+ onVoice?: (event: CustomEvent<unknown>) => void;
89
+ }
90
+
91
+ export const KitnChat = createKitnComponent<KitnChatProps>(
92
+ 'kitn-chat',
93
+ ["theme","messages","value","placeholder","loading","suggestions","suggestionMode","proseSize","codeTheme","codeHighlight","chatTitle","models","currentModel","context","scrollButton","search","voice","slashCommands","slashActiveIds","slashCompact"],
94
+ { onMessageaction: 'messageaction', onModelchange: 'modelchange', onSearch: 'search', onSlashselect: 'slashselect', onSubmit: 'submit', onSuggestionclick: 'suggestionclick', onValuechange: 'valuechange', onVoice: 'voice' },
95
+ );
96
+
97
+ export interface KitnChatScopePickerProps extends KitnBaseProps {
98
+ /** Authors to offer as scope filters. Set as a JS property. */
99
+ availableAuthors: string[];
100
+ /** Tags to offer as scope filters. Set as a JS property. */
101
+ availableTags: string[];
102
+ /** The label shown on the trigger for the active scope. */
103
+ currentLabel?: string;
104
+ /** A scope was chosen (`undefined` filters = "All Content"). */
105
+ onScopechange?: (event: CustomEvent<{ filters: undefined | { tags?: undefined | string[]; authors?: undefined | string[]; contentType?: undefined | "transcript" | "markdown"; dateRange?: undefined | { from: string; to: string } } }>) => void;
106
+ }
107
+
108
+ export const KitnChatScopePicker = createKitnComponent<KitnChatScopePickerProps>(
109
+ 'kitn-chat-scope-picker',
110
+ ["theme","availableAuthors","availableTags","currentLabel"],
111
+ { onScopechange: 'scopechange' },
112
+ );
113
+
114
+ export interface KitnCheckpointProps extends KitnBaseProps {
115
+ /** Optional text beside the icon. */
116
+ label?: string;
117
+ /** Tooltip on hover. */
118
+ tooltip?: string;
119
+ /** Visual button style. */
120
+ variant?: "ghost" | "default" | "outline";
121
+ /** Button size (use an `icon*` size for an icon-only checkpoint). */
122
+ size?: "sm" | "lg" | "md" | "icon" | "icon-sm";
123
+ /** The checkpoint was clicked. */
124
+ onSelect?: (event: CustomEvent) => void;
125
+ }
126
+
127
+ export const KitnCheckpoint = createKitnComponent<KitnCheckpointProps>(
128
+ 'kitn-checkpoint',
129
+ ["theme","label","tooltip","variant","size"],
130
+ { onSelect: 'select' },
131
+ );
132
+
133
+ export interface KitnCodeBlockProps extends KitnBaseProps {
134
+ /** The source code to render. */
135
+ code: string;
136
+ /** Language grammar (e.g. `js`, `python`). Defaults to `tsx`. */
137
+ language?: string;
138
+ /** Shiki theme name. */
139
+ codeTheme?: string;
140
+ /** Disable syntax highlighting (renders plain text, no Shiki). */
141
+ codeHighlight?: boolean;
142
+ /** Code text sizing. */
143
+ proseSize?: "xs" | "sm" | "base" | "lg";
144
+ }
145
+
146
+ export const KitnCodeBlock = createKitnComponent<KitnCodeBlockProps>(
147
+ 'kitn-code-block',
148
+ ["theme","code","language","codeTheme","codeHighlight","proseSize"],
149
+ { },
150
+ );
151
+
152
+ export interface KitnContextMeterProps extends KitnBaseProps {
153
+ /** Token-usage data. Set as a JS property. */
154
+ context?: { usedTokens: number; maxTokens: number; inputTokens?: number; outputTokens?: number; reasoningTokens?: number; cacheTokens?: number; estimatedCost?: number };
155
+ }
156
+
157
+ export const KitnContextMeter = createKitnComponent<KitnContextMeterProps>(
158
+ 'kitn-context-meter',
159
+ ["theme","context"],
160
+ { },
161
+ );
162
+
163
+ export interface KitnConversationListProps extends KitnBaseProps {
164
+ /** Pre-bucketed conversation groups (e.g. "Today", "Yesterday"), each with its own conversations. Use this when you want to control the grouping/headers yourself; otherwise pass a flat `conversations` array. Set as a JS property. */
165
+ groups: { id: string; userId?: undefined | string; teamId?: undefined | string; name: string; sortOrder: number; createdAt: string }[];
166
+ /** A flat list of conversation summaries; the component buckets them by recency for you. Ignored when `groups` is provided. Set as a JS property. */
167
+ conversations: { id: string; title: string; groupId?: undefined | string; scope: { type: "document" | "collection"; documentId?: undefined | string; filters?: undefined | { tags?: undefined | string[]; authors?: undefined | string[]; contentType?: undefined | "transcript" | "markdown"; dateRange?: undefined | { from: string; to: string } } }; messageCount: number; lastMessageAt: string; updatedAt: string }[];
168
+ /** The id of the currently-open conversation, highlighted in the list. */
169
+ activeId?: string;
170
+ onNewchat?: (event: CustomEvent<unknown>) => void;
171
+ onSelect?: (event: CustomEvent<unknown>) => void;
172
+ onTogglesidebar?: (event: CustomEvent<unknown>) => void;
173
+ }
174
+
175
+ export const KitnConversationList = createKitnComponent<KitnConversationListProps>(
176
+ 'kitn-conversation-list',
177
+ ["theme","groups","conversations","activeId"],
178
+ { onNewchat: 'newchat', onSelect: 'select', onTogglesidebar: 'togglesidebar' },
179
+ );
180
+
181
+ export interface KitnEmptyProps extends KitnBaseProps {
182
+ /** Title text. Attribute: `empty-title` (`title` is a global HTML attribute). */
183
+ emptyTitle?: string;
184
+ /** Description text. */
185
+ description?: string;
186
+ }
187
+
188
+ export const KitnEmpty = createKitnComponent<KitnEmptyProps>(
189
+ 'kitn-empty',
190
+ ["theme","emptyTitle","description"],
191
+ { },
192
+ );
193
+
194
+ export interface KitnFeedbackBarProps extends KitnBaseProps {
195
+ /** The banner label (e.g. "Was this helpful?"). Attribute: `bar-title` (`title` is avoided — it's a global HTML attribute). */
196
+ barTitle?: string;
197
+ /** The user dismissed the banner. */
198
+ onClose?: (event: CustomEvent) => void;
199
+ /** The user clicked thumbs-up. */
200
+ onHelpful?: (event: CustomEvent) => void;
201
+ /** The user clicked thumbs-down. */
202
+ onNothelpful?: (event: CustomEvent) => void;
203
+ }
204
+
205
+ export const KitnFeedbackBar = createKitnComponent<KitnFeedbackBarProps>(
206
+ 'kitn-feedback-bar',
207
+ ["theme","barTitle"],
208
+ { onClose: 'close', onHelpful: 'helpful', onNothelpful: 'nothelpful' },
209
+ );
210
+
211
+ export interface KitnFileUploadProps extends KitnBaseProps {
212
+ /** Allow selecting multiple files (default true). */
213
+ multiple?: boolean;
214
+ /** `accept` attribute for the file picker (e.g. `image/*`). */
215
+ accept?: string;
216
+ /** Disable the dropzone — no clicking, no drag-and-drop. */
217
+ disabled?: boolean;
218
+ /** Default dropzone label (overridable via the default slot). */
219
+ label?: string;
220
+ /** Files were picked or dropped. */
221
+ onFilesadded?: (event: CustomEvent<{ files: File[] }>) => void;
222
+ }
223
+
224
+ export const KitnFileUpload = createKitnComponent<KitnFileUploadProps>(
225
+ 'kitn-file-upload',
226
+ ["theme","multiple","accept","disabled","label"],
227
+ { onFilesadded: 'filesadded' },
228
+ );
229
+
230
+ export interface KitnImageProps extends KitnBaseProps {
231
+ /** Base64-encoded image data (pair with `media-type`). */
232
+ base64?: string;
233
+ /** Raw image bytes (set as a JS property). */
234
+ bytes?: Uint8Array;
235
+ /** Alt text. */
236
+ alt?: string;
237
+ /** MIME type (default `image/png`). */
238
+ mediaType?: string;
239
+ }
240
+
241
+ export const KitnImage = createKitnComponent<KitnImageProps>(
242
+ 'kitn-image',
243
+ ["theme","base64","bytes","alt","mediaType"],
244
+ { },
245
+ );
246
+
247
+ export interface KitnLoaderProps extends KitnBaseProps {
248
+ /** The animation style: `'circular' | 'classic' | 'pulse' | 'pulse-dot' | 'dots' | 'typing' | 'wave' | 'bars' | 'terminal' | 'text-blink' | 'text-shimmer' | 'loading-dots'`. Defaults to `'circular'`. */
249
+ variant?: "circular" | "classic" | "pulse" | "pulse-dot" | "dots" | "typing" | "wave" | "bars" | "terminal" | "text-blink" | "text-shimmer" | "loading-dots";
250
+ /** Loader size: `'sm' | 'md' | 'lg'`. Defaults to `'md'`. */
251
+ size?: "sm" | "lg" | "md";
252
+ /** Label for the text-based variants. */
253
+ text?: string;
254
+ }
255
+
256
+ export const KitnLoader = createKitnComponent<KitnLoaderProps>(
257
+ 'kitn-loader',
258
+ ["theme","variant","size","text"],
259
+ { },
260
+ );
261
+
262
+ export interface KitnMarkdownProps extends KitnBaseProps {
263
+ /** The markdown source to render. */
264
+ content: string;
265
+ /** Text/markdown sizing. */
266
+ proseSize?: "xs" | "sm" | "base" | "lg";
267
+ /** Shiki theme for fenced code blocks. */
268
+ codeTheme?: string;
269
+ /** Disable syntax highlighting (no Shiki loads). */
270
+ codeHighlight?: boolean;
271
+ }
272
+
273
+ export const KitnMarkdown = createKitnComponent<KitnMarkdownProps>(
274
+ 'kitn-markdown',
275
+ ["theme","content","proseSize","codeTheme","codeHighlight"],
276
+ { },
277
+ );
278
+
279
+ export interface KitnMessageProps extends KitnBaseProps {
280
+ /** The full message object. Set as a JS property. */
281
+ message?: { id: string; role: "user" | "assistant"; content: string; reasoning?: { text: string; label?: string }; tools?: { type: string; state: "input-streaming" | "input-available" | "output-available" | "output-error"; input?: Record<string, unknown>; output?: Record<string, unknown>; toolCallId?: string; errorText?: string }[]; attachments?: { id: string; type: "file" | "source-document"; filename?: string; mediaType?: string; url?: string; title?: string }[]; actions?: ("copy" | "like" | "dislike" | "regenerate" | "edit")[] };
282
+ /** Convenience for simple cases when not passing a `message` object. */
283
+ role?: "user" | "assistant";
284
+ /** Convenience content (used when `message` is not set). */
285
+ content?: string;
286
+ /** Force markdown on/off. Defaults to on for assistant, off for user. */
287
+ markdown?: boolean;
288
+ /** Text/markdown sizing for the message body. */
289
+ proseSize?: "xs" | "sm" | "base" | "lg";
290
+ /** Shiki theme name used for fenced code blocks in the content. */
291
+ codeTheme?: string;
292
+ /** Disable syntax highlighting for code blocks (no Shiki loads). */
293
+ codeHighlight?: boolean;
294
+ /** An action button was clicked. */
295
+ onMessageaction?: (event: CustomEvent<{ messageId: string; action: "copy" | "like" | "dislike" | "regenerate" | "edit" }>) => void;
296
+ }
297
+
298
+ export const KitnMessage = createKitnComponent<KitnMessageProps>(
299
+ 'kitn-message',
300
+ ["theme","message","role","content","markdown","proseSize","codeTheme","codeHighlight"],
301
+ { onMessageaction: 'messageaction' },
302
+ );
303
+
304
+ export interface KitnMessageSkillsProps extends KitnBaseProps {
305
+ /** The active skills to badge. Set as a JS property. */
306
+ skills: { id: string; name: string }[];
307
+ }
308
+
309
+ export const KitnMessageSkills = createKitnComponent<KitnMessageSkillsProps>(
310
+ 'kitn-message-skills',
311
+ ["theme","skills"],
312
+ { },
313
+ );
314
+
315
+ export interface KitnModelSwitcherProps extends KitnBaseProps {
316
+ /** The selectable models. Set as a JS property (array). */
317
+ models: { id: string; name: string; provider?: undefined | string }[];
318
+ /** The currently-selected model id. Defaults to the first model. */
319
+ currentModel?: string;
320
+ /** A model was selected. */
321
+ onModelchange?: (event: CustomEvent<{ modelId: string }>) => void;
322
+ }
323
+
324
+ export const KitnModelSwitcher = createKitnComponent<KitnModelSwitcherProps>(
325
+ 'kitn-model-switcher',
326
+ ["theme","models","currentModel"],
327
+ { onModelchange: 'modelchange' },
328
+ );
329
+
330
+ export interface KitnPromptInputProps extends KitnBaseProps {
331
+ /** Controlled value of the input. When set, the host owns the text and must update it on `valuechange`; leave unset for uncontrolled behavior. */
332
+ value?: string;
333
+ /** Placeholder text shown in the empty input. */
334
+ placeholder?: string;
335
+ /** Disable the input and submit button entirely (non-interactive). */
336
+ disabled?: boolean;
337
+ /** Show the loading/streaming state and block submit (use while awaiting a reply). */
338
+ loading?: boolean;
339
+ /** Starter prompts shown above the input. Clicking one follows `suggestionMode`. Set as a JS property. */
340
+ suggestions?: string[];
341
+ /** What clicking a suggestion does: `'submit'` (default) sends it immediately as if typed and submitted; `'fill'` just places it in the input. */
342
+ suggestionMode?: "submit" | "fill";
343
+ /** Slash commands — when set, typing `/` opens the command palette. Set as a JS property. */
344
+ slashCommands?: { id: string; label: string; description?: string; category?: string }[];
345
+ /** Command ids to highlight as active. */
346
+ slashActiveIds?: string[];
347
+ /** Single-line palette rows. */
348
+ slashCompact?: boolean;
349
+ /** Show a Search (Globe) button in the left toolbar; clicking it fires a `search` event. */
350
+ search?: boolean;
351
+ /** Show a Voice (Mic) button in the left toolbar; clicking it fires a `voice` event. */
352
+ voice?: boolean;
353
+ /** Attachments to seed the input with (so a consumer can pre-populate staged files without an upload). Set as a JS property; the element then manages its own attachment state from there (add via the paperclip, remove per chip). */
354
+ attachments?: { id: string; type: "file" | "source-document"; filename?: string; mediaType?: string; url?: string; title?: string }[];
355
+ /** The Search (Globe) toolbar button was clicked. */
356
+ onSearch?: (event: CustomEvent<undefined>) => void;
357
+ /** A slash command was chosen from the palette. */
358
+ onSlashselect?: (event: CustomEvent<{ command: { id: string; label: string; description?: undefined | string; category?: undefined | string } }>) => void;
359
+ /** The user submitted the prompt (Enter or send button) with its attachments. */
360
+ onSubmit?: (event: CustomEvent<{ value: string; attachments: { id: string; type: "file" | "source-document"; filename?: undefined | string; mediaType?: undefined | string; url?: undefined | string; title?: undefined | string }[] }>) => void;
361
+ /** A suggestion was clicked while `suggestion-mode="fill"`. */
362
+ onSuggestionclick?: (event: CustomEvent<{ value: string }>) => void;
363
+ /** The input text changed (fires on every keystroke). */
364
+ onValuechange?: (event: CustomEvent<{ value: string }>) => void;
365
+ /** The Voice (Mic) toolbar button was clicked. */
366
+ onVoice?: (event: CustomEvent<undefined>) => void;
367
+ }
368
+
369
+ export const KitnPromptInput = createKitnComponent<KitnPromptInputProps>(
370
+ 'kitn-prompt-input',
371
+ ["theme","value","placeholder","disabled","loading","suggestions","suggestionMode","slashCommands","slashActiveIds","slashCompact","search","voice","attachments"],
372
+ { onSearch: 'search', onSlashselect: 'slashselect', onSubmit: 'submit', onSuggestionclick: 'suggestionclick', onValuechange: 'valuechange', onVoice: 'voice' },
373
+ );
374
+
375
+ export interface KitnPromptSuggestionsProps extends KitnBaseProps {
376
+ /** The suggestions. Strings, or `{ label, value }` when the displayed text and the emitted value differ. Set as a JS property. */
377
+ suggestions: (string | { label: string; value?: undefined | string })[];
378
+ /** Chip style: `'outline'` (default), `'ghost'`, or `'default'` (filled). */
379
+ variant?: "ghost" | "default" | "outline";
380
+ /** Size preset for each chip. Defaults to the pill default (`'lg'`); pass `'sm'` for smaller pills (or `'md'`). */
381
+ size?: "sm" | "lg" | "md" | "icon" | "icon-sm";
382
+ /** Full-width left-aligned rows instead of pills. */
383
+ block?: boolean;
384
+ /** Substring to highlight within each suggestion. */
385
+ highlight?: string;
386
+ /** A suggestion was clicked. */
387
+ onSelect?: (event: CustomEvent<{ value: string }>) => void;
388
+ }
389
+
390
+ export const KitnPromptSuggestions = createKitnComponent<KitnPromptSuggestionsProps>(
391
+ 'kitn-prompt-suggestions',
392
+ ["theme","suggestions","variant","size","block","highlight"],
393
+ { onSelect: 'select' },
394
+ );
395
+
396
+ export interface KitnReasoningProps extends KitnBaseProps {
397
+ /** The reasoning text to display. */
398
+ text: string;
399
+ /** Trigger label. */
400
+ label?: string;
401
+ /** Controlled open state — set as a property (`el.open = true`). Omit for uncontrolled (the trigger toggles it). */
402
+ open?: boolean;
403
+ /** While true, auto-expands (and re-collapses when it flips false). */
404
+ streaming?: boolean;
405
+ /** Render `text` as markdown. */
406
+ markdown?: boolean;
407
+ /** Open state changed (via the trigger or streaming auto-open). */
408
+ onOpenchange?: (event: CustomEvent<{ open: boolean }>) => void;
409
+ }
410
+
411
+ export const KitnReasoning = createKitnComponent<KitnReasoningProps>(
412
+ 'kitn-reasoning',
413
+ ["theme","text","label","open","streaming","markdown"],
414
+ { onOpenchange: 'openchange' },
415
+ );
416
+
417
+ export interface KitnResponseStreamProps extends KitnBaseProps {
418
+ /** Text to stream. A string, or an `AsyncIterable<string>` (set as a JS property — async iterables can't be HTML attributes). */
419
+ text?: string | AsyncIterable<string>;
420
+ /** Reveal animation. */
421
+ mode?: "typewriter" | "fade";
422
+ /** Characters/segments per tick. */
423
+ speed?: number;
424
+ /** Element tag to render as. */
425
+ as?: string;
426
+ /** Streaming finished. */
427
+ onComplete?: (event: CustomEvent) => void;
428
+ }
429
+
430
+ export const KitnResponseStream = createKitnComponent<KitnResponseStreamProps>(
431
+ 'kitn-response-stream',
432
+ ["theme","text","mode","speed","as"],
433
+ { onComplete: 'complete' },
434
+ );
435
+
436
+ export interface KitnSourceProps extends KitnBaseProps {
437
+ /** The URL this citation links to (the domain also seeds the default label/favicon). */
438
+ href?: string;
439
+ /** Trigger label (defaults to the domain). */
440
+ label?: string;
441
+ /** Hover-card headline. Attribute: `headline` (`title` is avoided — it's a global HTML attribute that reflects in a CE constructor and breaks it). */
442
+ headline?: string;
443
+ /** Hover-card body text describing the source. */
444
+ description?: string;
445
+ /** Show the source's favicon next to the trigger label. */
446
+ showFavicon?: boolean;
447
+ }
448
+
449
+ export const KitnSource = createKitnComponent<KitnSourceProps>(
450
+ 'kitn-source',
451
+ ["theme","href","label","headline","description","showFavicon"],
452
+ { },
453
+ );
454
+
455
+ export interface KitnSourceListProps extends KitnBaseProps {
456
+ /** The sources to render. Set as a JS property. */
457
+ sources: { href: string; title?: undefined | string; description?: undefined | string; label?: undefined | string; showFavicon?: undefined | boolean }[];
458
+ /** Show favicons on all items (per-item `showFavicon` overrides). */
459
+ showFavicon?: boolean;
460
+ }
461
+
462
+ export const KitnSourceList = createKitnComponent<KitnSourceListProps>(
463
+ 'kitn-source-list',
464
+ ["theme","sources","showFavicon"],
465
+ { },
466
+ );
467
+
468
+ export interface KitnTextShimmerProps extends KitnBaseProps {
469
+ /** The text to shimmer. */
470
+ text?: string;
471
+ /** Element tag to render as (default `span`). */
472
+ as?: string;
473
+ /** Animation duration in seconds. */
474
+ duration?: number;
475
+ /** Gradient spread (5–45). */
476
+ spread?: number;
477
+ }
478
+
479
+ export const KitnTextShimmer = createKitnComponent<KitnTextShimmerProps>(
480
+ 'kitn-text-shimmer',
481
+ ["theme","text","as","duration","spread"],
482
+ { },
483
+ );
484
+
485
+ export interface KitnThinkingBarProps extends KitnBaseProps {
486
+ /** The shimmering label, e.g. "Thinking…". */
487
+ text?: string;
488
+ /** When true, show a "stop" affordance that fires a `stop` event. */
489
+ stoppable?: boolean;
490
+ /** Label for the stop affordance. */
491
+ stopLabel?: string;
492
+ /** The "stop / answer now" affordance was clicked. */
493
+ onStop?: (event: CustomEvent) => void;
494
+ }
495
+
496
+ export const KitnThinkingBar = createKitnComponent<KitnThinkingBarProps>(
497
+ 'kitn-thinking-bar',
498
+ ["theme","text","stoppable","stopLabel"],
499
+ { onStop: 'stop' },
500
+ );
501
+
502
+ export interface KitnToolProps extends KitnBaseProps {
503
+ /** The tool-call to display. Set as a JS property. */
504
+ tool?: { type: string; state: "input-streaming" | "input-available" | "output-available" | "output-error"; input?: Record<string, unknown>; output?: Record<string, unknown>; toolCallId?: string; errorText?: string };
505
+ /** Start expanded. */
506
+ open?: boolean;
507
+ }
508
+
509
+ export const KitnTool = createKitnComponent<KitnToolProps>(
510
+ 'kitn-tool',
511
+ ["theme","tool","open"],
512
+ { },
513
+ );
514
+
515
+ export interface KitnVoiceInputProps extends KitnBaseProps {
516
+ /** Transcriber the host supplies — records audio, returns the text. This is a **function-valued property** (`el.transcribe = async blob => '...'`) because a value-returning callback can't be modelled as a fire-and-forget event. */
517
+ transcribe?: (audio: Blob) => Promise<string>;
518
+ /** Disable the mic button (non-interactive). */
519
+ disabled?: boolean;
520
+ /** Raw audio captured (before transcription) — for hosts that prefer to handle transcription themselves instead of via the `transcribe` property. */
521
+ onAudiocaptured?: (event: CustomEvent<{ blob: Blob }>) => void;
522
+ /** Transcription completed (the `transcribe` property resolved). */
523
+ onTranscription?: (event: CustomEvent<{ text: string }>) => void;
524
+ }
525
+
526
+ export const KitnVoiceInput = createKitnComponent<KitnVoiceInputProps>(
527
+ 'kitn-voice-input',
528
+ ["theme","transcribe","disabled"],
529
+ { onAudiocaptured: 'audiocaptured', onTranscription: 'transcription' },
530
+ );
@@ -0,0 +1,94 @@
1
+ // Runtime for the generated React wrappers (react/index.tsx). Renders the custom
2
+ // element and bridges the React world to it: rich props are assigned as DOM
3
+ // *properties* (via a ref, so arrays/objects pass through unstringified), and
4
+ // `on<Event>` handlers are wired as `addEventListener` for the element's
5
+ // CustomEvents. Layout props (className/style/id) pass straight through.
6
+ import {
7
+ createElement,
8
+ forwardRef,
9
+ useImperativeHandle,
10
+ useLayoutEffect,
11
+ useRef,
12
+ type CSSProperties,
13
+ type ForwardRefExoticComponent,
14
+ type PropsWithoutRef,
15
+ type ReactNode,
16
+ type RefAttributes,
17
+ } from 'react';
18
+
19
+ export interface KitnBaseProps {
20
+ /** Color mode (`auto` follows prefers-color-scheme). */
21
+ theme?: 'light' | 'dark' | 'auto';
22
+ className?: string;
23
+ style?: CSSProperties;
24
+ id?: string;
25
+ /** Light-DOM children passed through to the element (slots). */
26
+ children?: ReactNode;
27
+ }
28
+
29
+ export function createKitnComponent<P extends KitnBaseProps>(
30
+ tagName: string,
31
+ /** DOM-property names to assign from props (incl. `theme`). */
32
+ propNames: readonly string[],
33
+ /** Map of React handler prop → DOM event name, e.g. `{ onMessageaction: 'messageaction' }`. */
34
+ eventMap: Record<string, string>,
35
+ ): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<HTMLElement>> {
36
+ const eventEntries = Object.entries(eventMap);
37
+
38
+ const Component = forwardRef<HTMLElement, P>((props, ref) => {
39
+ const elRef = useRef<HTMLElement | null>(null);
40
+ useImperativeHandle(ref, () => elRef.current as HTMLElement, []);
41
+ const p = props as Record<string, unknown>;
42
+
43
+ // Hold the latest handlers in a ref so the registered listeners always call
44
+ // the current handler (no stale closures) without re-binding on every render.
45
+ const handlersRef = useRef<Record<string, unknown>>({});
46
+ for (const reactName of Object.keys(eventMap)) handlersRef.current[reactName] = p[reactName];
47
+
48
+ // Assign rich props as DOM properties every render (idempotent). Arrays and
49
+ // objects pass through unstringified; booleans become real boolean
50
+ // properties so the element's `flag()` reads them. Updated props re-assign
51
+ // because this effect runs after every render.
52
+ useLayoutEffect(() => {
53
+ const el = elRef.current;
54
+ if (!el) return;
55
+ for (const name of propNames) {
56
+ if (name in p && p[name] !== undefined) (el as unknown as Record<string, unknown>)[name] = p[name];
57
+ }
58
+ });
59
+
60
+ // Wire CustomEvent listeners ONCE per element. Each stable listener reads the
61
+ // latest handler from handlersRef, so changing a handler's identity across
62
+ // renders takes effect without add/remove churn, and listeners are removed on
63
+ // unmount (no leaks).
64
+ useLayoutEffect(() => {
65
+ const el = elRef.current;
66
+ if (!el) return;
67
+ const added: Array<[string, EventListener]> = [];
68
+ for (const [reactName, domName] of eventEntries) {
69
+ const fn: EventListener = (e) => {
70
+ const handler = handlersRef.current[reactName];
71
+ if (typeof handler === 'function') (handler as (e: Event) => void)(e);
72
+ };
73
+ el.addEventListener(domName, fn);
74
+ added.push([domName, fn]);
75
+ }
76
+ return () => added.forEach(([n, fn]) => el.removeEventListener(n, fn));
77
+ }, []);
78
+
79
+ return createElement(
80
+ tagName,
81
+ {
82
+ ref: elRef,
83
+ className: p.className as string | undefined,
84
+ style: p.style as CSSProperties | undefined,
85
+ id: p.id as string | undefined,
86
+ },
87
+ // Light-DOM children pass straight through to the element (slots).
88
+ (p.children ?? null) as never,
89
+ );
90
+ });
91
+
92
+ Component.displayName = tagName;
93
+ return Component as ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<HTMLElement>>;
94
+ }