@kitnai/chat 0.1.0 → 0.2.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/dist/css-M7EaDHN_.js +6 -0
- package/dist/html-CPZ3oZQ7.js +10 -0
- package/dist/kitn-chat.es.js +1580 -1249
- package/package.json +1 -1
- package/src/elements/chat.tsx +23 -1
- package/src/elements/define.tsx +34 -6
- package/src/primitives/highlighter.ts +6 -5
- package/src/stories/docs/Theming.mdx +8 -10
- package/dist/tsx-B8rCNbgL.js +0 -6
- package/dist/typescript-RycA9KXf.js +0 -6
package/package.json
CHANGED
package/src/elements/chat.tsx
CHANGED
|
@@ -5,7 +5,10 @@ import { ChatContainer, ChatContainerContent, ChatContainerScrollAnchor } from '
|
|
|
5
5
|
import { Message, MessageContent, MessageActions } from '../components/message';
|
|
6
6
|
import { Reasoning, ReasoningTrigger, ReasoningContent } from '../components/reasoning';
|
|
7
7
|
import { Tool } from '../components/tool';
|
|
8
|
+
import { Attachments, Attachment, AttachmentPreview, AttachmentInfo } from '../components/attachments';
|
|
8
9
|
import { Button } from '../ui/button';
|
|
10
|
+
import { Copy, ThumbsUp, ThumbsDown, RefreshCw, Pencil } from 'lucide-solid';
|
|
11
|
+
import type { Component } from 'solid-js';
|
|
9
12
|
import { DefaultPromptInput } from './default-input';
|
|
10
13
|
import type { ChatMessage, ChatMessageAction } from './chat-types';
|
|
11
14
|
import type { ProseSize } from '../primitives/chat-config';
|
|
@@ -25,6 +28,10 @@ const ACTION_LABEL: Record<ChatMessageAction, string> = {
|
|
|
25
28
|
copy: 'Copy', like: 'Like', dislike: 'Dislike', regenerate: 'Regenerate', edit: 'Edit',
|
|
26
29
|
};
|
|
27
30
|
|
|
31
|
+
const ACTION_ICON: Record<ChatMessageAction, Component<{ class?: string }>> = {
|
|
32
|
+
copy: Copy, like: ThumbsUp, dislike: ThumbsDown, regenerate: RefreshCw, edit: Pencil,
|
|
33
|
+
};
|
|
34
|
+
|
|
28
35
|
defineKitnElement<Props>('kitn-chat', {
|
|
29
36
|
messages: [],
|
|
30
37
|
value: undefined,
|
|
@@ -62,6 +69,18 @@ defineKitnElement<Props>('kitn-chat', {
|
|
|
62
69
|
<For each={m.tools ?? []}>
|
|
63
70
|
{(tp) => <Tool toolPart={tp} class="mb-2 w-full" />}
|
|
64
71
|
</For>
|
|
72
|
+
<Show when={m.attachments?.length}>
|
|
73
|
+
<Attachments variant="inline" class={m.role === 'user' ? 'mb-2 justify-end' : 'mb-2'}>
|
|
74
|
+
<For each={m.attachments!}>
|
|
75
|
+
{(att) => (
|
|
76
|
+
<Attachment data={att}>
|
|
77
|
+
<AttachmentPreview />
|
|
78
|
+
<AttachmentInfo />
|
|
79
|
+
</Attachment>
|
|
80
|
+
)}
|
|
81
|
+
</For>
|
|
82
|
+
</Attachments>
|
|
83
|
+
</Show>
|
|
65
84
|
<MessageContent
|
|
66
85
|
markdown={m.role === 'assistant'}
|
|
67
86
|
class={m.role === 'user'
|
|
@@ -80,7 +99,10 @@ defineKitnElement<Props>('kitn-chat', {
|
|
|
80
99
|
aria-label={ACTION_LABEL[a]}
|
|
81
100
|
onClick={() => dispatch('messageaction', { messageId: m.id, action: a })}
|
|
82
101
|
>
|
|
83
|
-
|
|
102
|
+
{(() => {
|
|
103
|
+
const Icon = ACTION_ICON[a];
|
|
104
|
+
return <Icon class="size-3.5" />;
|
|
105
|
+
})()}
|
|
84
106
|
</Button>
|
|
85
107
|
)}
|
|
86
108
|
</For>
|
package/src/elements/define.tsx
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import { customElement } from 'solid-element';
|
|
2
2
|
import { ChatConfig } from '../primitives/chat-config';
|
|
3
3
|
import { KITN_CSS } from './css';
|
|
4
|
-
import type
|
|
4
|
+
import { createSignal, onCleanup, type JSX } from 'solid-js';
|
|
5
|
+
|
|
6
|
+
/** Resolve whether the element should render dark, given its `theme` and the
|
|
7
|
+
* system preference. `auto` (the default) follows `prefers-color-scheme`. */
|
|
8
|
+
function createDarkMode(getTheme: () => string | undefined) {
|
|
9
|
+
const [systemDark, setSystemDark] = createSignal(false);
|
|
10
|
+
if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {
|
|
11
|
+
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
12
|
+
setSystemDark(mq.matches);
|
|
13
|
+
const onChange = (e: MediaQueryListEvent) => setSystemDark(e.matches);
|
|
14
|
+
mq.addEventListener('change', onChange);
|
|
15
|
+
onCleanup(() => mq.removeEventListener('change', onChange));
|
|
16
|
+
}
|
|
17
|
+
return () => {
|
|
18
|
+
const theme = getTheme() ?? 'auto';
|
|
19
|
+
return theme === 'dark' || (theme === 'auto' && systemDark());
|
|
20
|
+
};
|
|
21
|
+
}
|
|
5
22
|
|
|
6
23
|
export interface KitnElementContext {
|
|
7
24
|
/** The custom-element host node. */
|
|
@@ -32,7 +49,13 @@ export function defineKitnElement<P extends Record<string, unknown>>(
|
|
|
32
49
|
): void {
|
|
33
50
|
if (typeof customElements !== 'undefined' && customElements.get(tag)) return;
|
|
34
51
|
|
|
35
|
-
|
|
52
|
+
// Every element gets a `theme` property/attribute: 'light' | 'dark' | 'auto'
|
|
53
|
+
// (default 'auto' = follow the OS `prefers-color-scheme`). It drives a `.dark`
|
|
54
|
+
// class on an inner wrapper, which the injected kit CSS already styles — so dark
|
|
55
|
+
// mode works in standalone Shadow-DOM usage with no token duplication.
|
|
56
|
+
const defaults = { theme: 'auto', ...propDefaults };
|
|
57
|
+
|
|
58
|
+
customElement(tag, defaults, (props: typeof defaults, options: { element: object }) => {
|
|
36
59
|
const element = options.element as HTMLElement;
|
|
37
60
|
let portalNode!: HTMLDivElement;
|
|
38
61
|
|
|
@@ -41,13 +64,18 @@ export function defineKitnElement<P extends Record<string, unknown>>(
|
|
|
41
64
|
new CustomEvent(type, { detail, bubbles: false, composed: false }),
|
|
42
65
|
);
|
|
43
66
|
|
|
67
|
+
const isDark = createDarkMode(() => props.theme as string | undefined);
|
|
68
|
+
|
|
44
69
|
return (
|
|
45
70
|
<>
|
|
46
71
|
<style>{KITN_CSS}</style>
|
|
47
|
-
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
72
|
+
{/* display:contents — no layout box; just carries the .dark token scope. */}
|
|
73
|
+
<div classList={{ dark: isDark() }} style={{ display: 'contents' }}>
|
|
74
|
+
<div ref={portalNode} />
|
|
75
|
+
<ChatConfig portalMount={portalNode}>
|
|
76
|
+
{Facade(props as unknown as P, { element, dispatch })}
|
|
77
|
+
</ChatConfig>
|
|
78
|
+
</div>
|
|
51
79
|
</>
|
|
52
80
|
);
|
|
53
81
|
});
|
|
@@ -14,14 +14,15 @@ type Loader = () => Promise<unknown>;
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Minimal default language set — each a separate lazy chunk, loaded only on use.
|
|
17
|
-
* Kept deliberately small; hosts add more
|
|
17
|
+
* Kept deliberately small to keep the bundle lean; hosts add more at runtime via
|
|
18
|
+
* `configureCodeHighlighting({ languages })` (no rebuild needed — see that fn).
|
|
18
19
|
*/
|
|
19
20
|
const DEFAULT_LANGUAGES: Record<string, Loader> = {
|
|
21
|
+
bash: () => import('@shikijs/langs/bash'),
|
|
20
22
|
javascript: () => import('@shikijs/langs/javascript'),
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
html: () => import('@shikijs/langs/html'),
|
|
24
|
+
css: () => import('@shikijs/langs/css'),
|
|
23
25
|
json: () => import('@shikijs/langs/json'),
|
|
24
|
-
bash: () => import('@shikijs/langs/bash'),
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
const DEFAULT_THEMES: Record<string, Loader> = {
|
|
@@ -31,8 +32,8 @@ const DEFAULT_THEMES: Record<string, Loader> = {
|
|
|
31
32
|
|
|
32
33
|
const DEFAULT_ALIASES: Record<string, string> = {
|
|
33
34
|
js: 'javascript',
|
|
34
|
-
ts: 'typescript',
|
|
35
35
|
sh: 'bash',
|
|
36
|
+
shell: 'bash',
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
const FALLBACK_THEME = 'github-dark-dimmed';
|
|
@@ -31,21 +31,19 @@ Edit every token for **light and dark** modes and watch a real chat UI re-skin l
|
|
|
31
31
|
<a
|
|
32
32
|
href="?path=/story/theming-editor--editor"
|
|
33
33
|
style={{
|
|
34
|
-
display: 'inline-
|
|
35
|
-
alignItems: 'center',
|
|
36
|
-
gap: '0.45rem',
|
|
34
|
+
display: 'inline-block',
|
|
37
35
|
background: 'var(--color-primary, #18181b)',
|
|
38
|
-
color: 'var(--color-primary-foreground, #fafafa)',
|
|
39
|
-
padding: '0.55rem 1rem',
|
|
40
36
|
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
41
|
-
|
|
42
|
-
fontSize: '0.875rem',
|
|
43
|
-
lineHeight: 1,
|
|
37
|
+
padding: '0.4rem 0.85rem',
|
|
44
38
|
textDecoration: 'none',
|
|
45
|
-
boxShadow: '0 1px 2px rgba(0,0,0,0.08)',
|
|
46
39
|
}}
|
|
47
40
|
>
|
|
48
|
-
|
|
41
|
+
<span style={{
|
|
42
|
+
color: 'var(--color-primary-foreground, #fafafa)',
|
|
43
|
+
fontWeight: 600,
|
|
44
|
+
fontSize: '0.8125rem',
|
|
45
|
+
lineHeight: 1.3,
|
|
46
|
+
}}>Open the theme editor</span>
|
|
49
47
|
</a>
|
|
50
48
|
|
|
51
49
|
_(Opens full-screen — it needs more room than this docs column.)_
|