@sogni-ai/sogni-client 4.0.0-alpha.3 → 4.0.0-alpha.31
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 +220 -0
- package/README.md +279 -28
- package/dist/Account/index.d.ts +18 -16
- package/dist/Account/index.js +31 -20
- package/dist/Account/index.js.map +1 -1
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.d.ts +66 -0
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.js +332 -0
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.js.map +1 -0
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.d.ts +28 -0
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js +203 -0
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js.map +1 -0
- package/dist/ApiClient/WebSocketClient/events.d.ts +11 -0
- package/dist/ApiClient/WebSocketClient/index.d.ts +2 -2
- package/dist/ApiClient/WebSocketClient/index.js +13 -3
- package/dist/ApiClient/WebSocketClient/index.js.map +1 -1
- package/dist/ApiClient/WebSocketClient/types.d.ts +13 -0
- package/dist/ApiClient/index.d.ts +4 -4
- package/dist/ApiClient/index.js +23 -4
- package/dist/ApiClient/index.js.map +1 -1
- package/dist/Projects/Job.d.ts +44 -4
- package/dist/Projects/Job.js +83 -16
- package/dist/Projects/Job.js.map +1 -1
- package/dist/Projects/Project.d.ts +18 -0
- package/dist/Projects/Project.js +34 -6
- package/dist/Projects/Project.js.map +1 -1
- package/dist/Projects/createJobRequestMessage.js +109 -15
- package/dist/Projects/createJobRequestMessage.js.map +1 -1
- package/dist/Projects/index.d.ts +110 -11
- package/dist/Projects/index.js +423 -42
- package/dist/Projects/index.js.map +1 -1
- package/dist/Projects/types/EstimationResponse.d.ts +2 -0
- package/dist/Projects/types/SamplerParams.d.ts +13 -0
- package/dist/Projects/types/SamplerParams.js +26 -0
- package/dist/Projects/types/SamplerParams.js.map +1 -0
- package/dist/Projects/types/SchedulerParams.d.ts +14 -0
- package/dist/Projects/types/SchedulerParams.js +24 -0
- package/dist/Projects/types/SchedulerParams.js.map +1 -0
- package/dist/Projects/types/events.d.ts +5 -1
- package/dist/Projects/types/index.d.ts +150 -39
- package/dist/Projects/types/index.js +13 -0
- package/dist/Projects/types/index.js.map +1 -1
- package/dist/Projects/utils.d.ts +19 -1
- package/dist/Projects/utils.js +68 -0
- package/dist/Projects/utils.js.map +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/AuthManager/TokenAuthManager.js +0 -2
- package/dist/lib/AuthManager/TokenAuthManager.js.map +1 -1
- package/dist/lib/DataEntity.js +4 -2
- package/dist/lib/DataEntity.js.map +1 -1
- package/dist/lib/validation.d.ts +7 -0
- package/dist/lib/validation.js +36 -0
- package/dist/lib/validation.js.map +1 -1
- package/package.json +4 -4
- package/src/Account/index.ts +30 -19
- package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/ChannelCoordinator.ts +426 -0
- package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/index.ts +237 -0
- package/src/ApiClient/WebSocketClient/events.ts +13 -0
- package/src/ApiClient/WebSocketClient/index.ts +15 -5
- package/src/ApiClient/WebSocketClient/types.ts +16 -0
- package/src/ApiClient/index.ts +30 -8
- package/src/Projects/Job.ts +97 -16
- package/src/Projects/Project.ts +42 -9
- package/src/Projects/createJobRequestMessage.ts +155 -36
- package/src/Projects/index.ts +447 -46
- package/src/Projects/types/EstimationResponse.ts +2 -0
- package/src/Projects/types/SamplerParams.ts +24 -0
- package/src/Projects/types/SchedulerParams.ts +22 -0
- package/src/Projects/types/events.ts +6 -0
- package/src/Projects/types/index.ts +181 -47
- package/src/Projects/utils.ts +66 -1
- package/src/index.ts +38 -11
- package/src/lib/AuthManager/TokenAuthManager.ts +0 -2
- package/src/lib/DataEntity.ts +4 -2
- package/src/lib/validation.ts +41 -0
package/dist/Projects/index.js
CHANGED
|
@@ -20,9 +20,37 @@ const getUUID_1 = __importDefault(require("../lib/getUUID"));
|
|
|
20
20
|
const Cache_1 = __importDefault(require("../lib/Cache"));
|
|
21
21
|
const Job_1 = require("./Job");
|
|
22
22
|
const utils_1 = require("./utils");
|
|
23
|
+
const validation_1 = require("../lib/validation");
|
|
23
24
|
const sizePresetCache = new Cache_1.default(10 * 60 * 1000);
|
|
24
25
|
const GARBAGE_COLLECT_TIMEOUT = 30000;
|
|
25
26
|
const MODELS_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 24 hours
|
|
27
|
+
/**
|
|
28
|
+
* Detect content type from a file object.
|
|
29
|
+
* For File objects in browser, uses the type property.
|
|
30
|
+
* Returns undefined if content type cannot be detected.
|
|
31
|
+
*/
|
|
32
|
+
function getFileContentType(file) {
|
|
33
|
+
if (file instanceof Blob && 'type' in file && file.type) {
|
|
34
|
+
return file.type;
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Convert file to a format compatible with fetch body.
|
|
40
|
+
* Converts Node.js Buffer to Blob for cross-platform compatibility.
|
|
41
|
+
*/
|
|
42
|
+
function toFetchBody(file) {
|
|
43
|
+
// Node.js Buffer is not supported in browsers, so we can skip this conversion
|
|
44
|
+
if (typeof Buffer === 'undefined') {
|
|
45
|
+
return file;
|
|
46
|
+
}
|
|
47
|
+
if (Buffer.isBuffer(file)) {
|
|
48
|
+
// Copy Buffer data to a new ArrayBuffer to ensure type compatibility
|
|
49
|
+
const arrayBuffer = file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength);
|
|
50
|
+
return new Blob([arrayBuffer]);
|
|
51
|
+
}
|
|
52
|
+
return file;
|
|
53
|
+
}
|
|
26
54
|
function mapErrorCodes(code) {
|
|
27
55
|
switch (code) {
|
|
28
56
|
case 'serverRestarting':
|
|
@@ -43,6 +71,20 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
43
71
|
get availableModels() {
|
|
44
72
|
return this._availableModels;
|
|
45
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if a model produces video output using the cached models list.
|
|
76
|
+
* Uses the `media` property from the models API when available,
|
|
77
|
+
* falls back to model ID prefix check if models aren't loaded yet.
|
|
78
|
+
*/
|
|
79
|
+
isVideoModelId(modelId) {
|
|
80
|
+
var _a;
|
|
81
|
+
const model = (_a = this._supportedModels.data) === null || _a === void 0 ? void 0 : _a.find((m) => m.id === modelId);
|
|
82
|
+
if (model) {
|
|
83
|
+
return model.media === 'video';
|
|
84
|
+
}
|
|
85
|
+
// Fallback to prefix check if models not loaded
|
|
86
|
+
return (0, utils_1.isVideoModel)(modelId);
|
|
87
|
+
}
|
|
46
88
|
constructor(config) {
|
|
47
89
|
super(config);
|
|
48
90
|
this._availableModels = [];
|
|
@@ -56,6 +98,7 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
56
98
|
this.client.socket.on('swarmModels', this.handleSwarmModels.bind(this));
|
|
57
99
|
this.client.socket.on('jobState', this.handleJobState.bind(this));
|
|
58
100
|
this.client.socket.on('jobProgress', this.handleJobProgress.bind(this));
|
|
101
|
+
this.client.socket.on('jobETA', this.handleJobETA.bind(this));
|
|
59
102
|
this.client.socket.on('jobError', this.handleJobError.bind(this));
|
|
60
103
|
this.client.socket.on('jobResult', this.handleJobResult.bind(this));
|
|
61
104
|
// Listen to the server disconnect event
|
|
@@ -64,6 +107,16 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
64
107
|
this.on('project', this.handleProjectEvent.bind(this));
|
|
65
108
|
this.on('job', this.handleJobEvent.bind(this));
|
|
66
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Retrieves a list of projects created and tracked by this SogniClient instance.
|
|
112
|
+
*
|
|
113
|
+
* Note: When a project is finished, it will be removed from this list after 30 seconds
|
|
114
|
+
*
|
|
115
|
+
* @return {Array} A copy of the array containing the tracked projects.
|
|
116
|
+
*/
|
|
117
|
+
get trackedProjects() {
|
|
118
|
+
return this.projects.slice(0);
|
|
119
|
+
}
|
|
67
120
|
handleChangeNetwork() {
|
|
68
121
|
this._availableModels = [];
|
|
69
122
|
this.emit('availableModels', this._availableModels);
|
|
@@ -82,11 +135,12 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
82
135
|
return acc;
|
|
83
136
|
}, {});
|
|
84
137
|
this._availableModels = Object.entries(data).map(([id, workerCount]) => {
|
|
85
|
-
var _a;
|
|
138
|
+
var _a, _b;
|
|
86
139
|
return ({
|
|
87
140
|
id,
|
|
88
141
|
name: ((_a = modelIndex[id]) === null || _a === void 0 ? void 0 : _a.name) || id.replace(/-/g, ' '),
|
|
89
|
-
workerCount
|
|
142
|
+
workerCount,
|
|
143
|
+
media: ((_b = modelIndex[id]) === null || _b === void 0 ? void 0 : _b.media) || 'image'
|
|
90
144
|
});
|
|
91
145
|
});
|
|
92
146
|
this.emit('availableModels', this._availableModels);
|
|
@@ -154,6 +208,16 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
154
208
|
}
|
|
155
209
|
});
|
|
156
210
|
}
|
|
211
|
+
handleJobETA(data) {
|
|
212
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
213
|
+
this.emit('job', {
|
|
214
|
+
type: 'jobETA',
|
|
215
|
+
projectId: data.jobID,
|
|
216
|
+
jobId: data.imgID || '',
|
|
217
|
+
etaSeconds: data.etaSeconds
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
}
|
|
157
221
|
handleJobResult(data) {
|
|
158
222
|
return __awaiter(this, void 0, void 0, function* () {
|
|
159
223
|
const project = this.projects.find((p) => p.id === data.jobID);
|
|
@@ -162,11 +226,22 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
162
226
|
// If NSFW filter is triggered, image will be only available for download if user explicitly
|
|
163
227
|
// disabled the filter for this project
|
|
164
228
|
if (passNSFWCheck && !data.userCanceled) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
229
|
+
// Use media endpoint for video models, image endpoint for image models
|
|
230
|
+
const isVideo = project && this.isVideoModelId(project.params.modelId);
|
|
231
|
+
if (isVideo) {
|
|
232
|
+
downloadUrl = yield this.mediaDownloadUrl({
|
|
233
|
+
jobId: data.jobID,
|
|
234
|
+
id: data.imgID,
|
|
235
|
+
type: 'complete'
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
downloadUrl = yield this.downloadUrl({
|
|
240
|
+
jobId: data.jobID,
|
|
241
|
+
imageId: data.imgID,
|
|
242
|
+
type: 'complete'
|
|
243
|
+
});
|
|
244
|
+
}
|
|
170
245
|
}
|
|
171
246
|
this.emit('job', {
|
|
172
247
|
type: 'completed',
|
|
@@ -238,7 +313,11 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
238
313
|
if (project.finished) {
|
|
239
314
|
// Sync project data with the server and remove it from the list after some time
|
|
240
315
|
project._syncToServer().catch((e) => {
|
|
241
|
-
|
|
316
|
+
// 404 errors are expected when project is still initializing
|
|
317
|
+
// Only log non-404 errors to avoid confusing users
|
|
318
|
+
if (e.status !== 404) {
|
|
319
|
+
this.client.logger.error(e);
|
|
320
|
+
}
|
|
242
321
|
});
|
|
243
322
|
setTimeout(() => {
|
|
244
323
|
this.projects = this.projects.filter((p) => !p.finished);
|
|
@@ -246,6 +325,7 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
246
325
|
}
|
|
247
326
|
}
|
|
248
327
|
handleJobEvent(event) {
|
|
328
|
+
var _a, _b, _c;
|
|
249
329
|
let project = this.projects.find((p) => p.id === event.projectId);
|
|
250
330
|
if (!project) {
|
|
251
331
|
return;
|
|
@@ -257,7 +337,7 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
257
337
|
projectId: event.projectId,
|
|
258
338
|
status: 'pending',
|
|
259
339
|
step: 0,
|
|
260
|
-
stepCount: project.params.steps
|
|
340
|
+
stepCount: (_a = project.params.steps) !== null && _a !== void 0 ? _a : 0
|
|
261
341
|
});
|
|
262
342
|
}
|
|
263
343
|
switch (event.type) {
|
|
@@ -284,7 +364,7 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
284
364
|
case 'progress':
|
|
285
365
|
job._update({
|
|
286
366
|
status: 'processing',
|
|
287
|
-
//
|
|
367
|
+
// Just in case event comes out of order
|
|
288
368
|
step: Math.max(event.step, job.step),
|
|
289
369
|
stepCount: event.stepCount
|
|
290
370
|
});
|
|
@@ -292,6 +372,26 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
292
372
|
project._update({ status: 'processing' });
|
|
293
373
|
}
|
|
294
374
|
break;
|
|
375
|
+
case 'jobETA': {
|
|
376
|
+
const newEta = new Date(Date.now() + event.etaSeconds * 1000);
|
|
377
|
+
if (((_b = job.eta) === null || _b === void 0 ? void 0 : _b.getTime()) !== (newEta === null || newEta === void 0 ? void 0 : newEta.getTime())) {
|
|
378
|
+
job._update({ eta: newEta });
|
|
379
|
+
const maxEta = project.jobs.reduce((max, j) => { var _a; return Math.max(max, ((_a = j.eta) === null || _a === void 0 ? void 0 : _a.getTime()) || 0); }, 0);
|
|
380
|
+
const projectETA = maxEta ? new Date(maxEta) : undefined;
|
|
381
|
+
if (((_c = project.eta) === null || _c === void 0 ? void 0 : _c.getTime()) !== (projectETA === null || projectETA === void 0 ? void 0 : projectETA.getTime())) {
|
|
382
|
+
project._update({ eta: projectETA });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
// ETA updates keep the project alive (refreshes lastUpdated) and store the ETA value.
|
|
387
|
+
// This is critical for long-running jobs like video generation that can take several
|
|
388
|
+
// minutes and may not send frequent progress updates.
|
|
389
|
+
// We call _keepAlive() directly to ensure lastUpdated is refreshed even if the ETA
|
|
390
|
+
// value hasn't changed (job._update only emits 'updated' when values actually change).
|
|
391
|
+
project._keepAlive();
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
295
395
|
case 'preview':
|
|
296
396
|
job._update({ previewUrl: event.url });
|
|
297
397
|
break;
|
|
@@ -308,6 +408,17 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
308
408
|
}
|
|
309
409
|
case 'error':
|
|
310
410
|
job._update({ status: 'failed', error: event.error });
|
|
411
|
+
// Check if project should also fail when a job fails
|
|
412
|
+
// For video jobs (single image) or when all jobs have failed, propagate to project
|
|
413
|
+
const allJobsStarted = project.jobs.length >= project.params.numberOfMedia;
|
|
414
|
+
const allJobsFailed = allJobsStarted && project.jobs.every((j) => j.status === 'failed');
|
|
415
|
+
const isSingleJobProject = project.params.numberOfMedia === 1;
|
|
416
|
+
if (isSingleJobProject || allJobsFailed) {
|
|
417
|
+
project._update({
|
|
418
|
+
status: 'failed',
|
|
419
|
+
error: event.error
|
|
420
|
+
});
|
|
421
|
+
}
|
|
311
422
|
break;
|
|
312
423
|
}
|
|
313
424
|
}
|
|
@@ -328,41 +439,67 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
328
439
|
return Promise.resolve(this._availableModels);
|
|
329
440
|
}
|
|
330
441
|
return new Promise((resolve, reject) => {
|
|
442
|
+
let settled = false;
|
|
331
443
|
const timeoutId = setTimeout(() => {
|
|
332
|
-
|
|
444
|
+
if (!settled) {
|
|
445
|
+
settled = true;
|
|
446
|
+
this.off('availableModels', handler);
|
|
447
|
+
reject(new Error('Timeout waiting for models'));
|
|
448
|
+
}
|
|
333
449
|
}, timeout);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
450
|
+
const handler = (models) => {
|
|
451
|
+
// Only resolve when we get a non-empty models list
|
|
452
|
+
// Empty arrays may be emitted during disconnects/reconnects
|
|
453
|
+
if (models.length && !settled) {
|
|
454
|
+
settled = true;
|
|
455
|
+
clearTimeout(timeoutId);
|
|
456
|
+
this.off('availableModels', handler);
|
|
337
457
|
resolve(models);
|
|
338
458
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
});
|
|
459
|
+
};
|
|
460
|
+
this.on('availableModels', handler);
|
|
343
461
|
});
|
|
344
462
|
}
|
|
345
463
|
/**
|
|
346
464
|
* Send new project request to the network. Returns project instance which can be used to track
|
|
347
|
-
* progress and get resulting images.
|
|
465
|
+
* progress and get resulting images or videos.
|
|
348
466
|
* @param data
|
|
349
467
|
*/
|
|
350
468
|
create(data) {
|
|
351
469
|
return __awaiter(this, void 0, void 0, function* () {
|
|
352
|
-
var _a, _b;
|
|
353
470
|
const project = new Project_1.default(Object.assign({}, data), { api: this, logger: this.client.logger });
|
|
471
|
+
const request = (0, createJobRequestMessage_1.default)(project.id, data);
|
|
472
|
+
switch (data.type) {
|
|
473
|
+
case 'image':
|
|
474
|
+
yield this._processImageAssets(project, data);
|
|
475
|
+
break;
|
|
476
|
+
case 'video':
|
|
477
|
+
yield this._processVideoAssets(project, data);
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
yield this.client.socket.send('jobRequest', request);
|
|
481
|
+
this.projects.push(project);
|
|
482
|
+
return project;
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
_processImageAssets(project, data) {
|
|
486
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
487
|
+
var _a, _b;
|
|
488
|
+
//Guide image
|
|
354
489
|
if (data.startingImage && data.startingImage !== true) {
|
|
355
490
|
yield this.uploadGuideImage(project.id, data.startingImage);
|
|
356
491
|
}
|
|
492
|
+
// ControlNet image
|
|
357
493
|
if (((_a = data.controlNet) === null || _a === void 0 ? void 0 : _a.image) && data.controlNet.image !== true) {
|
|
358
494
|
yield this.uploadCNImage(project.id, data.controlNet.image);
|
|
359
495
|
}
|
|
496
|
+
// Context images (Flux.2 Dev, Qwen Image Edit Plus support up to 3; Flux Kontext supports up to 2)
|
|
360
497
|
if ((_b = data.contextImages) === null || _b === void 0 ? void 0 : _b.length) {
|
|
361
|
-
if (data.contextImages.length >
|
|
498
|
+
if (data.contextImages.length > 3) {
|
|
362
499
|
throw new ApiClient_1.ApiError(500, {
|
|
363
500
|
status: 'error',
|
|
364
501
|
errorCode: 0,
|
|
365
|
-
message: `Up to
|
|
502
|
+
message: `Up to 3 context images are supported`
|
|
366
503
|
});
|
|
367
504
|
}
|
|
368
505
|
yield Promise.all(data.contextImages.map((image, index) => {
|
|
@@ -371,10 +508,22 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
371
508
|
}
|
|
372
509
|
}));
|
|
373
510
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
_processVideoAssets(project, data) {
|
|
514
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
515
|
+
if ((data === null || data === void 0 ? void 0 : data.referenceImage) && data.referenceImage !== true) {
|
|
516
|
+
yield this.uploadReferenceImage(project.id, data.referenceImage);
|
|
517
|
+
}
|
|
518
|
+
if ((data === null || data === void 0 ? void 0 : data.referenceImageEnd) && data.referenceImageEnd !== true) {
|
|
519
|
+
yield this.uploadReferenceImageEnd(project.id, data.referenceImageEnd);
|
|
520
|
+
}
|
|
521
|
+
if ((data === null || data === void 0 ? void 0 : data.referenceAudio) && data.referenceAudio !== true) {
|
|
522
|
+
yield this.uploadReferenceAudio(project.id, data.referenceAudio);
|
|
523
|
+
}
|
|
524
|
+
if ((data === null || data === void 0 ? void 0 : data.referenceVideo) && data.referenceVideo !== true) {
|
|
525
|
+
yield this.uploadReferenceVideo(project.id, data.referenceVideo);
|
|
526
|
+
}
|
|
378
527
|
});
|
|
379
528
|
}
|
|
380
529
|
/**
|
|
@@ -425,13 +574,13 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
425
574
|
return __awaiter(this, void 0, void 0, function* () {
|
|
426
575
|
const imageId = (0, getUUID_1.default)();
|
|
427
576
|
const presignedUrl = yield this.uploadUrl({
|
|
428
|
-
imageId
|
|
577
|
+
imageId,
|
|
429
578
|
jobId: projectId,
|
|
430
579
|
type: 'startingImage'
|
|
431
580
|
});
|
|
432
581
|
const res = yield fetch(presignedUrl, {
|
|
433
582
|
method: 'PUT',
|
|
434
|
-
body: file
|
|
583
|
+
body: toFetchBody(file)
|
|
435
584
|
});
|
|
436
585
|
if (!res.ok) {
|
|
437
586
|
throw new ApiClient_1.ApiError(res.status, {
|
|
@@ -447,13 +596,13 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
447
596
|
return __awaiter(this, void 0, void 0, function* () {
|
|
448
597
|
const imageId = (0, getUUID_1.default)();
|
|
449
598
|
const presignedUrl = yield this.uploadUrl({
|
|
450
|
-
imageId
|
|
599
|
+
imageId,
|
|
451
600
|
jobId: projectId,
|
|
452
601
|
type: 'cnImage'
|
|
453
602
|
});
|
|
454
603
|
const res = yield fetch(presignedUrl, {
|
|
455
604
|
method: 'PUT',
|
|
456
|
-
body: file
|
|
605
|
+
body: toFetchBody(file)
|
|
457
606
|
});
|
|
458
607
|
if (!res.ok) {
|
|
459
608
|
throw new ApiClient_1.ApiError(res.status, {
|
|
@@ -476,7 +625,7 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
476
625
|
});
|
|
477
626
|
const res = yield fetch(presignedUrl, {
|
|
478
627
|
method: 'PUT',
|
|
479
|
-
body: file
|
|
628
|
+
body: toFetchBody(file)
|
|
480
629
|
});
|
|
481
630
|
if (!res.ok) {
|
|
482
631
|
throw new ApiClient_1.ApiError(res.status, {
|
|
@@ -488,14 +637,132 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
488
637
|
return imageId;
|
|
489
638
|
});
|
|
490
639
|
}
|
|
640
|
+
// ============================================
|
|
641
|
+
// VIDEO WORKFLOW UPLOADS (WAN 2.2)
|
|
642
|
+
// ============================================
|
|
491
643
|
/**
|
|
492
|
-
*
|
|
644
|
+
* Upload reference image for WAN video workflows
|
|
645
|
+
* @internal
|
|
646
|
+
*/
|
|
647
|
+
uploadReferenceImage(projectId, file) {
|
|
648
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
649
|
+
const imageId = (0, getUUID_1.default)();
|
|
650
|
+
const presignedUrl = yield this.uploadUrl({
|
|
651
|
+
imageId,
|
|
652
|
+
jobId: projectId,
|
|
653
|
+
type: 'referenceImage'
|
|
654
|
+
});
|
|
655
|
+
const res = yield fetch(presignedUrl, {
|
|
656
|
+
method: 'PUT',
|
|
657
|
+
body: toFetchBody(file)
|
|
658
|
+
});
|
|
659
|
+
if (!res.ok) {
|
|
660
|
+
throw new ApiClient_1.ApiError(res.status, {
|
|
661
|
+
status: 'error',
|
|
662
|
+
errorCode: 0,
|
|
663
|
+
message: 'Failed to upload reference image'
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
return imageId;
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Upload reference image end for i2v interpolation
|
|
671
|
+
* @internal
|
|
672
|
+
*/
|
|
673
|
+
uploadReferenceImageEnd(projectId, file) {
|
|
674
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
675
|
+
const imageId = (0, getUUID_1.default)();
|
|
676
|
+
const presignedUrl = yield this.uploadUrl({
|
|
677
|
+
imageId,
|
|
678
|
+
jobId: projectId,
|
|
679
|
+
type: 'referenceImageEnd'
|
|
680
|
+
});
|
|
681
|
+
const res = yield fetch(presignedUrl, {
|
|
682
|
+
method: 'PUT',
|
|
683
|
+
body: toFetchBody(file)
|
|
684
|
+
});
|
|
685
|
+
if (!res.ok) {
|
|
686
|
+
throw new ApiClient_1.ApiError(res.status, {
|
|
687
|
+
status: 'error',
|
|
688
|
+
errorCode: 0,
|
|
689
|
+
message: 'Failed to upload reference image end'
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
return imageId;
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Upload reference audio for s2v workflows
|
|
697
|
+
* Supported formats: mp3, m4a, wav
|
|
698
|
+
* @internal
|
|
699
|
+
*/
|
|
700
|
+
uploadReferenceAudio(projectId, file) {
|
|
701
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
702
|
+
const contentType = getFileContentType(file);
|
|
703
|
+
const presignedUrl = yield this.mediaUploadUrl({
|
|
704
|
+
jobId: projectId,
|
|
705
|
+
type: 'referenceAudio'
|
|
706
|
+
});
|
|
707
|
+
const headers = {};
|
|
708
|
+
if (contentType) {
|
|
709
|
+
headers['Content-Type'] = contentType;
|
|
710
|
+
}
|
|
711
|
+
const res = yield fetch(presignedUrl, {
|
|
712
|
+
method: 'PUT',
|
|
713
|
+
body: toFetchBody(file),
|
|
714
|
+
headers
|
|
715
|
+
});
|
|
716
|
+
if (!res.ok) {
|
|
717
|
+
throw new ApiClient_1.ApiError(res.status, {
|
|
718
|
+
status: 'error',
|
|
719
|
+
errorCode: 0,
|
|
720
|
+
message: 'Failed to upload reference audio'
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Upload reference video for animate workflows
|
|
727
|
+
* Supported formats: mp4, mov
|
|
728
|
+
* @internal
|
|
729
|
+
*/
|
|
730
|
+
uploadReferenceVideo(projectId, file) {
|
|
731
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
732
|
+
const contentType = getFileContentType(file);
|
|
733
|
+
const presignedUrl = yield this.mediaUploadUrl({
|
|
734
|
+
jobId: projectId,
|
|
735
|
+
type: 'referenceVideo'
|
|
736
|
+
});
|
|
737
|
+
const headers = {};
|
|
738
|
+
if (contentType) {
|
|
739
|
+
headers['Content-Type'] = contentType;
|
|
740
|
+
}
|
|
741
|
+
const res = yield fetch(presignedUrl, {
|
|
742
|
+
method: 'PUT',
|
|
743
|
+
body: toFetchBody(file),
|
|
744
|
+
headers
|
|
745
|
+
});
|
|
746
|
+
if (!res.ok) {
|
|
747
|
+
throw new ApiClient_1.ApiError(res.status, {
|
|
748
|
+
status: 'error',
|
|
749
|
+
errorCode: 0,
|
|
750
|
+
message: 'Failed to upload reference video'
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
// ============================================
|
|
756
|
+
// COST ESTIMATION
|
|
757
|
+
// ============================================
|
|
758
|
+
/**
|
|
759
|
+
* Estimate image project cost
|
|
493
760
|
*/
|
|
494
761
|
estimateCost(_a) {
|
|
495
|
-
return __awaiter(this, arguments, void 0, function* ({ network, tokenType, model, imageCount, stepCount, previewCount, cnEnabled, startingImageStrength, width, height, sizePreset, guidance,
|
|
762
|
+
return __awaiter(this, arguments, void 0, function* ({ network, tokenType, model, imageCount, stepCount, previewCount, cnEnabled, startingImageStrength, width, height, sizePreset, guidance, sampler, contextImages }) {
|
|
496
763
|
let apiVersion = 2;
|
|
497
764
|
const pathParams = [
|
|
498
|
-
tokenType || '
|
|
765
|
+
tokenType || 'spark',
|
|
499
766
|
network,
|
|
500
767
|
model,
|
|
501
768
|
imageCount,
|
|
@@ -518,21 +785,28 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
518
785
|
else {
|
|
519
786
|
pathParams.push(0, 0);
|
|
520
787
|
}
|
|
521
|
-
if (
|
|
788
|
+
if (sampler) {
|
|
522
789
|
apiVersion = 3;
|
|
523
790
|
pathParams.push(guidance || 0);
|
|
524
|
-
pathParams.push(
|
|
791
|
+
pathParams.push((0, validation_1.validateSampler)(sampler));
|
|
525
792
|
pathParams.push(contextImages || 0);
|
|
526
793
|
}
|
|
527
794
|
const r = yield this.client.socket.get(`/api/v${apiVersion}/job/estimate/${pathParams.join('/')}`);
|
|
528
795
|
return {
|
|
529
796
|
token: r.quote.project.costInToken,
|
|
530
|
-
usd: r.quote.project.costInUSD
|
|
797
|
+
usd: r.quote.project.costInUSD,
|
|
798
|
+
spark: r.quote.project.costInSpark,
|
|
799
|
+
sogni: r.quote.project.costInSogni
|
|
531
800
|
};
|
|
532
801
|
});
|
|
533
802
|
}
|
|
803
|
+
/**
|
|
804
|
+
* Estimate image enhancement cost
|
|
805
|
+
* @param strength
|
|
806
|
+
* @param tokenType
|
|
807
|
+
*/
|
|
534
808
|
estimateEnhancementCost(strength_1) {
|
|
535
|
-
return __awaiter(this, arguments, void 0, function* (strength, tokenType = '
|
|
809
|
+
return __awaiter(this, arguments, void 0, function* (strength, tokenType = 'spark') {
|
|
536
810
|
return this.estimateCost({
|
|
537
811
|
network: Job_1.enhancementDefaults.network,
|
|
538
812
|
tokenType,
|
|
@@ -545,10 +819,50 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
545
819
|
});
|
|
546
820
|
});
|
|
547
821
|
}
|
|
822
|
+
/**
|
|
823
|
+
* Estimates the cost of generating a video based on the provided parameters.
|
|
824
|
+
*
|
|
825
|
+
* @param {VideoEstimateRequest} params - The parameters required for video cost estimation. This includes:
|
|
826
|
+
* - tokenType: The type of token to be used for generation.
|
|
827
|
+
* - model: The model to be used for video generation.
|
|
828
|
+
* - width: The width of the video in pixels.
|
|
829
|
+
* - height: The height of the video in pixels.
|
|
830
|
+
* - frames: The total number of frames in the video.
|
|
831
|
+
* - fps: The frames per second for the video.
|
|
832
|
+
* - steps: Number of steps.
|
|
833
|
+
* @return {Promise<Object>} Returns an object containing the estimated costs for the video in different units:
|
|
834
|
+
* - token: Cost in tokens.
|
|
835
|
+
* - usd: Cost in USD.
|
|
836
|
+
* - spark: Cost in Spark.
|
|
837
|
+
* - sogni: Cost in Sogni.
|
|
838
|
+
*/
|
|
839
|
+
estimateVideoCost(params) {
|
|
840
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
841
|
+
const pathParams = [
|
|
842
|
+
params.tokenType,
|
|
843
|
+
params.model,
|
|
844
|
+
params.width,
|
|
845
|
+
params.height,
|
|
846
|
+
params.frames,
|
|
847
|
+
params.fps,
|
|
848
|
+
params.steps
|
|
849
|
+
];
|
|
850
|
+
const path = pathParams.map((p) => encodeURIComponent(p)).join('/');
|
|
851
|
+
const r = yield this.client.socket.get(`/api/v1/job-video/estimate/${path}`);
|
|
852
|
+
return {
|
|
853
|
+
token: r.quote.project.costInToken,
|
|
854
|
+
usd: r.quote.project.costInUSD,
|
|
855
|
+
spark: r.quote.project.costInSpark,
|
|
856
|
+
sogni: r.quote.project.costInSogni
|
|
857
|
+
};
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
// ============================================
|
|
861
|
+
// URL HELPERS
|
|
862
|
+
// ============================================
|
|
548
863
|
/**
|
|
549
864
|
* Get upload URL for image
|
|
550
865
|
* @internal
|
|
551
|
-
* @param params
|
|
552
866
|
*/
|
|
553
867
|
uploadUrl(params) {
|
|
554
868
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -559,7 +873,6 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
559
873
|
/**
|
|
560
874
|
* Get download URL for image
|
|
561
875
|
* @internal
|
|
562
|
-
* @param params
|
|
563
876
|
*/
|
|
564
877
|
downloadUrl(params) {
|
|
565
878
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -567,6 +880,29 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
567
880
|
return r.data.downloadUrl;
|
|
568
881
|
});
|
|
569
882
|
}
|
|
883
|
+
/**
|
|
884
|
+
* Get upload URL for media (video/audio)
|
|
885
|
+
* @internal
|
|
886
|
+
*/
|
|
887
|
+
mediaUploadUrl(params) {
|
|
888
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
889
|
+
const r = yield this.client.rest.get(`/v1/media/uploadUrl`, params);
|
|
890
|
+
return r.data.uploadUrl;
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Get download URL for media (video/audio)
|
|
895
|
+
* @internal
|
|
896
|
+
*/
|
|
897
|
+
mediaDownloadUrl(params) {
|
|
898
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
899
|
+
const r = yield this.client.rest.get(`/v1/media/downloadUrl`, params);
|
|
900
|
+
return r.data.downloadUrl;
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
// ============================================
|
|
904
|
+
// MODEL/PRESET HELPERS
|
|
905
|
+
// ============================================
|
|
570
906
|
getSupportedModels() {
|
|
571
907
|
return __awaiter(this, arguments, void 0, function* (forceRefresh = false) {
|
|
572
908
|
if (this._supportedModels.data &&
|
|
@@ -584,7 +920,7 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
584
920
|
*
|
|
585
921
|
* @example
|
|
586
922
|
* ```ts
|
|
587
|
-
* const presets = await
|
|
923
|
+
* const presets = await sogni.projects.getSizePresets('fast', 'flux1-schnell-fp8');
|
|
588
924
|
* console.log(presets);
|
|
589
925
|
* ```
|
|
590
926
|
*
|
|
@@ -612,6 +948,50 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
612
948
|
return data;
|
|
613
949
|
});
|
|
614
950
|
}
|
|
951
|
+
/**
|
|
952
|
+
* Retrieves the video asset configuration for a given video model identifier.
|
|
953
|
+
* Validates whether the provided model ID corresponds to a video model. If it does,
|
|
954
|
+
* returns the appropriate video asset configuration based on the workflow type.
|
|
955
|
+
*
|
|
956
|
+
* @example Returned object for a model that implements image to video workflow:
|
|
957
|
+
* ```json
|
|
958
|
+
* {
|
|
959
|
+
* "workflowType": "i2v",
|
|
960
|
+
* "assets": {
|
|
961
|
+
* "referenceImage": "required",
|
|
962
|
+
* "referenceImageEnd": "optional",
|
|
963
|
+
* "referenceAudio": "forbidden",
|
|
964
|
+
* "referenceVideo": "forbidden"
|
|
965
|
+
* }
|
|
966
|
+
* }
|
|
967
|
+
* ```
|
|
968
|
+
*
|
|
969
|
+
* @param {string} modelId - The identifier of the video model to retrieve the configuration for.
|
|
970
|
+
* @return {Object} The video asset configuration object where key is asset field and value is
|
|
971
|
+
* either `required`, `forbidden` or `optional`. Returns `null` if no rules defined for the model.
|
|
972
|
+
* @throws {ApiError} Throws an error if the provided model ID is not a video model.
|
|
973
|
+
*/
|
|
974
|
+
getVideoAssetConfig(modelId) {
|
|
975
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
976
|
+
if (!this.isVideoModelId(modelId)) {
|
|
977
|
+
throw new ApiClient_1.ApiError(400, {
|
|
978
|
+
status: 'error',
|
|
979
|
+
errorCode: 0,
|
|
980
|
+
message: `Model ${modelId} is not a video model`
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
const workflow = (0, utils_1.getVideoWorkflowType)(modelId);
|
|
984
|
+
if (!workflow) {
|
|
985
|
+
return {
|
|
986
|
+
workflowType: null
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
return {
|
|
990
|
+
workflowType: workflow,
|
|
991
|
+
assets: utils_1.VIDEO_WORKFLOW_ASSETS[workflow]
|
|
992
|
+
};
|
|
993
|
+
});
|
|
994
|
+
}
|
|
615
995
|
/**
|
|
616
996
|
* Get available models and their worker counts. Normally, you would get list once you connect
|
|
617
997
|
* to the server, but you can also call this method to get the list of available models manually.
|
|
@@ -627,7 +1007,8 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
627
1007
|
return {
|
|
628
1008
|
id: (model === null || model === void 0 ? void 0 : model.id) || sid,
|
|
629
1009
|
name: (model === null || model === void 0 ? void 0 : model.name) || sid.replace(/-/g, ' '),
|
|
630
|
-
workerCount
|
|
1010
|
+
workerCount,
|
|
1011
|
+
media: (model === null || model === void 0 ? void 0 : model.media) || 'image'
|
|
631
1012
|
};
|
|
632
1013
|
});
|
|
633
1014
|
});
|