@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.
- package/README.md +35 -5
- package/dist/custom-elements.json +2969 -0
- package/dist/kitn-chat.es.js +52 -39
- package/dist/llms/llms-full.txt +718 -0
- package/dist/llms/llms.txt +104 -0
- package/dist/theme.tokens.css +137 -0
- package/frameworks/react/index.tsx +584 -0
- package/frameworks/react/runtime.tsx +94 -0
- package/llms-full.txt +718 -0
- package/llms.txt +104 -0
- package/package.json +53 -6
- package/src/components/attachments.tsx +4 -2
- package/src/components/chain-of-thought.tsx +1 -1
- package/src/components/chat-scope-picker.tsx +2 -2
- package/src/components/chat-thread.tsx +217 -0
- package/src/components/checkpoint.tsx +7 -3
- package/src/components/context.tsx +14 -18
- package/src/components/conversation-item.tsx +1 -1
- package/src/components/conversation-list.tsx +5 -4
- package/src/components/message-skills.tsx +1 -1
- package/src/components/message.tsx +1 -0
- package/src/components/model-switcher.tsx +3 -3
- package/src/components/prompt-input.tsx +20 -2
- package/src/components/reasoning.tsx +2 -2
- package/src/components/scroll-button.tsx +1 -0
- package/src/components/slash-command.tsx +17 -8
- package/src/components/source.tsx +2 -2
- package/src/components/thinking-bar.tsx +2 -2
- package/src/components/tool.tsx +17 -6
- package/src/components/voice-input.tsx +5 -1
- package/src/elements/attachments.tsx +132 -0
- package/src/elements/chain-of-thought.tsx +45 -0
- package/src/elements/chat-scope-picker.tsx +36 -0
- package/src/elements/chat-workspace.tsx +122 -0
- package/src/elements/chat.tsx +31 -228
- package/src/elements/checkpoint.tsx +43 -0
- package/src/elements/code-block.tsx +42 -0
- package/src/elements/compiled.css +1 -1
- package/src/elements/context-meter.tsx +71 -0
- package/src/elements/conversation-list.tsx +6 -0
- package/src/elements/default-input.tsx +22 -1
- package/src/elements/define.tsx +98 -12
- package/src/elements/element-types.d.ts +444 -0
- package/src/elements/empty.tsx +29 -0
- package/src/elements/feedback-bar.tsx +33 -0
- package/src/elements/file-upload.tsx +44 -0
- package/src/elements/image.tsx +32 -0
- package/src/elements/kitn-attachments.stories.tsx +181 -0
- package/src/elements/kitn-chain-of-thought.stories.tsx +75 -0
- package/src/elements/kitn-chat-scope-picker.stories.tsx +72 -0
- package/src/elements/kitn-chat-workspace.stories.tsx +195 -0
- package/src/elements/kitn-checkpoint.stories.tsx +71 -0
- package/src/elements/kitn-code-block.stories.tsx +82 -0
- package/src/elements/kitn-context-meter.stories.tsx +85 -0
- package/src/elements/kitn-empty.stories.tsx +110 -0
- package/src/elements/kitn-feedback-bar.stories.tsx +73 -0
- package/src/elements/kitn-file-upload.stories.tsx +81 -0
- package/src/elements/kitn-image.stories.tsx +70 -0
- package/src/elements/kitn-loader.stories.tsx +87 -0
- package/src/elements/kitn-markdown.stories.tsx +75 -0
- package/src/elements/kitn-message-skills.stories.tsx +74 -0
- package/src/elements/kitn-message.stories.tsx +105 -0
- package/src/elements/kitn-model-switcher.stories.tsx +80 -0
- package/src/elements/kitn-prompt-input.stories.tsx +74 -16
- package/src/elements/kitn-prompt-suggestions.stories.tsx +157 -0
- package/src/elements/kitn-reasoning.stories.tsx +76 -0
- package/src/elements/kitn-response-stream.stories.tsx +79 -0
- package/src/elements/kitn-source-list.stories.tsx +77 -0
- package/src/elements/kitn-source.stories.tsx +87 -0
- package/src/elements/kitn-text-shimmer.stories.tsx +63 -0
- package/src/elements/kitn-thinking-bar.stories.tsx +72 -0
- package/src/elements/kitn-tool.stories.tsx +88 -0
- package/src/elements/kitn-voice-input.stories.tsx +87 -0
- package/src/elements/loader.tsx +25 -0
- package/src/elements/markdown.tsx +38 -0
- package/src/elements/message-skills.tsx +22 -0
- package/src/elements/message.tsx +125 -0
- package/src/elements/model-switcher.tsx +35 -0
- package/src/elements/prompt-input.tsx +83 -7
- package/src/elements/prompt-suggestions.tsx +58 -0
- package/src/elements/reasoning.tsx +50 -0
- package/src/elements/register.ts +32 -0
- package/src/elements/response-stream.tsx +40 -0
- package/src/elements/source.tsx +67 -0
- package/src/elements/styles.css +14 -0
- package/src/elements/text-shimmer.tsx +28 -0
- package/src/elements/thinking-bar.tsx +34 -0
- package/src/elements/tool.tsx +23 -0
- package/src/elements/voice-input.tsx +41 -0
- package/src/index.ts +0 -1
- package/src/primitives/chat-config.tsx +3 -3
- package/src/stories/docs/Accessibility.mdx +119 -0
- package/src/stories/docs/ForAIAgents.mdx +93 -0
- package/src/stories/docs/GettingStarted.mdx +2 -2
- package/src/stories/docs/Installation.mdx +29 -2
- package/src/stories/docs/Integrations.mdx +417 -15
- package/src/stories/docs/Introduction.mdx +17 -8
- package/src/stories/docs/Theming.mdx +1 -1
- package/src/stories/pattern-centered-conversation.stories.tsx +93 -0
- package/src/stories/pattern-docked-widget.stories.tsx +93 -0
- package/src/stories/pattern-empty-state.stories.tsx +76 -0
- package/src/stories/typography.stories.tsx +78 -0
- package/src/ui/button.tsx +1 -1
- package/src/ui/collapsible.stories.tsx +70 -0
- package/src/ui/collapsible.tsx +119 -8
- package/src/ui/dropdown.stories.tsx +60 -0
- package/src/ui/dropdown.tsx +177 -12
- package/src/ui/hover-card.stories.tsx +78 -0
- package/src/ui/hover-card.tsx +147 -26
- package/src/ui/overlay.stories.tsx +115 -0
- package/src/ui/overlay.tsx +151 -0
- package/src/ui/scroll-area.stories.tsx +51 -0
- package/src/ui/textarea.stories.tsx +77 -0
- package/src/ui/textarea.tsx +1 -1
- package/src/ui/tooltip.stories.tsx +1 -1
- package/src/ui/tooltip.tsx +59 -13
- package/src/utils/cn.ts +19 -1
- package/theme.css +76 -43
- package/src/ui/dialog.tsx +0 -21
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defineKitnElement } from './define';
|
|
2
|
+
import { Markdown } from '../components/markdown';
|
|
3
|
+
import { ChatConfig, useChatConfig, type ProseSize } from '../primitives/chat-config';
|
|
4
|
+
|
|
5
|
+
interface Props extends Record<string, unknown> {
|
|
6
|
+
/** The markdown source to render. */
|
|
7
|
+
content: string;
|
|
8
|
+
/** Text/markdown sizing. */
|
|
9
|
+
proseSize?: ProseSize;
|
|
10
|
+
/** Shiki theme for fenced code blocks. */
|
|
11
|
+
codeTheme?: string;
|
|
12
|
+
/** Disable syntax highlighting (no Shiki loads). */
|
|
13
|
+
codeHighlight?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `<kitn-markdown>` — renders markdown (with fenced-code syntax highlighting) as
|
|
18
|
+
* a standalone element. Content via the `content` property; sizing/highlighting
|
|
19
|
+
* via attributes.
|
|
20
|
+
*/
|
|
21
|
+
defineKitnElement<Props>('kitn-markdown', {
|
|
22
|
+
content: '',
|
|
23
|
+
proseSize: 'sm',
|
|
24
|
+
codeTheme: 'github-dark-dimmed',
|
|
25
|
+
codeHighlight: true,
|
|
26
|
+
}, (props, { flag }) => {
|
|
27
|
+
const outer = useChatConfig();
|
|
28
|
+
return (
|
|
29
|
+
<ChatConfig
|
|
30
|
+
proseSize={props.proseSize}
|
|
31
|
+
codeTheme={props.codeTheme}
|
|
32
|
+
codeHighlight={flag('codeHighlight')}
|
|
33
|
+
portalMount={outer.portalMount()}
|
|
34
|
+
>
|
|
35
|
+
<Markdown content={props.content} />
|
|
36
|
+
</ChatConfig>
|
|
37
|
+
);
|
|
38
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineKitnElement } from './define';
|
|
2
|
+
import { MessageSkills } from '../components/message-skills';
|
|
3
|
+
|
|
4
|
+
interface Skill {
|
|
5
|
+
/** Stable identifier for the skill. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Human-readable skill name shown on the badge. */
|
|
8
|
+
name: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props extends Record<string, unknown> {
|
|
12
|
+
/** The active skills to badge. Set as a JS property. */
|
|
13
|
+
skills: Skill[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `<kitn-message-skills>` — badges showing which skills were active for a
|
|
18
|
+
* message. Data via the `skills` property.
|
|
19
|
+
*/
|
|
20
|
+
defineKitnElement<Props>('kitn-message-skills', {
|
|
21
|
+
skills: [],
|
|
22
|
+
}, (props) => <MessageSkills skills={props.skills} />);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { For, Show, type Component } from 'solid-js';
|
|
2
|
+
import { defineKitnElement } from './define';
|
|
3
|
+
import { ChatConfig, useChatConfig, type ProseSize } from '../primitives/chat-config';
|
|
4
|
+
import { Message, MessageContent, MessageActions } from '../components/message';
|
|
5
|
+
import { Reasoning, ReasoningTrigger, ReasoningContent } from '../components/reasoning';
|
|
6
|
+
import { Tool } from '../components/tool';
|
|
7
|
+
import { Attachments, Attachment, AttachmentPreview, AttachmentInfo } from '../components/attachments';
|
|
8
|
+
import { Button } from '../ui/button';
|
|
9
|
+
import { Copy, ThumbsUp, ThumbsDown, RefreshCw, Pencil } from 'lucide-solid';
|
|
10
|
+
import type { ChatMessage, ChatMessageAction } from './chat-types';
|
|
11
|
+
|
|
12
|
+
interface Props extends Record<string, unknown> {
|
|
13
|
+
/** The full message object. Set as a JS property. */
|
|
14
|
+
message?: ChatMessage;
|
|
15
|
+
/** Convenience for simple cases when not passing a `message` object. */
|
|
16
|
+
role?: 'user' | 'assistant';
|
|
17
|
+
/** Convenience content (used when `message` is not set). */
|
|
18
|
+
content?: string;
|
|
19
|
+
/** Force markdown on/off. Defaults to on for assistant, off for user. */
|
|
20
|
+
markdown?: boolean;
|
|
21
|
+
/** Text/markdown sizing for the message body. */
|
|
22
|
+
proseSize?: ProseSize;
|
|
23
|
+
/** Shiki theme name used for fenced code blocks in the content. */
|
|
24
|
+
codeTheme?: string;
|
|
25
|
+
/** Disable syntax highlighting for code blocks (no Shiki loads). */
|
|
26
|
+
codeHighlight?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Events fired by `<kitn-message>`. */
|
|
30
|
+
interface Events {
|
|
31
|
+
/** An action button was clicked. */
|
|
32
|
+
messageaction: { messageId: string; action: ChatMessageAction };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ACTION_LABEL: Record<ChatMessageAction, string> = {
|
|
36
|
+
copy: 'Copy', like: 'Like', dislike: 'Dislike', regenerate: 'Regenerate', edit: 'Edit',
|
|
37
|
+
};
|
|
38
|
+
const ACTION_ICON: Record<ChatMessageAction, Component<{ class?: string }>> = {
|
|
39
|
+
copy: Copy, like: ThumbsUp, dislike: ThumbsDown, regenerate: RefreshCw, edit: Pencil,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `<kitn-message>` — a single message row: markdown/plain content, reasoning,
|
|
44
|
+
* tool calls, attachments, and action buttons, rendered from one `message`
|
|
45
|
+
* object (the same shape `<kitn-chat>` uses per message). The keystone of the
|
|
46
|
+
* "compose your own message list" pattern. Emits `messageaction`.
|
|
47
|
+
*/
|
|
48
|
+
defineKitnElement<Props, Events>('kitn-message', {
|
|
49
|
+
message: undefined,
|
|
50
|
+
role: 'assistant',
|
|
51
|
+
content: undefined,
|
|
52
|
+
markdown: undefined,
|
|
53
|
+
proseSize: 'sm',
|
|
54
|
+
codeTheme: 'github-dark-dimmed',
|
|
55
|
+
codeHighlight: true,
|
|
56
|
+
}, (props, { dispatch, flag, element }) => {
|
|
57
|
+
const outer = useChatConfig();
|
|
58
|
+
const msg = (): ChatMessage =>
|
|
59
|
+
props.message ?? { id: 'message', role: props.role ?? 'assistant', content: props.content ?? '' };
|
|
60
|
+
const isUser = () => msg().role === 'user';
|
|
61
|
+
// markdown: explicit prop/attribute wins; otherwise default by role.
|
|
62
|
+
const markdownExplicit = () =>
|
|
63
|
+
element.hasAttribute('markdown') || props.markdown === true || props.markdown === false;
|
|
64
|
+
const useMarkdown = () => (markdownExplicit() ? flag('markdown') : !isUser());
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<ChatConfig
|
|
68
|
+
proseSize={props.proseSize}
|
|
69
|
+
codeTheme={props.codeTheme}
|
|
70
|
+
codeHighlight={flag('codeHighlight')}
|
|
71
|
+
portalMount={outer.portalMount()}
|
|
72
|
+
>
|
|
73
|
+
<Message class={isUser() ? 'flex-col items-end' : 'flex-col items-start'}>
|
|
74
|
+
<Show when={msg().reasoning}>
|
|
75
|
+
<Reasoning class="mb-2 w-full">
|
|
76
|
+
<ReasoningTrigger>{msg().reasoning!.label ?? 'Reasoning'}</ReasoningTrigger>
|
|
77
|
+
<ReasoningContent markdown>{msg().reasoning!.text}</ReasoningContent>
|
|
78
|
+
</Reasoning>
|
|
79
|
+
</Show>
|
|
80
|
+
<For each={msg().tools ?? []}>
|
|
81
|
+
{(tp) => <Tool toolPart={tp} class="mb-2 w-full" />}
|
|
82
|
+
</For>
|
|
83
|
+
<Show when={msg().attachments?.length}>
|
|
84
|
+
<Attachments variant="inline" class={isUser() ? 'mb-2 justify-end' : 'mb-2'}>
|
|
85
|
+
<For each={msg().attachments!}>
|
|
86
|
+
{(att) => (
|
|
87
|
+
<Attachment data={att}>
|
|
88
|
+
<AttachmentPreview />
|
|
89
|
+
<AttachmentInfo />
|
|
90
|
+
</Attachment>
|
|
91
|
+
)}
|
|
92
|
+
</For>
|
|
93
|
+
</Attachments>
|
|
94
|
+
</Show>
|
|
95
|
+
<MessageContent
|
|
96
|
+
markdown={useMarkdown()}
|
|
97
|
+
class={isUser()
|
|
98
|
+
? 'bg-muted text-primary max-w-[85%] rounded-2xl px-4 py-2'
|
|
99
|
+
: 'bg-transparent p-0'}
|
|
100
|
+
>
|
|
101
|
+
{msg().content}
|
|
102
|
+
</MessageContent>
|
|
103
|
+
<Show when={msg().actions?.length}>
|
|
104
|
+
<MessageActions class="mt-1 flex gap-0">
|
|
105
|
+
<For each={msg().actions!}>
|
|
106
|
+
{(a) => (
|
|
107
|
+
<Button
|
|
108
|
+
variant="ghost" size="icon-sm" class="rounded-full"
|
|
109
|
+
data-action={a}
|
|
110
|
+
aria-label={ACTION_LABEL[a]}
|
|
111
|
+
onClick={() => dispatch('messageaction', { messageId: msg().id, action: a })}
|
|
112
|
+
>
|
|
113
|
+
{(() => {
|
|
114
|
+
const Icon = ACTION_ICON[a];
|
|
115
|
+
return <Icon class="size-3.5" />;
|
|
116
|
+
})()}
|
|
117
|
+
</Button>
|
|
118
|
+
)}
|
|
119
|
+
</For>
|
|
120
|
+
</MessageActions>
|
|
121
|
+
</Show>
|
|
122
|
+
</Message>
|
|
123
|
+
</ChatConfig>
|
|
124
|
+
);
|
|
125
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineKitnElement } from './define';
|
|
2
|
+
import { ModelSwitcher } from '../components/model-switcher';
|
|
3
|
+
import type { ModelOption } from '../types';
|
|
4
|
+
|
|
5
|
+
interface Props extends Record<string, unknown> {
|
|
6
|
+
/** The selectable models. Set as a JS property (array). */
|
|
7
|
+
models: ModelOption[];
|
|
8
|
+
/** The currently-selected model id. Defaults to the first model. */
|
|
9
|
+
currentModel?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Events fired by `<kitn-model-switcher>`. */
|
|
13
|
+
interface Events {
|
|
14
|
+
/** A model was selected. */
|
|
15
|
+
modelchange: { modelId: string };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* `<kitn-model-switcher>` — an event-emitting leaf element. Data in via the
|
|
20
|
+
* `models` property, selection out via a `modelchange` event. Mirrors the
|
|
21
|
+
* header switcher inside `<kitn-chat>` as a standalone, composable piece.
|
|
22
|
+
*
|
|
23
|
+
* Note: like the underlying primitive, this only renders when more than one
|
|
24
|
+
* model is provided.
|
|
25
|
+
*/
|
|
26
|
+
defineKitnElement<Props, Events>('kitn-model-switcher', {
|
|
27
|
+
models: [],
|
|
28
|
+
currentModel: undefined,
|
|
29
|
+
}, (props, { dispatch }) => (
|
|
30
|
+
<ModelSwitcher
|
|
31
|
+
models={props.models}
|
|
32
|
+
currentModelId={props.currentModel ?? props.models[0]?.id ?? ''}
|
|
33
|
+
onModelChange={(modelId) => dispatch('modelchange', { modelId })}
|
|
34
|
+
/>
|
|
35
|
+
));
|
|
@@ -1,25 +1,84 @@
|
|
|
1
|
-
import { createSignal } from 'solid-js';
|
|
1
|
+
import { createEffect, createSignal } from 'solid-js';
|
|
2
2
|
import { defineKitnElement } from './define';
|
|
3
3
|
import { DefaultPromptInput } from './default-input';
|
|
4
4
|
import type { AttachmentData } from '../components/attachments';
|
|
5
|
+
import type { SlashCommandItem } from '../components/slash-command';
|
|
5
6
|
|
|
6
7
|
interface Props extends Record<string, unknown> {
|
|
8
|
+
/** Controlled value of the input. When set, the host owns the text and must
|
|
9
|
+
* update it on `valuechange`; leave unset for uncontrolled behavior. */
|
|
7
10
|
value?: string;
|
|
11
|
+
/** Placeholder text shown in the empty input. */
|
|
8
12
|
placeholder?: string;
|
|
13
|
+
/** Disable the input and submit button entirely (non-interactive). */
|
|
9
14
|
disabled?: boolean;
|
|
15
|
+
/** Show the loading/streaming state and block submit (use while awaiting a
|
|
16
|
+
* reply). */
|
|
10
17
|
loading?: boolean;
|
|
18
|
+
/** Starter prompts shown above the input. Clicking one follows
|
|
19
|
+
* `suggestionMode`. Set as a JS property. */
|
|
11
20
|
suggestions?: string[];
|
|
21
|
+
/** What clicking a suggestion does: `'submit'` (default) sends it immediately
|
|
22
|
+
* as if typed and submitted; `'fill'` just places it in the input. */
|
|
23
|
+
suggestionMode?: 'submit' | 'fill';
|
|
24
|
+
/** Slash commands — when set, typing `/` opens the command palette. Set as a
|
|
25
|
+
* JS property. */
|
|
26
|
+
slashCommands?: SlashCommandItem[];
|
|
27
|
+
/** Command ids to highlight as active. */
|
|
28
|
+
slashActiveIds?: string[];
|
|
29
|
+
/** Single-line palette rows. */
|
|
30
|
+
slashCompact?: boolean;
|
|
31
|
+
/** Show a Search (Globe) button in the left toolbar; clicking it fires a
|
|
32
|
+
* `search` event. */
|
|
33
|
+
search?: boolean;
|
|
34
|
+
/** Show a Voice (Mic) button in the left toolbar; clicking it fires a `voice`
|
|
35
|
+
* event. */
|
|
36
|
+
voice?: boolean;
|
|
37
|
+
/** Attachments to seed the input with (so a consumer can pre-populate staged
|
|
38
|
+
* files without an upload). Set as a JS property; the element then manages its
|
|
39
|
+
* own attachment state from there (add via the paperclip, remove per chip). */
|
|
40
|
+
attachments?: AttachmentData[];
|
|
12
41
|
}
|
|
13
42
|
|
|
14
|
-
|
|
43
|
+
/** Events fired by `<kitn-prompt-input>`. */
|
|
44
|
+
interface Events {
|
|
45
|
+
/** The user submitted the prompt (Enter or send button) with its attachments. */
|
|
46
|
+
submit: { value: string; attachments: AttachmentData[] };
|
|
47
|
+
/** The input text changed (fires on every keystroke). */
|
|
48
|
+
valuechange: { value: string };
|
|
49
|
+
/** A suggestion was clicked while `suggestion-mode="fill"`. */
|
|
50
|
+
suggestionclick: { value: string };
|
|
51
|
+
/** A slash command was chosen from the palette. */
|
|
52
|
+
slashselect: { command: SlashCommandItem };
|
|
53
|
+
/** The Search (Globe) toolbar button was clicked. */
|
|
54
|
+
search: undefined;
|
|
55
|
+
/** The Voice (Mic) toolbar button was clicked. */
|
|
56
|
+
voice: undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
defineKitnElement<Props, Events>('kitn-prompt-input', {
|
|
15
60
|
value: undefined,
|
|
16
61
|
placeholder: 'Send a message...',
|
|
17
62
|
disabled: false,
|
|
18
63
|
loading: false,
|
|
19
64
|
suggestions: undefined,
|
|
20
|
-
|
|
65
|
+
suggestionMode: 'submit',
|
|
66
|
+
slashCommands: undefined,
|
|
67
|
+
slashActiveIds: undefined,
|
|
68
|
+
slashCompact: false,
|
|
69
|
+
search: false,
|
|
70
|
+
voice: false,
|
|
71
|
+
attachments: undefined,
|
|
72
|
+
}, (props, { dispatch, flag }) => {
|
|
21
73
|
const [internal, setInternal] = createSignal(props.value ?? '');
|
|
22
|
-
|
|
74
|
+
// Seed staged attachments from the `attachments` property; the element manages
|
|
75
|
+
// its own state from there (paperclip adds, per-chip remove deletes).
|
|
76
|
+
const [attachments, setAttachments] = createSignal<AttachmentData[]>(props.attachments ?? []);
|
|
77
|
+
// Re-seed when the `attachments` property is (re)assigned by the consumer
|
|
78
|
+
// (e.g. set via a `ref` after mount). Subsequent in-element edits stay local.
|
|
79
|
+
createEffect(() => {
|
|
80
|
+
if (props.attachments) setAttachments(props.attachments);
|
|
81
|
+
});
|
|
23
82
|
const current = () => props.value ?? internal();
|
|
24
83
|
|
|
25
84
|
const handleChange = (v: string) => { setInternal(v); dispatch('valuechange', { value: v }); };
|
|
@@ -27,20 +86,37 @@ defineKitnElement<Props>('kitn-prompt-input', {
|
|
|
27
86
|
dispatch('submit', { value: current(), attachments: attachments() });
|
|
28
87
|
setAttachments([]);
|
|
29
88
|
};
|
|
30
|
-
const handleSuggestionClick = (v: string) => {
|
|
89
|
+
const handleSuggestionClick = (v: string) => {
|
|
90
|
+
if ((props.suggestionMode ?? 'submit') === 'fill') {
|
|
91
|
+
handleChange(v);
|
|
92
|
+
dispatch('suggestionclick', { value: v });
|
|
93
|
+
} else {
|
|
94
|
+
// Default: behave as if the user typed the suggestion and pressed submit.
|
|
95
|
+
dispatch('submit', { value: v, attachments: attachments() });
|
|
96
|
+
setAttachments([]);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
31
99
|
|
|
32
100
|
return (
|
|
33
101
|
<DefaultPromptInput
|
|
34
102
|
value={current()}
|
|
35
103
|
placeholder={props.placeholder}
|
|
36
|
-
disabled={
|
|
37
|
-
loading={
|
|
104
|
+
disabled={flag('disabled')}
|
|
105
|
+
loading={flag('loading')}
|
|
38
106
|
suggestions={props.suggestions}
|
|
39
107
|
attachments={attachments()}
|
|
108
|
+
slashCommands={props.slashCommands}
|
|
109
|
+
slashActiveIds={props.slashActiveIds}
|
|
110
|
+
slashCompact={flag('slashCompact')}
|
|
111
|
+
search={flag('search')}
|
|
112
|
+
voice={flag('voice')}
|
|
40
113
|
onValueChange={handleChange}
|
|
41
114
|
onSubmit={handleSubmit}
|
|
42
115
|
onSuggestionClick={handleSuggestionClick}
|
|
43
116
|
onAttachmentsChange={setAttachments}
|
|
117
|
+
onSearch={() => dispatch('search')}
|
|
118
|
+
onVoice={() => dispatch('voice')}
|
|
119
|
+
onSlashSelect={(command) => dispatch('slashselect', { command })}
|
|
44
120
|
/>
|
|
45
121
|
);
|
|
46
122
|
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { For } from 'solid-js';
|
|
2
|
+
import { defineKitnElement } from './define';
|
|
3
|
+
import { PromptSuggestion } from '../components/prompt-suggestion';
|
|
4
|
+
|
|
5
|
+
type Item = string | { label: string; value?: string };
|
|
6
|
+
|
|
7
|
+
interface Props extends Record<string, unknown> {
|
|
8
|
+
/** The suggestions. Strings, or `{ label, value }` when the displayed text
|
|
9
|
+
* and the emitted value differ. Set as a JS property. */
|
|
10
|
+
suggestions: Item[];
|
|
11
|
+
/** Chip style: `'outline'` (default), `'ghost'`, or `'default'` (filled). */
|
|
12
|
+
variant?: 'outline' | 'ghost' | 'default';
|
|
13
|
+
/** Size preset for each chip. Defaults to the pill default (`'lg'`); pass
|
|
14
|
+
* `'sm'` for smaller pills (or `'md'`). */
|
|
15
|
+
size?: 'sm' | 'md' | 'lg' | 'icon' | 'icon-sm';
|
|
16
|
+
/** Full-width left-aligned rows instead of pills. */
|
|
17
|
+
block?: boolean;
|
|
18
|
+
/** Substring to highlight within each suggestion. */
|
|
19
|
+
highlight?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Events fired by `<kitn-prompt-suggestions>`. */
|
|
23
|
+
interface Events {
|
|
24
|
+
/** A suggestion was clicked. */
|
|
25
|
+
select: { value: string };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const labelOf = (s: Item) => (typeof s === 'string' ? s : s.label);
|
|
29
|
+
const valueOf = (s: Item) => (typeof s === 'string' ? s : s.value ?? s.label);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `<kitn-prompt-suggestions>` — a row/list of suggestion chips. Data via the
|
|
33
|
+
* `suggestions` property; `variant`/`block`/`highlight` attributes; emits
|
|
34
|
+
* `select`.
|
|
35
|
+
*/
|
|
36
|
+
defineKitnElement<Props, Events>('kitn-prompt-suggestions', {
|
|
37
|
+
suggestions: [],
|
|
38
|
+
variant: 'outline',
|
|
39
|
+
size: undefined,
|
|
40
|
+
block: false,
|
|
41
|
+
highlight: undefined,
|
|
42
|
+
}, (props, { dispatch, flag }) => (
|
|
43
|
+
<div class={flag('block') ? 'flex flex-col gap-2' : 'flex flex-wrap gap-2'}>
|
|
44
|
+
<For each={props.suggestions}>
|
|
45
|
+
{(s) => (
|
|
46
|
+
<PromptSuggestion
|
|
47
|
+
variant={props.variant}
|
|
48
|
+
size={props.size}
|
|
49
|
+
block={flag('block')}
|
|
50
|
+
highlight={props.highlight}
|
|
51
|
+
onClick={() => dispatch('select', { value: valueOf(s) })}
|
|
52
|
+
>
|
|
53
|
+
{labelOf(s)}
|
|
54
|
+
</PromptSuggestion>
|
|
55
|
+
)}
|
|
56
|
+
</For>
|
|
57
|
+
</div>
|
|
58
|
+
));
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defineKitnElement } from './define';
|
|
2
|
+
import { Reasoning, ReasoningTrigger, ReasoningContent } from '../components/reasoning';
|
|
3
|
+
import { ChatConfig, useChatConfig } from '../primitives/chat-config';
|
|
4
|
+
|
|
5
|
+
interface Props extends Record<string, unknown> {
|
|
6
|
+
/** The reasoning text to display. */
|
|
7
|
+
text: string;
|
|
8
|
+
/** Trigger label. */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Controlled open state — set as a property (`el.open = true`). Omit for
|
|
11
|
+
* uncontrolled (the trigger toggles it). */
|
|
12
|
+
open?: boolean;
|
|
13
|
+
/** While true, auto-expands (and re-collapses when it flips false). */
|
|
14
|
+
streaming?: boolean;
|
|
15
|
+
/** Render `text` as markdown. */
|
|
16
|
+
markdown?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Events fired by `<kitn-reasoning>`. */
|
|
20
|
+
interface Events {
|
|
21
|
+
/** Open state changed (via the trigger or streaming auto-open). */
|
|
22
|
+
openchange: { open: boolean };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `<kitn-reasoning>` — a collapsible reasoning/thinking block that auto-expands
|
|
27
|
+
* while `streaming`. Text via the `text` property; `markdown`/`streaming` flags;
|
|
28
|
+
* `open` is a controlled property; emits `openchange`.
|
|
29
|
+
*/
|
|
30
|
+
defineKitnElement<Props, Events>('kitn-reasoning', {
|
|
31
|
+
text: '',
|
|
32
|
+
label: 'Reasoning',
|
|
33
|
+
open: undefined,
|
|
34
|
+
streaming: false,
|
|
35
|
+
markdown: true,
|
|
36
|
+
}, (props, { dispatch, flag }) => {
|
|
37
|
+
const outer = useChatConfig();
|
|
38
|
+
return (
|
|
39
|
+
<ChatConfig portalMount={outer.portalMount()}>
|
|
40
|
+
<Reasoning
|
|
41
|
+
open={props.open}
|
|
42
|
+
isStreaming={flag('streaming')}
|
|
43
|
+
onOpenChange={(open) => dispatch('openchange', { open })}
|
|
44
|
+
>
|
|
45
|
+
<ReasoningTrigger>{props.label}</ReasoningTrigger>
|
|
46
|
+
<ReasoningContent markdown={flag('markdown')}>{props.text}</ReasoningContent>
|
|
47
|
+
</Reasoning>
|
|
48
|
+
</ChatConfig>
|
|
49
|
+
);
|
|
50
|
+
});
|
package/src/elements/register.ts
CHANGED
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
import './conversation-list';
|
|
4
4
|
import './prompt-input';
|
|
5
5
|
import './chat';
|
|
6
|
+
import './chat-workspace';
|
|
7
|
+
// Composable leaf elements (spike — see docs/handoff + examples/composable)
|
|
8
|
+
import './thinking-bar';
|
|
9
|
+
import './model-switcher';
|
|
10
|
+
import './attachments';
|
|
11
|
+
// Phase 1 — message-rendering core
|
|
12
|
+
import './message';
|
|
13
|
+
import './markdown';
|
|
14
|
+
import './code-block';
|
|
15
|
+
import './reasoning';
|
|
16
|
+
import './tool';
|
|
17
|
+
// Phase 2 — header / meta
|
|
18
|
+
import './context-meter';
|
|
19
|
+
import './feedback-bar';
|
|
20
|
+
import './chat-scope-picker';
|
|
21
|
+
// Phase 3 — input ecosystem
|
|
22
|
+
// (NB: SlashCommand is context-bound to PromptInput — it observes the input
|
|
23
|
+
// value via usePromptInput() — so it is NOT a standalone element. It will fold
|
|
24
|
+
// into <kitn-prompt-input> as a `slash-commands` property in a later pass.)
|
|
25
|
+
import './prompt-suggestions';
|
|
26
|
+
import './file-upload';
|
|
27
|
+
import './voice-input';
|
|
28
|
+
// Phase 4 — indicators & leaves
|
|
29
|
+
import './loader';
|
|
30
|
+
import './text-shimmer';
|
|
31
|
+
import './image';
|
|
32
|
+
import './checkpoint';
|
|
33
|
+
import './message-skills';
|
|
34
|
+
import './source';
|
|
35
|
+
import './response-stream';
|
|
36
|
+
import './empty';
|
|
37
|
+
import './chain-of-thought';
|
|
6
38
|
|
|
7
39
|
export type { ChatMessage, ChatMessageAction } from './chat-types';
|
|
8
40
|
export { configureCodeHighlighting, isCodeHighlightingEnabled } from '../primitives/highlighter';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { defineKitnElement } from './define';
|
|
2
|
+
import { ResponseStream, type Mode } from '../components/response-stream';
|
|
3
|
+
|
|
4
|
+
interface Props extends Record<string, unknown> {
|
|
5
|
+
/** Text to stream. A string, or an `AsyncIterable<string>` (set as a JS
|
|
6
|
+
* property — async iterables can't be HTML attributes). */
|
|
7
|
+
text?: string | AsyncIterable<string>;
|
|
8
|
+
/** Reveal animation. */
|
|
9
|
+
mode?: Mode;
|
|
10
|
+
/** Characters/segments per tick. */
|
|
11
|
+
speed?: number;
|
|
12
|
+
/** Element tag to render as. */
|
|
13
|
+
as?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Events fired by `<kitn-response-stream>`. */
|
|
17
|
+
interface Events {
|
|
18
|
+
/** Streaming finished. */
|
|
19
|
+
complete: void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `<kitn-response-stream>` — reveals text with a typewriter or fade animation.
|
|
24
|
+
* Text via the `text` property; `mode`/`speed` attributes; emits `complete`.
|
|
25
|
+
*/
|
|
26
|
+
defineKitnElement<Props, Events>('kitn-response-stream', {
|
|
27
|
+
text: '',
|
|
28
|
+
mode: 'typewriter',
|
|
29
|
+
speed: 20,
|
|
30
|
+
as: undefined,
|
|
31
|
+
}, (props, { dispatch }) => (
|
|
32
|
+
<ResponseStream
|
|
33
|
+
textStream={props.text ?? ''}
|
|
34
|
+
mode={props.mode}
|
|
35
|
+
speed={props.speed}
|
|
36
|
+
as={props.as}
|
|
37
|
+
class="text-body"
|
|
38
|
+
onComplete={() => dispatch('complete')}
|
|
39
|
+
/>
|
|
40
|
+
));
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { For, Show } from 'solid-js';
|
|
2
|
+
import { defineKitnElement } from './define';
|
|
3
|
+
import { Source, SourceTrigger, SourceContent, SourceList } from '../components/source';
|
|
4
|
+
|
|
5
|
+
// --- <kitn-source> — a single citation link with hover preview ---
|
|
6
|
+
|
|
7
|
+
interface SourceProps extends Record<string, unknown> {
|
|
8
|
+
/** The URL this citation links to (the domain also seeds the default label/favicon). */
|
|
9
|
+
href?: string;
|
|
10
|
+
/** Trigger label (defaults to the domain). */
|
|
11
|
+
label?: string;
|
|
12
|
+
/** Hover-card headline. Attribute: `headline` (`title` is avoided — it's a
|
|
13
|
+
* global HTML attribute that reflects in a CE constructor and breaks it). */
|
|
14
|
+
headline?: string;
|
|
15
|
+
/** Hover-card body text describing the source. */
|
|
16
|
+
description?: string;
|
|
17
|
+
/** Show the source's favicon next to the trigger label. */
|
|
18
|
+
showFavicon?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
defineKitnElement<SourceProps>('kitn-source', {
|
|
22
|
+
href: '',
|
|
23
|
+
label: undefined,
|
|
24
|
+
headline: '',
|
|
25
|
+
description: '',
|
|
26
|
+
showFavicon: false,
|
|
27
|
+
}, (props, { flag }) => (
|
|
28
|
+
<Show when={props.href}>
|
|
29
|
+
<Source href={props.href!}>
|
|
30
|
+
<SourceTrigger label={props.label} showFavicon={flag('showFavicon')} />
|
|
31
|
+
<SourceContent title={props.headline ?? ''} description={props.description ?? ''} />
|
|
32
|
+
</Source>
|
|
33
|
+
</Show>
|
|
34
|
+
));
|
|
35
|
+
|
|
36
|
+
// --- <kitn-source-list> — a wrapped list of citation links ---
|
|
37
|
+
|
|
38
|
+
interface SourceItem {
|
|
39
|
+
href: string;
|
|
40
|
+
title?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
label?: string;
|
|
43
|
+
showFavicon?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface SourceListProps extends Record<string, unknown> {
|
|
47
|
+
/** The sources to render. Set as a JS property. */
|
|
48
|
+
sources: SourceItem[];
|
|
49
|
+
/** Show favicons on all items (per-item `showFavicon` overrides). */
|
|
50
|
+
showFavicon?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
defineKitnElement<SourceListProps>('kitn-source-list', {
|
|
54
|
+
sources: [],
|
|
55
|
+
showFavicon: false,
|
|
56
|
+
}, (props, { flag }) => (
|
|
57
|
+
<SourceList>
|
|
58
|
+
<For each={props.sources}>
|
|
59
|
+
{(s) => (
|
|
60
|
+
<Source href={s.href}>
|
|
61
|
+
<SourceTrigger label={s.label} showFavicon={s.showFavicon ?? flag('showFavicon')} />
|
|
62
|
+
<SourceContent title={s.title ?? ''} description={s.description ?? ''} />
|
|
63
|
+
</Source>
|
|
64
|
+
)}
|
|
65
|
+
</For>
|
|
66
|
+
</SourceList>
|
|
67
|
+
));
|
package/src/elements/styles.css
CHANGED
|
@@ -9,4 +9,18 @@
|
|
|
9
9
|
@layer base {
|
|
10
10
|
:host { display: block; }
|
|
11
11
|
* { border-color: var(--color-border); }
|
|
12
|
+
|
|
13
|
+
/* One token-driven focus ring for EVERY focusable element in the shadow root.
|
|
14
|
+
Blue by default (via --color-ring), consistent in light + dark, and
|
|
15
|
+
overridable through --kitn-color-ring. Uses `outline` (not a box-shadow
|
|
16
|
+
ring) so it never clips on overflow and follows the element's radius;
|
|
17
|
+
`:focus-visible` so it only appears for keyboard navigation, never on a
|
|
18
|
+
mouse click. Components that render their own ring set
|
|
19
|
+
`focus-visible:outline-none` (a utility in a later cascade layer) and win
|
|
20
|
+
over this — so there's no double ring, just a guaranteed blue fallback for
|
|
21
|
+
the many controls that have no explicit focus style. */
|
|
22
|
+
:focus-visible {
|
|
23
|
+
outline: 2px solid var(--color-ring);
|
|
24
|
+
outline-offset: 2px;
|
|
25
|
+
}
|
|
12
26
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineKitnElement } from './define';
|
|
2
|
+
import { TextShimmer } from '../components/text-shimmer';
|
|
3
|
+
|
|
4
|
+
interface Props extends Record<string, unknown> {
|
|
5
|
+
/** The text to shimmer. */
|
|
6
|
+
text?: string;
|
|
7
|
+
/** Element tag to render as (default `span`). */
|
|
8
|
+
as?: string;
|
|
9
|
+
/** Animation duration in seconds. */
|
|
10
|
+
duration?: number;
|
|
11
|
+
/** Gradient spread (5–45). */
|
|
12
|
+
spread?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `<kitn-text-shimmer>` — animated shimmering text. Text via the `text`
|
|
17
|
+
* attribute; `duration`/`spread` tune the effect.
|
|
18
|
+
*/
|
|
19
|
+
defineKitnElement<Props>('kitn-text-shimmer', {
|
|
20
|
+
text: '',
|
|
21
|
+
as: 'span',
|
|
22
|
+
duration: 4,
|
|
23
|
+
spread: 20,
|
|
24
|
+
}, (props) => (
|
|
25
|
+
<TextShimmer as={props.as} duration={props.duration} spread={props.spread} class="text-body">
|
|
26
|
+
{props.text}
|
|
27
|
+
</TextShimmer>
|
|
28
|
+
));
|