@kitnai/chat 0.2.0 → 0.3.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/dist/bash-InADTalH.js +1 -6
- package/dist/core-AYMC6_lb.js +12 -5874
- package/dist/css-M7EaDHN_.js +1 -6
- package/dist/engine-javascript-vq0WuIJl.js +14 -2516
- package/dist/github-dark-dimmed-DUshB20C.js +1 -4
- package/dist/github-light-JYsPkUQd.js +1 -4
- package/dist/html-CPZ3oZQ7.js +1 -10
- package/dist/javascript-C25yR2R2.js +1 -6
- package/dist/json-DxJze_jm.js +1 -6
- package/dist/kitn-chat.es.js +62 -6961
- package/package.json +1 -1
- package/src/components/conversation-item.tsx +2 -4
- package/src/elements/chat.tsx +104 -3
- package/src/elements/compiled.css +1 -1
- package/src/elements/default-input.tsx +116 -2
- package/src/elements/define.tsx +5 -2
- package/src/elements/prompt-input.tsx +8 -1
|
@@ -2,6 +2,15 @@ import { For, Show } from 'solid-js';
|
|
|
2
2
|
import { PromptInput, PromptInputTextarea, PromptInputActions } from '../components/prompt-input';
|
|
3
3
|
import { PromptSuggestion } from '../components/prompt-suggestion';
|
|
4
4
|
import { Button } from '../ui/button';
|
|
5
|
+
import { Paperclip, Globe, Mic } from 'lucide-solid';
|
|
6
|
+
import {
|
|
7
|
+
Attachments,
|
|
8
|
+
Attachment,
|
|
9
|
+
AttachmentPreview,
|
|
10
|
+
AttachmentInfo,
|
|
11
|
+
AttachmentRemove,
|
|
12
|
+
type AttachmentData,
|
|
13
|
+
} from '../components/attachments';
|
|
5
14
|
|
|
6
15
|
export interface DefaultPromptInputProps {
|
|
7
16
|
value: string;
|
|
@@ -9,12 +18,50 @@ export interface DefaultPromptInputProps {
|
|
|
9
18
|
disabled?: boolean;
|
|
10
19
|
loading?: boolean;
|
|
11
20
|
suggestions?: string[];
|
|
21
|
+
/** Attachments staged in the input. Provide `onAttachmentsChange` to enable
|
|
22
|
+
* the attach button + removable previews. */
|
|
23
|
+
attachments?: AttachmentData[];
|
|
24
|
+
/** Show a Search (Globe) button in the left toolbar; calls `onSearch`. */
|
|
25
|
+
search?: boolean;
|
|
26
|
+
/** Show a Voice (Mic) button in the left toolbar; calls `onVoice`. */
|
|
27
|
+
voice?: boolean;
|
|
12
28
|
onValueChange: (v: string) => void;
|
|
13
29
|
onSubmit: () => void;
|
|
14
30
|
onSuggestionClick: (v: string) => void;
|
|
31
|
+
onAttachmentsChange?: (attachments: AttachmentData[]) => void;
|
|
32
|
+
onSearch?: () => void;
|
|
33
|
+
onVoice?: () => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function fileToAttachment(file: File): AttachmentData {
|
|
37
|
+
const id =
|
|
38
|
+
typeof crypto !== 'undefined' && crypto.randomUUID
|
|
39
|
+
? crypto.randomUUID()
|
|
40
|
+
: `${file.name}-${file.size}-${file.lastModified}`;
|
|
41
|
+
return {
|
|
42
|
+
id,
|
|
43
|
+
type: 'file',
|
|
44
|
+
filename: file.name,
|
|
45
|
+
mediaType: file.type || undefined,
|
|
46
|
+
url: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,
|
|
47
|
+
};
|
|
15
48
|
}
|
|
16
49
|
|
|
17
50
|
export function DefaultPromptInput(props: DefaultPromptInputProps) {
|
|
51
|
+
let fileInput: HTMLInputElement | undefined;
|
|
52
|
+
const attachments = () => props.attachments ?? [];
|
|
53
|
+
const canAttach = () => !!props.onAttachmentsChange;
|
|
54
|
+
|
|
55
|
+
const addFiles = (files: FileList | null) => {
|
|
56
|
+
if (!files?.length || !props.onAttachmentsChange) return;
|
|
57
|
+
props.onAttachmentsChange([...attachments(), ...Array.from(files).map(fileToAttachment)]);
|
|
58
|
+
};
|
|
59
|
+
const removeAttachment = (id: string) =>
|
|
60
|
+
props.onAttachmentsChange?.(attachments().filter((a) => a.id !== id));
|
|
61
|
+
|
|
62
|
+
const sendDisabled = () =>
|
|
63
|
+
props.disabled || props.loading || (!props.value.trim() && attachments().length === 0);
|
|
64
|
+
|
|
18
65
|
return (
|
|
19
66
|
<>
|
|
20
67
|
<Show when={props.suggestions?.length}>
|
|
@@ -33,13 +80,80 @@ export function DefaultPromptInput(props: DefaultPromptInputProps) {
|
|
|
33
80
|
isLoading={props.loading}
|
|
34
81
|
disabled={props.disabled}
|
|
35
82
|
>
|
|
83
|
+
<Show when={canAttach() && attachments().length}>
|
|
84
|
+
<div class="px-3 pt-3">
|
|
85
|
+
<Attachments variant="inline">
|
|
86
|
+
<For each={attachments()}>
|
|
87
|
+
{(att) => (
|
|
88
|
+
<Attachment data={att} onRemove={() => removeAttachment(att.id)}>
|
|
89
|
+
<AttachmentPreview />
|
|
90
|
+
<AttachmentInfo />
|
|
91
|
+
<AttachmentRemove />
|
|
92
|
+
</Attachment>
|
|
93
|
+
)}
|
|
94
|
+
</For>
|
|
95
|
+
</Attachments>
|
|
96
|
+
</div>
|
|
97
|
+
</Show>
|
|
36
98
|
<PromptInputTextarea placeholder={props.placeholder} class="min-h-[44px] pt-3 pl-4" />
|
|
37
|
-
<PromptInputActions class="mt-2 flex w-full items-center justify-
|
|
99
|
+
<PromptInputActions class="mt-2 flex w-full items-center justify-between gap-2 px-3 pb-3">
|
|
100
|
+
<div class="flex items-center gap-2">
|
|
101
|
+
<Show when={canAttach()}>
|
|
102
|
+
<input
|
|
103
|
+
ref={fileInput}
|
|
104
|
+
type="file"
|
|
105
|
+
multiple
|
|
106
|
+
class="hidden"
|
|
107
|
+
onChange={(e) => {
|
|
108
|
+
addFiles(e.currentTarget.files);
|
|
109
|
+
e.currentTarget.value = ''; // allow re-picking the same file
|
|
110
|
+
}}
|
|
111
|
+
/>
|
|
112
|
+
<Button
|
|
113
|
+
type="button"
|
|
114
|
+
variant="outline"
|
|
115
|
+
size="icon-sm"
|
|
116
|
+
class="rounded-full"
|
|
117
|
+
aria-label="Attach files"
|
|
118
|
+
disabled={props.disabled}
|
|
119
|
+
onClick={() => fileInput?.click()}
|
|
120
|
+
>
|
|
121
|
+
<Paperclip class="size-4" />
|
|
122
|
+
</Button>
|
|
123
|
+
</Show>
|
|
124
|
+
<Show when={props.search}>
|
|
125
|
+
<Button
|
|
126
|
+
type="button"
|
|
127
|
+
variant="outline"
|
|
128
|
+
size="sm"
|
|
129
|
+
class="rounded-full gap-1"
|
|
130
|
+
aria-label="Search the web"
|
|
131
|
+
disabled={props.disabled}
|
|
132
|
+
onClick={() => props.onSearch?.()}
|
|
133
|
+
>
|
|
134
|
+
<Globe class="size-4" />
|
|
135
|
+
Search
|
|
136
|
+
</Button>
|
|
137
|
+
</Show>
|
|
138
|
+
<Show when={props.voice}>
|
|
139
|
+
<Button
|
|
140
|
+
type="button"
|
|
141
|
+
variant="outline"
|
|
142
|
+
size="icon-sm"
|
|
143
|
+
class="rounded-full"
|
|
144
|
+
aria-label="Voice input"
|
|
145
|
+
disabled={props.disabled}
|
|
146
|
+
onClick={() => props.onVoice?.()}
|
|
147
|
+
>
|
|
148
|
+
<Mic class="size-4" />
|
|
149
|
+
</Button>
|
|
150
|
+
</Show>
|
|
151
|
+
</div>
|
|
38
152
|
<Button
|
|
39
153
|
size="icon-sm"
|
|
40
154
|
class="rounded-full"
|
|
41
155
|
data-testid="send"
|
|
42
|
-
disabled={
|
|
156
|
+
disabled={sendDisabled()}
|
|
43
157
|
onClick={props.onSubmit}
|
|
44
158
|
>
|
|
45
159
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
package/src/elements/define.tsx
CHANGED
|
@@ -69,8 +69,11 @@ export function defineKitnElement<P extends Record<string, unknown>>(
|
|
|
69
69
|
return (
|
|
70
70
|
<>
|
|
71
71
|
<style>{KITN_CSS}</style>
|
|
72
|
-
{/* display:contents — no layout box;
|
|
73
|
-
|
|
72
|
+
{/* display:contents — no layout box; carries the .dark token scope and
|
|
73
|
+
re-roots the inherited `color` to the active mode's foreground, so text
|
|
74
|
+
without an explicit color class (e.g. attachment filename labels) follows
|
|
75
|
+
light/dark instead of inheriting the host page's color. */}
|
|
76
|
+
<div classList={{ dark: isDark() }} style={{ display: 'contents', color: 'var(--color-foreground)' }}>
|
|
74
77
|
<div ref={portalNode} />
|
|
75
78
|
<ChatConfig portalMount={portalNode}>
|
|
76
79
|
{Facade(props as unknown as P, { element, dispatch })}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createSignal } from 'solid-js';
|
|
2
2
|
import { defineKitnElement } from './define';
|
|
3
3
|
import { DefaultPromptInput } from './default-input';
|
|
4
|
+
import type { AttachmentData } from '../components/attachments';
|
|
4
5
|
|
|
5
6
|
interface Props extends Record<string, unknown> {
|
|
6
7
|
value?: string;
|
|
@@ -18,10 +19,14 @@ defineKitnElement<Props>('kitn-prompt-input', {
|
|
|
18
19
|
suggestions: undefined,
|
|
19
20
|
}, (props, { dispatch }) => {
|
|
20
21
|
const [internal, setInternal] = createSignal(props.value ?? '');
|
|
22
|
+
const [attachments, setAttachments] = createSignal<AttachmentData[]>([]);
|
|
21
23
|
const current = () => props.value ?? internal();
|
|
22
24
|
|
|
23
25
|
const handleChange = (v: string) => { setInternal(v); dispatch('valuechange', { value: v }); };
|
|
24
|
-
const handleSubmit = () =>
|
|
26
|
+
const handleSubmit = () => {
|
|
27
|
+
dispatch('submit', { value: current(), attachments: attachments() });
|
|
28
|
+
setAttachments([]);
|
|
29
|
+
};
|
|
25
30
|
const handleSuggestionClick = (v: string) => { handleChange(v); dispatch('suggestionclick', { value: v }); };
|
|
26
31
|
|
|
27
32
|
return (
|
|
@@ -31,9 +36,11 @@ defineKitnElement<Props>('kitn-prompt-input', {
|
|
|
31
36
|
disabled={props.disabled}
|
|
32
37
|
loading={props.loading}
|
|
33
38
|
suggestions={props.suggestions}
|
|
39
|
+
attachments={attachments()}
|
|
34
40
|
onValueChange={handleChange}
|
|
35
41
|
onSubmit={handleSubmit}
|
|
36
42
|
onSuggestionClick={handleSuggestionClick}
|
|
43
|
+
onAttachmentsChange={setAttachments}
|
|
37
44
|
/>
|
|
38
45
|
);
|
|
39
46
|
});
|