@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,74 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { createSignal } from 'solid-js';
|
|
3
|
+
import { ConfirmCard, type ConfirmCardData } from './confirm-card';
|
|
4
|
+
import { componentDescription } from '../stories/docs/element-controls';
|
|
5
|
+
import type { CardEvent, CardHost, CardContext } from '../primitives/card-contract';
|
|
6
|
+
|
|
7
|
+
const ctx: CardContext = { theme: { mode: 'light' }, locale: 'en' };
|
|
8
|
+
|
|
9
|
+
/** Renders the Solid <ConfirmCard> with a capturing `host`, logging emitted events. */
|
|
10
|
+
function Demo(props: { def: ConfirmCardData; heading?: string; cardId: string }) {
|
|
11
|
+
const [log, setLog] = createSignal<CardEvent[]>([]);
|
|
12
|
+
const host: CardHost = { context: () => ctx, emit: (e) => setLog((p) => [...p, e]) };
|
|
13
|
+
return (
|
|
14
|
+
<div style={{ 'max-width': '460px', display: 'flex', 'flex-direction': 'column', gap: '12px' }}>
|
|
15
|
+
<ConfirmCard host={host} data={props.def} heading={props.heading} cardId={props.cardId} />
|
|
16
|
+
<pre
|
|
17
|
+
style={{
|
|
18
|
+
margin: 0,
|
|
19
|
+
'max-height': '180px',
|
|
20
|
+
overflow: 'auto',
|
|
21
|
+
background: 'var(--color-muted, #f4f4f5)',
|
|
22
|
+
'border-radius': '8px',
|
|
23
|
+
padding: '8px',
|
|
24
|
+
'font-size': '12px',
|
|
25
|
+
}}
|
|
26
|
+
>
|
|
27
|
+
{log().length === 0 ? '// emitted CardEvents appear here' : JSON.stringify(log(), null, 2)}
|
|
28
|
+
</pre>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const APPROVE: ConfirmCardData = {
|
|
34
|
+
body: 'This will apply 3 pending migrations to production. This cannot be undone.',
|
|
35
|
+
tone: 'warning',
|
|
36
|
+
actions: [
|
|
37
|
+
{ id: 'approve', label: 'Run migration', style: 'primary', default: true },
|
|
38
|
+
{ id: 'reject', label: 'Cancel' },
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const DESTRUCTIVE: ConfirmCardData = {
|
|
43
|
+
body: 'Permanently delete 12 files? This cannot be undone.',
|
|
44
|
+
tone: 'danger',
|
|
45
|
+
actions: [
|
|
46
|
+
{ id: 'delete', label: 'Delete files', style: 'destructive', default: true },
|
|
47
|
+
{ id: 'cancel', label: 'Keep them' },
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const meta = {
|
|
52
|
+
title: 'Components/ConfirmCard',
|
|
53
|
+
component: ConfirmCard,
|
|
54
|
+
tags: ['autodocs'],
|
|
55
|
+
parameters: {
|
|
56
|
+
layout: 'padded',
|
|
57
|
+
docs: {
|
|
58
|
+
description: componentDescription([
|
|
59
|
+
'The SolidJS layer behind `<kc-confirm>`. Pass a `host` (a `CardHost`) to receive the emitted `CardEvent`s directly (the native-host path), or wrap in a `CardProvider`. Activating an action emits the `action` verb and resolves the card.',
|
|
60
|
+
]),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
} satisfies Meta<typeof ConfirmCard>;
|
|
64
|
+
|
|
65
|
+
export default meta;
|
|
66
|
+
type Story = StoryObj<typeof ConfirmCard>;
|
|
67
|
+
|
|
68
|
+
export const ApproveReject: Story = {
|
|
69
|
+
render: () => <Demo def={APPROVE} heading="Run database migration?" cardId="card-approve" />,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const Destructive: Story = {
|
|
73
|
+
render: () => <Demo def={DESTRUCTIVE} heading="Delete files?" cardId="card-delete" />,
|
|
74
|
+
};
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type JSX,
|
|
3
|
+
For,
|
|
4
|
+
Show,
|
|
5
|
+
splitProps,
|
|
6
|
+
mergeProps,
|
|
7
|
+
createSignal,
|
|
8
|
+
createMemo,
|
|
9
|
+
createEffect,
|
|
10
|
+
on,
|
|
11
|
+
ErrorBoundary,
|
|
12
|
+
} from 'solid-js';
|
|
13
|
+
import { cn } from '../utils/cn';
|
|
14
|
+
import { Button } from '../ui/button';
|
|
15
|
+
import { Card } from './card';
|
|
16
|
+
import type { CardEnvelope, CardEvent, CardHost } from '../primitives/card-contract';
|
|
17
|
+
import { emitCardEvent } from '../primitives/card-routing';
|
|
18
|
+
import { useCardHost } from '../primitives/card-host';
|
|
19
|
+
import { AlertTriangle, Check, X } from 'lucide-solid';
|
|
20
|
+
|
|
21
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
// Types (confirm.schema.json) — see src/primitives/card-schemas/confirm.schema.json
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export type ConfirmActionStyle = 'primary' | 'default' | 'destructive';
|
|
26
|
+
export type ConfirmTone = 'default' | 'warning' | 'danger';
|
|
27
|
+
|
|
28
|
+
export interface ConfirmAction {
|
|
29
|
+
id: string;
|
|
30
|
+
label: string;
|
|
31
|
+
style?: ConfirmActionStyle;
|
|
32
|
+
payload?: unknown;
|
|
33
|
+
default?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ConfirmCardData {
|
|
37
|
+
heading?: string;
|
|
38
|
+
body?: string;
|
|
39
|
+
tone?: ConfirmTone;
|
|
40
|
+
actions: ConfirmAction[]; // 1..4
|
|
41
|
+
dismissible?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type ConfirmCardEnvelope = CardEnvelope<'confirm', ConfirmCardData>;
|
|
45
|
+
|
|
46
|
+
export const CONFIRM_CARD_TYPE = 'confirm' as const;
|
|
47
|
+
|
|
48
|
+
const VALID_STYLES = new Set<ConfirmActionStyle>(['primary', 'default', 'destructive']);
|
|
49
|
+
|
|
50
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
// Pure helpers (unit-tested in isolation).
|
|
52
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
/** Map an action's `style` to a Button variant (falls back + warns on unknown). */
|
|
55
|
+
export function buttonVariantForStyle(
|
|
56
|
+
style: ConfirmActionStyle | undefined,
|
|
57
|
+
): 'default' | 'outline' | 'destructive' {
|
|
58
|
+
switch (style) {
|
|
59
|
+
case 'primary':
|
|
60
|
+
return 'default';
|
|
61
|
+
case 'destructive':
|
|
62
|
+
return 'destructive';
|
|
63
|
+
case 'default':
|
|
64
|
+
case undefined:
|
|
65
|
+
return 'outline';
|
|
66
|
+
default:
|
|
67
|
+
// eslint-disable-next-line no-console
|
|
68
|
+
console.warn(`[kc-confirm] unknown action style "${style as string}"; using default`);
|
|
69
|
+
return 'outline';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** De-dupe actions by id (first wins) and validate their shape. Returns the usable
|
|
74
|
+
* list + an optional error message when there's nothing renderable. */
|
|
75
|
+
export function normalizeActions(actions: unknown): {
|
|
76
|
+
actions: ConfirmAction[];
|
|
77
|
+
error?: string;
|
|
78
|
+
} {
|
|
79
|
+
if (!Array.isArray(actions) || actions.length === 0) {
|
|
80
|
+
return { actions: [], error: "This card couldn't be displayed." };
|
|
81
|
+
}
|
|
82
|
+
const seen = new Set<string>();
|
|
83
|
+
const out: ConfirmAction[] = [];
|
|
84
|
+
for (const a of actions) {
|
|
85
|
+
if (!a || typeof a !== 'object') continue;
|
|
86
|
+
const action = a as Partial<ConfirmAction>;
|
|
87
|
+
if (typeof action.id !== 'string' || action.id.length === 0) continue;
|
|
88
|
+
if (typeof action.label !== 'string' || action.label.length === 0) continue;
|
|
89
|
+
if (seen.has(action.id)) {
|
|
90
|
+
// eslint-disable-next-line no-console
|
|
91
|
+
console.warn(`[kc-confirm] duplicate action id "${action.id}" ignored`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
seen.add(action.id);
|
|
95
|
+
const style = VALID_STYLES.has(action.style as ConfirmActionStyle)
|
|
96
|
+
? (action.style as ConfirmActionStyle)
|
|
97
|
+
: undefined;
|
|
98
|
+
if (action.style !== undefined && style === undefined) {
|
|
99
|
+
// eslint-disable-next-line no-console
|
|
100
|
+
console.warn(`[kc-confirm] unknown action style "${String(action.style)}"; using default`);
|
|
101
|
+
}
|
|
102
|
+
out.push({
|
|
103
|
+
id: action.id,
|
|
104
|
+
label: action.label,
|
|
105
|
+
style,
|
|
106
|
+
payload: action.payload,
|
|
107
|
+
default: action.default === true,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (out.length === 0) return { actions: [], error: "This card couldn't be displayed." };
|
|
111
|
+
return { actions: out };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The id of the default action (first with `default:true`), if any. */
|
|
115
|
+
export function defaultActionId(actions: ConfirmAction[]): string | undefined {
|
|
116
|
+
return actions.find((a) => a.default)?.id;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
120
|
+
// The <ConfirmCard> component.
|
|
121
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
export interface ConfirmCardProps {
|
|
124
|
+
/** The confirm definition (CardEnvelope.data). */
|
|
125
|
+
data?: ConfirmCardData;
|
|
126
|
+
/** The card id used to correlate every emitted CardEvent. */
|
|
127
|
+
cardId?: string;
|
|
128
|
+
/** The envelope title rendered in the card chrome. */
|
|
129
|
+
heading?: string;
|
|
130
|
+
/** Optional explicit CardHost (otherwise read from a CardProvider, otherwise the
|
|
131
|
+
* bubbling `kc-card` CustomEvent off `hostElement`). */
|
|
132
|
+
host?: CardHost;
|
|
133
|
+
/** The custom-element host node, for the bubbling `kc-card` fallback emit. */
|
|
134
|
+
hostElement?: HTMLElement;
|
|
135
|
+
/** Focus the default action on mount. Default OFF (no focus-stealing mid-stream). */
|
|
136
|
+
autofocus?: boolean;
|
|
137
|
+
class?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* `ConfirmCard` — a named-intent approval card. Renders a title + body + a small
|
|
142
|
+
* set of action buttons inside `Card` chrome. Activating an action emits the Card
|
|
143
|
+
* contract's `action` verb (`{ kind:'action', cardId, action, payload }`) and
|
|
144
|
+
* resolves the card (other actions disabled, the chosen one marked) so the same
|
|
145
|
+
* approval can't double-fire. Emits `ready` on mount, `dismiss` for the optional
|
|
146
|
+
* close affordance, and `error` for an unusable definition (inline error state).
|
|
147
|
+
*/
|
|
148
|
+
export function ConfirmCard(props: ConfirmCardProps): JSX.Element {
|
|
149
|
+
const merged = mergeProps({ cardId: 'kc-confirm', autofocus: false }, props);
|
|
150
|
+
const [local] = splitProps(merged, [
|
|
151
|
+
'data',
|
|
152
|
+
'cardId',
|
|
153
|
+
'heading',
|
|
154
|
+
'host',
|
|
155
|
+
'hostElement',
|
|
156
|
+
'autofocus',
|
|
157
|
+
'class',
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
const ctxHost = useCardHost();
|
|
161
|
+
|
|
162
|
+
const emit = (event: CardEvent): void => {
|
|
163
|
+
const h = local.host ?? ctxHost;
|
|
164
|
+
if (h) h.emit(event);
|
|
165
|
+
else if (local.hostElement) emitCardEvent(local.hostElement, event);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const normalized = createMemo(() => normalizeActions(local.data?.actions));
|
|
169
|
+
const valid = createMemo(() => normalized().error === undefined);
|
|
170
|
+
const errorMessage = createMemo(() => normalized().error ?? '');
|
|
171
|
+
const actions = createMemo(() => normalized().actions);
|
|
172
|
+
const tone = (): ConfirmTone => local.data?.tone ?? 'default';
|
|
173
|
+
const isDanger = () => tone() === 'danger';
|
|
174
|
+
const defaultId = createMemo(() => defaultActionId(actions()));
|
|
175
|
+
|
|
176
|
+
const [resolved, setResolved] = createSignal<string | undefined>(undefined);
|
|
177
|
+
|
|
178
|
+
// Reset resolved state whenever a NEW definition arrives.
|
|
179
|
+
createEffect(on(() => local.data, () => setResolved(undefined)));
|
|
180
|
+
|
|
181
|
+
// ready / error lifecycle emits.
|
|
182
|
+
createEffect(
|
|
183
|
+
on(valid, (ok) => {
|
|
184
|
+
if (ok) emit({ kind: 'ready', cardId: local.cardId });
|
|
185
|
+
else emit({ kind: 'error', cardId: local.cardId, message: errorMessage() });
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const onAction = (action: ConfirmAction): void => {
|
|
190
|
+
if (resolved() !== undefined) return; // single-shot
|
|
191
|
+
emit({
|
|
192
|
+
kind: 'action',
|
|
193
|
+
cardId: local.cardId,
|
|
194
|
+
action: action.id,
|
|
195
|
+
...(action.payload !== undefined ? { payload: action.payload } : {}),
|
|
196
|
+
});
|
|
197
|
+
setResolved(action.id);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const onDismiss = (): void => emit({ kind: 'dismiss', cardId: local.cardId });
|
|
201
|
+
|
|
202
|
+
let bodyRef: HTMLDivElement | undefined;
|
|
203
|
+
|
|
204
|
+
// Surface the resolved action id for host styling.
|
|
205
|
+
createEffect(() => {
|
|
206
|
+
const el = local.hostElement;
|
|
207
|
+
if (!el) return;
|
|
208
|
+
const id = resolved();
|
|
209
|
+
if (id !== undefined) el.setAttribute('data-kc-resolved', id);
|
|
210
|
+
else el.removeAttribute('data-kc-resolved');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<Show when={valid()} fallback={<Card heading={local.heading} errorMessage={errorMessage()} />}>
|
|
215
|
+
<ErrorBoundary
|
|
216
|
+
fallback={() => {
|
|
217
|
+
emit({ kind: 'error', cardId: local.cardId, message: 'The card failed to render.' });
|
|
218
|
+
return <Card heading={local.heading} errorMessage="The card failed to render." />;
|
|
219
|
+
}}
|
|
220
|
+
>
|
|
221
|
+
<Card
|
|
222
|
+
heading={local.heading}
|
|
223
|
+
actions={
|
|
224
|
+
<div class="flex w-full flex-wrap items-center justify-between gap-2">
|
|
225
|
+
<Show when={local.data?.dismissible === true}>
|
|
226
|
+
<Button
|
|
227
|
+
type="button"
|
|
228
|
+
variant="ghost"
|
|
229
|
+
size="icon-sm"
|
|
230
|
+
aria-label="Dismiss"
|
|
231
|
+
onClick={onDismiss}
|
|
232
|
+
>
|
|
233
|
+
<X size={16} aria-hidden="true" />
|
|
234
|
+
</Button>
|
|
235
|
+
</Show>
|
|
236
|
+
<div class="ml-auto flex flex-wrap items-center gap-2">
|
|
237
|
+
<For each={actions()}>
|
|
238
|
+
{(action) => {
|
|
239
|
+
const isChosen = () => resolved() === action.id;
|
|
240
|
+
return (
|
|
241
|
+
<Button
|
|
242
|
+
type="button"
|
|
243
|
+
variant={buttonVariantForStyle(action.style)}
|
|
244
|
+
disabled={resolved() !== undefined && !isChosen()}
|
|
245
|
+
aria-pressed={isChosen() ? 'true' : undefined}
|
|
246
|
+
data-action-id={action.id}
|
|
247
|
+
data-kc-default={action.default ? 'true' : undefined}
|
|
248
|
+
ref={(el) => {
|
|
249
|
+
if (local.autofocus && action.id === defaultId()) {
|
|
250
|
+
queueMicrotask(() => el.focus());
|
|
251
|
+
}
|
|
252
|
+
}}
|
|
253
|
+
onClick={() => onAction(action)}
|
|
254
|
+
>
|
|
255
|
+
<Show when={isChosen()}>
|
|
256
|
+
<Check size={16} aria-hidden="true" />
|
|
257
|
+
</Show>
|
|
258
|
+
{action.label}
|
|
259
|
+
</Button>
|
|
260
|
+
);
|
|
261
|
+
}}
|
|
262
|
+
</For>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
}
|
|
266
|
+
>
|
|
267
|
+
<div
|
|
268
|
+
ref={bodyRef}
|
|
269
|
+
class={cn('flex flex-col gap-2', local.class)}
|
|
270
|
+
onKeyDown={(e) => {
|
|
271
|
+
// Enter on the card body (not on a focused button) invokes the default action.
|
|
272
|
+
if (e.key !== 'Enter') return;
|
|
273
|
+
const target = e.target as HTMLElement;
|
|
274
|
+
if (target.tagName === 'BUTTON') return;
|
|
275
|
+
const id = defaultId();
|
|
276
|
+
if (id === undefined) return;
|
|
277
|
+
const action = actions().find((a) => a.id === id);
|
|
278
|
+
if (action) onAction(action);
|
|
279
|
+
}}
|
|
280
|
+
tabindex={defaultId() !== undefined ? 0 : undefined}
|
|
281
|
+
>
|
|
282
|
+
<Show when={isDanger()}>
|
|
283
|
+
<div class="flex items-center gap-2 text-sm font-medium text-destructive dark:text-red-400">
|
|
284
|
+
<AlertTriangle size={16} class="shrink-0" aria-hidden="true" />
|
|
285
|
+
<span>Heads up</span>
|
|
286
|
+
</div>
|
|
287
|
+
</Show>
|
|
288
|
+
<Show when={local.data?.heading}>
|
|
289
|
+
<p class="text-sm font-semibold text-foreground">{local.data?.heading}</p>
|
|
290
|
+
</Show>
|
|
291
|
+
<Show when={local.data?.body}>
|
|
292
|
+
<p class="text-sm text-foreground">{local.data?.body}</p>
|
|
293
|
+
</Show>
|
|
294
|
+
</div>
|
|
295
|
+
</Card>
|
|
296
|
+
</ErrorBoundary>
|
|
297
|
+
</Show>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ContextReasoningUsage,
|
|
12
12
|
ContextCacheUsage,
|
|
13
13
|
} from './context';
|
|
14
|
+
import { componentDescription } from '../stories/docs/element-controls';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Story for the compound `Context` family. `Context` is the root provider that
|
|
@@ -25,14 +26,12 @@ const meta = {
|
|
|
25
26
|
parameters: {
|
|
26
27
|
layout: 'padded',
|
|
27
28
|
docs: {
|
|
28
|
-
description:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
].join('\n\n'),
|
|
35
|
-
},
|
|
29
|
+
description: componentDescription([
|
|
30
|
+
'A model context-window usage indicator: a hover-card trigger showing the used-percent ring, with a popover breaking down token usage (input / output / reasoning / cache) and estimated cost.',
|
|
31
|
+
'**When to use:** to surface how much of a model\'s context window a conversation has consumed and roughly what it costs — near the prompt input or in a chat header.',
|
|
32
|
+
'**How to use:** wrap the composition in `<Context>` and pass `usedTokens` / `maxTokens` (plus optional `inputTokens`, `outputTokens`, `reasoningTokens`, `cacheTokens`, `estimatedCost`). Compose `ContextTrigger`, `ContextContent` (with `Header`/`Body`/`Footer`), and the usage rows.',
|
|
33
|
+
'**Placement:** chat toolbars, prompt-input action bars, and conversation headers.',
|
|
34
|
+
]),
|
|
36
35
|
controls: { exclude: ['use:eventListener'] },
|
|
37
36
|
},
|
|
38
37
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
2
|
import { fn } from 'storybook/test';
|
|
3
3
|
import { ConversationItem } from './conversation-item';
|
|
4
|
+
import { componentDescription } from '../stories/docs/element-controls';
|
|
4
5
|
|
|
5
6
|
const baseConversation = {
|
|
6
7
|
id: '1',
|
|
@@ -22,14 +23,12 @@ const meta = {
|
|
|
22
23
|
parameters: {
|
|
23
24
|
layout: 'padded',
|
|
24
25
|
docs: {
|
|
25
|
-
description:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
].join('\n\n'),
|
|
32
|
-
},
|
|
26
|
+
description: componentDescription([
|
|
27
|
+
'A single conversation row — shows the title and message count, truncating long titles, with a highlighted active state.',
|
|
28
|
+
'**When to use:** as the leaf item inside a chat history sidebar. Usually rendered for you by `ConversationList`; use it directly to build a custom list.',
|
|
29
|
+
'**How to use:** pass a `conversation` summary, an `isActive` flag, and an `onSelect(id)` handler fired when the row is clicked.',
|
|
30
|
+
'**Placement:** inside a conversation/chat history sidebar list.',
|
|
31
|
+
]),
|
|
33
32
|
controls: { exclude: ['use:eventListener'] },
|
|
34
33
|
},
|
|
35
34
|
},
|
|
@@ -9,8 +9,8 @@ export function ConversationItem(props: ConversationItemProps) {
|
|
|
9
9
|
return (
|
|
10
10
|
<button data-conversation-id={local.conversation.id} onClick={() => local.onSelect(local.conversation.id)}
|
|
11
11
|
class={cn('w-full text-left rounded-lg px-2.5 py-2 transition-colors', local.isActive ? 'bg-muted' : 'hover:bg-muted/50', local.class)}>
|
|
12
|
-
<div class={cn('truncate text-sm', local.isActive ? 'text-foreground font-medium' : 'text-
|
|
13
|
-
<div class=
|
|
12
|
+
<div class={cn('truncate text-sm', local.isActive ? 'text-foreground font-medium' : 'text-foreground/80')}>{local.conversation.title}</div>
|
|
13
|
+
<div class={cn('truncate mt-0.5 text-xs', local.isActive ? 'text-foreground/70' : 'text-muted-foreground')}>{local.conversation.messageCount} messages</div>
|
|
14
14
|
</button>
|
|
15
15
|
);
|
|
16
16
|
}
|
|
@@ -2,6 +2,7 @@ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
|
2
2
|
import { fn } from 'storybook/test';
|
|
3
3
|
import { ConversationList, type ConversationListProps } from './conversation-list';
|
|
4
4
|
import type { ConversationSummary, ConversationGroup } from '../types';
|
|
5
|
+
import { componentDescription } from '../stories/docs/element-controls';
|
|
5
6
|
|
|
6
7
|
const scope = { type: 'document' as const };
|
|
7
8
|
|
|
@@ -31,14 +32,12 @@ const meta = {
|
|
|
31
32
|
parameters: {
|
|
32
33
|
layout: 'padded',
|
|
33
34
|
docs: {
|
|
34
|
-
description:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
].join('\n\n'),
|
|
41
|
-
},
|
|
35
|
+
description: componentDescription([
|
|
36
|
+
'A full chat-history sidebar: header (sidebar toggle + new chat), a built-in search box that filters by title, and conversations bucketed into collapsible, count-badged groups.',
|
|
37
|
+
'**When to use:** as the left-hand navigation for a chat app — browsing, searching, and switching between past conversations.',
|
|
38
|
+
'**How to use:** pass `groups` and `conversations` arrays, the `activeId`, and handlers `onSelect(id)` / `onNewChat()` (plus optional `onToggleSidebar()`). Give it a sized, overflow-hidden container.',
|
|
39
|
+
'**Placement:** the persistent left sidebar of a chat layout.',
|
|
40
|
+
]),
|
|
42
41
|
controls: { exclude: ['use:eventListener'] },
|
|
43
42
|
},
|
|
44
43
|
},
|
|
@@ -83,7 +83,7 @@ function GroupSection(props: { name: string; count: number; conversations: Conve
|
|
|
83
83
|
const [open, setOpen] = createSignal(true);
|
|
84
84
|
return (
|
|
85
85
|
<Collapsible open={open()} onOpenChange={setOpen}>
|
|
86
|
-
<CollapsibleTrigger class="flex items-center gap-1.5 w-full px-1.5 py-1 rounded-md bg-muted
|
|
86
|
+
<CollapsibleTrigger class="flex items-center gap-1.5 w-full px-1.5 py-1 rounded-md bg-muted text-[13px] text-foreground/80 font-medium hover:bg-muted/70 transition-colors cursor-pointer mt-1.5">
|
|
87
87
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
88
88
|
class={cn('transition-transform', !open() && '-rotate-90')}><polyline points="6 9 12 15 18 9"/></svg>
|
|
89
89
|
<span>{props.name}</span>
|