@kitnai/chat 0.3.1 → 0.5.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 (119) hide show
  1. package/README.md +35 -5
  2. package/dist/custom-elements.json +2969 -0
  3. package/dist/kitn-chat.es.js +52 -39
  4. package/dist/llms/llms-full.txt +718 -0
  5. package/dist/llms/llms.txt +104 -0
  6. package/dist/theme.tokens.css +137 -0
  7. package/frameworks/react/index.tsx +584 -0
  8. package/frameworks/react/runtime.tsx +94 -0
  9. package/llms-full.txt +718 -0
  10. package/llms.txt +104 -0
  11. package/package.json +53 -6
  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/chat-thread.tsx +217 -0
  16. package/src/components/checkpoint.tsx +7 -3
  17. package/src/components/context.tsx +14 -18
  18. package/src/components/conversation-item.tsx +1 -1
  19. package/src/components/conversation-list.tsx +5 -4
  20. package/src/components/message-skills.tsx +1 -1
  21. package/src/components/message.tsx +1 -0
  22. package/src/components/model-switcher.tsx +3 -3
  23. package/src/components/prompt-input.tsx +20 -2
  24. package/src/components/reasoning.tsx +2 -2
  25. package/src/components/scroll-button.tsx +1 -0
  26. package/src/components/slash-command.tsx +17 -8
  27. package/src/components/source.tsx +2 -2
  28. package/src/components/thinking-bar.tsx +2 -2
  29. package/src/components/tool.tsx +17 -6
  30. package/src/components/voice-input.tsx +5 -1
  31. package/src/elements/attachments.tsx +132 -0
  32. package/src/elements/chain-of-thought.tsx +45 -0
  33. package/src/elements/chat-scope-picker.tsx +36 -0
  34. package/src/elements/chat-workspace.tsx +122 -0
  35. package/src/elements/chat.tsx +31 -228
  36. package/src/elements/checkpoint.tsx +43 -0
  37. package/src/elements/code-block.tsx +42 -0
  38. package/src/elements/compiled.css +1 -1
  39. package/src/elements/context-meter.tsx +71 -0
  40. package/src/elements/conversation-list.tsx +6 -0
  41. package/src/elements/default-input.tsx +22 -1
  42. package/src/elements/define.tsx +98 -12
  43. package/src/elements/element-types.d.ts +444 -0
  44. package/src/elements/empty.tsx +29 -0
  45. package/src/elements/feedback-bar.tsx +33 -0
  46. package/src/elements/file-upload.tsx +44 -0
  47. package/src/elements/image.tsx +32 -0
  48. package/src/elements/kitn-attachments.stories.tsx +181 -0
  49. package/src/elements/kitn-chain-of-thought.stories.tsx +75 -0
  50. package/src/elements/kitn-chat-scope-picker.stories.tsx +72 -0
  51. package/src/elements/kitn-chat-workspace.stories.tsx +195 -0
  52. package/src/elements/kitn-checkpoint.stories.tsx +71 -0
  53. package/src/elements/kitn-code-block.stories.tsx +82 -0
  54. package/src/elements/kitn-context-meter.stories.tsx +85 -0
  55. package/src/elements/kitn-empty.stories.tsx +110 -0
  56. package/src/elements/kitn-feedback-bar.stories.tsx +73 -0
  57. package/src/elements/kitn-file-upload.stories.tsx +81 -0
  58. package/src/elements/kitn-image.stories.tsx +70 -0
  59. package/src/elements/kitn-loader.stories.tsx +87 -0
  60. package/src/elements/kitn-markdown.stories.tsx +75 -0
  61. package/src/elements/kitn-message-skills.stories.tsx +74 -0
  62. package/src/elements/kitn-message.stories.tsx +105 -0
  63. package/src/elements/kitn-model-switcher.stories.tsx +80 -0
  64. package/src/elements/kitn-prompt-input.stories.tsx +74 -16
  65. package/src/elements/kitn-prompt-suggestions.stories.tsx +157 -0
  66. package/src/elements/kitn-reasoning.stories.tsx +76 -0
  67. package/src/elements/kitn-response-stream.stories.tsx +79 -0
  68. package/src/elements/kitn-source-list.stories.tsx +77 -0
  69. package/src/elements/kitn-source.stories.tsx +87 -0
  70. package/src/elements/kitn-text-shimmer.stories.tsx +63 -0
  71. package/src/elements/kitn-thinking-bar.stories.tsx +72 -0
  72. package/src/elements/kitn-tool.stories.tsx +88 -0
  73. package/src/elements/kitn-voice-input.stories.tsx +87 -0
  74. package/src/elements/loader.tsx +25 -0
  75. package/src/elements/markdown.tsx +38 -0
  76. package/src/elements/message-skills.tsx +22 -0
  77. package/src/elements/message.tsx +125 -0
  78. package/src/elements/model-switcher.tsx +35 -0
  79. package/src/elements/prompt-input.tsx +83 -7
  80. package/src/elements/prompt-suggestions.tsx +58 -0
  81. package/src/elements/reasoning.tsx +50 -0
  82. package/src/elements/register.ts +32 -0
  83. package/src/elements/response-stream.tsx +40 -0
  84. package/src/elements/source.tsx +67 -0
  85. package/src/elements/styles.css +14 -0
  86. package/src/elements/text-shimmer.tsx +28 -0
  87. package/src/elements/thinking-bar.tsx +34 -0
  88. package/src/elements/tool.tsx +23 -0
  89. package/src/elements/voice-input.tsx +41 -0
  90. package/src/index.ts +0 -1
  91. package/src/primitives/chat-config.tsx +3 -3
  92. package/src/stories/docs/Accessibility.mdx +119 -0
  93. package/src/stories/docs/ForAIAgents.mdx +93 -0
  94. package/src/stories/docs/GettingStarted.mdx +2 -2
  95. package/src/stories/docs/Installation.mdx +29 -2
  96. package/src/stories/docs/Integrations.mdx +417 -15
  97. package/src/stories/docs/Introduction.mdx +17 -8
  98. package/src/stories/docs/Theming.mdx +1 -1
  99. package/src/stories/pattern-centered-conversation.stories.tsx +93 -0
  100. package/src/stories/pattern-docked-widget.stories.tsx +93 -0
  101. package/src/stories/pattern-empty-state.stories.tsx +76 -0
  102. package/src/stories/typography.stories.tsx +78 -0
  103. package/src/ui/button.tsx +1 -1
  104. package/src/ui/collapsible.stories.tsx +70 -0
  105. package/src/ui/collapsible.tsx +119 -8
  106. package/src/ui/dropdown.stories.tsx +60 -0
  107. package/src/ui/dropdown.tsx +177 -12
  108. package/src/ui/hover-card.stories.tsx +78 -0
  109. package/src/ui/hover-card.tsx +147 -26
  110. package/src/ui/overlay.stories.tsx +115 -0
  111. package/src/ui/overlay.tsx +151 -0
  112. package/src/ui/scroll-area.stories.tsx +51 -0
  113. package/src/ui/textarea.stories.tsx +77 -0
  114. package/src/ui/textarea.tsx +1 -1
  115. package/src/ui/tooltip.stories.tsx +1 -1
  116. package/src/ui/tooltip.tsx +59 -13
  117. package/src/utils/cn.ts +19 -1
  118. package/theme.css +76 -43
  119. package/src/ui/dialog.tsx +0 -21
@@ -1,7 +1,29 @@
1
1
  import { customElement } from 'solid-element';
2
2
  import { ChatConfig } from '../primitives/chat-config';
3
3
  import { KITN_CSS } from './css';
4
- import { createSignal, onCleanup, type JSX } from 'solid-js';
4
+ import { createSignal, onCleanup, onMount, Show, type JSX } from 'solid-js';
5
+
6
+ /**
7
+ * Shared constructable stylesheet, built once and adopted by every element's
8
+ * shadow root. This avoids duplicating the full compiled kit CSS (~77 KB) as an
9
+ * inline `<style>` in each instance — important now that composing many small
10
+ * elements on a page is a supported pattern. Falls back to `null` where
11
+ * Constructable Stylesheets aren't available, in which case the facade renders
12
+ * an inline `<style>` instead (see below).
13
+ */
14
+ let sharedSheet: CSSStyleSheet | null | undefined;
15
+ function getSharedSheet(): CSSStyleSheet | null {
16
+ if (sharedSheet !== undefined) return sharedSheet;
17
+ try {
18
+ if (typeof CSSStyleSheet === 'undefined') throw new Error('no CSSStyleSheet');
19
+ const sheet = new CSSStyleSheet();
20
+ sheet.replaceSync(KITN_CSS);
21
+ sharedSheet = sheet;
22
+ } catch {
23
+ sharedSheet = null;
24
+ }
25
+ return sharedSheet;
26
+ }
5
27
 
6
28
  /** Resolve whether the element should render dark, given its `theme` and the
7
29
  * system preference. `auto` (the default) follows `prefers-color-scheme`. */
@@ -20,15 +42,45 @@ function createDarkMode(getTheme: () => string | undefined) {
20
42
  };
21
43
  }
22
44
 
23
- export interface KitnElementContext {
45
+ /**
46
+ * Context handed to every element facade. `E` is the element's event map —
47
+ * `{ eventName: detailType }` — which types `dispatch` so a facade can only fire
48
+ * its declared events with the right `detail` shape.
49
+ */
50
+ export interface KitnElementContext<E = Record<string, unknown>> {
24
51
  /** The custom-element host node. */
25
52
  element: HTMLElement;
26
53
  /** Fire a non-bubbling, non-composed CustomEvent off the host. Consumers
27
- * listen directly on the element (`el.addEventListener(...)`). */
28
- dispatch: (type: string, detail?: unknown) => void;
54
+ * listen directly on the element (`el.addEventListener(...)`). Typed by the
55
+ * element's event map `E`. */
56
+ dispatch: <K extends keyof E & string>(type: K, detail?: E[K]) => void;
57
+ /**
58
+ * Resolve a boolean flag from a prop the way HTML authors expect.
59
+ *
60
+ * `component-register` parses a *bare* boolean attribute (`<el removable>`) to
61
+ * `undefined`, not `true` — so a facade can't rely on the prop value alone.
62
+ * `flag('removable')` returns ON when the property is `true`, OR when the
63
+ * matching attribute is present and not explicitly `="false"`. So all of
64
+ * `<el removable>`, `<el removable="true">`, and `el.removable = true` turn it
65
+ * on; `<el removable="false">`, absent, and `el.removable = false` turn it off.
66
+ *
67
+ * `name` is the camelCase prop name; the matching kebab attribute is derived.
68
+ */
69
+ flag: (name: string) => boolean;
29
70
  }
30
71
 
31
- type FacadeComponent<P> = (props: P, ctx: KitnElementContext) => JSX.Element;
72
+ /** camelCase prop name kebab-case attribute (`hoverCard` → `hover-card`). */
73
+ function toAttr(name: string): string {
74
+ return name.replace(/([A-Z])/g, '-$1').toLowerCase();
75
+ }
76
+
77
+ /** Underlying flag resolution; see `KitnElementContext.flag`. */
78
+ function resolveFlag(element: HTMLElement, value: unknown, attribute: string): boolean {
79
+ if (value === true) return true;
80
+ return element.hasAttribute(attribute) && element.getAttribute(attribute) !== 'false';
81
+ }
82
+
83
+ type FacadeComponent<P, E> = (props: P, ctx: KitnElementContext<E>) => JSX.Element;
32
84
 
33
85
  /**
34
86
  * Register a Solid facade as a Shadow-DOM custom element.
@@ -36,19 +88,36 @@ type FacadeComponent<P> = (props: P, ctx: KitnElementContext) => JSX.Element;
36
88
  * - Renders into the element's shadow root (solid-element's default), with the
37
89
  * compiled kit CSS injected via a `<style>` so Tailwind classes apply inside.
38
90
  * - Creates a portal mount node inside the shadow root and provides it through
39
- * `ChatConfig` so Kobalte overlays stay inside the shadow root.
91
+ * `ChatConfig` so the kit's overlays stay inside the shadow root.
40
92
  * - Gives the facade a `dispatch(type, detail)` helper that fires non-bubbling,
41
93
  * non-composed CustomEvents off the host element (consumers listen directly on
42
94
  * the element, so bubbling/composed would only cause consumer collisions).
43
95
  * - Idempotent: redefining an already-registered tag is a no-op.
44
96
  */
45
- export function defineKitnElement<P extends Record<string, unknown>>(
97
+ export function defineKitnElement<P extends Record<string, unknown>, E = Record<string, unknown>>(
46
98
  tag: string,
47
99
  propDefaults: P,
48
- Facade: FacadeComponent<P>,
100
+ Facade: FacadeComponent<P, E>,
49
101
  ): void {
50
102
  if (typeof customElements !== 'undefined' && customElements.get(tag)) return;
51
103
 
104
+ // Guard against prop names that collide with global reflected HTMLElement IDL
105
+ // attributes. component-register sets `this[prop] = undefined` in the element
106
+ // constructor; for these, the native setter coerces undefined → "undefined"
107
+ // and reflects it to an attribute — which is illegal in a CE constructor and
108
+ // throws a cryptic "result must not have attributes". Fail loud and early with
109
+ // a name + fix instead. (Use a prefixed attribute, e.g. `bar-title`/`headline`.)
110
+ const RESERVED = ['title', 'id', 'slot', 'lang'];
111
+ for (const key of Object.keys(propDefaults)) {
112
+ if (RESERVED.includes(key)) {
113
+ throw new Error(
114
+ `defineKitnElement(${tag}): prop "${key}" collides with a global HTMLElement ` +
115
+ `attribute and will break the element constructor. Rename it (e.g. ` +
116
+ `"bar-title" → barTitle, a source title → headline).`,
117
+ );
118
+ }
119
+ }
120
+
52
121
  // Every element gets a `theme` property/attribute: 'light' | 'dark' | 'auto'
53
122
  // (default 'auto' = follow the OS `prefers-color-scheme`). It drives a `.dark`
54
123
  // class on an inner wrapper, which the injected kit CSS already styles — so dark
@@ -59,16 +128,33 @@ export function defineKitnElement<P extends Record<string, unknown>>(
59
128
  const element = options.element as HTMLElement;
60
129
  let portalNode!: HTMLDivElement;
61
130
 
62
- const dispatch = (type: string, detail?: unknown) =>
131
+ const dispatch = ((type: string, detail?: unknown) =>
63
132
  element.dispatchEvent(
64
133
  new CustomEvent(type, { detail, bubbles: false, composed: false }),
65
- );
134
+ )) as KitnElementContext<E>['dispatch'];
135
+
136
+ // Reads `props[name]` (reactive) and falls back to attribute presence so
137
+ // bare boolean attributes behave like normal HTML. See KitnElementContext.
138
+ const flag = (name: string) =>
139
+ resolveFlag(element, (props as Record<string, unknown>)[name], toAttr(name));
66
140
 
67
141
  const isDark = createDarkMode(() => props.theme as string | undefined);
68
142
 
143
+ // Prefer a single shared stylesheet adopted into this shadow root; only emit
144
+ // an inline <style> when Constructable Stylesheets aren't supported.
145
+ const sheet = getSharedSheet();
146
+ onMount(() => {
147
+ const root = element.shadowRoot;
148
+ if (sheet && root && 'adoptedStyleSheets' in root) {
149
+ root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
150
+ }
151
+ });
152
+
69
153
  return (
70
154
  <>
71
- <style>{KITN_CSS}</style>
155
+ <Show when={!sheet}>
156
+ <style>{KITN_CSS}</style>
157
+ </Show>
72
158
  {/* display:contents — no layout box; carries the .dark token scope and
73
159
  re-roots the inherited `color` to the active mode's foreground, so text
74
160
  without an explicit color class (e.g. attachment filename labels) follows
@@ -76,7 +162,7 @@ export function defineKitnElement<P extends Record<string, unknown>>(
76
162
  <div classList={{ dark: isDark() }} style={{ display: 'contents', color: 'var(--color-foreground)' }}>
77
163
  <div ref={portalNode} />
78
164
  <ChatConfig portalMount={portalNode}>
79
- {Facade(props as unknown as P, { element, dispatch })}
165
+ {Facade(props as unknown as P, { element, dispatch, flag })}
80
166
  </ChatConfig>
81
167
  </div>
82
168
  </>
@@ -0,0 +1,444 @@
1
+ // AUTO-GENERATED by scripts/gen-element-api.mjs — do not edit by hand.
2
+ // Typed custom-element interfaces + HTMLElementTagNameMap augmentation, so
3
+ // `document.querySelector('kitn-message')` is typed and gets prop autocomplete.
4
+
5
+
6
+ // Re-exports for `import { … } from '@kitnai/chat/elements'`.
7
+ export type { ChatMessage, ChatMessageAction } from './chat-types';
8
+ export { configureCodeHighlighting, isCodeHighlightingEnabled } from '../primitives/highlighter';
9
+ export type { CodeHighlightingOptions } from '../primitives/highlighter';
10
+
11
+ export interface KitnAttachmentsElement extends HTMLElement {
12
+ /** Color mode (`auto` follows prefers-color-scheme). */
13
+ theme?: 'light' | 'dark' | 'auto';
14
+ /** The attachments to render. Set as a JS property (array). */
15
+ items: { id: string; type: "file" | "source-document"; filename?: undefined | string; mediaType?: undefined | string; url?: undefined | string; title?: undefined | string }[];
16
+ /** Layout: `grid` = visual tiles, `inline` = icon + label chips, `list` = rows. */
17
+ variant?: "grid" | "inline" | "list";
18
+ /** Wrap each item in a hover card that previews its details. */
19
+ hoverCard?: boolean;
20
+ /** Show a remove button per item; clicking it fires a `remove` event. */
21
+ removable?: boolean;
22
+ /** Also show the media type beneath the filename (non-grid variants). */
23
+ showMediaType?: boolean;
24
+ /** Text shown when `items` is empty. */
25
+ emptyText?: string;
26
+ }
27
+
28
+ export interface KitnChainOfThoughtElement extends HTMLElement {
29
+ /** Color mode (`auto` follows prefers-color-scheme). */
30
+ theme?: 'light' | 'dark' | 'auto';
31
+ /** The reasoning steps. Set as a JS property. Compound sub-parts collapse to this one data model (Route 1). */
32
+ steps: { label: string; content?: undefined | string }[];
33
+ }
34
+
35
+ export interface KitnChatElement extends HTMLElement {
36
+ /** Color mode (`auto` follows prefers-color-scheme). */
37
+ theme?: 'light' | 'dark' | 'auto';
38
+ /** 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 = [...]`). */
39
+ 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")[] }[];
40
+ /** Controlled value of the input. When set, the host owns the input text and must update it on `valuechange`; leave unset for uncontrolled behavior. */
41
+ value?: string;
42
+ /** Placeholder text shown in the empty input. */
43
+ placeholder?: string;
44
+ /** When true, shows the loading/streaming state and disables submit (use while awaiting the assistant's reply). */
45
+ loading?: boolean;
46
+ /** Starter prompts shown above the input when the thread is empty. Clicking one follows `suggestionMode`. Set as a JS property. */
47
+ suggestions?: string[];
48
+ /** What clicking a suggestion does: `'submit'` (default) sends it immediately as if typed and submitted; `'fill'` just places it in the input. */
49
+ suggestionMode?: "submit" | "fill";
50
+ /** Body/prose font scale for rendered markdown (`'xs' | 'sm' | 'base' | 'lg'`). Defaults to `'sm'`. */
51
+ proseSize?: "xs" | "sm" | "base" | "lg";
52
+ /** Shiki theme name for syntax-highlighted code blocks (e.g. `'github-dark-dimmed'`). */
53
+ codeTheme?: string;
54
+ /** Enable Shiki syntax highlighting in code blocks. Turn off to render plain `<pre>` blocks (lighter, no highlighter load). Default true. */
55
+ codeHighlight?: boolean;
56
+ /** Optional header title shown on the left of the header. */
57
+ chatTitle?: string;
58
+ /** Optional model list. When set (>1 model) a ModelSwitcher is shown in the header and a `modelchange` event fires on selection. */
59
+ models?: { id: string; name: string; provider?: string }[];
60
+ /** The currently selected model id (pairs with `models`). */
61
+ currentModel?: string;
62
+ /** Optional context-window token usage. When set, a Context token meter is shown in the header. */
63
+ context?: { usedTokens: number; maxTokens: number; inputTokens?: number; outputTokens?: number; estimatedCost?: number };
64
+ /** Show the scroll-to-bottom button inside the scroll area. Default true. */
65
+ scrollButton?: boolean;
66
+ /** Show a Search (Globe) button in the input toolbar; fires a `search` event. */
67
+ search?: boolean;
68
+ /** Show a Voice (Mic) button in the input toolbar; fires a `voice` event. */
69
+ voice?: boolean;
70
+ /** Slash commands — when set, typing `/` in the input opens the command palette and fires `slashselect`. Set as a JS property. */
71
+ slashCommands?: { id: string; label: string; description?: string; category?: string }[];
72
+ /** Command ids to highlight as active in the palette. */
73
+ slashActiveIds?: string[];
74
+ /** Single-line palette rows. */
75
+ slashCompact?: boolean;
76
+ }
77
+
78
+ export interface KitnChatScopePickerElement extends HTMLElement {
79
+ /** Color mode (`auto` follows prefers-color-scheme). */
80
+ theme?: 'light' | 'dark' | 'auto';
81
+ /** Authors to offer as scope filters. Set as a JS property. */
82
+ availableAuthors: string[];
83
+ /** Tags to offer as scope filters. Set as a JS property. */
84
+ availableTags: string[];
85
+ /** The label shown on the trigger for the active scope. */
86
+ currentLabel?: string;
87
+ }
88
+
89
+ export interface KitnChatWorkspaceElement extends HTMLElement {
90
+ /** Color mode (`auto` follows prefers-color-scheme). */
91
+ theme?: 'light' | 'dark' | 'auto';
92
+ /** Pre-bucketed conversation groups for the sidebar. Set as a JS property. */
93
+ groups: { id: string; userId?: undefined | string; teamId?: undefined | string; name: string; sortOrder: number; createdAt: string }[];
94
+ /** Flat conversation list (auto-bucketed if `groups` is empty). Set as a JS property. */
95
+ 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 }[];
96
+ /** Id of the open conversation, highlighted in the sidebar. */
97
+ activeId?: string;
98
+ /** The active conversation's message thread, newest last. Set as a JS property. */
99
+ 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")[] }[];
100
+ value?: string;
101
+ placeholder?: string;
102
+ loading?: boolean;
103
+ suggestions?: string[];
104
+ suggestionMode?: "submit" | "fill";
105
+ proseSize?: "xs" | "sm" | "base" | "lg";
106
+ codeTheme?: string;
107
+ codeHighlight?: boolean;
108
+ chatTitle?: string;
109
+ models?: { id: string; name: string; provider?: string }[];
110
+ currentModel?: string;
111
+ context?: { usedTokens: number; maxTokens: number; inputTokens?: number; outputTokens?: number; estimatedCost?: number };
112
+ scrollButton?: boolean;
113
+ search?: boolean;
114
+ voice?: boolean;
115
+ slashCommands?: { id: string; label: string; description?: string; category?: string }[];
116
+ slashActiveIds?: string[];
117
+ slashCompact?: boolean;
118
+ /** Sidebar default width as a percent of the workspace (default 22). */
119
+ sidebarWidth?: number;
120
+ /** Sidebar min width in px (default 200). */
121
+ sidebarMinWidth?: number;
122
+ /** Sidebar max width in px (default 420). */
123
+ sidebarMaxWidth?: number;
124
+ /** Initial collapsed state of the sidebar (default false). */
125
+ sidebarCollapsed?: boolean;
126
+ }
127
+
128
+ export interface KitnCheckpointElement extends HTMLElement {
129
+ /** Color mode (`auto` follows prefers-color-scheme). */
130
+ theme?: 'light' | 'dark' | 'auto';
131
+ /** Optional text beside the icon. */
132
+ label?: string;
133
+ /** Tooltip on hover. */
134
+ tooltip?: string;
135
+ /** Visual button style. */
136
+ variant?: "ghost" | "default" | "outline";
137
+ /** Button size (use an `icon*` size for an icon-only checkpoint). */
138
+ size?: "sm" | "lg" | "md" | "icon" | "icon-sm";
139
+ }
140
+
141
+ export interface KitnCodeBlockElement extends HTMLElement {
142
+ /** Color mode (`auto` follows prefers-color-scheme). */
143
+ theme?: 'light' | 'dark' | 'auto';
144
+ /** The source code to render. */
145
+ code: string;
146
+ /** Language grammar (e.g. `js`, `python`). Defaults to `tsx`. */
147
+ language?: string;
148
+ /** Shiki theme name. */
149
+ codeTheme?: string;
150
+ /** Disable syntax highlighting (renders plain text, no Shiki). */
151
+ codeHighlight?: boolean;
152
+ /** Code text sizing. */
153
+ proseSize?: "xs" | "sm" | "base" | "lg";
154
+ }
155
+
156
+ export interface KitnContextMeterElement extends HTMLElement {
157
+ /** Color mode (`auto` follows prefers-color-scheme). */
158
+ theme?: 'light' | 'dark' | 'auto';
159
+ /** Token-usage data. Set as a JS property. */
160
+ context?: { usedTokens: number; maxTokens: number; inputTokens?: number; outputTokens?: number; reasoningTokens?: number; cacheTokens?: number; estimatedCost?: number };
161
+ }
162
+
163
+ export interface KitnConversationListElement extends HTMLElement {
164
+ /** Color mode (`auto` follows prefers-color-scheme). */
165
+ theme?: 'light' | 'dark' | 'auto';
166
+ /** 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. */
167
+ groups: { id: string; userId?: undefined | string; teamId?: undefined | string; name: string; sortOrder: number; createdAt: string }[];
168
+ /** A flat list of conversation summaries; the component buckets them by recency for you. Ignored when `groups` is provided. Set as a JS property. */
169
+ 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 }[];
170
+ /** The id of the currently-open conversation, highlighted in the list. */
171
+ activeId?: string;
172
+ }
173
+
174
+ export interface KitnEmptyElement extends HTMLElement {
175
+ /** Color mode (`auto` follows prefers-color-scheme). */
176
+ theme?: 'light' | 'dark' | 'auto';
177
+ /** Title text. Attribute: `empty-title` (`title` is a global HTML attribute). */
178
+ emptyTitle?: string;
179
+ /** Description text. */
180
+ description?: string;
181
+ }
182
+
183
+ export interface KitnFeedbackBarElement extends HTMLElement {
184
+ /** Color mode (`auto` follows prefers-color-scheme). */
185
+ theme?: 'light' | 'dark' | 'auto';
186
+ /** The banner label (e.g. "Was this helpful?"). Attribute: `bar-title` (`title` is avoided — it's a global HTML attribute). */
187
+ barTitle?: string;
188
+ }
189
+
190
+ export interface KitnFileUploadElement extends HTMLElement {
191
+ /** Color mode (`auto` follows prefers-color-scheme). */
192
+ theme?: 'light' | 'dark' | 'auto';
193
+ /** Allow selecting multiple files (default true). */
194
+ multiple?: boolean;
195
+ /** `accept` attribute for the file picker (e.g. `image/*`). */
196
+ accept?: string;
197
+ /** Disable the dropzone — no clicking, no drag-and-drop. */
198
+ disabled?: boolean;
199
+ /** Default dropzone label (overridable via the default slot). */
200
+ label?: string;
201
+ }
202
+
203
+ export interface KitnImageElement extends HTMLElement {
204
+ /** Color mode (`auto` follows prefers-color-scheme). */
205
+ theme?: 'light' | 'dark' | 'auto';
206
+ /** Base64-encoded image data (pair with `media-type`). */
207
+ base64?: string;
208
+ /** Raw image bytes (set as a JS property). */
209
+ bytes?: Uint8Array;
210
+ /** Alt text. */
211
+ alt?: string;
212
+ /** MIME type (default `image/png`). */
213
+ mediaType?: string;
214
+ }
215
+
216
+ export interface KitnLoaderElement extends HTMLElement {
217
+ /** Color mode (`auto` follows prefers-color-scheme). */
218
+ theme?: 'light' | 'dark' | 'auto';
219
+ /** The animation style: `'circular' | 'classic' | 'pulse' | 'pulse-dot' | 'dots' | 'typing' | 'wave' | 'bars' | 'terminal' | 'text-blink' | 'text-shimmer' | 'loading-dots'`. Defaults to `'circular'`. */
220
+ variant?: "circular" | "classic" | "pulse" | "pulse-dot" | "dots" | "typing" | "wave" | "bars" | "terminal" | "text-blink" | "text-shimmer" | "loading-dots";
221
+ /** Loader size: `'sm' | 'md' | 'lg'`. Defaults to `'md'`. */
222
+ size?: "sm" | "lg" | "md";
223
+ /** Label for the text-based variants. */
224
+ text?: string;
225
+ }
226
+
227
+ export interface KitnMarkdownElement extends HTMLElement {
228
+ /** Color mode (`auto` follows prefers-color-scheme). */
229
+ theme?: 'light' | 'dark' | 'auto';
230
+ /** The markdown source to render. */
231
+ content: string;
232
+ /** Text/markdown sizing. */
233
+ proseSize?: "xs" | "sm" | "base" | "lg";
234
+ /** Shiki theme for fenced code blocks. */
235
+ codeTheme?: string;
236
+ /** Disable syntax highlighting (no Shiki loads). */
237
+ codeHighlight?: boolean;
238
+ }
239
+
240
+ export interface KitnMessageElement extends HTMLElement {
241
+ /** Color mode (`auto` follows prefers-color-scheme). */
242
+ theme?: 'light' | 'dark' | 'auto';
243
+ /** The full message object. Set as a JS property. */
244
+ 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")[] };
245
+ /** Convenience for simple cases when not passing a `message` object. */
246
+ role?: "user" | "assistant";
247
+ /** Convenience content (used when `message` is not set). */
248
+ content?: string;
249
+ /** Force markdown on/off. Defaults to on for assistant, off for user. */
250
+ markdown?: boolean;
251
+ /** Text/markdown sizing for the message body. */
252
+ proseSize?: "xs" | "sm" | "base" | "lg";
253
+ /** Shiki theme name used for fenced code blocks in the content. */
254
+ codeTheme?: string;
255
+ /** Disable syntax highlighting for code blocks (no Shiki loads). */
256
+ codeHighlight?: boolean;
257
+ }
258
+
259
+ export interface KitnMessageSkillsElement extends HTMLElement {
260
+ /** Color mode (`auto` follows prefers-color-scheme). */
261
+ theme?: 'light' | 'dark' | 'auto';
262
+ /** The active skills to badge. Set as a JS property. */
263
+ skills: { id: string; name: string }[];
264
+ }
265
+
266
+ export interface KitnModelSwitcherElement extends HTMLElement {
267
+ /** Color mode (`auto` follows prefers-color-scheme). */
268
+ theme?: 'light' | 'dark' | 'auto';
269
+ /** The selectable models. Set as a JS property (array). */
270
+ models: { id: string; name: string; provider?: undefined | string }[];
271
+ /** The currently-selected model id. Defaults to the first model. */
272
+ currentModel?: string;
273
+ }
274
+
275
+ export interface KitnPromptInputElement extends HTMLElement {
276
+ /** Color mode (`auto` follows prefers-color-scheme). */
277
+ theme?: 'light' | 'dark' | 'auto';
278
+ /** Controlled value of the input. When set, the host owns the text and must update it on `valuechange`; leave unset for uncontrolled behavior. */
279
+ value?: string;
280
+ /** Placeholder text shown in the empty input. */
281
+ placeholder?: string;
282
+ /** Disable the input and submit button entirely (non-interactive). */
283
+ disabled?: boolean;
284
+ /** Show the loading/streaming state and block submit (use while awaiting a reply). */
285
+ loading?: boolean;
286
+ /** Starter prompts shown above the input. Clicking one follows `suggestionMode`. Set as a JS property. */
287
+ suggestions?: string[];
288
+ /** What clicking a suggestion does: `'submit'` (default) sends it immediately as if typed and submitted; `'fill'` just places it in the input. */
289
+ suggestionMode?: "submit" | "fill";
290
+ /** Slash commands — when set, typing `/` opens the command palette. Set as a JS property. */
291
+ slashCommands?: { id: string; label: string; description?: string; category?: string }[];
292
+ /** Command ids to highlight as active. */
293
+ slashActiveIds?: string[];
294
+ /** Single-line palette rows. */
295
+ slashCompact?: boolean;
296
+ /** Show a Search (Globe) button in the left toolbar; clicking it fires a `search` event. */
297
+ search?: boolean;
298
+ /** Show a Voice (Mic) button in the left toolbar; clicking it fires a `voice` event. */
299
+ voice?: boolean;
300
+ /** 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). */
301
+ attachments?: { id: string; type: "file" | "source-document"; filename?: string; mediaType?: string; url?: string; title?: string }[];
302
+ }
303
+
304
+ export interface KitnPromptSuggestionsElement extends HTMLElement {
305
+ /** Color mode (`auto` follows prefers-color-scheme). */
306
+ theme?: 'light' | 'dark' | 'auto';
307
+ /** The suggestions. Strings, or `{ label, value }` when the displayed text and the emitted value differ. Set as a JS property. */
308
+ suggestions: (string | { label: string; value?: undefined | string })[];
309
+ /** Chip style: `'outline'` (default), `'ghost'`, or `'default'` (filled). */
310
+ variant?: "ghost" | "default" | "outline";
311
+ /** Size preset for each chip. Defaults to the pill default (`'lg'`); pass `'sm'` for smaller pills (or `'md'`). */
312
+ size?: "sm" | "lg" | "md" | "icon" | "icon-sm";
313
+ /** Full-width left-aligned rows instead of pills. */
314
+ block?: boolean;
315
+ /** Substring to highlight within each suggestion. */
316
+ highlight?: string;
317
+ }
318
+
319
+ export interface KitnReasoningElement extends HTMLElement {
320
+ /** Color mode (`auto` follows prefers-color-scheme). */
321
+ theme?: 'light' | 'dark' | 'auto';
322
+ /** The reasoning text to display. */
323
+ text: string;
324
+ /** Trigger label. */
325
+ label?: string;
326
+ /** Controlled open state — set as a property (`el.open = true`). Omit for uncontrolled (the trigger toggles it). */
327
+ open?: boolean;
328
+ /** While true, auto-expands (and re-collapses when it flips false). */
329
+ streaming?: boolean;
330
+ /** Render `text` as markdown. */
331
+ markdown?: boolean;
332
+ }
333
+
334
+ export interface KitnResponseStreamElement extends HTMLElement {
335
+ /** Color mode (`auto` follows prefers-color-scheme). */
336
+ theme?: 'light' | 'dark' | 'auto';
337
+ /** Text to stream. A string, or an `AsyncIterable<string>` (set as a JS property — async iterables can't be HTML attributes). */
338
+ text?: string | AsyncIterable<string>;
339
+ /** Reveal animation. */
340
+ mode?: "typewriter" | "fade";
341
+ /** Characters/segments per tick. */
342
+ speed?: number;
343
+ /** Element tag to render as. */
344
+ as?: string;
345
+ }
346
+
347
+ export interface KitnSourceElement extends HTMLElement {
348
+ /** Color mode (`auto` follows prefers-color-scheme). */
349
+ theme?: 'light' | 'dark' | 'auto';
350
+ /** The URL this citation links to (the domain also seeds the default label/favicon). */
351
+ href?: string;
352
+ /** Trigger label (defaults to the domain). */
353
+ label?: string;
354
+ /** Hover-card headline. Attribute: `headline` (`title` is avoided — it's a global HTML attribute that reflects in a CE constructor and breaks it). */
355
+ headline?: string;
356
+ /** Hover-card body text describing the source. */
357
+ description?: string;
358
+ /** Show the source's favicon next to the trigger label. */
359
+ showFavicon?: boolean;
360
+ }
361
+
362
+ export interface KitnSourceListElement extends HTMLElement {
363
+ /** Color mode (`auto` follows prefers-color-scheme). */
364
+ theme?: 'light' | 'dark' | 'auto';
365
+ /** The sources to render. Set as a JS property. */
366
+ sources: { href: string; title?: undefined | string; description?: undefined | string; label?: undefined | string; showFavicon?: undefined | boolean }[];
367
+ /** Show favicons on all items (per-item `showFavicon` overrides). */
368
+ showFavicon?: boolean;
369
+ }
370
+
371
+ export interface KitnTextShimmerElement extends HTMLElement {
372
+ /** Color mode (`auto` follows prefers-color-scheme). */
373
+ theme?: 'light' | 'dark' | 'auto';
374
+ /** The text to shimmer. */
375
+ text?: string;
376
+ /** Element tag to render as (default `span`). */
377
+ as?: string;
378
+ /** Animation duration in seconds. */
379
+ duration?: number;
380
+ /** Gradient spread (5–45). */
381
+ spread?: number;
382
+ }
383
+
384
+ export interface KitnThinkingBarElement extends HTMLElement {
385
+ /** Color mode (`auto` follows prefers-color-scheme). */
386
+ theme?: 'light' | 'dark' | 'auto';
387
+ /** The shimmering label, e.g. "Thinking…". */
388
+ text?: string;
389
+ /** When true, show a "stop" affordance that fires a `stop` event. */
390
+ stoppable?: boolean;
391
+ /** Label for the stop affordance. */
392
+ stopLabel?: string;
393
+ }
394
+
395
+ export interface KitnToolElement extends HTMLElement {
396
+ /** Color mode (`auto` follows prefers-color-scheme). */
397
+ theme?: 'light' | 'dark' | 'auto';
398
+ /** The tool-call to display. Set as a JS property. */
399
+ tool?: { type: string; state: "input-streaming" | "input-available" | "output-available" | "output-error"; input?: Record<string, unknown>; output?: Record<string, unknown>; toolCallId?: string; errorText?: string };
400
+ /** Start expanded. */
401
+ open?: boolean;
402
+ }
403
+
404
+ export interface KitnVoiceInputElement extends HTMLElement {
405
+ /** Color mode (`auto` follows prefers-color-scheme). */
406
+ theme?: 'light' | 'dark' | 'auto';
407
+ /** 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. */
408
+ transcribe?: (audio: Blob) => Promise<string>;
409
+ /** Disable the mic button (non-interactive). */
410
+ disabled?: boolean;
411
+ }
412
+
413
+ declare global {
414
+ interface HTMLElementTagNameMap {
415
+ 'kitn-attachments': KitnAttachmentsElement;
416
+ 'kitn-chain-of-thought': KitnChainOfThoughtElement;
417
+ 'kitn-chat': KitnChatElement;
418
+ 'kitn-chat-scope-picker': KitnChatScopePickerElement;
419
+ 'kitn-chat-workspace': KitnChatWorkspaceElement;
420
+ 'kitn-checkpoint': KitnCheckpointElement;
421
+ 'kitn-code-block': KitnCodeBlockElement;
422
+ 'kitn-context-meter': KitnContextMeterElement;
423
+ 'kitn-conversation-list': KitnConversationListElement;
424
+ 'kitn-empty': KitnEmptyElement;
425
+ 'kitn-feedback-bar': KitnFeedbackBarElement;
426
+ 'kitn-file-upload': KitnFileUploadElement;
427
+ 'kitn-image': KitnImageElement;
428
+ 'kitn-loader': KitnLoaderElement;
429
+ 'kitn-markdown': KitnMarkdownElement;
430
+ 'kitn-message': KitnMessageElement;
431
+ 'kitn-message-skills': KitnMessageSkillsElement;
432
+ 'kitn-model-switcher': KitnModelSwitcherElement;
433
+ 'kitn-prompt-input': KitnPromptInputElement;
434
+ 'kitn-prompt-suggestions': KitnPromptSuggestionsElement;
435
+ 'kitn-reasoning': KitnReasoningElement;
436
+ 'kitn-response-stream': KitnResponseStreamElement;
437
+ 'kitn-source': KitnSourceElement;
438
+ 'kitn-source-list': KitnSourceListElement;
439
+ 'kitn-text-shimmer': KitnTextShimmerElement;
440
+ 'kitn-thinking-bar': KitnThinkingBarElement;
441
+ 'kitn-tool': KitnToolElement;
442
+ 'kitn-voice-input': KitnVoiceInputElement;
443
+ }
444
+ }
@@ -0,0 +1,29 @@
1
+ import { Show } from 'solid-js';
2
+ import { defineKitnElement } from './define';
3
+ import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent } from '../components/empty';
4
+
5
+ interface Props extends Record<string, unknown> {
6
+ /** Title text. Attribute: `empty-title` (`title` is a global HTML attribute). */
7
+ emptyTitle?: string;
8
+ /** Description text. */
9
+ description?: string;
10
+ }
11
+
12
+ /**
13
+ * `<kitn-empty>` — an empty-state block. `empty-title`/`description` via
14
+ * attributes; slot your own icon into `slot="media"` and actions into the
15
+ * default slot (Route 2 slots).
16
+ */
17
+ defineKitnElement<Props>('kitn-empty', {
18
+ emptyTitle: '',
19
+ description: '',
20
+ }, (props) => (
21
+ <Empty>
22
+ <EmptyHeader>
23
+ <EmptyMedia variant="icon"><slot name="media" /></EmptyMedia>
24
+ <Show when={props.emptyTitle}><EmptyTitle>{props.emptyTitle}</EmptyTitle></Show>
25
+ <Show when={props.description}><EmptyDescription>{props.description}</EmptyDescription></Show>
26
+ </EmptyHeader>
27
+ <EmptyContent><slot /></EmptyContent>
28
+ </Empty>
29
+ ));