ai-world-sdk 1.0.12 → 1.0.13
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/README.md +273 -0
- package/dist/download.d.ts +64 -0
- package/dist/download.js +212 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/dist/log.d.ts +1 -1
- package/dist/log.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ TypeScript SDK for AI World Platform - 一个功能完整的 AI 应用开发 SDK
|
|
|
10
10
|
- 🤖 **聊天模型**: 兼容 LangChain.js 接口(OpenAI、Gemini、Claude、Doubao)
|
|
11
11
|
- 🎨 **图像生成**: 支持豆包 Seedream 和 Google Gemini
|
|
12
12
|
- 🎬 **视频生成**: 支持豆包 Seedance
|
|
13
|
+
- 📥 **下载代理**: 支持流式下载和普通下载任意 URL 的二进制文件
|
|
13
14
|
- ⚙️ **全局配置**: 自动从浏览器环境获取配置,简化初始化
|
|
14
15
|
- 🐛 **调试模式**: 支持详细的请求/响应日志
|
|
15
16
|
|
|
@@ -131,6 +132,38 @@ if (result.status === 'succeeded') {
|
|
|
131
132
|
}
|
|
132
133
|
```
|
|
133
134
|
|
|
135
|
+
### 5. 下载代理
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { DownloadClient } from 'ai-world-sdk';
|
|
139
|
+
|
|
140
|
+
const downloadClient = new DownloadClient({});
|
|
141
|
+
|
|
142
|
+
// 普通下载(返回 Blob)
|
|
143
|
+
const blob = await downloadClient.download({
|
|
144
|
+
url: 'https://example.com/file.zip',
|
|
145
|
+
filename: 'myfile.zip', // 可选
|
|
146
|
+
});
|
|
147
|
+
console.log('文件大小:', blob.size, '字节');
|
|
148
|
+
|
|
149
|
+
// 下载并保存到本地
|
|
150
|
+
await downloadClient.downloadAsFile({
|
|
151
|
+
url: 'https://example.com/file.zip',
|
|
152
|
+
filename: 'myfile.zip',
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// 流式下载(使用 for await 迭代)
|
|
156
|
+
let totalSize = 0;
|
|
157
|
+
for await (const chunk of downloadClient.streamDownload({
|
|
158
|
+
url: 'https://example.com/large-file.zip',
|
|
159
|
+
filename: 'large-file.zip',
|
|
160
|
+
})) {
|
|
161
|
+
totalSize += chunk.length;
|
|
162
|
+
console.log(`已下载: ${totalSize} 字节`);
|
|
163
|
+
}
|
|
164
|
+
console.log('下载完成,总大小:', totalSize, '字节');
|
|
165
|
+
```
|
|
166
|
+
|
|
134
167
|
## API 参考
|
|
135
168
|
|
|
136
169
|
### 聊天模型
|
|
@@ -339,6 +372,158 @@ const result = await client.poll(task.id, {
|
|
|
339
372
|
});
|
|
340
373
|
```
|
|
341
374
|
|
|
375
|
+
### 下载代理
|
|
376
|
+
|
|
377
|
+
#### DownloadClient
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
const client = new DownloadClient({
|
|
381
|
+
baseUrl: 'https://your-api.com', // 可选,默认使用全局配置
|
|
382
|
+
token: 'your-token', // 可选,默认使用全局配置
|
|
383
|
+
headers: { // 可选,自定义请求头
|
|
384
|
+
'X-Custom-Header': 'value',
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
#### 方法
|
|
390
|
+
|
|
391
|
+
##### `download(options: DownloadOptions): Promise<Blob>`
|
|
392
|
+
|
|
393
|
+
普通下载文件,返回 Blob 对象。
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
const blob = await client.download({
|
|
397
|
+
url: 'https://example.com/file.zip',
|
|
398
|
+
filename: 'myfile.zip', // 可选,下载时的文件名
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// 使用 Blob
|
|
402
|
+
const text = await blob.text(); // 读取为文本
|
|
403
|
+
const arrayBuffer = await blob.arrayBuffer(); // 读取为 ArrayBuffer
|
|
404
|
+
const url = URL.createObjectURL(blob); // 创建对象 URL
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**参数:**
|
|
408
|
+
- `url` (string, 必需): 要下载的文件 URL(必须是 http:// 或 https://)
|
|
409
|
+
- `filename` (string, 可选): 下载时的文件名,不提供则从 URL 或响应头中提取
|
|
410
|
+
|
|
411
|
+
**返回:** `Promise<Blob>` - 文件的 Blob 对象
|
|
412
|
+
|
|
413
|
+
**错误处理:**
|
|
414
|
+
```typescript
|
|
415
|
+
try {
|
|
416
|
+
const blob = await client.download({
|
|
417
|
+
url: 'https://example.com/file.zip',
|
|
418
|
+
});
|
|
419
|
+
console.log('下载成功:', blob.size, '字节');
|
|
420
|
+
} catch (error) {
|
|
421
|
+
if (error instanceof Error) {
|
|
422
|
+
console.error('下载失败:', error.message);
|
|
423
|
+
// 常见错误:
|
|
424
|
+
// - "URL is required" - URL 参数缺失
|
|
425
|
+
// - "Download failed: HTTP 404" - 文件不存在
|
|
426
|
+
// - "Download failed: HTTP 403" - 访问被拒绝
|
|
427
|
+
// - "下载超时,请稍后重试" - 下载超时
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
##### `downloadAsFile(options: DownloadOptions, downloadFilename?: string): Promise<void>`
|
|
433
|
+
|
|
434
|
+
下载文件并触发浏览器保存对话框。
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
await client.downloadAsFile({
|
|
438
|
+
url: 'https://example.com/file.zip',
|
|
439
|
+
filename: 'myfile.zip',
|
|
440
|
+
}, 'custom-filename.zip'); // 可选,覆盖 filename
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**参数:**
|
|
444
|
+
- `options`: 同 `download()` 方法
|
|
445
|
+
- `downloadFilename` (string, 可选): 下载时的文件名,会覆盖 `options.filename`
|
|
446
|
+
|
|
447
|
+
**返回:** `Promise<void>`
|
|
448
|
+
|
|
449
|
+
##### `streamDownload(options: StreamDownloadOptions): AsyncGenerator<Uint8Array>`
|
|
450
|
+
|
|
451
|
+
流式下载文件,返回异步迭代器,支持 `for await` 迭代。
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
let totalSize = 0;
|
|
455
|
+
for await (const chunk of client.streamDownload({
|
|
456
|
+
url: 'https://example.com/large-file.zip',
|
|
457
|
+
filename: 'large-file.zip',
|
|
458
|
+
})) {
|
|
459
|
+
totalSize += chunk.length;
|
|
460
|
+
console.log(`已下载: ${totalSize} 字节`);
|
|
461
|
+
// 可以在这里处理每个数据块(例如:实时处理、显示进度等)
|
|
462
|
+
}
|
|
463
|
+
console.log('下载完成,总大小:', totalSize, '字节');
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**参数:**
|
|
467
|
+
- `url` (string, 必需): 要下载的文件 URL
|
|
468
|
+
- `filename` (string, 可选): 下载时的文件名
|
|
469
|
+
|
|
470
|
+
**返回:** `AsyncGenerator<Uint8Array>` - 异步迭代器,每次迭代返回一个 `Uint8Array` 数据块
|
|
471
|
+
|
|
472
|
+
**使用场景:**
|
|
473
|
+
- 下载大文件时显示实时进度(通过累计 chunk.length)
|
|
474
|
+
- 需要处理数据流(例如:实时解析、转码等)
|
|
475
|
+
- 需要监控下载状态
|
|
476
|
+
- 需要流式处理文件内容
|
|
477
|
+
|
|
478
|
+
**示例:带进度显示**
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
// 如果服务器提供了 Content-Length,可以从响应头获取总大小
|
|
482
|
+
let totalSize = 0;
|
|
483
|
+
const total = 1024 * 1024 * 10; // 假设已知总大小(实际应从响应头获取)
|
|
484
|
+
|
|
485
|
+
for await (const chunk of client.streamDownload({
|
|
486
|
+
url: 'https://example.com/large-file.zip',
|
|
487
|
+
})) {
|
|
488
|
+
totalSize += chunk.length;
|
|
489
|
+
const percent = total ? Math.round((totalSize / total) * 100) : null;
|
|
490
|
+
console.log(`下载进度: ${totalSize}/${total || '未知'} (${percent || '?'}%)`);
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**示例:流式处理并保存**
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
// 流式下载并实时保存到文件系统(Node.js 环境)
|
|
498
|
+
import { createWriteStream } from 'fs';
|
|
499
|
+
import { pipeline } from 'stream/promises';
|
|
500
|
+
|
|
501
|
+
const chunks: Uint8Array[] = [];
|
|
502
|
+
let totalSize = 0;
|
|
503
|
+
|
|
504
|
+
for await (const chunk of client.streamDownload({
|
|
505
|
+
url: 'https://example.com/large-file.zip',
|
|
506
|
+
filename: 'large-file.zip',
|
|
507
|
+
})) {
|
|
508
|
+
chunks.push(chunk);
|
|
509
|
+
totalSize += chunk.length;
|
|
510
|
+
console.log(`已接收: ${totalSize} 字节`);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// 合并所有块并保存
|
|
514
|
+
const blob = new Blob(chunks);
|
|
515
|
+
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
516
|
+
await fs.promises.writeFile('large-file.zip', buffer);
|
|
517
|
+
console.log('文件保存完成');
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
**注意事项:**
|
|
521
|
+
- 流式下载适合大文件,可以实时显示进度
|
|
522
|
+
- 普通下载适合小文件,会一次性加载到内存
|
|
523
|
+
- URL 必须是 `http://` 或 `https://` 协议
|
|
524
|
+
- 文件名会自动从 URL 或响应头中提取(如果未提供)
|
|
525
|
+
- 流式下载时,数据块大小由服务器决定,通常为 8KB-64KB
|
|
526
|
+
|
|
342
527
|
### 全局配置
|
|
343
528
|
|
|
344
529
|
```typescript
|
|
@@ -740,6 +925,94 @@ if (result.status === 'succeeded') {
|
|
|
740
925
|
}
|
|
741
926
|
```
|
|
742
927
|
|
|
928
|
+
### 下载代理工作流
|
|
929
|
+
|
|
930
|
+
```typescript
|
|
931
|
+
import { DownloadClient } from 'ai-world-sdk';
|
|
932
|
+
|
|
933
|
+
const client = new DownloadClient({});
|
|
934
|
+
|
|
935
|
+
// 1. 普通下载(小文件)
|
|
936
|
+
const blob = await client.download({
|
|
937
|
+
url: 'https://example.com/small-file.json',
|
|
938
|
+
filename: 'data.json',
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
// 读取文件内容
|
|
942
|
+
const text = await blob.text();
|
|
943
|
+
const data = JSON.parse(text);
|
|
944
|
+
console.log('下载的数据:', data);
|
|
945
|
+
|
|
946
|
+
// 2. 下载并保存到本地
|
|
947
|
+
await client.downloadAsFile({
|
|
948
|
+
url: 'https://example.com/document.pdf',
|
|
949
|
+
filename: 'document.pdf',
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
// 3. 流式下载大文件(带进度显示)
|
|
953
|
+
let totalSize = 0;
|
|
954
|
+
const total = 1024 * 1024 * 100; // 假设已知总大小(实际应从响应头获取)
|
|
955
|
+
|
|
956
|
+
for await (const chunk of client.streamDownload({
|
|
957
|
+
url: 'https://example.com/large-video.mp4',
|
|
958
|
+
filename: 'video.mp4',
|
|
959
|
+
})) {
|
|
960
|
+
totalSize += chunk.length;
|
|
961
|
+
|
|
962
|
+
// 显示进度
|
|
963
|
+
if (total) {
|
|
964
|
+
const percent = Math.round((totalSize / total) * 100);
|
|
965
|
+
console.log(`下载进度: ${percent}% (${totalSize}/${total} 字节)`);
|
|
966
|
+
} else {
|
|
967
|
+
console.log(`已下载: ${totalSize} 字节`);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// 可以在这里处理每个数据块
|
|
971
|
+
// 例如:实时验证、显示下载速度等
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
console.log(`下载完成!总大小: ${totalSize} 字节`);
|
|
975
|
+
|
|
976
|
+
// 4. 批量下载多个文件
|
|
977
|
+
const urls = [
|
|
978
|
+
'https://example.com/file1.zip',
|
|
979
|
+
'https://example.com/file2.zip',
|
|
980
|
+
'https://example.com/file3.zip',
|
|
981
|
+
];
|
|
982
|
+
|
|
983
|
+
for (const url of urls) {
|
|
984
|
+
try {
|
|
985
|
+
await client.downloadAsFile({
|
|
986
|
+
url,
|
|
987
|
+
filename: url.split('/').pop() || 'download',
|
|
988
|
+
});
|
|
989
|
+
console.log(`已下载: ${url}`);
|
|
990
|
+
} catch (error) {
|
|
991
|
+
console.error(`下载失败 ${url}:`, error);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// 5. 流式下载并实时处理(例如:解析 JSON 流)
|
|
996
|
+
let buffer = '';
|
|
997
|
+
for await (const chunk of client.streamDownload({
|
|
998
|
+
url: 'https://example.com/large-data.jsonl',
|
|
999
|
+
})) {
|
|
1000
|
+
// 将 Uint8Array 转换为字符串
|
|
1001
|
+
buffer += new TextDecoder().decode(chunk);
|
|
1002
|
+
|
|
1003
|
+
// 处理完整的行
|
|
1004
|
+
const lines = buffer.split('\n');
|
|
1005
|
+
buffer = lines.pop() || ''; // 保留不完整的行
|
|
1006
|
+
|
|
1007
|
+
for (const line of lines) {
|
|
1008
|
+
if (line.trim()) {
|
|
1009
|
+
const data = JSON.parse(line);
|
|
1010
|
+
console.log('处理数据:', data);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
```
|
|
1015
|
+
|
|
743
1016
|
## 支持的模型
|
|
744
1017
|
|
|
745
1018
|
### 聊天模型
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Download Client
|
|
3
|
+
* 下载客户端
|
|
4
|
+
* 支持流式下载和普通下载任意 URL 的二进制文件
|
|
5
|
+
*/
|
|
6
|
+
export interface DownloadConfig {
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
token?: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
export interface DownloadOptions {
|
|
12
|
+
url: string;
|
|
13
|
+
filename?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface StreamDownloadOptions extends DownloadOptions {
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Download Client
|
|
19
|
+
* 下载客户端类
|
|
20
|
+
*/
|
|
21
|
+
export declare class DownloadClient {
|
|
22
|
+
private baseUrl;
|
|
23
|
+
private headers;
|
|
24
|
+
constructor(config?: DownloadConfig);
|
|
25
|
+
/**
|
|
26
|
+
* Download a file from a URL (non-streaming)
|
|
27
|
+
* 从 URL 下载文件(非流式)
|
|
28
|
+
*
|
|
29
|
+
* @param options - Download options
|
|
30
|
+
* @returns Promise that resolves to a Blob
|
|
31
|
+
*/
|
|
32
|
+
download(options: DownloadOptions): Promise<Blob>;
|
|
33
|
+
/**
|
|
34
|
+
* Stream download a file from a URL
|
|
35
|
+
* 流式下载文件,返回异步迭代器
|
|
36
|
+
*
|
|
37
|
+
* @param options - Stream download options
|
|
38
|
+
* @returns AsyncGenerator that yields data chunks
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* let totalSize = 0;
|
|
42
|
+
* for await (const chunk of client.streamDownload({ url: '...' })) {
|
|
43
|
+
* totalSize += chunk.length;
|
|
44
|
+
* // 处理每个数据块
|
|
45
|
+
* console.log(`已下载: ${totalSize} 字节`);
|
|
46
|
+
* }
|
|
47
|
+
* console.log(`下载完成,总大小: ${totalSize} 字节`);
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
streamDownload(options: StreamDownloadOptions): AsyncGenerator<Uint8Array, void, unknown>;
|
|
51
|
+
/**
|
|
52
|
+
* Download a file and trigger browser download
|
|
53
|
+
* 下载文件并触发浏览器下载
|
|
54
|
+
*
|
|
55
|
+
* @param options - Download options
|
|
56
|
+
* @param downloadFilename - Optional filename for the download (overrides options.filename)
|
|
57
|
+
*/
|
|
58
|
+
downloadAsFile(options: DownloadOptions, downloadFilename?: string): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Extract filename from URL
|
|
61
|
+
* 从 URL 中提取文件名
|
|
62
|
+
*/
|
|
63
|
+
private extractFilenameFromUrl;
|
|
64
|
+
}
|
package/dist/download.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Download Client
|
|
4
|
+
* 下载客户端
|
|
5
|
+
* 支持流式下载和普通下载任意 URL 的二进制文件
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.DownloadClient = void 0;
|
|
9
|
+
const config_1 = require("./config");
|
|
10
|
+
const log_1 = require("./log");
|
|
11
|
+
/**
|
|
12
|
+
* Download Client
|
|
13
|
+
* 下载客户端类
|
|
14
|
+
*/
|
|
15
|
+
class DownloadClient {
|
|
16
|
+
constructor(config = {}) {
|
|
17
|
+
// 使用配置的 baseUrl 或全局配置
|
|
18
|
+
this.baseUrl =
|
|
19
|
+
config.baseUrl ||
|
|
20
|
+
config_1.sdkConfig.getServerUrl() ||
|
|
21
|
+
(typeof window !== "undefined" ? window.location.origin : "");
|
|
22
|
+
// 合并 headers
|
|
23
|
+
const globalHeaders = config_1.sdkConfig.getHeaders();
|
|
24
|
+
const globalToken = config_1.sdkConfig.getToken() || config.token;
|
|
25
|
+
this.headers = {
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
...globalHeaders,
|
|
28
|
+
...config.headers,
|
|
29
|
+
};
|
|
30
|
+
// 如果有 token,添加到 Authorization header
|
|
31
|
+
if (globalToken) {
|
|
32
|
+
this.headers["Authorization"] = `Bearer ${globalToken}`;
|
|
33
|
+
}
|
|
34
|
+
else if (config.token) {
|
|
35
|
+
this.headers["Authorization"] = `Bearer ${config.token}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Download a file from a URL (non-streaming)
|
|
40
|
+
* 从 URL 下载文件(非流式)
|
|
41
|
+
*
|
|
42
|
+
* @param options - Download options
|
|
43
|
+
* @returns Promise that resolves to a Blob
|
|
44
|
+
*/
|
|
45
|
+
async download(options) {
|
|
46
|
+
const { url, filename } = options;
|
|
47
|
+
if (!url) {
|
|
48
|
+
throw new Error("URL is required");
|
|
49
|
+
}
|
|
50
|
+
// 构建请求 URL
|
|
51
|
+
const apiUrl = `${this.baseUrl}/api/download-proxy/download`;
|
|
52
|
+
const params = new URLSearchParams({ url });
|
|
53
|
+
if (filename) {
|
|
54
|
+
params.append("filename", filename);
|
|
55
|
+
}
|
|
56
|
+
const fullUrl = `${apiUrl}?${params.toString()}`;
|
|
57
|
+
(0, log_1.debugLog)("Download request:", { url, filename, fullUrl });
|
|
58
|
+
(0, log_1.logRequest)("GET", fullUrl, this.headers);
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(fullUrl, {
|
|
61
|
+
method: "GET",
|
|
62
|
+
headers: this.headers,
|
|
63
|
+
});
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
const errorText = await response.text();
|
|
66
|
+
let errorMessage = `Download failed: ${response.status} ${response.statusText}`;
|
|
67
|
+
try {
|
|
68
|
+
const errorJson = JSON.parse(errorText);
|
|
69
|
+
errorMessage = errorJson.detail || errorMessage;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
errorMessage = errorText || errorMessage;
|
|
73
|
+
}
|
|
74
|
+
throw new Error(errorMessage);
|
|
75
|
+
}
|
|
76
|
+
const blob = await response.blob();
|
|
77
|
+
(0, log_1.debugLog)("Download completed:", {
|
|
78
|
+
size: blob.size,
|
|
79
|
+
type: blob.type,
|
|
80
|
+
});
|
|
81
|
+
return blob;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
85
|
+
(0, log_1.debugLog)("Download error:", errorMessage);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Stream download a file from a URL
|
|
91
|
+
* 流式下载文件,返回异步迭代器
|
|
92
|
+
*
|
|
93
|
+
* @param options - Stream download options
|
|
94
|
+
* @returns AsyncGenerator that yields data chunks
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* let totalSize = 0;
|
|
98
|
+
* for await (const chunk of client.streamDownload({ url: '...' })) {
|
|
99
|
+
* totalSize += chunk.length;
|
|
100
|
+
* // 处理每个数据块
|
|
101
|
+
* console.log(`已下载: ${totalSize} 字节`);
|
|
102
|
+
* }
|
|
103
|
+
* console.log(`下载完成,总大小: ${totalSize} 字节`);
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
async *streamDownload(options) {
|
|
107
|
+
const { url, filename } = options;
|
|
108
|
+
if (!url) {
|
|
109
|
+
throw new Error("URL is required");
|
|
110
|
+
}
|
|
111
|
+
// 构建请求 URL
|
|
112
|
+
const apiUrl = `${this.baseUrl}/api/download-proxy/download`;
|
|
113
|
+
const params = new URLSearchParams({ url });
|
|
114
|
+
if (filename) {
|
|
115
|
+
params.append("filename", filename);
|
|
116
|
+
}
|
|
117
|
+
const fullUrl = `${apiUrl}?${params.toString()}`;
|
|
118
|
+
(0, log_1.debugLog)("Stream download request:", { url, filename, fullUrl });
|
|
119
|
+
(0, log_1.logRequest)("GET", fullUrl, this.headers);
|
|
120
|
+
let response;
|
|
121
|
+
try {
|
|
122
|
+
response = await fetch(fullUrl, {
|
|
123
|
+
method: "GET",
|
|
124
|
+
headers: this.headers,
|
|
125
|
+
});
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
const errorText = await response.text();
|
|
128
|
+
let errorMessage = `Download failed: ${response.status} ${response.statusText}`;
|
|
129
|
+
try {
|
|
130
|
+
const errorJson = JSON.parse(errorText);
|
|
131
|
+
errorMessage = errorJson.detail || errorMessage;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
errorMessage = errorText || errorMessage;
|
|
135
|
+
}
|
|
136
|
+
throw new Error(errorMessage);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
141
|
+
(0, log_1.debugLog)("Stream download error:", errorMessage);
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
// // 获取内容长度(如果可用)
|
|
145
|
+
// const contentLength = response.headers.get("content-length");
|
|
146
|
+
// const total = contentLength ? parseInt(contentLength, 10) : undefined;
|
|
147
|
+
// 创建 ReadableStream 来处理流式数据
|
|
148
|
+
const reader = response.body?.getReader();
|
|
149
|
+
if (!reader) {
|
|
150
|
+
throw new Error("Response body is not readable");
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
while (true) {
|
|
154
|
+
const { done, value } = await reader.read();
|
|
155
|
+
if (done) {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
if (value) {
|
|
159
|
+
// yield 数据块,用户可以在迭代时累计大小
|
|
160
|
+
yield value;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
reader.releaseLock();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Download a file and trigger browser download
|
|
170
|
+
* 下载文件并触发浏览器下载
|
|
171
|
+
*
|
|
172
|
+
* @param options - Download options
|
|
173
|
+
* @param downloadFilename - Optional filename for the download (overrides options.filename)
|
|
174
|
+
*/
|
|
175
|
+
async downloadAsFile(options, downloadFilename) {
|
|
176
|
+
const blob = await this.download(options);
|
|
177
|
+
// 确定下载文件名
|
|
178
|
+
const filename = downloadFilename ||
|
|
179
|
+
options.filename ||
|
|
180
|
+
this.extractFilenameFromUrl(options.url) ||
|
|
181
|
+
"download";
|
|
182
|
+
// 创建下载链接
|
|
183
|
+
const url = URL.createObjectURL(blob);
|
|
184
|
+
const a = document.createElement("a");
|
|
185
|
+
a.href = url;
|
|
186
|
+
a.download = filename;
|
|
187
|
+
document.body.appendChild(a);
|
|
188
|
+
a.click();
|
|
189
|
+
document.body.removeChild(a);
|
|
190
|
+
// 清理 URL
|
|
191
|
+
setTimeout(() => URL.revokeObjectURL(url), 100);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Extract filename from URL
|
|
195
|
+
* 从 URL 中提取文件名
|
|
196
|
+
*/
|
|
197
|
+
extractFilenameFromUrl(url) {
|
|
198
|
+
try {
|
|
199
|
+
const urlObj = new URL(url);
|
|
200
|
+
const pathname = urlObj.pathname;
|
|
201
|
+
const filename = pathname.split("/").pop();
|
|
202
|
+
if (filename && filename.includes(".")) {
|
|
203
|
+
return filename.split("?")[0]; // 移除查询参数
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
exports.DownloadClient = DownloadClient;
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { BaseChatModel, BaseChatModelParams } from "./base";
|
|
|
8
8
|
import { DoubaoImageGenerationClient, DoubaoImageSize, type DoubaoImageGenerationConfig, type DoubaoImageGenerationRequest, type DoubaoImageGenerationResponse } from "./doubao-image-generation";
|
|
9
9
|
import { GeminiImageGenerationClient, type GeminiImageGenerationConfig, type GeminiImageGenerationRequest, type GeminiImageGenerationResponse } from "./gemini-image-generation";
|
|
10
10
|
import { VideoGenerationClient, type VideoGenerationConfig, type VideoGenerationRequest, type ContentGenerationTaskID, type ContentGenerationTask } from "./video_generation";
|
|
11
|
+
import { DownloadClient, type DownloadConfig, type DownloadOptions, type StreamDownloadOptions } from "./download";
|
|
11
12
|
import { sdkConfig } from "./config";
|
|
12
13
|
export { BaseMessage, HumanMessage, AIMessage, SystemMessage, AIMessageChunk, type MessageContent, type AIMessageChunkData, } from "./messages";
|
|
13
14
|
export { BaseChatModel, type BaseChatModelParams, type AIModelProvider, type ToolDefinition, type BindOptions, } from "./base";
|
|
@@ -22,6 +23,7 @@ export interface LangchainClientConfig {
|
|
|
22
23
|
export { DoubaoImageGenerationClient, type DoubaoImageGenerationConfig, type DoubaoImageGenerationRequest, type DoubaoImageGenerationResponse, type DoubaoImageSize, };
|
|
23
24
|
export { GeminiImageGenerationClient, type GeminiImageGenerationConfig, type GeminiImageGenerationRequest, type GeminiImageGenerationResponse, };
|
|
24
25
|
export { VideoGenerationClient, type VideoGenerationConfig, type VideoGenerationRequest, type ContentGenerationTaskID, type ContentGenerationTask, };
|
|
26
|
+
export { DownloadClient, type DownloadConfig, type DownloadOptions, type StreamDownloadOptions, };
|
|
25
27
|
export { sdkConfig };
|
|
26
28
|
/**
|
|
27
29
|
* Create a chat model instance based on model name
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @see https://github.com/langchain-ai/langchainjs
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.sdkConfig = exports.VideoGenerationClient = exports.GeminiImageGenerationClient = exports.DoubaoImageGenerationClient = exports.ChatAnthropic = exports.ChatGoogleGenerativeAI = exports.ChatOpenAI = exports.BaseChatModel = exports.AIMessageChunk = exports.SystemMessage = exports.AIMessage = exports.HumanMessage = void 0;
|
|
9
|
+
exports.sdkConfig = exports.DownloadClient = exports.VideoGenerationClient = exports.GeminiImageGenerationClient = exports.DoubaoImageGenerationClient = exports.ChatAnthropic = exports.ChatGoogleGenerativeAI = exports.ChatOpenAI = exports.BaseChatModel = exports.AIMessageChunk = exports.SystemMessage = exports.AIMessage = exports.HumanMessage = void 0;
|
|
10
10
|
exports.createChatModel = createChatModel;
|
|
11
11
|
const openai_1 = require("./chat_models/openai");
|
|
12
12
|
const google_1 = require("./chat_models/google");
|
|
@@ -16,6 +16,8 @@ const gemini_image_generation_1 = require("./gemini-image-generation");
|
|
|
16
16
|
Object.defineProperty(exports, "GeminiImageGenerationClient", { enumerable: true, get: function () { return gemini_image_generation_1.GeminiImageGenerationClient; } });
|
|
17
17
|
const video_generation_1 = require("./video_generation");
|
|
18
18
|
Object.defineProperty(exports, "VideoGenerationClient", { enumerable: true, get: function () { return video_generation_1.VideoGenerationClient; } });
|
|
19
|
+
const download_1 = require("./download");
|
|
20
|
+
Object.defineProperty(exports, "DownloadClient", { enumerable: true, get: function () { return download_1.DownloadClient; } });
|
|
19
21
|
const config_1 = require("./config");
|
|
20
22
|
Object.defineProperty(exports, "sdkConfig", { enumerable: true, get: function () { return config_1.sdkConfig; } });
|
|
21
23
|
// Re-export types and classes
|
package/dist/log.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare function debugLog(...args: any[]): void;
|
|
|
11
11
|
* Log HTTP request
|
|
12
12
|
* 记录 HTTP 请求
|
|
13
13
|
*/
|
|
14
|
-
export declare function logRequest(method: string, url: string, headers
|
|
14
|
+
export declare function logRequest(method: string, url: string, headers?: Record<string, string>, body?: any): void;
|
|
15
15
|
/**
|
|
16
16
|
* Log HTTP response
|
|
17
17
|
* 记录 HTTP 响应
|
package/dist/log.js
CHANGED
|
@@ -27,7 +27,7 @@ function logRequest(method, url, headers, body) {
|
|
|
27
27
|
debugLog("📤 HTTP Request");
|
|
28
28
|
debugLog(" Method:", method);
|
|
29
29
|
debugLog(" URL:", url);
|
|
30
|
-
debugLog(" Headers:", { ...headers, Authorization: headers
|
|
30
|
+
debugLog(" Headers:", { ...headers, Authorization: headers?.Authorization ? "Bearer ***" : undefined });
|
|
31
31
|
if (body) {
|
|
32
32
|
try {
|
|
33
33
|
const bodyStr = typeof body === "string" ? body : JSON.stringify(body, null, 2);
|
package/package.json
CHANGED