@baishuyun/chat-sdk 0.1.3 → 0.1.5
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/CHANGELOG.md +12 -0
- package/dist/chat-sdk.js +4432 -4393
- package/dist/chat-sdk.js.map +1 -1
- package/dist/chat-sdk.umd.cjs +102 -102
- package/dist/chat-sdk.umd.cjs.map +1 -1
- package/package.json +1 -1
- package/src/components/biz-comp/multi-modal-input/index.tsx +19 -28
- package/src/lib/utils.ts +70 -1
- package/src/sdk.impl.tsx +23 -6
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
useRef,
|
|
12
12
|
useState,
|
|
13
13
|
} from 'react';
|
|
14
|
-
import { cn } from '@/lib/utils';
|
|
14
|
+
import { cn, uploadFile } from '@/lib/utils';
|
|
15
15
|
import {
|
|
16
16
|
PromptInput,
|
|
17
17
|
PromptInputSubmit,
|
|
@@ -154,28 +154,10 @@ function PureMultimodalInput({
|
|
|
154
154
|
|
|
155
155
|
const toast = useToast();
|
|
156
156
|
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
const response = await fetch(fileUploadEndpoint, {
|
|
163
|
-
method: 'POST',
|
|
164
|
-
body: formData,
|
|
165
|
-
signal,
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
if (response.ok) {
|
|
169
|
-
const data = await response.json();
|
|
170
|
-
|
|
171
|
-
console.log(data);
|
|
172
|
-
|
|
173
|
-
return data;
|
|
174
|
-
}
|
|
175
|
-
const { error } = await response.json();
|
|
176
|
-
console.error('upload error', error);
|
|
177
|
-
} catch (_error) {}
|
|
178
|
-
}, []);
|
|
157
|
+
const upload = useCallback(
|
|
158
|
+
(file: File, signal?: AbortSignal) => uploadFile(file, fileUploadEndpoint, signal),
|
|
159
|
+
[fileUploadEndpoint]
|
|
160
|
+
);
|
|
179
161
|
|
|
180
162
|
const handleFileChange = useCallback(
|
|
181
163
|
async (event: ChangeEvent<HTMLInputElement>) => {
|
|
@@ -208,7 +190,7 @@ function PureMultimodalInput({
|
|
|
208
190
|
const uploadPromises = files.map((file) => {
|
|
209
191
|
const controller = new AbortController();
|
|
210
192
|
uploadAbortControllers.current.set(file.name, controller);
|
|
211
|
-
return
|
|
193
|
+
return upload(file, controller.signal);
|
|
212
194
|
});
|
|
213
195
|
const uploadedAttachments = await Promise.all(uploadPromises);
|
|
214
196
|
const successfullyUploadedAttachments = uploadedAttachments.filter(
|
|
@@ -228,7 +210,7 @@ function PureMultimodalInput({
|
|
|
228
210
|
uploadAbortControllers.current.clear();
|
|
229
211
|
}
|
|
230
212
|
},
|
|
231
|
-
[setAttachments,
|
|
213
|
+
[setAttachments, upload, attachments]
|
|
232
214
|
);
|
|
233
215
|
|
|
234
216
|
const handlePaste = useCallback(
|
|
@@ -253,7 +235,7 @@ function PureMultimodalInput({
|
|
|
253
235
|
const uploadPromises = imageItems
|
|
254
236
|
.map((item) => item.getAsFile())
|
|
255
237
|
.filter((file): file is File => file !== null)
|
|
256
|
-
.map((file) =>
|
|
238
|
+
.map((file) => upload(file));
|
|
257
239
|
|
|
258
240
|
const uploadedAttachments = await Promise.all(uploadPromises);
|
|
259
241
|
const successfullyUploadedAttachments = uploadedAttachments.filter(
|
|
@@ -270,10 +252,19 @@ function PureMultimodalInput({
|
|
|
270
252
|
setUploadQueue([]);
|
|
271
253
|
}
|
|
272
254
|
},
|
|
273
|
-
[setAttachments,
|
|
255
|
+
[setAttachments, upload]
|
|
274
256
|
);
|
|
275
257
|
|
|
276
|
-
const accept = useChatPreference()?.acceptAttachmentFileType
|
|
258
|
+
const accept = (useChatPreference()?.acceptAttachmentFileType || [
|
|
259
|
+
'.csv',
|
|
260
|
+
'text/csv',
|
|
261
|
+
'text/plain',
|
|
262
|
+
'.txt',
|
|
263
|
+
'image/*',
|
|
264
|
+
'.xls',
|
|
265
|
+
'.xlsx',
|
|
266
|
+
])?.join(',');
|
|
267
|
+
|
|
277
268
|
const hasAcceptableFileType = !!accept;
|
|
278
269
|
|
|
279
270
|
// Add paste event listener to textarea
|
package/src/lib/utils.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { clsx, type ClassValue } from 'clsx';
|
|
|
2
2
|
import { twMerge } from 'tailwind-merge';
|
|
3
3
|
import { UIDataTypes, UIMessagePart, UITools } from 'ai';
|
|
4
4
|
import { Field } from '@/plugins/form-builder-base-plugin/types';
|
|
5
|
-
import { IFilledField, IQueryResult } from '@baishuyun/types';
|
|
5
|
+
import { Attachment, IFilledField, IQueryResult } from '@baishuyun/types';
|
|
6
6
|
import { FORM_ICON_OPTIONS } from '@/const/ui';
|
|
7
7
|
|
|
8
8
|
export function cn(...inputs: ClassValue[]) {
|
|
@@ -418,3 +418,72 @@ export const formatJSONIfValid = (str: string): string | null => {
|
|
|
418
418
|
return null;
|
|
419
419
|
}
|
|
420
420
|
};
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* 从 URL 下载文件,返回 File 对象
|
|
424
|
+
*/
|
|
425
|
+
export const downloadFile = async (url: string): Promise<File> => {
|
|
426
|
+
const response = await fetch(url);
|
|
427
|
+
if (!response.ok) {
|
|
428
|
+
throw new Error(`Failed to download file from ${url}: ${response.status}`);
|
|
429
|
+
}
|
|
430
|
+
const blob = await response.blob();
|
|
431
|
+
const contentType = response.headers.get('content-type') || 'application/octet-stream';
|
|
432
|
+
const filename = new URL(url).pathname.split('/').pop() || 'attachment';
|
|
433
|
+
return new File([blob], filename, { type: contentType });
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* 上传文件到指定 endpoint,返回 Attachment 信息
|
|
438
|
+
*/
|
|
439
|
+
export const uploadFile = async (
|
|
440
|
+
file: File,
|
|
441
|
+
uploadEndpoint: string,
|
|
442
|
+
signal?: AbortSignal
|
|
443
|
+
): Promise<Attachment | undefined> => {
|
|
444
|
+
const formData = new FormData();
|
|
445
|
+
formData.append('file', file);
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
const response = await fetch(uploadEndpoint, {
|
|
449
|
+
method: 'POST',
|
|
450
|
+
body: formData,
|
|
451
|
+
signal,
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
if (response.ok) {
|
|
455
|
+
return await response.json();
|
|
456
|
+
}
|
|
457
|
+
const { error } = await response.json();
|
|
458
|
+
console.error('upload error', error);
|
|
459
|
+
return undefined;
|
|
460
|
+
} catch (error) {
|
|
461
|
+
console.error('Error uploading file:', error);
|
|
462
|
+
return undefined;
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
export const urls2fileParts = async (attachmentsUrl: string[], uploadEndpoint: string) => {
|
|
467
|
+
if (!attachmentsUrl?.length) {
|
|
468
|
+
return [];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const results = await Promise.all(
|
|
472
|
+
attachmentsUrl.map(async (url) => {
|
|
473
|
+
const file = await downloadFile(url);
|
|
474
|
+
return uploadFile(file, uploadEndpoint);
|
|
475
|
+
})
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
return results
|
|
479
|
+
.filter((attachment): attachment is Attachment => attachment !== undefined)
|
|
480
|
+
.map((attachment) => ({
|
|
481
|
+
type: 'file' as const,
|
|
482
|
+
url: attachment.url,
|
|
483
|
+
name: attachment.name,
|
|
484
|
+
mediaType: attachment.contentType,
|
|
485
|
+
providerMetadata: {
|
|
486
|
+
uploadMetadata: attachment,
|
|
487
|
+
},
|
|
488
|
+
}));
|
|
489
|
+
};
|
package/src/sdk.impl.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { EvtBus } from './lib/event-emitter';
|
|
14
14
|
import type { UseChatHelpers } from '@ai-sdk/react';
|
|
15
15
|
import { ChatRequestOptions, JSONValue } from 'ai';
|
|
16
|
+
import { ensureEndSlash, urls2fileParts } from './lib/utils';
|
|
16
17
|
|
|
17
18
|
export class ChatSDK implements IChatSDK {
|
|
18
19
|
static instances: ChatSDK[] = [];
|
|
@@ -116,16 +117,32 @@ export class ChatSDK implements IChatSDK {
|
|
|
116
117
|
this._appendFakeBotMessage?.(text);
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
public sendMessage = async (text: string, customVariable: JSONValue, options: ChatRequestOptions
|
|
120
|
+
public sendMessage = async (text: string, customVariable: JSONValue, options: ChatRequestOptions & {
|
|
121
|
+
attachmentsUrl?: string[];
|
|
122
|
+
} = {}) => {
|
|
120
123
|
await this.whenChatComponentReady();
|
|
121
124
|
|
|
122
|
-
|
|
125
|
+
const { attachmentsUrl, ...restOptions } = options;
|
|
126
|
+
|
|
127
|
+
const uploadEndpoint = `${ensureEndSlash(this.options.backendApiEndpoint!)}form/attachment/upload`;
|
|
128
|
+
let fileParts: ChatMessage['parts'] = await urls2fileParts(attachmentsUrl || [], uploadEndpoint);
|
|
129
|
+
|
|
130
|
+
const msg = fileParts.length > 0
|
|
131
|
+
? {
|
|
132
|
+
role: 'user',
|
|
133
|
+
parts: [
|
|
134
|
+
...fileParts,
|
|
135
|
+
{ type: 'text' as const, text },
|
|
136
|
+
],
|
|
137
|
+
} as ChatMessage
|
|
138
|
+
: { text };
|
|
139
|
+
|
|
123
140
|
this._sendMessage?.(
|
|
124
|
-
|
|
125
|
-
{
|
|
126
|
-
...
|
|
141
|
+
msg,
|
|
142
|
+
{
|
|
143
|
+
...restOptions,
|
|
127
144
|
body: {
|
|
128
|
-
...(
|
|
145
|
+
...(restOptions?.body || {}),
|
|
129
146
|
userVar: customVariable,
|
|
130
147
|
},
|
|
131
148
|
}
|