@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.
- package/README.md +9 -9
- package/dist/custom-elements.json +1626 -883
- package/dist/kitn-chat.es.js +36 -36
- package/dist/llms/llms-full.txt +303 -142
- package/dist/llms/llms.txt +18 -18
- package/dist/schemas/card-envelope.schema.json +14 -0
- package/dist/schemas/card-event.schema.json +12 -0
- package/dist/schemas/confirm.schema.json +65 -0
- package/dist/schemas/embed.schema.json +65 -0
- package/dist/schemas/form.result.schema.json +7 -0
- package/dist/schemas/form.schema.json +33 -0
- package/dist/schemas/link.schema.json +56 -0
- package/dist/schemas/task-list.result.schema.json +16 -0
- package/dist/schemas/task-list.schema.json +78 -0
- package/dist/theme.tokens.css +65 -65
- package/dist/tsx-B8rCNbgL.js +1 -0
- package/dist/typescript-RycA9KXf.js +1 -0
- package/frameworks/react/index.tsx +356 -189
- package/frameworks/react/runtime.tsx +2 -2
- package/llms-full.txt +303 -142
- package/llms.txt +18 -18
- package/package.json +5 -2
- package/src/components/artifact.stories.tsx +138 -0
- package/src/components/artifact.tsx +581 -0
- package/src/components/attachments.stories.tsx +7 -8
- package/src/components/attachments.tsx +2 -2
- package/src/components/card.tsx +110 -0
- package/src/components/chain-of-thought.stories.tsx +7 -8
- package/src/components/chat-container.stories.tsx +7 -8
- package/src/components/chat-container.tsx +4 -0
- package/src/components/checkpoint.stories.tsx +7 -8
- package/src/components/checkpoint.tsx +3 -0
- package/src/components/code-block.stories.tsx +8 -9
- package/src/components/code-block.tsx +5 -2
- package/src/components/component-meta.json +3419 -0
- package/src/components/confirm-card.stories.tsx +74 -0
- package/src/components/confirm-card.tsx +299 -0
- package/src/components/context.stories.tsx +7 -8
- package/src/components/conversation-item.stories.tsx +7 -8
- package/src/components/conversation-item.tsx +2 -2
- package/src/components/conversation-list.stories.tsx +7 -8
- package/src/components/conversation-list.tsx +1 -1
- package/src/components/embed.tsx +196 -0
- package/src/components/empty.stories.tsx +8 -9
- package/src/components/feedback-bar.stories.tsx +7 -8
- package/src/components/file-tree.stories.tsx +73 -0
- package/src/components/file-tree.tsx +383 -0
- package/src/components/file-upload.stories.tsx +7 -8
- package/src/components/form-widgets.tsx +461 -0
- package/src/components/form.tsx +796 -0
- package/src/components/image.stories.tsx +7 -8
- package/src/components/link-card.tsx +194 -0
- package/src/components/loader.stories.tsx +7 -8
- package/src/components/markdown.stories.tsx +7 -8
- package/src/components/message-narrow.stories.tsx +12 -13
- package/src/components/message-skills.stories.tsx +16 -17
- package/src/components/message.stories.tsx +17 -18
- package/src/components/model-switcher.stories.tsx +7 -8
- package/src/components/prompt-input.stories.tsx +8 -9
- package/src/components/prompt-suggestion.stories.tsx +7 -8
- package/src/components/prompt-suggestion.tsx +3 -3
- package/src/components/reasoning.stories.tsx +7 -8
- package/src/components/scroll-button.stories.tsx +7 -8
- package/src/components/slash-command.stories.tsx +8 -9
- package/src/components/slash-command.tsx +2 -2
- package/src/components/source.stories.tsx +7 -8
- package/src/components/source.tsx +1 -1
- package/src/components/task-list-card.stories.tsx +78 -0
- package/src/components/task-list-card.tsx +388 -0
- package/src/components/text-shimmer.stories.tsx +7 -8
- package/src/components/thinking-bar.stories.tsx +7 -8
- package/src/components/tool.stories.tsx +7 -8
- package/src/components/tool.tsx +2 -2
- package/src/components/voice-input.stories.tsx +7 -8
- package/src/elements/artifact.stories.tsx +291 -0
- package/src/elements/artifact.tsx +72 -0
- package/src/elements/{kitn-attachments.stories.tsx → attachments.stories.tsx} +11 -20
- package/src/elements/attachments.tsx +4 -4
- package/src/elements/card.stories.tsx +118 -0
- package/src/elements/card.tsx +40 -0
- package/src/elements/catalog.stories.tsx +491 -0
- package/src/elements/{kitn-chain-of-thought.stories.tsx → chain-of-thought.stories.tsx} +13 -22
- package/src/elements/chain-of-thought.tsx +3 -3
- package/src/elements/{kitn-chat-scope-picker.stories.tsx → chat-scope-picker.stories.tsx} +10 -19
- package/src/elements/chat-scope-picker.tsx +4 -4
- package/src/elements/{kitn-chat-workspace.stories.tsx → chat-workspace.stories.tsx} +15 -23
- package/src/elements/chat-workspace.tsx +2 -2
- package/src/elements/{kitn-chat.stories.tsx → chat.stories.tsx} +12 -20
- package/src/elements/chat.tsx +2 -2
- package/src/elements/{kitn-checkpoint.stories.tsx → checkpoint.stories.tsx} +11 -20
- package/src/elements/checkpoint.tsx +8 -4
- package/src/elements/{kitn-code-block.stories.tsx → code-block.stories.tsx} +10 -19
- package/src/elements/code-block.tsx +3 -3
- package/src/elements/compiled.css +1 -1
- package/src/elements/composed-shell.stories.tsx +316 -0
- package/src/elements/confirm-card.stories.tsx +186 -0
- package/src/elements/confirm-card.tsx +45 -0
- package/src/elements/{kitn-context-meter.stories.tsx → context-meter.stories.tsx} +10 -19
- package/src/elements/context-meter.tsx +3 -3
- package/src/elements/{kitn-conversation-list.stories.tsx → conversation-list.stories.tsx} +12 -20
- package/src/elements/conversation-list.tsx +2 -2
- package/src/elements/css.ts +1 -1
- package/src/elements/define.tsx +10 -10
- package/src/elements/element-meta.json +1379 -733
- package/src/elements/element-types.d.ts +251 -125
- package/src/elements/embed.stories.tsx +197 -0
- package/src/elements/embed.tsx +35 -0
- package/src/elements/{kitn-empty.stories.tsx → empty.stories.tsx} +12 -21
- package/src/elements/empty.tsx +3 -3
- package/src/elements/{kitn-feedback-bar.stories.tsx → feedback-bar.stories.tsx} +11 -20
- package/src/elements/feedback-bar.tsx +4 -4
- package/src/elements/file-tree.stories.tsx +133 -0
- package/src/elements/file-tree.tsx +52 -0
- package/src/elements/{kitn-file-upload.stories.tsx → file-upload.stories.tsx} +12 -21
- package/src/elements/file-upload.tsx +4 -4
- package/src/elements/form.stories.tsx +204 -0
- package/src/elements/form.tsx +37 -0
- package/src/elements/{kitn-image.stories.tsx → image.stories.tsx} +10 -19
- package/src/elements/image.tsx +3 -3
- package/src/elements/link-card.stories.tsx +193 -0
- package/src/elements/link-card.tsx +34 -0
- package/src/elements/{kitn-loader.stories.tsx → loader.stories.tsx} +11 -20
- package/src/elements/loader.tsx +3 -3
- package/src/elements/{kitn-markdown.stories.tsx → markdown.stories.tsx} +10 -19
- package/src/elements/markdown.tsx +3 -3
- package/src/elements/{kitn-message-skills.stories.tsx → message-skills.stories.tsx} +10 -19
- package/src/elements/message-skills.tsx +3 -3
- package/src/elements/{kitn-message.stories.tsx → message.stories.tsx} +12 -21
- package/src/elements/message.tsx +5 -5
- package/src/elements/{kitn-model-switcher.stories.tsx → model-switcher.stories.tsx} +10 -19
- package/src/elements/model-switcher.tsx +5 -5
- package/src/elements/{kitn-prompt-input.stories.tsx → prompt-input.stories.tsx} +14 -22
- package/src/elements/prompt-input.tsx +3 -3
- package/src/elements/{kitn-prompt-suggestions.stories.tsx → prompt-suggestions.stories.tsx} +13 -22
- package/src/elements/prompt-suggestions.tsx +4 -4
- package/src/elements/{kitn-reasoning.stories.tsx → reasoning.stories.tsx} +10 -19
- package/src/elements/reasoning.tsx +4 -4
- package/src/elements/register.ts +11 -1
- package/src/elements/resizable.stories.tsx +200 -0
- package/src/elements/resizable.tsx +264 -0
- package/src/elements/{kitn-response-stream.stories.tsx → response-stream.stories.tsx} +10 -19
- package/src/elements/response-stream.tsx +4 -4
- package/src/elements/{kitn-source-list.stories.tsx → source-list.stories.tsx} +11 -20
- package/src/elements/{kitn-source.stories.tsx → source.stories.tsx} +12 -21
- package/src/elements/source.tsx +5 -5
- package/src/elements/styles.css +140 -1
- package/src/elements/task-list-card.stories.tsx +194 -0
- package/src/elements/task-list-card.tsx +40 -0
- package/src/elements/{kitn-text-shimmer.stories.tsx → text-shimmer.stories.tsx} +10 -19
- package/src/elements/text-shimmer.tsx +3 -3
- package/src/elements/{kitn-thinking-bar.stories.tsx → thinking-bar.stories.tsx} +11 -20
- package/src/elements/thinking-bar.tsx +5 -5
- package/src/elements/{kitn-tool.stories.tsx → tool.stories.tsx} +10 -19
- package/src/elements/tool.tsx +3 -3
- package/src/elements/{kitn-voice-input.stories.tsx → voice-input.stories.tsx} +10 -19
- package/src/elements/voice-input.tsx +4 -4
- package/src/index.ts +94 -2
- package/src/primitives/card-contract.ts +60 -0
- package/src/primitives/card-host.tsx +35 -0
- package/src/primitives/card-routing.ts +79 -0
- package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
- package/src/primitives/card-schemas/card-event.schema.json +12 -0
- package/src/primitives/card-schemas/confirm.schema.json +65 -0
- package/src/primitives/card-schemas/embed.schema.json +65 -0
- package/src/primitives/card-schemas/form.result.schema.json +7 -0
- package/src/primitives/card-schemas/form.schema.json +33 -0
- package/src/primitives/card-schemas/link.schema.json +56 -0
- package/src/primitives/card-schemas/task-list.result.schema.json +16 -0
- package/src/primitives/card-schemas/task-list.schema.json +78 -0
- package/src/primitives/card-validate.ts +95 -0
- package/src/primitives/embed-providers.ts +254 -0
- package/src/primitives/highlighter.ts +4 -0
- package/src/primitives/link-preview.ts +87 -0
- package/src/primitives/pdf-preview.ts +121 -0
- package/src/stories/chat-panel-layout.stories.tsx +2 -1
- package/src/stories/chat-scene.tsx +22 -21
- package/src/stories/checkpoint-restore.stories.tsx +10 -10
- package/src/stories/conversation-with-reasoning.stories.tsx +4 -4
- package/src/stories/conversation-with-sources.stories.tsx +7 -7
- package/src/stories/docs/Accessibility.mdx +2 -2
- package/src/stories/docs/ForAIAgents.mdx +3 -3
- package/src/stories/docs/GettingStarted.mdx +2 -2
- package/src/stories/docs/Installation.mdx +2 -2
- package/src/stories/docs/Integrations.mdx +29 -29
- package/src/stories/docs/Introduction.mdx +3 -3
- package/src/stories/docs/Theming.mdx +2 -2
- package/src/stories/docs/element-controls.ts +32 -0
- package/src/stories/docs/theme-editor/theme-editor.tsx +1 -0
- package/src/stories/examples/ChoosingComponents.mdx +94 -0
- package/src/stories/examples/sample-data.ts +79 -0
- package/src/stories/message-actions.stories.tsx +13 -13
- package/src/stories/pattern-centered-conversation.stories.tsx +3 -3
- package/src/stories/pattern-docked-widget.stories.tsx +1 -1
- package/src/stories/pattern-empty-state.stories.tsx +3 -3
- package/src/stories/prompt-input-variants.stories.tsx +13 -13
- package/src/stories/streaming-response.stories.tsx +3 -3
- package/src/stories/typography.stories.tsx +4 -4
- package/src/ui/avatar.stories.tsx +7 -8
- package/src/ui/badge.stories.tsx +7 -8
- package/src/ui/button.stories.tsx +8 -9
- package/src/ui/button.tsx +1 -0
- package/src/ui/collapsible.stories.tsx +6 -7
- package/src/ui/dropdown.stories.tsx +6 -7
- package/src/ui/hover-card.stories.tsx +6 -7
- package/src/ui/resizable.stories.tsx +74 -9
- package/src/ui/resizable.tsx +351 -71
- package/src/ui/scroll-area.stories.tsx +6 -7
- package/src/ui/scroll-area.tsx +3 -1
- package/src/ui/separator.stories.tsx +7 -8
- package/src/ui/skeleton.stories.tsx +7 -8
- package/src/ui/textarea.stories.tsx +6 -7
- package/src/ui/tooltip.stories.tsx +8 -9
- package/theme.css +65 -65
- package/src/stories/docs/element-spec.tsx +0 -86
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { createSignal, onMount, onCleanup, For, Show, type JSX } from 'solid-js';
|
|
2
|
+
import { defineWebComponent } from './define';
|
|
3
|
+
import { ResizableHandle, normalizeSize } from '../ui/resizable';
|
|
4
|
+
|
|
5
|
+
type Orientation = 'horizontal' | 'vertical';
|
|
6
|
+
|
|
7
|
+
/** Parsed view of a `<kc-resizable-item>` light child. */
|
|
8
|
+
interface ItemInfo {
|
|
9
|
+
el: HTMLElement;
|
|
10
|
+
size?: string;
|
|
11
|
+
min?: string;
|
|
12
|
+
max?: string;
|
|
13
|
+
locked: boolean;
|
|
14
|
+
hidden: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Max number of panels a single group lays out (nest for more). */
|
|
18
|
+
const MAX_ITEMS = 3;
|
|
19
|
+
|
|
20
|
+
/** Convert a px-or-% bound to `data-*` attribute values the handle understands. */
|
|
21
|
+
function boundAttrs(value: string | undefined): { pxAttr?: string; pctAttr?: string } {
|
|
22
|
+
if (value === undefined || value === '') return {};
|
|
23
|
+
const t = value.trim();
|
|
24
|
+
if (t.endsWith('px')) return { pxAttr: String(parseFloat(t)) };
|
|
25
|
+
if (t.endsWith('%')) return { pctAttr: String(parseFloat(t)) };
|
|
26
|
+
if (Number.isFinite(Number(t))) return { pctAttr: t };
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface GroupProps extends Record<string, unknown> {
|
|
31
|
+
/** Layout axis: `horizontal` (row, default) or `vertical` (column). */
|
|
32
|
+
orientation?: Orientation;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface GroupEvents extends Record<string, unknown> {
|
|
36
|
+
/** Fired on drag-end / keyboard resize / visibility change. `detail.sizes` = panel sizes in percent. */
|
|
37
|
+
change: { sizes: number[] };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `<kc-resizable>` — a composable, resizable multi-panel layout (up to 3 panels)
|
|
42
|
+
* with auto-inserted draggable dividers. It lays out its `<kc-resizable-item>`
|
|
43
|
+
* light children along an axis (`orientation`), reading each item's `size`,
|
|
44
|
+
* `min`, `max`, `locked` and `hidden` attributes via a `MutationObserver`. A
|
|
45
|
+
* divider is interactive only between two unlocked, visible panels. Emits a
|
|
46
|
+
* `change` event (`detail.sizes`, percent) on resize / visibility change.
|
|
47
|
+
*/
|
|
48
|
+
defineWebComponent<GroupProps, GroupEvents>('kc-resizable', {
|
|
49
|
+
orientation: 'horizontal',
|
|
50
|
+
}, (props, { element, dispatch }) => {
|
|
51
|
+
const [items, setItems] = createSignal<ItemInfo[]>([]);
|
|
52
|
+
const orientation = (): Orientation => (props.orientation === 'vertical' ? 'vertical' : 'horizontal');
|
|
53
|
+
|
|
54
|
+
/** Read the current `<kc-resizable-item>` light children + their attributes. */
|
|
55
|
+
function readItems() {
|
|
56
|
+
const all = Array.from(element.children).filter(
|
|
57
|
+
(c): c is HTMLElement => c.tagName.toLowerCase() === 'kc-resizable-item',
|
|
58
|
+
);
|
|
59
|
+
if (all.length > MAX_ITEMS) {
|
|
60
|
+
// eslint-disable-next-line no-console
|
|
61
|
+
console.warn(
|
|
62
|
+
`<kc-resizable>: only the first ${MAX_ITEMS} <kc-resizable-item> children are laid out ` +
|
|
63
|
+
`(got ${all.length}). Nest <kc-resizable> for more.`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const capped = all.slice(0, MAX_ITEMS);
|
|
67
|
+
const parsed: ItemInfo[] = capped.map((el) => ({
|
|
68
|
+
el,
|
|
69
|
+
size: el.getAttribute('size') ?? undefined,
|
|
70
|
+
min: el.getAttribute('min') ?? undefined,
|
|
71
|
+
max: el.getAttribute('max') ?? undefined,
|
|
72
|
+
locked: el.hasAttribute('locked') && el.getAttribute('locked') !== 'false',
|
|
73
|
+
// Honour both the `hidden` boolean attribute and the IDL property — Solid
|
|
74
|
+
// (and direct `el.hidden = true`) set the property, which doesn't reflect
|
|
75
|
+
// to the attribute on a custom element.
|
|
76
|
+
hidden: el.hidden || (el.hasAttribute('hidden') && el.getAttribute('hidden') !== 'false'),
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
// Assign each visible item to its panel slot by visible order; clear the
|
|
80
|
+
// rest so hidden/extra items don't leak into a slot.
|
|
81
|
+
let visIdx = 0;
|
|
82
|
+
for (const info of parsed) {
|
|
83
|
+
if (info.hidden) {
|
|
84
|
+
info.el.removeAttribute('slot');
|
|
85
|
+
} else {
|
|
86
|
+
info.el.setAttribute('slot', `p${visIdx}`);
|
|
87
|
+
visIdx++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const el of all.slice(MAX_ITEMS)) el.removeAttribute('slot');
|
|
91
|
+
|
|
92
|
+
setItems(parsed);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const visible = () => items().filter((i) => !i.hidden);
|
|
96
|
+
|
|
97
|
+
/** Compute current panel sizes (percent of container) from the rendered DOM. */
|
|
98
|
+
function currentSizes(): number[] {
|
|
99
|
+
return visible().map((info) => {
|
|
100
|
+
const panel = info.el.assignedSlot?.closest('[data-panel]') as HTMLElement | null;
|
|
101
|
+
const target = panel ?? info.el;
|
|
102
|
+
const parent = target.parentElement;
|
|
103
|
+
const dim = orientation() === 'horizontal' ? 'width' : 'height';
|
|
104
|
+
const total = parent ? parent.getBoundingClientRect()[dim] : 0;
|
|
105
|
+
const val = target.getBoundingClientRect()[dim];
|
|
106
|
+
return total > 0 ? Math.round((val / total) * 100) : 0;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function emitChange() {
|
|
111
|
+
dispatch('change', { sizes: currentSizes() });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
onMount(() => {
|
|
115
|
+
readItems();
|
|
116
|
+
const mo = new MutationObserver(() => {
|
|
117
|
+
readItems();
|
|
118
|
+
// A childList / attribute change (e.g. show/hide) re-lays out → emit.
|
|
119
|
+
queueMicrotask(emitChange);
|
|
120
|
+
});
|
|
121
|
+
mo.observe(element, {
|
|
122
|
+
childList: true,
|
|
123
|
+
// Attribute changes happen on the <kc-resizable-item> *children*, so we
|
|
124
|
+
// must observe the subtree (filtered to the config attributes) — not just
|
|
125
|
+
// the host's own attributes.
|
|
126
|
+
subtree: true,
|
|
127
|
+
attributes: true,
|
|
128
|
+
attributeFilter: ['size', 'locked', 'min', 'max', 'hidden'],
|
|
129
|
+
});
|
|
130
|
+
onCleanup(() => mo.disconnect());
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const isHoriz = () => orientation() === 'horizontal';
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<>
|
|
137
|
+
{/* A resizable layout fills its container — default the host to a block
|
|
138
|
+
that stretches, so consumers only need to give a parent (or the element)
|
|
139
|
+
a height. Override with an explicit height on the element if needed. */}
|
|
140
|
+
<style>{':host{display:block;height:100%}'}</style>
|
|
141
|
+
<div
|
|
142
|
+
data-resizable-root
|
|
143
|
+
data-orientation={orientation()}
|
|
144
|
+
style={{
|
|
145
|
+
display: 'flex',
|
|
146
|
+
'flex-direction': isHoriz() ? 'row' : 'column',
|
|
147
|
+
width: '100%',
|
|
148
|
+
height: '100%',
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<For each={visible()}>
|
|
152
|
+
{(info, i) => {
|
|
153
|
+
const min = boundAttrs(info.min);
|
|
154
|
+
const max = boundAttrs(info.max);
|
|
155
|
+
const basis = normalizeSize(info.size);
|
|
156
|
+
const prev = () => visible()[i() - 1];
|
|
157
|
+
const isStatic = () => !!(info.locked || prev()?.locked);
|
|
158
|
+
return (
|
|
159
|
+
<>
|
|
160
|
+
<Show when={i() > 0}>
|
|
161
|
+
<ResizableHandle
|
|
162
|
+
withHandle
|
|
163
|
+
orientation={orientation()}
|
|
164
|
+
static={isStatic()}
|
|
165
|
+
onPanelResize={() => queueMicrotask(emitChange)}
|
|
166
|
+
/>
|
|
167
|
+
</Show>
|
|
168
|
+
<div
|
|
169
|
+
data-panel
|
|
170
|
+
data-locked={info.locked ? '' : undefined}
|
|
171
|
+
data-min-size={min.pxAttr}
|
|
172
|
+
data-min-size-pct={min.pctAttr}
|
|
173
|
+
data-max-size={max.pxAttr}
|
|
174
|
+
data-max-size-pct={max.pctAttr}
|
|
175
|
+
style={{
|
|
176
|
+
// The panel is a flex item of the root: `flex-basis` sizes its
|
|
177
|
+
// MAIN axis (width when horizontal, height when vertical) and the
|
|
178
|
+
// drag/keyboard handlers rewrite that basis — so the layout spine
|
|
179
|
+
// stays a plain flex row/column (drag math unchanged).
|
|
180
|
+
//
|
|
181
|
+
// For the FILL, the panel is itself a `display:grid` with a single
|
|
182
|
+
// `minmax(0,1fr)` cell on BOTH axes. A grid item (the slotted
|
|
183
|
+
// `<kc-resizable-item>`) stretches to fill its cell on both axes by
|
|
184
|
+
// default (`place-items:stretch`), and a `1fr` track inside a
|
|
185
|
+
// definite-sized grid is a *definite* length — so content fills
|
|
186
|
+
// height AND width whether or not it sets `height:100%`, in both
|
|
187
|
+
// orientations. A flex panel only stretches the CROSS axis (which is
|
|
188
|
+
// why the previous `display:flex` panel collapsed the main axis to
|
|
189
|
+
// content height). This mirrors how Shoelace/Web Awesome
|
|
190
|
+
// `<sl-split-panel>` lay panels out with grid for guaranteed fill,
|
|
191
|
+
// adapted here to a per-panel grid cell (our double slot boundary
|
|
192
|
+
// makes a slot-as-grid-item unusable — a slot with assigned nodes
|
|
193
|
+
// has a zero box). min:0 on both axes enables shrink-to-scroll;
|
|
194
|
+
// overflow clips (the item owns the scroll).
|
|
195
|
+
...(basis !== undefined
|
|
196
|
+
? { 'flex-basis': basis, 'flex-grow': '0', 'flex-shrink': '0' }
|
|
197
|
+
: { flex: '1 1 0%' }),
|
|
198
|
+
display: 'grid',
|
|
199
|
+
'grid-template-rows': 'minmax(0, 1fr)',
|
|
200
|
+
'grid-template-columns': 'minmax(0, 1fr)',
|
|
201
|
+
'min-width': '0',
|
|
202
|
+
'min-height': '0',
|
|
203
|
+
overflow: 'hidden',
|
|
204
|
+
}}
|
|
205
|
+
>
|
|
206
|
+
<slot name={`p${i()}`} />
|
|
207
|
+
</div>
|
|
208
|
+
</>
|
|
209
|
+
);
|
|
210
|
+
}}
|
|
211
|
+
</For>
|
|
212
|
+
</div>
|
|
213
|
+
</>
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
interface ItemProps extends Record<string, unknown> {
|
|
218
|
+
/** Initial main-axis size: `"280px"` (fixed) or `"25%"`/`25` (percent). Omitted → flexible. */
|
|
219
|
+
size?: string;
|
|
220
|
+
/** Minimum size during resize (px or %). */
|
|
221
|
+
min?: string;
|
|
222
|
+
/** Maximum size during resize (px or %). */
|
|
223
|
+
max?: string;
|
|
224
|
+
/** Fix this panel's size; adjacent dividers become non-draggable. */
|
|
225
|
+
locked?: boolean;
|
|
226
|
+
/** Hide this panel; its divider is dropped and the rest reflow. */
|
|
227
|
+
hidden?: boolean;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* `<kc-resizable-item>` — a passive config-carrier inside `<kc-resizable>`. It
|
|
232
|
+
* renders its own slotted light content (`<slot/>`); the parent `<kc-resizable>`
|
|
233
|
+
* reads its `size`/`min`/`max`/`locked`/`hidden` attributes to lay it out.
|
|
234
|
+
*/
|
|
235
|
+
defineWebComponent<ItemProps>('kc-resizable-item', {
|
|
236
|
+
size: undefined,
|
|
237
|
+
min: undefined,
|
|
238
|
+
max: undefined,
|
|
239
|
+
locked: false,
|
|
240
|
+
hidden: false,
|
|
241
|
+
}, () => (
|
|
242
|
+
<>
|
|
243
|
+
{/* The item host fills the panel's single grid cell (the panel stretches it on
|
|
244
|
+
both axes) and OWNS the scroll: `display:block; width/height:100%;
|
|
245
|
+
overflow:auto`. min:0 enables shrink-to-scroll.
|
|
246
|
+
|
|
247
|
+
The FILL of the *slotted* content happens one level in: a `display:grid`
|
|
248
|
+
wrapper with a single `minmax(0,1fr)` cell whose ONLY child is the `<slot>`.
|
|
249
|
+
A grid item stretches to fill its cell on BOTH axes by default
|
|
250
|
+
(`place-items:stretch`) into a *definite* `1fr` track, so slotted content
|
|
251
|
+
fills width AND height whether or not it sets `height:100%`. The grid MUST
|
|
252
|
+
be its own element wrapping the slot — NOT the host — because the element
|
|
253
|
+
facade renders an empty portal-mount `<div>` (for overlays) as a sibling of
|
|
254
|
+
the facade output; if the host itself were the grid, that portal div would
|
|
255
|
+
become a second grid item and steal a track (collapsing content to the auto
|
|
256
|
+
row). Scoping the grid to a wrapper whose sole child is the slot keeps
|
|
257
|
+
exactly one grid item and is robust to the facade's portal sibling.
|
|
258
|
+
Mirrors how Shoelace/Web Awesome `<sl-split-panel>` use grid for fill. */}
|
|
259
|
+
<style>{':host{display:block;width:100%;height:100%;min-width:0;min-height:0;overflow:auto}'}</style>
|
|
260
|
+
<div style={{ display: 'grid', 'grid-template-rows': 'minmax(0, 1fr)', 'grid-template-columns': 'minmax(0, 1fr)', width: '100%', height: '100%', 'min-width': '0', 'min-height': '0' }}>
|
|
261
|
+
<slot />
|
|
262
|
+
</div>
|
|
263
|
+
</>
|
|
264
|
+
) as unknown as JSX.Element);
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
2
|
import { onMount } from 'solid-js';
|
|
3
3
|
import './register'; // side effect: registers the custom elements
|
|
4
|
-
import {
|
|
5
|
-
import { argTypesFor } from '../stories/docs/element-controls';
|
|
4
|
+
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
6
5
|
|
|
7
6
|
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
8
7
|
declare module 'solid-js' {
|
|
9
8
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
10
9
|
namespace JSX {
|
|
11
10
|
interface IntrinsicElements {
|
|
12
|
-
'
|
|
11
|
+
'kc-response-stream': JSX.HTMLAttributes<HTMLElement> & {
|
|
13
12
|
mode?: string;
|
|
14
13
|
speed?: number;
|
|
15
14
|
};
|
|
@@ -20,14 +19,14 @@ declare module 'solid-js' {
|
|
|
20
19
|
const STREAM_TEXT =
|
|
21
20
|
"This text reveals with a typewriter animation, streamed character by character — exactly how you'd render a live assistant reply.";
|
|
22
21
|
|
|
23
|
-
/** Render `<
|
|
22
|
+
/** Render `<kc-response-stream>` with the `text` set as a JS property. */
|
|
24
23
|
function StreamElement(props: { text: string; mode?: string; speed?: number }) {
|
|
25
24
|
let el: (HTMLElement & { text?: string }) | undefined;
|
|
26
25
|
onMount(() => {
|
|
27
26
|
if (el) el.text = props.text;
|
|
28
27
|
});
|
|
29
28
|
return (
|
|
30
|
-
<
|
|
29
|
+
<kc-response-stream
|
|
31
30
|
ref={(e) => (el = e as HTMLElement)}
|
|
32
31
|
mode={props.mode}
|
|
33
32
|
speed={props.speed}
|
|
@@ -37,7 +36,7 @@ function StreamElement(props: { text: string; mode?: string; speed?: number }) {
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
40
|
-
<
|
|
39
|
+
<kc-response-stream id="stream" mode="typewriter" speed="20"></kc-response-stream>
|
|
41
40
|
|
|
42
41
|
<script type="module">
|
|
43
42
|
import '@kitnai/chat/elements'; // registers the custom elements
|
|
@@ -49,20 +48,18 @@ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
|
49
48
|
</script>`;
|
|
50
49
|
|
|
51
50
|
const meta = {
|
|
52
|
-
title: 'Web Components/
|
|
51
|
+
title: 'Web Components/kc-response-stream',
|
|
53
52
|
tags: ['autodocs'],
|
|
54
|
-
argTypes: argTypesFor('
|
|
53
|
+
argTypes: argTypesFor('kc-response-stream'),
|
|
55
54
|
parameters: {
|
|
56
55
|
layout: 'fullscreen',
|
|
57
56
|
docs: {
|
|
58
|
-
description:
|
|
59
|
-
|
|
60
|
-
'`<kitn-response-stream>` is the framework-agnostic **web component** that reveals text with a typewriter or fade animation — the building block for streamed assistant replies, isolated in **Shadow DOM**.',
|
|
57
|
+
description: specDescription('kc-response-stream', [
|
|
58
|
+
'`<kc-response-stream>` is the framework-agnostic **web component** that reveals text with a typewriter or fade animation — the building block for streamed assistant replies, isolated in **Shadow DOM**.',
|
|
61
59
|
'**When to use:** animating a response as it arrives. Pass a finished string to replay an animation, or an `AsyncIterable<string>` to drive it from a live stream. In SolidJS, use the `ResponseStream` primitive.',
|
|
62
60
|
"**How to use:** register once with `import '@kitnai/chat/elements'`, set the `text` **property** (string or async iterable), tune `mode` (`typewriter` / `fade`) and `speed`, and listen for the `complete` **CustomEvent**.",
|
|
63
61
|
'See the **Code** tab for HTML usage.',
|
|
64
|
-
]
|
|
65
|
-
},
|
|
62
|
+
]),
|
|
66
63
|
},
|
|
67
64
|
},
|
|
68
65
|
} satisfies Meta;
|
|
@@ -70,12 +67,6 @@ const meta = {
|
|
|
70
67
|
export default meta;
|
|
71
68
|
type Story = StoryObj;
|
|
72
69
|
|
|
73
|
-
/** Full generated API reference — properties, events, tokens, and composed-from. */
|
|
74
|
-
export const API: Story = {
|
|
75
|
-
render: () => <ElementSpec tag="kitn-response-stream" />,
|
|
76
|
-
parameters: { layout: 'padded' },
|
|
77
|
-
};
|
|
78
|
-
|
|
79
70
|
/** Typewriter reveal (the default). */
|
|
80
71
|
export const Typewriter: Story = {
|
|
81
72
|
render: () => <StreamElement text={STREAM_TEXT} mode="typewriter" speed={20} />,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineWebComponent } from './define';
|
|
2
2
|
import { ResponseStream, type Mode } from '../components/response-stream';
|
|
3
3
|
|
|
4
4
|
interface Props extends Record<string, unknown> {
|
|
@@ -13,17 +13,17 @@ interface Props extends Record<string, unknown> {
|
|
|
13
13
|
as?: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/** Events fired by `<
|
|
16
|
+
/** Events fired by `<kc-response-stream>`. */
|
|
17
17
|
interface Events {
|
|
18
18
|
/** Streaming finished. */
|
|
19
19
|
complete: void;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* `<
|
|
23
|
+
* `<kc-response-stream>` — reveals text with a typewriter or fade animation.
|
|
24
24
|
* Text via the `text` property; `mode`/`speed` attributes; emits `complete`.
|
|
25
25
|
*/
|
|
26
|
-
|
|
26
|
+
defineWebComponent<Props, Events>('kc-response-stream', {
|
|
27
27
|
text: '',
|
|
28
28
|
mode: 'typewriter',
|
|
29
29
|
speed: 20,
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
2
|
import { onMount } from 'solid-js';
|
|
3
3
|
import './register'; // side effect: registers the custom elements
|
|
4
|
-
import {
|
|
5
|
-
import { argTypesFor } from '../stories/docs/element-controls';
|
|
4
|
+
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
6
5
|
|
|
7
6
|
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
8
7
|
declare module 'solid-js' {
|
|
9
8
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
10
9
|
namespace JSX {
|
|
11
10
|
interface IntrinsicElements {
|
|
12
|
-
'
|
|
11
|
+
'kc-sources': JSX.HTMLAttributes<HTMLElement>;
|
|
13
12
|
}
|
|
14
13
|
}
|
|
15
14
|
}
|
|
@@ -27,19 +26,19 @@ const sampleSources: SourceItem[] = [
|
|
|
27
26
|
{ href: 'https://solidjs.com', title: 'SolidJS', description: 'A reactive UI library.', showFavicon: true },
|
|
28
27
|
];
|
|
29
28
|
|
|
30
|
-
/** Render the actual `<
|
|
29
|
+
/** Render the actual `<kc-sources>` custom element with a `sources` property. */
|
|
31
30
|
function SourceListElement() {
|
|
32
31
|
let el: (HTMLElement & { sources?: SourceItem[] }) | undefined;
|
|
33
32
|
onMount(() => {
|
|
34
33
|
if (el) el.sources = sampleSources;
|
|
35
34
|
});
|
|
36
35
|
return (
|
|
37
|
-
<
|
|
36
|
+
<kc-sources ref={(e) => (el = e as HTMLElement)} style={{ display: 'block', padding: '16px', 'max-width': '720px' }} />
|
|
38
37
|
);
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
42
|
-
<
|
|
41
|
+
<kc-sources id="srcs" show-favicon></kc-sources>
|
|
43
42
|
|
|
44
43
|
<script type="module">
|
|
45
44
|
import '@kitnai/chat/elements'; // registers the custom elements
|
|
@@ -52,20 +51,18 @@ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
|
52
51
|
</script>`;
|
|
53
52
|
|
|
54
53
|
const meta = {
|
|
55
|
-
title: 'Web Components/
|
|
54
|
+
title: 'Web Components/kc-sources',
|
|
56
55
|
tags: ['autodocs'],
|
|
57
|
-
argTypes: argTypesFor('
|
|
56
|
+
argTypes: argTypesFor('kc-sources'),
|
|
58
57
|
parameters: {
|
|
59
58
|
layout: 'fullscreen',
|
|
60
59
|
docs: {
|
|
61
|
-
description:
|
|
62
|
-
|
|
63
|
-
'
|
|
64
|
-
'**When to use:** showing the sources behind an assistant answer in a non-Solid app. For a single citation, use `<kitn-source>`; in SolidJS, compose `SourceList` + `Source`.',
|
|
60
|
+
description: specDescription('kc-sources', [
|
|
61
|
+
'`<kc-sources>` is the framework-agnostic **web component** for a wrapped row of citation links (each with its own hover-card preview), isolated in **Shadow DOM**.',
|
|
62
|
+
'**When to use:** showing the sources behind an assistant answer in a non-Solid app. For a single citation, use `<kc-source>`; in SolidJS, compose `SourceList` + `Source`.',
|
|
65
63
|
"**How to use:** register once with `import '@kitnai/chat/elements'`, set the data via the `sources` **property** (each item: `href`, `title`, `description`, `label`, `showFavicon`), and set `show-favicon` to enable favicons for all items (a per-item `showFavicon` overrides it).",
|
|
66
64
|
'See the **Code** tab for HTML usage.',
|
|
67
|
-
]
|
|
68
|
-
},
|
|
65
|
+
]),
|
|
69
66
|
},
|
|
70
67
|
},
|
|
71
68
|
} satisfies Meta;
|
|
@@ -73,12 +70,6 @@ const meta = {
|
|
|
73
70
|
export default meta;
|
|
74
71
|
type Story = StoryObj;
|
|
75
72
|
|
|
76
|
-
/** Full generated API reference — properties, events, tokens, and composed-from. */
|
|
77
|
-
export const API: Story = {
|
|
78
|
-
render: () => <ElementSpec tag="kitn-source-list" />,
|
|
79
|
-
parameters: { layout: 'padded' },
|
|
80
|
-
};
|
|
81
|
-
|
|
82
73
|
/** Two citations rendered as a wrapped list with favicons. */
|
|
83
74
|
export const Default: Story = {
|
|
84
75
|
render: () => <SourceListElement />,
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
2
|
import { onMount } from 'solid-js';
|
|
3
3
|
import './register'; // side effect: registers the custom elements
|
|
4
|
-
import {
|
|
5
|
-
import { argTypesFor } from '../stories/docs/element-controls';
|
|
4
|
+
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
6
5
|
|
|
7
6
|
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
8
7
|
declare module 'solid-js' {
|
|
9
8
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
10
9
|
namespace JSX {
|
|
11
10
|
interface IntrinsicElements {
|
|
12
|
-
'
|
|
11
|
+
'kc-source': JSX.HTMLAttributes<HTMLElement>;
|
|
13
12
|
}
|
|
14
13
|
}
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
/** Render the actual `<
|
|
16
|
+
/** Render the actual `<kc-source>` custom element configured by attributes. */
|
|
18
17
|
function SourceElement(props: {
|
|
19
18
|
href: string;
|
|
20
19
|
label?: string;
|
|
@@ -31,37 +30,35 @@ function SourceElement(props: {
|
|
|
31
30
|
if (props.description) el.setAttribute('description', props.description);
|
|
32
31
|
if (props.showFavicon) el.setAttribute('show-favicon', '');
|
|
33
32
|
});
|
|
34
|
-
return <
|
|
33
|
+
return <kc-source ref={(e) => (el = e as HTMLElement)} style={{ display: 'inline-block', padding: '16px' }} />;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
38
|
-
<
|
|
37
|
+
<kc-source
|
|
39
38
|
href="https://kitn.dev"
|
|
40
39
|
label="kitn"
|
|
41
40
|
headline="kitn — the kit"
|
|
42
41
|
description="Composable SolidJS + web-component chat UI."
|
|
43
42
|
show-favicon
|
|
44
|
-
></
|
|
43
|
+
></kc-source>
|
|
45
44
|
|
|
46
45
|
<script type="module">
|
|
47
46
|
import '@kitnai/chat/elements'; // registers the custom elements
|
|
48
47
|
</script>`;
|
|
49
48
|
|
|
50
49
|
const meta = {
|
|
51
|
-
title: 'Web Components/
|
|
50
|
+
title: 'Web Components/kc-source',
|
|
52
51
|
tags: ['autodocs'],
|
|
53
|
-
argTypes: argTypesFor('
|
|
52
|
+
argTypes: argTypesFor('kc-source'),
|
|
54
53
|
parameters: {
|
|
55
54
|
layout: 'fullscreen',
|
|
56
55
|
docs: {
|
|
57
|
-
description:
|
|
58
|
-
|
|
59
|
-
'
|
|
60
|
-
'**When to use:** inlining a single source citation in a non-Solid app. For multiple sources, use `<kitn-source-list>`; in SolidJS, compose the `Source` primitives.',
|
|
56
|
+
description: specDescription('kc-source', [
|
|
57
|
+
'`<kc-source>` is the framework-agnostic **web component** for a single citation link with a hover-card preview, isolated in **Shadow DOM**.',
|
|
58
|
+
'**When to use:** inlining a single source citation in a non-Solid app. For multiple sources, use `<kc-sources>`; in SolidJS, compose the `Source` primitives.',
|
|
61
59
|
"**How to use:** register once with `import '@kitnai/chat/elements'`, then set `href` (the link, also the default label/favicon source), `label`, `headline` (the hover headline — note `headline`, not `title`), `description`, and the `show-favicon` flag via attributes.",
|
|
62
60
|
'See the **Code** tab for HTML usage.',
|
|
63
|
-
]
|
|
64
|
-
},
|
|
61
|
+
]),
|
|
65
62
|
},
|
|
66
63
|
},
|
|
67
64
|
} satisfies Meta;
|
|
@@ -69,12 +66,6 @@ const meta = {
|
|
|
69
66
|
export default meta;
|
|
70
67
|
type Story = StoryObj;
|
|
71
68
|
|
|
72
|
-
/** Full generated API reference — properties, events, tokens, and composed-from. */
|
|
73
|
-
export const API: Story = {
|
|
74
|
-
render: () => <ElementSpec tag="kitn-source" />,
|
|
75
|
-
parameters: { layout: 'padded' },
|
|
76
|
-
};
|
|
77
|
-
|
|
78
69
|
/** A citation with a custom label, hover headline/description, and favicon. */
|
|
79
70
|
export const Default: Story = {
|
|
80
71
|
render: () => (
|
package/src/elements/source.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { For, Show } from 'solid-js';
|
|
2
|
-
import {
|
|
2
|
+
import { defineWebComponent } from './define';
|
|
3
3
|
import { Source, SourceTrigger, SourceContent, SourceList } from '../components/source';
|
|
4
4
|
|
|
5
|
-
// --- <
|
|
5
|
+
// --- <kc-source> — a single citation link with hover preview ---
|
|
6
6
|
|
|
7
7
|
interface SourceProps extends Record<string, unknown> {
|
|
8
8
|
/** The URL this citation links to (the domain also seeds the default label/favicon). */
|
|
@@ -18,7 +18,7 @@ interface SourceProps extends Record<string, unknown> {
|
|
|
18
18
|
showFavicon?: boolean;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
defineWebComponent<SourceProps>('kc-source', {
|
|
22
22
|
href: '',
|
|
23
23
|
label: undefined,
|
|
24
24
|
headline: '',
|
|
@@ -33,7 +33,7 @@ defineKitnElement<SourceProps>('kitn-source', {
|
|
|
33
33
|
</Show>
|
|
34
34
|
));
|
|
35
35
|
|
|
36
|
-
// --- <
|
|
36
|
+
// --- <kc-sources> — a wrapped list of citation links ---
|
|
37
37
|
|
|
38
38
|
interface SourceItem {
|
|
39
39
|
href: string;
|
|
@@ -50,7 +50,7 @@ interface SourceListProps extends Record<string, unknown> {
|
|
|
50
50
|
showFavicon?: boolean;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
defineWebComponent<SourceListProps>('kc-sources', {
|
|
54
54
|
sources: [],
|
|
55
55
|
showFavicon: false,
|
|
56
56
|
}, (props, { flag }) => (
|