@aleph-alpha/chat-kit 2.1.0 → 2.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/{CkInput.vue_vue_type_script_setup_true_lang-DPZedAQS.js → CkInput.vue_vue_type_script_setup_true_lang-CjfKRuZs.js} +291 -61
- package/dist/chat-kit.css +40 -5
- package/dist/components/base/CkDropZoneOverlay/CkDropZoneIllustration.vue.d.ts +4 -0
- package/dist/components/base/CkDropZoneOverlay/CkDropZoneIllustration.vue.d.ts.map +1 -0
- package/dist/components/base/CkDropZoneOverlay/CkDropZoneOverlay.stories.d.ts +17 -0
- package/dist/components/base/CkDropZoneOverlay/CkDropZoneOverlay.stories.d.ts.map +1 -0
- package/dist/components/base/CkDropZoneOverlay/CkDropZoneOverlay.vue.d.ts +31 -0
- package/dist/components/base/CkDropZoneOverlay/CkDropZoneOverlay.vue.d.ts.map +1 -0
- package/dist/components/base/CkDropZoneOverlay/index.d.ts +3 -0
- package/dist/components/base/CkDropZoneOverlay/index.d.ts.map +1 -0
- package/dist/components/base/CkDropZoneOverlay/types.d.ts +88 -0
- package/dist/components/base/CkDropZoneOverlay/types.d.ts.map +1 -0
- package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.vue.d.ts.map +1 -1
- package/dist/components/base/index.d.ts +2 -0
- package/dist/components/base/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +19 -18
- package/dist/composables/fileUpload/index.d.ts +1 -1
- package/dist/composables/fileUpload/index.d.ts.map +1 -1
- package/dist/composables/fileUpload/types.d.ts +11 -1
- package/dist/composables/fileUpload/types.d.ts.map +1 -1
- package/dist/composables/fileUpload/useFileUpload.d.ts.map +1 -1
- package/dist/composables/fileUpload/useFileUploadWrapper.d.ts.map +1 -1
- package/dist/composables/index.js +1 -1
- package/dist/{index-DrtHeZpl.js → index-CfXEGjc7.js} +73 -27
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -20
- package/package.json +1 -1
- package/src/components/base/CkDropZoneOverlay/CkDropZoneIllustration.vue +125 -0
- package/src/components/base/CkDropZoneOverlay/CkDropZoneOverlay.spec.ts +104 -0
- package/src/components/base/CkDropZoneOverlay/CkDropZoneOverlay.stories.ts +130 -0
- package/src/components/base/CkDropZoneOverlay/CkDropZoneOverlay.vue +272 -0
- package/src/components/base/CkDropZoneOverlay/index.ts +2 -0
- package/src/components/base/CkDropZoneOverlay/types.ts +93 -0
- package/src/components/base/CkFeedbackDialog/CkFeedbackDialog.vue +22 -1
- package/src/components/base/index.ts +7 -0
- package/src/components/index.ts +5 -0
- package/src/composables/fileUpload/index.spec.ts +39 -7
- package/src/composables/fileUpload/index.ts +41 -19
- package/src/composables/fileUpload/types.ts +15 -1
- package/src/composables/fileUpload/useFileUpload.spec.ts +147 -0
- package/src/composables/fileUpload/useFileUpload.ts +18 -4
- package/src/composables/fileUpload/useFileUploadWrapper.spec.ts +202 -0
- package/src/composables/fileUpload/useFileUploadWrapper.ts +25 -3
- package/src/index.ts +5 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { createDocumentUploadProvider } from './useFileUploadWrapper';
|
|
2
|
+
import type { DocumentUploadConfig } from './types';
|
|
3
|
+
|
|
4
|
+
function createFile(name: string, size = 100): File {
|
|
5
|
+
return new File(['x'.repeat(size)], name, { type: 'application/pdf' });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function createMockFetchResponse(body: unknown, status = 200) {
|
|
9
|
+
return Promise.resolve({
|
|
10
|
+
ok: status >= 200 && status < 300,
|
|
11
|
+
status,
|
|
12
|
+
headers: new Headers({ 'content-length': '1' }),
|
|
13
|
+
json: () => Promise.resolve(body),
|
|
14
|
+
body: null,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makeInputEvent(files: File[]): Event {
|
|
19
|
+
const input = document.createElement('input');
|
|
20
|
+
input.type = 'file';
|
|
21
|
+
Object.defineProperty(input, 'files', {
|
|
22
|
+
value: files,
|
|
23
|
+
writable: true,
|
|
24
|
+
});
|
|
25
|
+
return { target: input } as unknown as Event;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildConfig(
|
|
29
|
+
overrides?: Partial<DocumentUploadConfig>,
|
|
30
|
+
): DocumentUploadConfig {
|
|
31
|
+
return {
|
|
32
|
+
baseUrl: 'https://test.example.com',
|
|
33
|
+
userId: 'user-1',
|
|
34
|
+
workflowId: 'wf-1',
|
|
35
|
+
searchStoreConfig: {
|
|
36
|
+
models: [
|
|
37
|
+
{
|
|
38
|
+
type: 'dense',
|
|
39
|
+
dimension: 1024,
|
|
40
|
+
distance: 'cosine',
|
|
41
|
+
modality: 'text',
|
|
42
|
+
embedding_model: 'test-model',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
fusion_strategy: { type: 'rrf' },
|
|
46
|
+
},
|
|
47
|
+
workflowConfig: { name: 'wf-1' },
|
|
48
|
+
...overrides,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('useFileUploadWrapper handleFilesSelected', () => {
|
|
53
|
+
let fetchMock: ReturnType<typeof vi.fn>;
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
fetchMock = vi.fn();
|
|
57
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
58
|
+
|
|
59
|
+
fetchMock.mockImplementation((url: string, init?: RequestInit) => {
|
|
60
|
+
const method = init?.method ?? 'GET';
|
|
61
|
+
|
|
62
|
+
if (method === 'GET' && url.includes('search-stores')) {
|
|
63
|
+
return createMockFetchResponse([{ id: 'store-1' }]);
|
|
64
|
+
}
|
|
65
|
+
if (method === 'POST' && url.includes('/files')) {
|
|
66
|
+
return createMockFetchResponse([{ id: 'remote-1' }]);
|
|
67
|
+
}
|
|
68
|
+
if (method === 'POST' && url.includes('/workflows')) {
|
|
69
|
+
return createMockFetchResponse({ id: 'run-1' });
|
|
70
|
+
}
|
|
71
|
+
if (url.includes('/event-stream')) {
|
|
72
|
+
return createMockFetchResponse(null, 200).then((res) => ({
|
|
73
|
+
...res,
|
|
74
|
+
body: new ReadableStream({
|
|
75
|
+
start(controller) {
|
|
76
|
+
controller.close();
|
|
77
|
+
},
|
|
78
|
+
}),
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
return createMockFetchResponse({});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
afterEach(() => {
|
|
86
|
+
vi.unstubAllGlobals();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('exposes maxFilesPerUpload from config', () => {
|
|
90
|
+
const provider = createDocumentUploadProvider(
|
|
91
|
+
buildConfig({ maxFilesPerUpload: 7 }),
|
|
92
|
+
);
|
|
93
|
+
const result = provider.useDocumentUpload('conv-1');
|
|
94
|
+
expect(result.maxFilesPerUpload).toBe(7);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('defaults maxFilesPerUpload to 20 when not configured', () => {
|
|
98
|
+
const provider = createDocumentUploadProvider(buildConfig());
|
|
99
|
+
const result = provider.useDocumentUpload('conv-1');
|
|
100
|
+
expect(result.maxFilesPerUpload).toBe(20);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('uploads all files when within the limit', async () => {
|
|
104
|
+
const provider = createDocumentUploadProvider(
|
|
105
|
+
buildConfig({ maxFilesPerUpload: 5 }),
|
|
106
|
+
);
|
|
107
|
+
const { handleFilesSelected } = provider.useDocumentUpload('conv-1');
|
|
108
|
+
|
|
109
|
+
const onFilesExceeded = vi.fn();
|
|
110
|
+
const event = makeInputEvent([createFile('a.pdf'), createFile('b.pdf')]);
|
|
111
|
+
|
|
112
|
+
handleFilesSelected(event, { onFilesExceeded });
|
|
113
|
+
|
|
114
|
+
expect(onFilesExceeded).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('truncates files to available slots and calls onFilesExceeded', () => {
|
|
118
|
+
const provider = createDocumentUploadProvider(
|
|
119
|
+
buildConfig({ maxFilesPerUpload: 2 }),
|
|
120
|
+
);
|
|
121
|
+
const { handleFilesSelected } = provider.useDocumentUpload('conv-1');
|
|
122
|
+
|
|
123
|
+
const onFilesExceeded = vi.fn();
|
|
124
|
+
const event = makeInputEvent([
|
|
125
|
+
createFile('a.pdf'),
|
|
126
|
+
createFile('b.pdf'),
|
|
127
|
+
createFile('c.pdf'),
|
|
128
|
+
createFile('d.pdf'),
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
handleFilesSelected(event, { onFilesExceeded });
|
|
132
|
+
|
|
133
|
+
expect(onFilesExceeded).toHaveBeenCalledWith({
|
|
134
|
+
selected: 4,
|
|
135
|
+
allowed: 2,
|
|
136
|
+
kept: 2,
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('resets input value after handling', () => {
|
|
141
|
+
const provider = createDocumentUploadProvider(
|
|
142
|
+
buildConfig({ maxFilesPerUpload: 5 }),
|
|
143
|
+
);
|
|
144
|
+
const { handleFilesSelected } = provider.useDocumentUpload('conv-1');
|
|
145
|
+
|
|
146
|
+
const event = makeInputEvent([createFile('a.pdf')]);
|
|
147
|
+
const input = (event as any).target as HTMLInputElement;
|
|
148
|
+
|
|
149
|
+
const valueSetter = vi.spyOn(input, 'value', 'set');
|
|
150
|
+
|
|
151
|
+
handleFilesSelected(event);
|
|
152
|
+
|
|
153
|
+
expect(valueSetter).toHaveBeenCalledWith('');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('does not upload when no slots are available', async () => {
|
|
157
|
+
const provider = createDocumentUploadProvider(
|
|
158
|
+
buildConfig({ maxFilesPerUpload: 0 }),
|
|
159
|
+
);
|
|
160
|
+
const { handleFilesSelected } = provider.useDocumentUpload('conv-1');
|
|
161
|
+
|
|
162
|
+
const onFilesExceeded = vi.fn();
|
|
163
|
+
const event = makeInputEvent([createFile('a.pdf')]);
|
|
164
|
+
|
|
165
|
+
handleFilesSelected(event, { onFilesExceeded });
|
|
166
|
+
|
|
167
|
+
expect(onFilesExceeded).toHaveBeenCalledWith({
|
|
168
|
+
selected: 1,
|
|
169
|
+
allowed: 0,
|
|
170
|
+
kept: 0,
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('does not call onFilesExceeded when exactly at the limit', () => {
|
|
175
|
+
const provider = createDocumentUploadProvider(
|
|
176
|
+
buildConfig({ maxFilesPerUpload: 3 }),
|
|
177
|
+
);
|
|
178
|
+
const { handleFilesSelected } = provider.useDocumentUpload('conv-1');
|
|
179
|
+
|
|
180
|
+
const onFilesExceeded = vi.fn();
|
|
181
|
+
const event = makeInputEvent([
|
|
182
|
+
createFile('a.pdf'),
|
|
183
|
+
createFile('b.pdf'),
|
|
184
|
+
createFile('c.pdf'),
|
|
185
|
+
]);
|
|
186
|
+
|
|
187
|
+
handleFilesSelected(event, { onFilesExceeded });
|
|
188
|
+
|
|
189
|
+
expect(onFilesExceeded).not.toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('works without onFilesExceeded callback (no crash)', () => {
|
|
193
|
+
const provider = createDocumentUploadProvider(
|
|
194
|
+
buildConfig({ maxFilesPerUpload: 1 }),
|
|
195
|
+
);
|
|
196
|
+
const { handleFilesSelected } = provider.useDocumentUpload('conv-1');
|
|
197
|
+
|
|
198
|
+
const event = makeInputEvent([createFile('a.pdf'), createFile('b.pdf')]);
|
|
199
|
+
|
|
200
|
+
expect(() => handleFilesSelected(event)).not.toThrow();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
DocumentUploadConfig,
|
|
10
10
|
DocumentUploadProvider,
|
|
11
11
|
DocumentUploadReturn,
|
|
12
|
+
HandleFilesSelectedOptions,
|
|
12
13
|
UploadEvent,
|
|
13
14
|
} from './types';
|
|
14
15
|
import { useFileUpload } from './useFileUpload';
|
|
@@ -166,6 +167,7 @@ export function createDocumentUploadProvider(
|
|
|
166
167
|
workflowConfig,
|
|
167
168
|
persist = true,
|
|
168
169
|
maxConcurrency = 3,
|
|
170
|
+
maxFilesPerUpload,
|
|
169
171
|
} = config;
|
|
170
172
|
|
|
171
173
|
const defaultHeaders: Record<string, string> = {
|
|
@@ -249,6 +251,7 @@ export function createDocumentUploadProvider(
|
|
|
249
251
|
workflowId,
|
|
250
252
|
persist,
|
|
251
253
|
maxConcurrency,
|
|
254
|
+
maxFilesPerUpload,
|
|
252
255
|
onEvent,
|
|
253
256
|
});
|
|
254
257
|
}
|
|
@@ -262,6 +265,7 @@ export function createDocumentUploadProvider(
|
|
|
262
265
|
): DocumentUploadReturn {
|
|
263
266
|
const fileInput = ref<HTMLInputElement | null>(null);
|
|
264
267
|
const upload = getUploadInstance();
|
|
268
|
+
const resolvedMaxFiles = upload.getSupportedFileTypes().maxFilesPerUpload;
|
|
265
269
|
const storeId = ref<string>(resolvedStoreId ?? '');
|
|
266
270
|
|
|
267
271
|
const currentScope = shallowRef<ScopedFileView | null>(
|
|
@@ -360,10 +364,27 @@ export function createDocumentUploadProvider(
|
|
|
360
364
|
}
|
|
361
365
|
}
|
|
362
366
|
|
|
363
|
-
function handleFilesSelected(
|
|
367
|
+
function handleFilesSelected(
|
|
368
|
+
event: Event,
|
|
369
|
+
options?: HandleFilesSelectedOptions,
|
|
370
|
+
) {
|
|
364
371
|
const input = event.target as HTMLInputElement;
|
|
365
|
-
|
|
366
|
-
|
|
372
|
+
let fileList = Array.from(input.files ?? []);
|
|
373
|
+
const availableSlots = resolvedMaxFiles - unassignedFiles.value.length;
|
|
374
|
+
|
|
375
|
+
if (fileList.length > availableSlots) {
|
|
376
|
+
const kept = Math.max(0, availableSlots);
|
|
377
|
+
options?.onFilesExceeded?.({
|
|
378
|
+
selected: fileList.length,
|
|
379
|
+
allowed: availableSlots,
|
|
380
|
+
kept,
|
|
381
|
+
});
|
|
382
|
+
fileList = fileList.slice(0, kept);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (fileList.length > 0) {
|
|
386
|
+
uploadFiles(fileList);
|
|
387
|
+
}
|
|
367
388
|
input.value = '';
|
|
368
389
|
}
|
|
369
390
|
|
|
@@ -371,6 +392,7 @@ export function createDocumentUploadProvider(
|
|
|
371
392
|
upload,
|
|
372
393
|
storeId,
|
|
373
394
|
fileInput,
|
|
395
|
+
maxFilesPerUpload: resolvedMaxFiles,
|
|
374
396
|
uploadFiles,
|
|
375
397
|
unassignedFiles,
|
|
376
398
|
assignFilesToMessage,
|
package/src/index.ts
CHANGED
|
@@ -66,6 +66,7 @@ export {
|
|
|
66
66
|
CkConversation,
|
|
67
67
|
CkConversationLayout,
|
|
68
68
|
CkCopyMessageActionButton,
|
|
69
|
+
CkDropZoneOverlay,
|
|
69
70
|
CkFeedbackButton,
|
|
70
71
|
CkFeedbackDialog,
|
|
71
72
|
CkFileItemsList,
|
|
@@ -87,6 +88,10 @@ export {
|
|
|
87
88
|
export type {
|
|
88
89
|
CkConversationLayoutProps,
|
|
89
90
|
CkConversationProps,
|
|
91
|
+
CkDropZoneOverlayProps,
|
|
92
|
+
CkDropZoneOverlayEmits,
|
|
93
|
+
CkDropZoneRejection,
|
|
94
|
+
CkDropZoneRejectionReason,
|
|
90
95
|
CkFeedbackButtonProps,
|
|
91
96
|
CkFeedbackDialogEmits,
|
|
92
97
|
CkFeedbackDialogProps,
|