@baishuyun/chat-sdk 0.1.5 → 0.1.7
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 +3425 -3368
- package/dist/chat-sdk.js.map +1 -1
- package/dist/chat-sdk.umd.cjs +94 -94
- package/dist/chat-sdk.umd.cjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/utils.ts +86 -10
- package/src/sdk.impl.tsx +10 -1
package/package.json
CHANGED
package/src/lib/utils.ts
CHANGED
|
@@ -419,17 +419,87 @@ export const formatJSONIfValid = (str: string): string | null => {
|
|
|
419
419
|
}
|
|
420
420
|
};
|
|
421
421
|
|
|
422
|
+
const MIME_MAP: Record<string, string> = {
|
|
423
|
+
// 文档
|
|
424
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
425
|
+
xls: 'application/vnd.ms-excel',
|
|
426
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
427
|
+
pdf: 'application/pdf',
|
|
428
|
+
txt: 'text/plain',
|
|
429
|
+
// 图片
|
|
430
|
+
png: 'image/png',
|
|
431
|
+
jpg: 'image/jpeg',
|
|
432
|
+
jpeg: 'image/jpeg',
|
|
433
|
+
gif: 'image/gif',
|
|
434
|
+
webp: 'image/webp',
|
|
435
|
+
svg: 'image/svg+xml',
|
|
436
|
+
// 音视频
|
|
437
|
+
mp4: 'video/mp4',
|
|
438
|
+
mp3: 'audio/mpeg',
|
|
439
|
+
// 压缩包
|
|
440
|
+
zip: 'application/zip',
|
|
441
|
+
rar: 'application/x-rar-compressed',
|
|
442
|
+
'7z': 'application/x-7z-compressed',
|
|
443
|
+
// 代码/数据
|
|
444
|
+
json: 'application/json',
|
|
445
|
+
js: 'application/javascript',
|
|
446
|
+
ts: 'application/typescript',
|
|
447
|
+
csv: 'text/csv',
|
|
448
|
+
xml: 'application/xml',
|
|
449
|
+
// 默认回退
|
|
450
|
+
default: 'application/octet-stream',
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const getMimeType = (filename: string, serverType: string | null): string => {
|
|
454
|
+
// 如果服务器给了具体类型(不是 octet-stream),就信任它
|
|
455
|
+
if (serverType && serverType !== 'application/octet-stream') {
|
|
456
|
+
return serverType;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// 否则从扩展名推断
|
|
460
|
+
const ext = filename.split('.').pop()?.toLowerCase() || '';
|
|
461
|
+
return MIME_MAP[ext] || MIME_MAP.default;
|
|
462
|
+
};
|
|
463
|
+
|
|
422
464
|
/**
|
|
423
465
|
* 从 URL 下载文件,返回 File 对象
|
|
424
466
|
*/
|
|
425
467
|
export const downloadFile = async (url: string): Promise<File> => {
|
|
426
|
-
const response = await fetch(url
|
|
468
|
+
const response = await fetch(url, {
|
|
469
|
+
credentials: 'include', // 必须携带,因为 Allow-Credentials: true
|
|
470
|
+
mode: 'cors',
|
|
471
|
+
});
|
|
472
|
+
|
|
427
473
|
if (!response.ok) {
|
|
428
|
-
throw new Error(`Failed to download
|
|
474
|
+
throw new Error(`Failed to download: ${response.status}`);
|
|
429
475
|
}
|
|
476
|
+
|
|
430
477
|
const blob = await response.blob();
|
|
431
|
-
|
|
432
|
-
|
|
478
|
+
|
|
479
|
+
// 1. 从 Content-Disposition 解析文件名(优先)
|
|
480
|
+
const disposition = response.headers.get('content-disposition');
|
|
481
|
+
let filename = 'attachment';
|
|
482
|
+
|
|
483
|
+
if (disposition) {
|
|
484
|
+
// 尝试 filename*=UTF-8'' 格式(RFC 5987)
|
|
485
|
+
const utf8Match = disposition.match(/filename\*=UTF-8''([^;]+)/i);
|
|
486
|
+
// 尝试 filename="..." 或 filename=... 格式(需要解码 MinIO 的 URL 编码)
|
|
487
|
+
const asciiMatch = disposition.match(/filename=["']?([^"';]+)["']?/i);
|
|
488
|
+
|
|
489
|
+
if (utf8Match) {
|
|
490
|
+
filename = decodeURIComponent(utf8Match[1]);
|
|
491
|
+
} else if (asciiMatch) {
|
|
492
|
+
// MinIO 特殊处理:filename 可能是 URL 编码的
|
|
493
|
+
filename = decodeURIComponent(asciiMatch[1]);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// 2. 修正 MIME 类型(如果服务器返回的是 octet-stream)
|
|
498
|
+
let contentType = response.headers.get('content-type') || 'application/octet-stream';
|
|
499
|
+
if (contentType === 'application/octet-stream') {
|
|
500
|
+
contentType = getMimeType(filename, contentType);
|
|
501
|
+
}
|
|
502
|
+
|
|
433
503
|
return new File([blob], filename, { type: contentType });
|
|
434
504
|
};
|
|
435
505
|
|
|
@@ -468,12 +538,18 @@ export const urls2fileParts = async (attachmentsUrl: string[], uploadEndpoint: s
|
|
|
468
538
|
return [];
|
|
469
539
|
}
|
|
470
540
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
541
|
+
let results: (Attachment | undefined)[];
|
|
542
|
+
try {
|
|
543
|
+
results = await Promise.all(
|
|
544
|
+
attachmentsUrl.map(async (url) => {
|
|
545
|
+
const file = await downloadFile(url);
|
|
546
|
+
return uploadFile(file, uploadEndpoint);
|
|
547
|
+
})
|
|
548
|
+
);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
console.error('Error processing attachments:', error);
|
|
551
|
+
return [];
|
|
552
|
+
}
|
|
477
553
|
|
|
478
554
|
return results
|
|
479
555
|
.filter((attachment): attachment is Attachment => attachment !== undefined)
|
package/src/sdk.impl.tsx
CHANGED
|
@@ -125,18 +125,27 @@ export class ChatSDK implements IChatSDK {
|
|
|
125
125
|
const { attachmentsUrl, ...restOptions } = options;
|
|
126
126
|
|
|
127
127
|
const uploadEndpoint = `${ensureEndSlash(this.options.backendApiEndpoint!)}form/attachment/upload`;
|
|
128
|
+
|
|
129
|
+
if (attachmentsUrl && attachmentsUrl.length > 0) {
|
|
130
|
+
this.Store.getState().setGlobalFakeLoadingMessage("正在处理附件...");
|
|
131
|
+
}
|
|
132
|
+
|
|
128
133
|
let fileParts: ChatMessage['parts'] = await urls2fileParts(attachmentsUrl || [], uploadEndpoint);
|
|
134
|
+
this.Store.getState().clearGlobalFakeLoadingMessage();
|
|
135
|
+
|
|
136
|
+
const textParts = text.trim() ? [{ type: 'text' as const, text }] : [];
|
|
129
137
|
|
|
130
138
|
const msg = fileParts.length > 0
|
|
131
139
|
? {
|
|
132
140
|
role: 'user',
|
|
133
141
|
parts: [
|
|
134
142
|
...fileParts,
|
|
135
|
-
|
|
143
|
+
...textParts,
|
|
136
144
|
],
|
|
137
145
|
} as ChatMessage
|
|
138
146
|
: { text };
|
|
139
147
|
|
|
148
|
+
|
|
140
149
|
this._sendMessage?.(
|
|
141
150
|
msg,
|
|
142
151
|
{
|