@tacoreai/web-sdk 1.0.12 → 1.3.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/README.md +404 -0
- package/database/core/apps/AppsAuthManager.js +336 -0
- package/database/core/apps/AppsClient.AI.js +229 -0
- package/database/core/apps/AppsClient.AppServer.js +92 -0
- package/database/core/apps/AppsClient.Crawler.js +71 -0
- package/database/core/apps/AppsClient.Data.js +187 -0
- package/database/core/apps/AppsClient.Debug.js +41 -0
- package/database/core/apps/AppsClient.Deploy.js +66 -0
- package/database/core/apps/AppsClient.Ecommerce.js +151 -0
- package/database/core/apps/AppsClient.KV.js +72 -0
- package/database/core/apps/AppsClient.KnowledgeBase.js +130 -0
- package/database/core/apps/AppsClient.Message.js +27 -0
- package/database/core/apps/AppsClient.Realtime.js +46 -0
- package/database/core/apps/AppsClient.Storage.js +275 -0
- package/database/core/apps/AppsClient.TextIn.js +341 -0
- package/database/core/apps/AppsClient.Tools.js +36 -0
- package/database/core/apps/AppsClient.Users.js +49 -0
- package/database/core/apps/AppsClient.VoiceVideo.js +106 -0
- package/database/core/apps/AppsClient.Volcengine.js +169 -0
- package/database/core/apps/AppsClient.js +215 -0
- package/database/core/apps/AppsPublicClient.js +107 -0
- package/database/core/apps/BaseAppsClient.js +216 -0
- package/index.js +9 -0
- package/package.json +11 -23
- package/utils/errorLogger.js +28 -0
- package/utils/index.js +114 -0
- package/dist/index.umd.js +0 -1
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { isBrowser } from "../../../utils/index.js";
|
|
2
|
+
|
|
3
|
+
export const appsClientTextInMethods = {
|
|
4
|
+
// ==================== TextIn OCR & File Convert ====================
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* TextIn OCR 识别
|
|
8
|
+
* @param {File | {data: string, encoding: 'base64', name: string} | string} input - 文件对象、Base64对象或URL字符串
|
|
9
|
+
* @param {Object} options - TextIn 识别参数 (如 pdf_page_count, page_start 等)
|
|
10
|
+
* @returns {Promise<Object>} 识别结果
|
|
11
|
+
*/
|
|
12
|
+
async recognizeText(input, options = {}) {
|
|
13
|
+
if (!input) throw new Error("input (file or url) is required");
|
|
14
|
+
|
|
15
|
+
// 构建查询参数
|
|
16
|
+
const queryParams = new URLSearchParams();
|
|
17
|
+
for (const [key, value] of Object.entries(options)) {
|
|
18
|
+
if (value !== undefined && value !== null) {
|
|
19
|
+
queryParams.append(key, value.toString());
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const queryString = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
23
|
+
const url = `${this.config.apiBaseUrl}/apps/textIn/recognize${queryString}`;
|
|
24
|
+
|
|
25
|
+
// 判断输入类型
|
|
26
|
+
if (typeof input === "string" && (input.startsWith("http://") || input.startsWith("https://"))) {
|
|
27
|
+
// URL 模式
|
|
28
|
+
return this._post(`/apps/textIn/recognize${queryString}`, { url: input });
|
|
29
|
+
}
|
|
30
|
+
// 文件模式 (FormData)
|
|
31
|
+
const form = new FormData();
|
|
32
|
+
form.append("appId", this.appId);
|
|
33
|
+
|
|
34
|
+
if (isBrowser) {
|
|
35
|
+
if (!(input instanceof File)) {
|
|
36
|
+
throw new Error("Invalid file input in browser. Provide a File object or URL string.");
|
|
37
|
+
}
|
|
38
|
+
form.append("file", input, input.name);
|
|
39
|
+
} else {
|
|
40
|
+
// isBackend
|
|
41
|
+
if (typeof input === "object" && input.data && input.name && input.encoding === "base64") {
|
|
42
|
+
const buffer = Buffer.from(input.data, "base64");
|
|
43
|
+
const fileBlob = new Blob([buffer]);
|
|
44
|
+
form.append("file", fileBlob, input.name);
|
|
45
|
+
} else {
|
|
46
|
+
throw new Error(
|
|
47
|
+
"Invalid file input in Node.js. Provide { data: base64_string, encoding: 'base64', name: string } or URL string."
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const headers = this._getRequestHeaders();
|
|
53
|
+
delete headers["Content-Type"]; // Let fetch set Content-Type for multipart/form-data
|
|
54
|
+
|
|
55
|
+
const resp = await fetch(url, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers,
|
|
58
|
+
body: form,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!resp.ok) {
|
|
62
|
+
const err = await resp.json().catch(() => ({}));
|
|
63
|
+
throw new Error(err.message || err.error || `HTTP ${resp.status}: ${resp.statusText}`);
|
|
64
|
+
}
|
|
65
|
+
return resp.json();
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Word 转 PDF
|
|
70
|
+
* @param {File | {data: string, encoding: 'base64', name: string} | string} input - 文件对象、Base64对象或URL字符串
|
|
71
|
+
* @returns {Promise<{success: boolean, data: {url: string}}>} 转换结果,包含文件下载链接
|
|
72
|
+
*/
|
|
73
|
+
async wordToPdf(input) {
|
|
74
|
+
if (!input) throw new Error("input (file or url) is required");
|
|
75
|
+
|
|
76
|
+
const url = `${this.config.apiBaseUrl}/apps/textIn/word-to-pdf`;
|
|
77
|
+
|
|
78
|
+
// 判断输入类型
|
|
79
|
+
if (typeof input === "string" && (input.startsWith("http://") || input.startsWith("https://"))) {
|
|
80
|
+
// URL 模式
|
|
81
|
+
return this._post("/apps/textIn/word-to-pdf", { url: input });
|
|
82
|
+
}
|
|
83
|
+
// 文件模式 (FormData)
|
|
84
|
+
const form = new FormData();
|
|
85
|
+
form.append("appId", this.appId);
|
|
86
|
+
|
|
87
|
+
if (isBrowser) {
|
|
88
|
+
if (!(input instanceof File)) {
|
|
89
|
+
throw new Error("Invalid file input in browser. Provide a File object or URL string.");
|
|
90
|
+
}
|
|
91
|
+
form.append("file", input, input.name);
|
|
92
|
+
} else {
|
|
93
|
+
// isBackend
|
|
94
|
+
if (typeof input === "object" && input.data && input.name && input.encoding === "base64") {
|
|
95
|
+
const buffer = Buffer.from(input.data, "base64");
|
|
96
|
+
const fileBlob = new Blob([buffer]);
|
|
97
|
+
form.append("file", fileBlob, input.name);
|
|
98
|
+
} else {
|
|
99
|
+
throw new Error(
|
|
100
|
+
"Invalid file input in Node.js. Provide { data: base64_string, encoding: 'base64', name: string } or URL string."
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const headers = this._getRequestHeaders();
|
|
106
|
+
delete headers["Content-Type"]; // Let fetch set Content-Type for multipart/form-data
|
|
107
|
+
|
|
108
|
+
const resp = await fetch(url, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers,
|
|
111
|
+
body: form,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (!resp.ok) {
|
|
115
|
+
const err = await resp.json().catch(() => ({}));
|
|
116
|
+
throw new Error(err.message || err.error || `HTTP ${resp.status}: ${resp.statusText}`);
|
|
117
|
+
}
|
|
118
|
+
return resp.json();
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* PDF 转 Word
|
|
123
|
+
* @param {File | {data: string, encoding: 'base64', name: string} | string} input - 文件对象、Base64对象或URL字符串
|
|
124
|
+
* @returns {Promise<{success: boolean, data: {url: string}}>} 转换结果,包含文件下载链接
|
|
125
|
+
*/
|
|
126
|
+
async pdfToWord(input) {
|
|
127
|
+
if (!input) throw new Error("input (file or url) is required");
|
|
128
|
+
|
|
129
|
+
const url = `${this.config.apiBaseUrl}/apps/textIn/pdf-to-word`;
|
|
130
|
+
|
|
131
|
+
// 判断输入类型
|
|
132
|
+
if (typeof input === "string" && (input.startsWith("http://") || input.startsWith("https://"))) {
|
|
133
|
+
// URL 模式
|
|
134
|
+
return this._post("/apps/textIn/pdf-to-word", { url: input });
|
|
135
|
+
}
|
|
136
|
+
// 文件模式 (FormData)
|
|
137
|
+
const form = new FormData();
|
|
138
|
+
form.append("appId", this.appId);
|
|
139
|
+
|
|
140
|
+
if (isBrowser) {
|
|
141
|
+
if (!(input instanceof File)) {
|
|
142
|
+
throw new Error("Invalid file input in browser. Provide a File object or URL string.");
|
|
143
|
+
}
|
|
144
|
+
form.append("file", input, input.name);
|
|
145
|
+
} else {
|
|
146
|
+
// isBackend
|
|
147
|
+
if (typeof input === "object" && input.data && input.name && input.encoding === "base64") {
|
|
148
|
+
const buffer = Buffer.from(input.data, "base64");
|
|
149
|
+
const fileBlob = new Blob([buffer]);
|
|
150
|
+
form.append("file", fileBlob, input.name);
|
|
151
|
+
} else {
|
|
152
|
+
throw new Error(
|
|
153
|
+
"Invalid file input in Node.js. Provide { data: base64_string, encoding: 'base64', name: string } or URL string."
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const headers = this._getRequestHeaders();
|
|
159
|
+
delete headers["Content-Type"]; // Let fetch set Content-Type for multipart/form-data
|
|
160
|
+
|
|
161
|
+
const resp = await fetch(url, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers,
|
|
164
|
+
body: form,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (!resp.ok) {
|
|
168
|
+
const err = await resp.json().catch(() => ({}));
|
|
169
|
+
throw new Error(err.message || err.error || `HTTP ${resp.status}: ${resp.statusText}`);
|
|
170
|
+
}
|
|
171
|
+
return resp.json();
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Excel 转 PDF
|
|
176
|
+
* @param {File | {data: string, encoding: 'base64', name: string} | string} input - 文件对象、Base64对象或URL字符串
|
|
177
|
+
* @returns {Promise<{success: boolean, data: {url: string}}>} 转换结果,包含文件下载链接
|
|
178
|
+
*/
|
|
179
|
+
async excelToPdf(input) {
|
|
180
|
+
if (!input) throw new Error("input (file or url) is required");
|
|
181
|
+
|
|
182
|
+
const url = `${this.config.apiBaseUrl}/apps/textIn/excel-to-pdf`;
|
|
183
|
+
|
|
184
|
+
// 判断输入类型
|
|
185
|
+
if (typeof input === "string" && (input.startsWith("http://") || input.startsWith("https://"))) {
|
|
186
|
+
// URL 模式
|
|
187
|
+
return this._post("/apps/textIn/excel-to-pdf", { url: input });
|
|
188
|
+
}
|
|
189
|
+
// 文件模式 (FormData)
|
|
190
|
+
const form = new FormData();
|
|
191
|
+
form.append("appId", this.appId);
|
|
192
|
+
|
|
193
|
+
if (isBrowser) {
|
|
194
|
+
if (!(input instanceof File)) {
|
|
195
|
+
throw new Error("Invalid file input in browser. Provide a File object or URL string.");
|
|
196
|
+
}
|
|
197
|
+
form.append("file", input, input.name);
|
|
198
|
+
} else {
|
|
199
|
+
// isBackend
|
|
200
|
+
if (typeof input === "object" && input.data && input.name && input.encoding === "base64") {
|
|
201
|
+
const buffer = Buffer.from(input.data, "base64");
|
|
202
|
+
const fileBlob = new Blob([buffer]);
|
|
203
|
+
form.append("file", fileBlob, input.name);
|
|
204
|
+
} else {
|
|
205
|
+
throw new Error(
|
|
206
|
+
"Invalid file input in Node.js. Provide { data: base64_string, encoding: 'base64', name: string } or URL string."
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const headers = this._getRequestHeaders();
|
|
212
|
+
delete headers["Content-Type"]; // Let fetch set Content-Type for multipart/form-data
|
|
213
|
+
|
|
214
|
+
const resp = await fetch(url, {
|
|
215
|
+
method: "POST",
|
|
216
|
+
headers,
|
|
217
|
+
body: form,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (!resp.ok) {
|
|
221
|
+
const err = await resp.json().catch(() => ({}));
|
|
222
|
+
throw new Error(err.message || err.error || `HTTP ${resp.status}: ${resp.statusText}`);
|
|
223
|
+
}
|
|
224
|
+
return resp.json();
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* PDF 转 Excel
|
|
229
|
+
* @param {File | {data: string, encoding: 'base64', name: string} | string} input - 文件对象、Base64对象或URL字符串
|
|
230
|
+
* @returns {Promise<{success: boolean, data: {url: string}}>} 转换结果,包含文件下载链接
|
|
231
|
+
*/
|
|
232
|
+
async pdfToExcel(input) {
|
|
233
|
+
if (!input) throw new Error("input (file or url) is required");
|
|
234
|
+
|
|
235
|
+
const url = `${this.config.apiBaseUrl}/apps/textIn/pdf-to-excel`;
|
|
236
|
+
|
|
237
|
+
// 判断输入类型
|
|
238
|
+
if (typeof input === "string" && (input.startsWith("http://") || input.startsWith("https://"))) {
|
|
239
|
+
// URL 模式
|
|
240
|
+
return this._post("/apps/textIn/pdf-to-excel", { url: input });
|
|
241
|
+
}
|
|
242
|
+
// 文件模式 (FormData)
|
|
243
|
+
const form = new FormData();
|
|
244
|
+
form.append("appId", this.appId);
|
|
245
|
+
|
|
246
|
+
if (isBrowser) {
|
|
247
|
+
if (!(input instanceof File)) {
|
|
248
|
+
throw new Error("Invalid file input in browser. Provide a File object or URL string.");
|
|
249
|
+
}
|
|
250
|
+
form.append("file", input, input.name);
|
|
251
|
+
} else {
|
|
252
|
+
// isBackend
|
|
253
|
+
if (typeof input === "object" && input.data && input.name && input.encoding === "base64") {
|
|
254
|
+
const buffer = Buffer.from(input.data, "base64");
|
|
255
|
+
const fileBlob = new Blob([buffer]);
|
|
256
|
+
form.append("file", fileBlob, input.name);
|
|
257
|
+
} else {
|
|
258
|
+
throw new Error(
|
|
259
|
+
"Invalid file input in Node.js. Provide { data: base64_string, encoding: 'base64', name: string } or URL string."
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const headers = this._getRequestHeaders();
|
|
265
|
+
delete headers["Content-Type"]; // Let fetch set Content-Type for multipart/form-data
|
|
266
|
+
|
|
267
|
+
const resp = await fetch(url, {
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers,
|
|
270
|
+
body: form,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (!resp.ok) {
|
|
274
|
+
const err = await resp.json().catch(() => ({}));
|
|
275
|
+
throw new Error(err.message || err.error || `HTTP ${resp.status}: ${resp.statusText}`);
|
|
276
|
+
}
|
|
277
|
+
return resp.json();
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 通用文件转 Markdown (支持 PDF, Word, Excel, PPT, 图片等)
|
|
282
|
+
* @param {File | {data: string, encoding: 'base64', name: string} | string} input - 文件对象、Base64对象或URL字符串
|
|
283
|
+
* @param {Object} options - 转换选项 (如 page_start, page_count 等)
|
|
284
|
+
* @returns {Promise<{success: boolean, data: {url: string}}>} 转换结果,包含 Markdown 文件下载链接
|
|
285
|
+
*/
|
|
286
|
+
async fileToMarkdown(input, options = {}) {
|
|
287
|
+
if (!input) throw new Error("input (file or url) is required");
|
|
288
|
+
|
|
289
|
+
// 构建查询参数
|
|
290
|
+
const queryParams = new URLSearchParams();
|
|
291
|
+
for (const [key, value] of Object.entries(options)) {
|
|
292
|
+
if (value !== undefined && value !== null) {
|
|
293
|
+
queryParams.append(key, value.toString());
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const queryString = queryParams.toString() ? `?${queryParams.toString()}` : "";
|
|
297
|
+
const url = `${this.config.apiBaseUrl}/apps/textIn/file-to-markdown${queryString}`;
|
|
298
|
+
|
|
299
|
+
// 判断输入类型
|
|
300
|
+
if (typeof input === "string" && (input.startsWith("http://") || input.startsWith("https://"))) {
|
|
301
|
+
// URL 模式
|
|
302
|
+
return this._post(`/apps/textIn/file-to-markdown${queryString}`, { url: input });
|
|
303
|
+
}
|
|
304
|
+
// 文件模式 (FormData)
|
|
305
|
+
const form = new FormData();
|
|
306
|
+
form.append("appId", this.appId);
|
|
307
|
+
|
|
308
|
+
if (isBrowser) {
|
|
309
|
+
if (!(input instanceof File)) {
|
|
310
|
+
throw new Error("Invalid file input in browser. Provide a File object or URL string.");
|
|
311
|
+
}
|
|
312
|
+
form.append("file", input, input.name);
|
|
313
|
+
} else {
|
|
314
|
+
// isBackend
|
|
315
|
+
if (typeof input === "object" && input.data && input.name && input.encoding === "base64") {
|
|
316
|
+
const buffer = Buffer.from(input.data, "base64");
|
|
317
|
+
const fileBlob = new Blob([buffer]);
|
|
318
|
+
form.append("file", fileBlob, input.name);
|
|
319
|
+
} else {
|
|
320
|
+
throw new Error(
|
|
321
|
+
"Invalid file input in Node.js. Provide { data: base64_string, encoding: 'base64', name: string } or URL string."
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const headers = this._getRequestHeaders();
|
|
327
|
+
delete headers["Content-Type"]; // Let fetch set Content-Type for multipart/form-data
|
|
328
|
+
|
|
329
|
+
const resp = await fetch(url, {
|
|
330
|
+
method: "POST",
|
|
331
|
+
headers,
|
|
332
|
+
body: form,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (!resp.ok) {
|
|
336
|
+
const err = await resp.json().catch(() => ({}));
|
|
337
|
+
throw new Error(err.message || err.error || `HTTP ${resp.status}: ${resp.statusText}`);
|
|
338
|
+
}
|
|
339
|
+
return resp.json();
|
|
340
|
+
},
|
|
341
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const appsClientToolsMethods = {
|
|
2
|
+
// ==================== PDF 水印 ====================
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 给 PDF 添加水印
|
|
6
|
+
* @param {object} options
|
|
7
|
+
* @param {string} options.url - PDF 文件的 URL
|
|
8
|
+
* @param {string} options.text - 水印文案
|
|
9
|
+
* @param {object} [options.style] - 水印样式 { size, opacity, color, rotate, fontUrl }
|
|
10
|
+
* @returns {Promise<{success: boolean, data: {url: string, fileId: string, size: number}}>}
|
|
11
|
+
*/
|
|
12
|
+
async addWatermark(options = {}) {
|
|
13
|
+
if (!options.url || !options.text) {
|
|
14
|
+
throw new Error("url and text are required for addWatermark.");
|
|
15
|
+
}
|
|
16
|
+
return this._post("/apps/watermark/process", {
|
|
17
|
+
appId: this.appId,
|
|
18
|
+
...options,
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
// ==================== 敏感信息检测 ====================
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 检测内容中的敏感信息
|
|
26
|
+
* @param {string} content - 待检测的内容(文本字符串、图片URL或文档URL)
|
|
27
|
+
* @returns {Promise<{isSensitive: boolean, result: string}>}
|
|
28
|
+
*/
|
|
29
|
+
async detectSensitiveInfo(content) {
|
|
30
|
+
if (!content) {
|
|
31
|
+
throw new Error("content is required for sensitive detection.");
|
|
32
|
+
}
|
|
33
|
+
const result = await this._post("/apps/sensitive/detect", { content });
|
|
34
|
+
return result.data;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const appsClientUserMethods = {
|
|
2
|
+
// ==================== 新增:应用用户与角色管理 ====================
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 获取当前 App 下的用户列表(含 appRole)
|
|
6
|
+
* @param {Object} params { page?: number, pageSize?: number }
|
|
7
|
+
* @returns {Promise<{ success: boolean, data: Array, pagination: Object }>}
|
|
8
|
+
*/
|
|
9
|
+
async listAppUsers(params = {}) {
|
|
10
|
+
const { page = 1, pageSize = 20 } = params || {};
|
|
11
|
+
return this._get("/apps/users/list", {
|
|
12
|
+
appId: this.appId,
|
|
13
|
+
page,
|
|
14
|
+
pageSize,
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* [新增] 为当前应用创建一个新的自定义角色
|
|
20
|
+
* @param {Object} params { roleName: string, description?: string }
|
|
21
|
+
* @returns {Promise<{success: boolean, data: {name: string, description: string}}>}
|
|
22
|
+
*/
|
|
23
|
+
async createRole({ roleName, description }) {
|
|
24
|
+
if (!roleName) {
|
|
25
|
+
throw new Error("roleName is required.");
|
|
26
|
+
}
|
|
27
|
+
return this._post("/apps/roles/create", {
|
|
28
|
+
appId: this.appId,
|
|
29
|
+
roleName,
|
|
30
|
+
description,
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* [新增] 更新或设置一个用户在当前应用中的角色
|
|
36
|
+
* @param {Object} params { userId: string, role: string }
|
|
37
|
+
* @returns {Promise<{success: boolean, data: {userId: string, appId: string, role: string}}>}
|
|
38
|
+
*/
|
|
39
|
+
async updateUserRole({ userId, role }) {
|
|
40
|
+
if (!userId || !role) {
|
|
41
|
+
throw new Error("userId and role are required.");
|
|
42
|
+
}
|
|
43
|
+
return this._post("/apps/users/update-role", {
|
|
44
|
+
appId: this.appId,
|
|
45
|
+
targetUserId: userId,
|
|
46
|
+
role: role,
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export const appsClientVoiceVideoMethods = {
|
|
2
|
+
// ==================== 新增:语音生成 ====================
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 生成语音文件和字幕(非流式)
|
|
6
|
+
* @param {object} options - { text, voiceId?, speed?, vol?, pitch?, projectId? }
|
|
7
|
+
* @returns {Promise<{audioUrl: string, fileID: string, subtitles: object, duration: number, cost: number}>}
|
|
8
|
+
*/
|
|
9
|
+
async generateVoice(options = {}) {
|
|
10
|
+
if (!options.text) {
|
|
11
|
+
throw new Error("text is required for voice generation.");
|
|
12
|
+
}
|
|
13
|
+
const result = await this._post("/apps/voice/generate", {
|
|
14
|
+
appId: this.appId,
|
|
15
|
+
...options,
|
|
16
|
+
});
|
|
17
|
+
return result.data;
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 获取语音流式响应(流式)
|
|
22
|
+
* @param {object} options - { text, voiceId?, speed?, vol?, pitch?, projectId? }
|
|
23
|
+
* @returns {Promise<Response>} - Fetch API 的 Response 对象,其 body 是一个 ReadableStream。
|
|
24
|
+
*/
|
|
25
|
+
async streamVoice(options = {}) {
|
|
26
|
+
if (!options.text) {
|
|
27
|
+
throw new Error("text is required for voice streaming.");
|
|
28
|
+
}
|
|
29
|
+
const url = `${this.config.apiBaseUrl}/apps/voice/stream`;
|
|
30
|
+
const headers = this._getRequestHeaders();
|
|
31
|
+
|
|
32
|
+
const response = await fetch(url, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers,
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
appId: this.appId,
|
|
37
|
+
...options,
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const errorData = await response.json().catch(() => ({ error: "Network error" }));
|
|
43
|
+
throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return response; // 直接返回 Response 对象
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// ==================== 新增:Sora 视频生成 ====================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 创建一个 Sora 视频生成任务
|
|
53
|
+
* @param {object} options - { prompt, model, size, seconds }
|
|
54
|
+
* @returns {Promise<object>} 视频任务文档
|
|
55
|
+
*/
|
|
56
|
+
async createVideoJob(options = {}) {
|
|
57
|
+
if (!options.prompt || !options.model) {
|
|
58
|
+
throw new Error("prompt and model are required for video generation.");
|
|
59
|
+
}
|
|
60
|
+
// The middleware uses appId from the body to find the organization
|
|
61
|
+
return this._post("/apps/sora/videos", {
|
|
62
|
+
appId: this.appId,
|
|
63
|
+
...options,
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 基于现有视频创建一个 Remix 任务
|
|
69
|
+
* @param {string} jobId - 原始视频任务的 wyID
|
|
70
|
+
* @param {object} options - { prompt }
|
|
71
|
+
* @returns {Promise<object>} 新的 Remix 任务初始状态的文档
|
|
72
|
+
*/
|
|
73
|
+
async remixVideoJob(jobId, options = {}) {
|
|
74
|
+
if (!jobId) {
|
|
75
|
+
throw new Error("jobId of the original video is required for remixing.");
|
|
76
|
+
}
|
|
77
|
+
if (!options.prompt) {
|
|
78
|
+
throw new Error("A new prompt is required for remixing.");
|
|
79
|
+
}
|
|
80
|
+
return this._post(`/apps/sora/videos/${jobId}/remix`, {
|
|
81
|
+
appId: this.appId,
|
|
82
|
+
prompt: options.prompt,
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 查询视频生成任务的状态
|
|
88
|
+
* @param {string} jobId - 任务的 wyID
|
|
89
|
+
* @returns {Promise<object>} 视频任务文档
|
|
90
|
+
*/
|
|
91
|
+
async getVideoJob(jobId) {
|
|
92
|
+
if (!jobId) {
|
|
93
|
+
throw new Error("jobId is required.");
|
|
94
|
+
}
|
|
95
|
+
return this._get(`/apps/sora/videos/${jobId}`, { appId: this.appId });
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 获取视频任务列表
|
|
100
|
+
* @param {object} [options] - { page = 1, pageSize = 10 }
|
|
101
|
+
* @returns {Promise<object>} 任务列表和分页信息
|
|
102
|
+
*/
|
|
103
|
+
async listVideoJobs(options = {}) {
|
|
104
|
+
return this._get("/apps/sora/videos", { appId: this.appId, ...options });
|
|
105
|
+
},
|
|
106
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 火山引擎实时语音对话客户端实现
|
|
3
|
+
* 内部类,直接集成在 AppsClient 中
|
|
4
|
+
*/
|
|
5
|
+
class VolcengineImpl {
|
|
6
|
+
/**
|
|
7
|
+
* @param {AppsClient} appsClient - AppsClient 实例
|
|
8
|
+
*/
|
|
9
|
+
constructor(appsClient) {
|
|
10
|
+
this.appsClient = appsClient;
|
|
11
|
+
this.ws = null;
|
|
12
|
+
this.sessionId = null;
|
|
13
|
+
this.listeners = {
|
|
14
|
+
asr: [],
|
|
15
|
+
content: [], // AI 回复的文本
|
|
16
|
+
audio: [], // TTS 音频数据 (Uint8Array)
|
|
17
|
+
sys_error: [],
|
|
18
|
+
close: [],
|
|
19
|
+
json: [], // 原始 JSON 事件
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 注册事件监听器
|
|
25
|
+
* @param {'asr'|'content'|'audio'|'sys_error'|'close'|'json'} event
|
|
26
|
+
* @param {Function} callback
|
|
27
|
+
*/
|
|
28
|
+
on(event, callback) {
|
|
29
|
+
if (this.listeners[event]) {
|
|
30
|
+
this.listeners[event].push(callback);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 移除事件监听器
|
|
36
|
+
* @param {'asr'|'content'|'audio'|'sys_error'|'close'|'json'} event
|
|
37
|
+
* @param {Function} callback
|
|
38
|
+
*/
|
|
39
|
+
off(event, callback) {
|
|
40
|
+
if (this.listeners[event]) {
|
|
41
|
+
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_emit(event, data) {
|
|
46
|
+
if (this.listeners[event]) {
|
|
47
|
+
this.listeners[event].forEach(cb => {
|
|
48
|
+
try {
|
|
49
|
+
cb(data);
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error(`[VolcengineClient] Error in ${event} listener:`, e);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 开启会话
|
|
59
|
+
* 1. 建立 WebSocket 连接
|
|
60
|
+
* 2. 发送 start 指令
|
|
61
|
+
*
|
|
62
|
+
* @param {Object} options - 配置项
|
|
63
|
+
* @param {string} [options.dataStoreId] - 知识库 ID (用于 RAG)
|
|
64
|
+
* @param {string} [options.botName] - 机器人名称 (如 "客服小助手")
|
|
65
|
+
* @param {string} [options.systemRole] - 系统角色设定 (如 "你是一个专业的客服...")
|
|
66
|
+
* @param {string} [options.speakingStyle] - 说话风格 (如 "亲切、温柔")
|
|
67
|
+
*/
|
|
68
|
+
async start(options = {}) {
|
|
69
|
+
// 1. 建立 WebSocket 连接
|
|
70
|
+
let baseUrl = this.appsClient.config.apiBaseUrl.replace(/^http/, "ws"); // http->ws, https->wss
|
|
71
|
+
|
|
72
|
+
// [Removed] 本地开发环境特殊处理已移除,现在通过 Vite 代理 (ws: true) 统一转发
|
|
73
|
+
|
|
74
|
+
const token = this.appsClient._getAccessToken();
|
|
75
|
+
const url = `${baseUrl}/apps/volcengine/connect?access_token=${token || ""}`;
|
|
76
|
+
|
|
77
|
+
this.ws = new WebSocket(url);
|
|
78
|
+
this.ws.binaryType = "arraybuffer"; // 关键:接收二进制音频
|
|
79
|
+
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
this.ws.onopen = () => {
|
|
82
|
+
console.log("[VolcengineClient] WebSocket Connected");
|
|
83
|
+
// 2. 连接成功后,发送 start 指令
|
|
84
|
+
this.ws.send(JSON.stringify({
|
|
85
|
+
type: "start",
|
|
86
|
+
...options,
|
|
87
|
+
}));
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
this.ws.onmessage = event => {
|
|
91
|
+
const data = event.data;
|
|
92
|
+
|
|
93
|
+
// === 处理二进制音频 (TTS) ===
|
|
94
|
+
if (data instanceof ArrayBuffer) {
|
|
95
|
+
const audioData = new Uint8Array(data);
|
|
96
|
+
this._emit("audio", audioData);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// === 处理 JSON 消息 ===
|
|
101
|
+
try {
|
|
102
|
+
const msg = JSON.parse(data);
|
|
103
|
+
|
|
104
|
+
if (msg.type === "started") {
|
|
105
|
+
this.sessionId = msg.sessionId;
|
|
106
|
+
resolve(msg);
|
|
107
|
+
} else if (msg.type === "json") {
|
|
108
|
+
this._emit("json", msg);
|
|
109
|
+
} else if (msg.type === "error") {
|
|
110
|
+
this._emit("sys_error", msg);
|
|
111
|
+
reject(new Error(msg.message || "Unknown error"));
|
|
112
|
+
} else if (msg.type === "close") {
|
|
113
|
+
this._emit("close", msg);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// === 业务事件分发 ===
|
|
117
|
+
if (msg.eventId === 451 && msg.data?.results) {
|
|
118
|
+
const asrText = msg.data.results[0]?.text;
|
|
119
|
+
if (asrText) this._emit("asr", { text: asrText });
|
|
120
|
+
}
|
|
121
|
+
if (msg.eventId === 550 && msg.data?.content) {
|
|
122
|
+
this._emit("content", { text: msg.data.content });
|
|
123
|
+
}
|
|
124
|
+
} catch (err) {
|
|
125
|
+
console.warn("[VolcengineClient] Failed to parse message:", data);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
this.ws.onerror = err => {
|
|
130
|
+
console.error("[VolcengineClient] WebSocket error:", err);
|
|
131
|
+
reject(err);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
this.ws.onclose = () => {
|
|
135
|
+
console.log("[VolcengineClient] WebSocket Closed");
|
|
136
|
+
this._emit("close", { reason: "closed" });
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 发送音频数据 (PCM)
|
|
143
|
+
* @param {ArrayBuffer|Uint8Array} audioData - 16k 16bit PCM 音频数据
|
|
144
|
+
*/
|
|
145
|
+
async sendAudio(audioData) {
|
|
146
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
147
|
+
// 直接发送二进制数据
|
|
148
|
+
this.ws.send(audioData);
|
|
149
|
+
} else {
|
|
150
|
+
console.warn("[VolcengineClient] Cannot send audio: WebSocket not open");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 结束会话
|
|
156
|
+
*/
|
|
157
|
+
async stop() {
|
|
158
|
+
if (this.ws) {
|
|
159
|
+
if (this.ws.readyState === WebSocket.OPEN) {
|
|
160
|
+
this.ws.send(JSON.stringify({ type: "stop" }));
|
|
161
|
+
this.ws.close();
|
|
162
|
+
}
|
|
163
|
+
this.ws = null;
|
|
164
|
+
this.sessionId = null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export { VolcengineImpl };
|