@sogni-ai/sogni-client 4.0.0-alpha.4 → 4.0.0-alpha.41
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 +282 -0
- package/README.md +262 -31
- 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 +12 -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 +36 -8
- package/dist/Projects/Project.js.map +1 -1
- package/dist/Projects/createJobRequestMessage.js +161 -15
- package/dist/Projects/createJobRequestMessage.js.map +1 -1
- package/dist/Projects/index.d.ts +112 -11
- package/dist/Projects/index.js +478 -47
- package/dist/Projects/index.js.map +1 -1
- package/dist/Projects/types/ComfySamplerParams.d.ts +28 -0
- package/dist/Projects/types/ComfySamplerParams.js +36 -0
- package/dist/Projects/types/ComfySamplerParams.js.map +1 -0
- package/dist/Projects/types/ComfySchedulerParams.d.ts +17 -0
- package/dist/Projects/types/ComfySchedulerParams.js +23 -0
- package/dist/Projects/types/ComfySchedulerParams.js.map +1 -0
- package/dist/Projects/types/EstimationResponse.d.ts +2 -0
- package/dist/Projects/types/ForgeSamplerParams.d.ts +27 -0
- package/dist/Projects/types/ForgeSamplerParams.js +39 -0
- package/dist/Projects/types/ForgeSamplerParams.js.map +1 -0
- package/dist/Projects/types/ForgeSchedulerParams.d.ts +17 -0
- package/dist/Projects/types/ForgeSchedulerParams.js +28 -0
- package/dist/Projects/types/ForgeSchedulerParams.js.map +1 -0
- package/dist/Projects/types/events.d.ts +5 -1
- package/dist/Projects/types/index.d.ts +189 -40
- package/dist/Projects/types/index.js +17 -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 +14 -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/RestClient.js +15 -2
- package/dist/lib/RestClient.js.map +1 -1
- package/dist/lib/validation.d.ts +26 -2
- package/dist/lib/validation.js +96 -11
- 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 +14 -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 +44 -11
- package/src/Projects/createJobRequestMessage.ts +211 -37
- package/src/Projects/index.ts +507 -51
- package/src/Projects/types/ComfySamplerParams.ts +34 -0
- package/src/Projects/types/ComfySchedulerParams.ts +21 -0
- package/src/Projects/types/EstimationResponse.ts +2 -0
- package/src/Projects/types/ForgeSamplerParams.ts +37 -0
- package/src/Projects/types/ForgeSchedulerParams.ts +26 -0
- package/src/Projects/types/events.ts +6 -0
- package/src/Projects/types/index.ts +227 -41
- package/src/Projects/utils.ts +66 -1
- package/src/index.ts +60 -8
- package/src/lib/AuthManager/TokenAuthManager.ts +0 -2
- package/src/lib/DataEntity.ts +4 -2
- package/src/lib/RestClient.ts +16 -2
- package/src/lib/validation.ts +111 -14
- package/dist/Projects/types/SamplerParams.d.ts +0 -15
- package/dist/Projects/types/SamplerParams.js +0 -21
- package/dist/Projects/types/SamplerParams.js.map +0 -1
- package/dist/Projects/types/SchedulerParams.d.ts +0 -13
- package/dist/Projects/types/SchedulerParams.js +0 -19
- package/dist/Projects/types/SchedulerParams.js.map +0 -1
- package/src/Projects/types/SamplerParams.ts +0 -19
- package/src/Projects/types/SchedulerParams.ts +0 -17
package/dist/Projects/index.js
CHANGED
|
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
const ApiGroup_1 = __importDefault(require("../ApiGroup"));
|
|
16
|
+
const types_1 = require("./types");
|
|
16
17
|
const Project_1 = __importDefault(require("./Project"));
|
|
17
18
|
const createJobRequestMessage_1 = __importDefault(require("./createJobRequestMessage"));
|
|
18
19
|
const ApiClient_1 = require("../ApiClient");
|
|
@@ -20,9 +21,37 @@ const getUUID_1 = __importDefault(require("../lib/getUUID"));
|
|
|
20
21
|
const Cache_1 = __importDefault(require("../lib/Cache"));
|
|
21
22
|
const Job_1 = require("./Job");
|
|
22
23
|
const utils_1 = require("./utils");
|
|
24
|
+
const validation_1 = require("../lib/validation");
|
|
23
25
|
const sizePresetCache = new Cache_1.default(10 * 60 * 1000);
|
|
24
26
|
const GARBAGE_COLLECT_TIMEOUT = 30000;
|
|
25
27
|
const MODELS_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 24 hours
|
|
28
|
+
/**
|
|
29
|
+
* Detect content type from a file object.
|
|
30
|
+
* For File objects in browser, uses the type property.
|
|
31
|
+
* Returns undefined if content type cannot be detected.
|
|
32
|
+
*/
|
|
33
|
+
function getFileContentType(file) {
|
|
34
|
+
if (file instanceof Blob && 'type' in file && file.type) {
|
|
35
|
+
return file.type;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Convert file to a format compatible with fetch body.
|
|
41
|
+
* Converts Node.js Buffer to Blob for cross-platform compatibility.
|
|
42
|
+
*/
|
|
43
|
+
function toFetchBody(file) {
|
|
44
|
+
// Node.js Buffer is not supported in browsers, so we can skip this conversion
|
|
45
|
+
if (typeof Buffer === 'undefined') {
|
|
46
|
+
return file;
|
|
47
|
+
}
|
|
48
|
+
if (Buffer.isBuffer(file)) {
|
|
49
|
+
// Copy Buffer data to a new ArrayBuffer to ensure type compatibility
|
|
50
|
+
const arrayBuffer = file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength);
|
|
51
|
+
return new Blob([arrayBuffer]);
|
|
52
|
+
}
|
|
53
|
+
return file;
|
|
54
|
+
}
|
|
26
55
|
function mapErrorCodes(code) {
|
|
27
56
|
switch (code) {
|
|
28
57
|
case 'serverRestarting':
|
|
@@ -43,6 +72,20 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
43
72
|
get availableModels() {
|
|
44
73
|
return this._availableModels;
|
|
45
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if a model produces video output using the cached models list.
|
|
77
|
+
* Uses the `media` property from the models API when available,
|
|
78
|
+
* falls back to model ID prefix check if models aren't loaded yet.
|
|
79
|
+
*/
|
|
80
|
+
isVideoModelId(modelId) {
|
|
81
|
+
var _a;
|
|
82
|
+
const model = (_a = this._supportedModels.data) === null || _a === void 0 ? void 0 : _a.find((m) => m.id === modelId);
|
|
83
|
+
if (model) {
|
|
84
|
+
return model.media === 'video';
|
|
85
|
+
}
|
|
86
|
+
// Fallback to prefix check if models not loaded
|
|
87
|
+
return (0, utils_1.isVideoModel)(modelId);
|
|
88
|
+
}
|
|
46
89
|
constructor(config) {
|
|
47
90
|
super(config);
|
|
48
91
|
this._availableModels = [];
|
|
@@ -56,14 +99,29 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
56
99
|
this.client.socket.on('swarmModels', this.handleSwarmModels.bind(this));
|
|
57
100
|
this.client.socket.on('jobState', this.handleJobState.bind(this));
|
|
58
101
|
this.client.socket.on('jobProgress', this.handleJobProgress.bind(this));
|
|
102
|
+
this.client.socket.on('jobETA', this.handleJobETA.bind(this));
|
|
59
103
|
this.client.socket.on('jobError', this.handleJobError.bind(this));
|
|
60
|
-
this.client.socket.on('jobResult',
|
|
104
|
+
this.client.socket.on('jobResult', (data) => {
|
|
105
|
+
this.handleJobResult(data).catch((err) => {
|
|
106
|
+
this.client.logger.error('Error in handleJobResult:', err);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
61
109
|
// Listen to the server disconnect event
|
|
62
110
|
this.client.on('disconnected', this.handleServerDisconnected.bind(this));
|
|
63
111
|
// Listen to project and job events and update project and job instances
|
|
64
112
|
this.on('project', this.handleProjectEvent.bind(this));
|
|
65
113
|
this.on('job', this.handleJobEvent.bind(this));
|
|
66
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Retrieves a list of projects created and tracked by this SogniClient instance.
|
|
117
|
+
*
|
|
118
|
+
* Note: When a project is finished, it will be removed from this list after 30 seconds
|
|
119
|
+
*
|
|
120
|
+
* @return {Array} A copy of the array containing the tracked projects.
|
|
121
|
+
*/
|
|
122
|
+
get trackedProjects() {
|
|
123
|
+
return this.projects.slice(0);
|
|
124
|
+
}
|
|
67
125
|
handleChangeNetwork() {
|
|
68
126
|
this._availableModels = [];
|
|
69
127
|
this.emit('availableModels', this._availableModels);
|
|
@@ -82,11 +140,12 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
82
140
|
return acc;
|
|
83
141
|
}, {});
|
|
84
142
|
this._availableModels = Object.entries(data).map(([id, workerCount]) => {
|
|
85
|
-
var _a;
|
|
143
|
+
var _a, _b;
|
|
86
144
|
return ({
|
|
87
145
|
id,
|
|
88
146
|
name: ((_a = modelIndex[id]) === null || _a === void 0 ? void 0 : _a.name) || id.replace(/-/g, ' '),
|
|
89
|
-
workerCount
|
|
147
|
+
workerCount,
|
|
148
|
+
media: ((_b = modelIndex[id]) === null || _b === void 0 ? void 0 : _b.media) || 'image'
|
|
90
149
|
});
|
|
91
150
|
});
|
|
92
151
|
this.emit('availableModels', this._availableModels);
|
|
@@ -154,20 +213,60 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
154
213
|
}
|
|
155
214
|
});
|
|
156
215
|
}
|
|
216
|
+
handleJobETA(data) {
|
|
217
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
218
|
+
this.emit('job', {
|
|
219
|
+
type: 'jobETA',
|
|
220
|
+
projectId: data.jobID,
|
|
221
|
+
jobId: data.imgID || '',
|
|
222
|
+
etaSeconds: data.etaSeconds
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
}
|
|
157
226
|
handleJobResult(data) {
|
|
158
227
|
return __awaiter(this, void 0, void 0, function* () {
|
|
159
228
|
const project = this.projects.find((p) => p.id === data.jobID);
|
|
160
229
|
const passNSFWCheck = !data.triggeredNSFWFilter || !project || project.params.disableNSFWFilter;
|
|
161
|
-
let downloadUrl = null;
|
|
162
|
-
// If
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
230
|
+
let downloadUrl = data.resultUrl || null; // Use resultUrl from event if provided
|
|
231
|
+
// If no resultUrl provided and NSFW check passes, generate download URL
|
|
232
|
+
if (!downloadUrl && passNSFWCheck && !data.userCanceled) {
|
|
233
|
+
// Use media endpoint for video models, image endpoint for image models
|
|
234
|
+
const isVideo = project && this.isVideoModelId(project.params.modelId);
|
|
235
|
+
try {
|
|
236
|
+
if (isVideo) {
|
|
237
|
+
downloadUrl = yield this.mediaDownloadUrl({
|
|
238
|
+
jobId: data.jobID,
|
|
239
|
+
id: data.imgID,
|
|
240
|
+
type: 'complete'
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
downloadUrl = yield this.downloadUrl({
|
|
245
|
+
jobId: data.jobID,
|
|
246
|
+
imageId: data.imgID,
|
|
247
|
+
type: 'complete'
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
// Continue with null downloadUrl - the event will indicate failure
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Update the job directly with the result URL to prevent duplicate API calls
|
|
256
|
+
if (project) {
|
|
257
|
+
const job = project.job(data.imgID);
|
|
258
|
+
if (job) {
|
|
259
|
+
job._update({
|
|
260
|
+
status: data.userCanceled ? 'canceled' : 'completed',
|
|
261
|
+
step: data.performedStepCount,
|
|
262
|
+
seed: Number(data.lastSeed),
|
|
263
|
+
resultUrl: downloadUrl,
|
|
264
|
+
isNSFW: data.triggeredNSFWFilter,
|
|
265
|
+
userCanceled: data.userCanceled
|
|
266
|
+
});
|
|
267
|
+
}
|
|
170
268
|
}
|
|
269
|
+
// Emit job completion event with the generated download URL
|
|
171
270
|
this.emit('job', {
|
|
172
271
|
type: 'completed',
|
|
173
272
|
projectId: data.jobID,
|
|
@@ -238,7 +337,11 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
238
337
|
if (project.finished) {
|
|
239
338
|
// Sync project data with the server and remove it from the list after some time
|
|
240
339
|
project._syncToServer().catch((e) => {
|
|
241
|
-
|
|
340
|
+
// 404 errors are expected when project is still initializing
|
|
341
|
+
// Only log non-404 errors to avoid confusing users
|
|
342
|
+
if (e.status !== 404) {
|
|
343
|
+
this.client.logger.error(e);
|
|
344
|
+
}
|
|
242
345
|
});
|
|
243
346
|
setTimeout(() => {
|
|
244
347
|
this.projects = this.projects.filter((p) => !p.finished);
|
|
@@ -246,6 +349,7 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
246
349
|
}
|
|
247
350
|
}
|
|
248
351
|
handleJobEvent(event) {
|
|
352
|
+
var _a, _b, _c;
|
|
249
353
|
let project = this.projects.find((p) => p.id === event.projectId);
|
|
250
354
|
if (!project) {
|
|
251
355
|
return;
|
|
@@ -257,7 +361,7 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
257
361
|
projectId: event.projectId,
|
|
258
362
|
status: 'pending',
|
|
259
363
|
step: 0,
|
|
260
|
-
stepCount: project.params.steps
|
|
364
|
+
stepCount: (_a = project.params.steps) !== null && _a !== void 0 ? _a : 0
|
|
261
365
|
});
|
|
262
366
|
}
|
|
263
367
|
switch (event.type) {
|
|
@@ -284,7 +388,7 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
284
388
|
case 'progress':
|
|
285
389
|
job._update({
|
|
286
390
|
status: 'processing',
|
|
287
|
-
//
|
|
391
|
+
// Just in case event comes out of order
|
|
288
392
|
step: Math.max(event.step, job.step),
|
|
289
393
|
stepCount: event.stepCount
|
|
290
394
|
});
|
|
@@ -292,6 +396,26 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
292
396
|
project._update({ status: 'processing' });
|
|
293
397
|
}
|
|
294
398
|
break;
|
|
399
|
+
case 'jobETA': {
|
|
400
|
+
const newEta = new Date(Date.now() + event.etaSeconds * 1000);
|
|
401
|
+
if (((_b = job.eta) === null || _b === void 0 ? void 0 : _b.getTime()) !== (newEta === null || newEta === void 0 ? void 0 : newEta.getTime())) {
|
|
402
|
+
job._update({ eta: newEta });
|
|
403
|
+
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);
|
|
404
|
+
const projectETA = maxEta ? new Date(maxEta) : undefined;
|
|
405
|
+
if (((_c = project.eta) === null || _c === void 0 ? void 0 : _c.getTime()) !== (projectETA === null || projectETA === void 0 ? void 0 : projectETA.getTime())) {
|
|
406
|
+
project._update({ eta: projectETA });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
// ETA updates keep the project alive (refreshes lastUpdated) and store the ETA value.
|
|
411
|
+
// This is critical for long-running jobs like video generation that can take several
|
|
412
|
+
// minutes and may not send frequent progress updates.
|
|
413
|
+
// We call _keepAlive() directly to ensure lastUpdated is refreshed even if the ETA
|
|
414
|
+
// value hasn't changed (job._update only emits 'updated' when values actually change).
|
|
415
|
+
project._keepAlive();
|
|
416
|
+
}
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
295
419
|
case 'preview':
|
|
296
420
|
job._update({ previewUrl: event.url });
|
|
297
421
|
break;
|
|
@@ -308,6 +432,17 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
308
432
|
}
|
|
309
433
|
case 'error':
|
|
310
434
|
job._update({ status: 'failed', error: event.error });
|
|
435
|
+
// Check if project should also fail when a job fails
|
|
436
|
+
// For video jobs (single image) or when all jobs have failed, propagate to project
|
|
437
|
+
const allJobsStarted = project.jobs.length >= project.params.numberOfMedia;
|
|
438
|
+
const allJobsFailed = allJobsStarted && project.jobs.every((j) => j.status === 'failed');
|
|
439
|
+
const isSingleJobProject = project.params.numberOfMedia === 1;
|
|
440
|
+
if (isSingleJobProject || allJobsFailed) {
|
|
441
|
+
project._update({
|
|
442
|
+
status: 'failed',
|
|
443
|
+
error: event.error
|
|
444
|
+
});
|
|
445
|
+
}
|
|
311
446
|
break;
|
|
312
447
|
}
|
|
313
448
|
}
|
|
@@ -328,41 +463,67 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
328
463
|
return Promise.resolve(this._availableModels);
|
|
329
464
|
}
|
|
330
465
|
return new Promise((resolve, reject) => {
|
|
466
|
+
let settled = false;
|
|
331
467
|
const timeoutId = setTimeout(() => {
|
|
332
|
-
|
|
468
|
+
if (!settled) {
|
|
469
|
+
settled = true;
|
|
470
|
+
this.off('availableModels', handler);
|
|
471
|
+
reject(new Error('Timeout waiting for models'));
|
|
472
|
+
}
|
|
333
473
|
}, timeout);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
474
|
+
const handler = (models) => {
|
|
475
|
+
// Only resolve when we get a non-empty models list
|
|
476
|
+
// Empty arrays may be emitted during disconnects/reconnects
|
|
477
|
+
if (models.length && !settled) {
|
|
478
|
+
settled = true;
|
|
479
|
+
clearTimeout(timeoutId);
|
|
480
|
+
this.off('availableModels', handler);
|
|
337
481
|
resolve(models);
|
|
338
482
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
});
|
|
483
|
+
};
|
|
484
|
+
this.on('availableModels', handler);
|
|
343
485
|
});
|
|
344
486
|
}
|
|
345
487
|
/**
|
|
346
488
|
* Send new project request to the network. Returns project instance which can be used to track
|
|
347
|
-
* progress and get resulting images.
|
|
489
|
+
* progress and get resulting images or videos.
|
|
348
490
|
* @param data
|
|
349
491
|
*/
|
|
350
492
|
create(data) {
|
|
351
493
|
return __awaiter(this, void 0, void 0, function* () {
|
|
352
|
-
var _a, _b;
|
|
353
494
|
const project = new Project_1.default(Object.assign({}, data), { api: this, logger: this.client.logger });
|
|
495
|
+
const request = (0, createJobRequestMessage_1.default)(project.id, data);
|
|
496
|
+
switch (data.type) {
|
|
497
|
+
case 'image':
|
|
498
|
+
yield this._processImageAssets(project, data);
|
|
499
|
+
break;
|
|
500
|
+
case 'video':
|
|
501
|
+
yield this._processVideoAssets(project, data);
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
yield this.client.socket.send('jobRequest', request);
|
|
505
|
+
this.projects.push(project);
|
|
506
|
+
return project;
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
_processImageAssets(project, data) {
|
|
510
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
511
|
+
var _a, _b;
|
|
512
|
+
//Guide image
|
|
354
513
|
if (data.startingImage && data.startingImage !== true) {
|
|
355
514
|
yield this.uploadGuideImage(project.id, data.startingImage);
|
|
356
515
|
}
|
|
516
|
+
// ControlNet image
|
|
357
517
|
if (((_a = data.controlNet) === null || _a === void 0 ? void 0 : _a.image) && data.controlNet.image !== true) {
|
|
358
518
|
yield this.uploadCNImage(project.id, data.controlNet.image);
|
|
359
519
|
}
|
|
520
|
+
// Context images (Flux.2 Dev, Qwen Image Edit Plus support up to 3; Flux Kontext supports up to 2)
|
|
360
521
|
if ((_b = data.contextImages) === null || _b === void 0 ? void 0 : _b.length) {
|
|
361
|
-
if (data.contextImages.length >
|
|
522
|
+
if (data.contextImages.length > 3) {
|
|
362
523
|
throw new ApiClient_1.ApiError(500, {
|
|
363
524
|
status: 'error',
|
|
364
525
|
errorCode: 0,
|
|
365
|
-
message: `Up to
|
|
526
|
+
message: `Up to 3 context images are supported`
|
|
366
527
|
});
|
|
367
528
|
}
|
|
368
529
|
yield Promise.all(data.contextImages.map((image, index) => {
|
|
@@ -371,10 +532,22 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
371
532
|
}
|
|
372
533
|
}));
|
|
373
534
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
_processVideoAssets(project, data) {
|
|
538
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
539
|
+
if ((data === null || data === void 0 ? void 0 : data.referenceImage) && data.referenceImage !== true) {
|
|
540
|
+
yield this.uploadReferenceImage(project.id, data.referenceImage);
|
|
541
|
+
}
|
|
542
|
+
if ((data === null || data === void 0 ? void 0 : data.referenceImageEnd) && data.referenceImageEnd !== true) {
|
|
543
|
+
yield this.uploadReferenceImageEnd(project.id, data.referenceImageEnd);
|
|
544
|
+
}
|
|
545
|
+
if ((data === null || data === void 0 ? void 0 : data.referenceAudio) && data.referenceAudio !== true) {
|
|
546
|
+
yield this.uploadReferenceAudio(project.id, data.referenceAudio);
|
|
547
|
+
}
|
|
548
|
+
if ((data === null || data === void 0 ? void 0 : data.referenceVideo) && data.referenceVideo !== true) {
|
|
549
|
+
yield this.uploadReferenceVideo(project.id, data.referenceVideo);
|
|
550
|
+
}
|
|
378
551
|
});
|
|
379
552
|
}
|
|
380
553
|
/**
|
|
@@ -425,13 +598,13 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
425
598
|
return __awaiter(this, void 0, void 0, function* () {
|
|
426
599
|
const imageId = (0, getUUID_1.default)();
|
|
427
600
|
const presignedUrl = yield this.uploadUrl({
|
|
428
|
-
imageId
|
|
601
|
+
imageId,
|
|
429
602
|
jobId: projectId,
|
|
430
603
|
type: 'startingImage'
|
|
431
604
|
});
|
|
432
605
|
const res = yield fetch(presignedUrl, {
|
|
433
606
|
method: 'PUT',
|
|
434
|
-
body: file
|
|
607
|
+
body: toFetchBody(file)
|
|
435
608
|
});
|
|
436
609
|
if (!res.ok) {
|
|
437
610
|
throw new ApiClient_1.ApiError(res.status, {
|
|
@@ -447,13 +620,13 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
447
620
|
return __awaiter(this, void 0, void 0, function* () {
|
|
448
621
|
const imageId = (0, getUUID_1.default)();
|
|
449
622
|
const presignedUrl = yield this.uploadUrl({
|
|
450
|
-
imageId
|
|
623
|
+
imageId,
|
|
451
624
|
jobId: projectId,
|
|
452
625
|
type: 'cnImage'
|
|
453
626
|
});
|
|
454
627
|
const res = yield fetch(presignedUrl, {
|
|
455
628
|
method: 'PUT',
|
|
456
|
-
body: file
|
|
629
|
+
body: toFetchBody(file)
|
|
457
630
|
});
|
|
458
631
|
if (!res.ok) {
|
|
459
632
|
throw new ApiClient_1.ApiError(res.status, {
|
|
@@ -474,9 +647,10 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
474
647
|
jobId: projectId,
|
|
475
648
|
type: `contextImage${imageIndex}`
|
|
476
649
|
});
|
|
650
|
+
const body = toFetchBody(file);
|
|
477
651
|
const res = yield fetch(presignedUrl, {
|
|
478
652
|
method: 'PUT',
|
|
479
|
-
body
|
|
653
|
+
body
|
|
480
654
|
});
|
|
481
655
|
if (!res.ok) {
|
|
482
656
|
throw new ApiClient_1.ApiError(res.status, {
|
|
@@ -488,14 +662,132 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
488
662
|
return imageId;
|
|
489
663
|
});
|
|
490
664
|
}
|
|
665
|
+
// ============================================
|
|
666
|
+
// VIDEO WORKFLOW UPLOADS (WAN 2.2)
|
|
667
|
+
// ============================================
|
|
491
668
|
/**
|
|
492
|
-
*
|
|
669
|
+
* Upload reference image for WAN video workflows
|
|
670
|
+
* @internal
|
|
671
|
+
*/
|
|
672
|
+
uploadReferenceImage(projectId, file) {
|
|
673
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
674
|
+
const imageId = (0, getUUID_1.default)();
|
|
675
|
+
const presignedUrl = yield this.uploadUrl({
|
|
676
|
+
imageId,
|
|
677
|
+
jobId: projectId,
|
|
678
|
+
type: 'referenceImage'
|
|
679
|
+
});
|
|
680
|
+
const res = yield fetch(presignedUrl, {
|
|
681
|
+
method: 'PUT',
|
|
682
|
+
body: toFetchBody(file)
|
|
683
|
+
});
|
|
684
|
+
if (!res.ok) {
|
|
685
|
+
throw new ApiClient_1.ApiError(res.status, {
|
|
686
|
+
status: 'error',
|
|
687
|
+
errorCode: 0,
|
|
688
|
+
message: 'Failed to upload reference image'
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
return imageId;
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Upload reference image end for i2v interpolation
|
|
696
|
+
* @internal
|
|
697
|
+
*/
|
|
698
|
+
uploadReferenceImageEnd(projectId, file) {
|
|
699
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
700
|
+
const imageId = (0, getUUID_1.default)();
|
|
701
|
+
const presignedUrl = yield this.uploadUrl({
|
|
702
|
+
imageId,
|
|
703
|
+
jobId: projectId,
|
|
704
|
+
type: 'referenceImageEnd'
|
|
705
|
+
});
|
|
706
|
+
const res = yield fetch(presignedUrl, {
|
|
707
|
+
method: 'PUT',
|
|
708
|
+
body: toFetchBody(file)
|
|
709
|
+
});
|
|
710
|
+
if (!res.ok) {
|
|
711
|
+
throw new ApiClient_1.ApiError(res.status, {
|
|
712
|
+
status: 'error',
|
|
713
|
+
errorCode: 0,
|
|
714
|
+
message: 'Failed to upload reference image end'
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
return imageId;
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Upload reference audio for s2v workflows
|
|
722
|
+
* Supported formats: mp3, m4a, wav
|
|
723
|
+
* @internal
|
|
724
|
+
*/
|
|
725
|
+
uploadReferenceAudio(projectId, file) {
|
|
726
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
727
|
+
const contentType = getFileContentType(file);
|
|
728
|
+
const presignedUrl = yield this.mediaUploadUrl({
|
|
729
|
+
jobId: projectId,
|
|
730
|
+
type: 'referenceAudio'
|
|
731
|
+
});
|
|
732
|
+
const headers = {};
|
|
733
|
+
if (contentType) {
|
|
734
|
+
headers['Content-Type'] = contentType;
|
|
735
|
+
}
|
|
736
|
+
const res = yield fetch(presignedUrl, {
|
|
737
|
+
method: 'PUT',
|
|
738
|
+
body: toFetchBody(file),
|
|
739
|
+
headers
|
|
740
|
+
});
|
|
741
|
+
if (!res.ok) {
|
|
742
|
+
throw new ApiClient_1.ApiError(res.status, {
|
|
743
|
+
status: 'error',
|
|
744
|
+
errorCode: 0,
|
|
745
|
+
message: 'Failed to upload reference audio'
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Upload reference video for animate workflows
|
|
752
|
+
* Supported formats: mp4, mov
|
|
753
|
+
* @internal
|
|
754
|
+
*/
|
|
755
|
+
uploadReferenceVideo(projectId, file) {
|
|
756
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
757
|
+
const contentType = getFileContentType(file);
|
|
758
|
+
const presignedUrl = yield this.mediaUploadUrl({
|
|
759
|
+
jobId: projectId,
|
|
760
|
+
type: 'referenceVideo'
|
|
761
|
+
});
|
|
762
|
+
const headers = {};
|
|
763
|
+
if (contentType) {
|
|
764
|
+
headers['Content-Type'] = contentType;
|
|
765
|
+
}
|
|
766
|
+
const res = yield fetch(presignedUrl, {
|
|
767
|
+
method: 'PUT',
|
|
768
|
+
body: toFetchBody(file),
|
|
769
|
+
headers
|
|
770
|
+
});
|
|
771
|
+
if (!res.ok) {
|
|
772
|
+
throw new ApiClient_1.ApiError(res.status, {
|
|
773
|
+
status: 'error',
|
|
774
|
+
errorCode: 0,
|
|
775
|
+
message: 'Failed to upload reference video'
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
// ============================================
|
|
781
|
+
// COST ESTIMATION
|
|
782
|
+
// ============================================
|
|
783
|
+
/**
|
|
784
|
+
* Estimate image project cost
|
|
493
785
|
*/
|
|
494
786
|
estimateCost(_a) {
|
|
495
|
-
return __awaiter(this, arguments, void 0, function* ({ network, tokenType, model, imageCount, stepCount, previewCount, cnEnabled, startingImageStrength, width, height, sizePreset, guidance,
|
|
787
|
+
return __awaiter(this, arguments, void 0, function* ({ network, tokenType, model, imageCount, stepCount, previewCount, cnEnabled, startingImageStrength, width, height, sizePreset, guidance, sampler, contextImages }) {
|
|
496
788
|
let apiVersion = 2;
|
|
497
789
|
const pathParams = [
|
|
498
|
-
tokenType || '
|
|
790
|
+
tokenType || 'spark',
|
|
499
791
|
network,
|
|
500
792
|
model,
|
|
501
793
|
imageCount,
|
|
@@ -518,21 +810,28 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
518
810
|
else {
|
|
519
811
|
pathParams.push(0, 0);
|
|
520
812
|
}
|
|
521
|
-
if (
|
|
813
|
+
if (sampler) {
|
|
522
814
|
apiVersion = 3;
|
|
523
815
|
pathParams.push(guidance || 0);
|
|
524
|
-
pathParams.push(
|
|
816
|
+
pathParams.push((0, validation_1.validateSampler)(model, sampler));
|
|
525
817
|
pathParams.push(contextImages || 0);
|
|
526
818
|
}
|
|
527
819
|
const r = yield this.client.socket.get(`/api/v${apiVersion}/job/estimate/${pathParams.join('/')}`);
|
|
528
820
|
return {
|
|
529
821
|
token: r.quote.project.costInToken,
|
|
530
|
-
usd: r.quote.project.costInUSD
|
|
822
|
+
usd: r.quote.project.costInUSD,
|
|
823
|
+
spark: r.quote.project.costInSpark,
|
|
824
|
+
sogni: r.quote.project.costInSogni
|
|
531
825
|
};
|
|
532
826
|
});
|
|
533
827
|
}
|
|
828
|
+
/**
|
|
829
|
+
* Estimate image enhancement cost
|
|
830
|
+
* @param strength
|
|
831
|
+
* @param tokenType
|
|
832
|
+
*/
|
|
534
833
|
estimateEnhancementCost(strength_1) {
|
|
535
|
-
return __awaiter(this, arguments, void 0, function* (strength, tokenType = '
|
|
834
|
+
return __awaiter(this, arguments, void 0, function* (strength, tokenType = 'spark') {
|
|
536
835
|
return this.estimateCost({
|
|
537
836
|
network: Job_1.enhancementDefaults.network,
|
|
538
837
|
tokenType,
|
|
@@ -545,10 +844,51 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
545
844
|
});
|
|
546
845
|
});
|
|
547
846
|
}
|
|
847
|
+
/**
|
|
848
|
+
* Estimates the cost of generating a video based on the provided parameters.
|
|
849
|
+
*
|
|
850
|
+
* @param {VideoEstimateRequest} params - The parameters required for video cost estimation. This includes:
|
|
851
|
+
* - tokenType: The type of token to be used for generation.
|
|
852
|
+
* - model: The model to be used for video generation.
|
|
853
|
+
* - width: The width of the video in pixels.
|
|
854
|
+
* - height: The height of the video in pixels.
|
|
855
|
+
* - frames: The total number of frames in the video.
|
|
856
|
+
* - fps: The frames per second for the video.
|
|
857
|
+
* - steps: Number of steps.
|
|
858
|
+
* @return {Promise<Object>} Returns an object containing the estimated costs for the video in different units:
|
|
859
|
+
* - token: Cost in tokens.
|
|
860
|
+
* - usd: Cost in USD.
|
|
861
|
+
* - spark: Cost in Spark.
|
|
862
|
+
* - sogni: Cost in Sogni.
|
|
863
|
+
*/
|
|
864
|
+
estimateVideoCost(params) {
|
|
865
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
866
|
+
const pathParams = [
|
|
867
|
+
params.tokenType,
|
|
868
|
+
params.model,
|
|
869
|
+
params.width,
|
|
870
|
+
params.height,
|
|
871
|
+
params.frames ? params.frames : params.duration * 16 + 1,
|
|
872
|
+
params.fps,
|
|
873
|
+
params.steps,
|
|
874
|
+
params.numberOfMedia
|
|
875
|
+
];
|
|
876
|
+
const path = pathParams.map((p) => encodeURIComponent(p)).join('/');
|
|
877
|
+
const r = yield this.client.socket.get(`/api/v1/job-video/estimate/${path}`);
|
|
878
|
+
return {
|
|
879
|
+
token: r.quote.project.costInToken,
|
|
880
|
+
usd: r.quote.project.costInUSD,
|
|
881
|
+
spark: r.quote.project.costInSpark,
|
|
882
|
+
sogni: r.quote.project.costInSogni
|
|
883
|
+
};
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
// ============================================
|
|
887
|
+
// URL HELPERS
|
|
888
|
+
// ============================================
|
|
548
889
|
/**
|
|
549
890
|
* Get upload URL for image
|
|
550
891
|
* @internal
|
|
551
|
-
* @param params
|
|
552
892
|
*/
|
|
553
893
|
uploadUrl(params) {
|
|
554
894
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -559,14 +899,44 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
559
899
|
/**
|
|
560
900
|
* Get download URL for image
|
|
561
901
|
* @internal
|
|
562
|
-
* @param params
|
|
563
902
|
*/
|
|
564
903
|
downloadUrl(params) {
|
|
565
904
|
return __awaiter(this, void 0, void 0, function* () {
|
|
905
|
+
var _a;
|
|
566
906
|
const r = yield this.client.rest.get(`/v1/image/downloadUrl`, params);
|
|
907
|
+
if (!((_a = r === null || r === void 0 ? void 0 : r.data) === null || _a === void 0 ? void 0 : _a.downloadUrl)) {
|
|
908
|
+
throw new Error(`API returned no downloadUrl: ${JSON.stringify(r)}`);
|
|
909
|
+
}
|
|
910
|
+
return r.data.downloadUrl;
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Get upload URL for media (video/audio)
|
|
915
|
+
* @internal
|
|
916
|
+
*/
|
|
917
|
+
mediaUploadUrl(params) {
|
|
918
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
919
|
+
const r = yield this.client.rest.get(`/v1/media/uploadUrl`, params);
|
|
920
|
+
return r.data.uploadUrl;
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Get download URL for media (video/audio)
|
|
925
|
+
* @internal
|
|
926
|
+
*/
|
|
927
|
+
mediaDownloadUrl(params) {
|
|
928
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
929
|
+
var _a;
|
|
930
|
+
const r = yield this.client.rest.get(`/v1/media/downloadUrl`, params);
|
|
931
|
+
if (!((_a = r === null || r === void 0 ? void 0 : r.data) === null || _a === void 0 ? void 0 : _a.downloadUrl)) {
|
|
932
|
+
throw new Error(`API returned no downloadUrl: ${JSON.stringify(r)}`);
|
|
933
|
+
}
|
|
567
934
|
return r.data.downloadUrl;
|
|
568
935
|
});
|
|
569
936
|
}
|
|
937
|
+
// ============================================
|
|
938
|
+
// MODEL/PRESET HELPERS
|
|
939
|
+
// ============================================
|
|
570
940
|
getSupportedModels() {
|
|
571
941
|
return __awaiter(this, arguments, void 0, function* (forceRefresh = false) {
|
|
572
942
|
if (this._supportedModels.data &&
|
|
@@ -584,7 +954,7 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
584
954
|
*
|
|
585
955
|
* @example
|
|
586
956
|
* ```ts
|
|
587
|
-
* const presets = await
|
|
957
|
+
* const presets = await sogni.projects.getSizePresets('fast', 'flux1-schnell-fp8');
|
|
588
958
|
* console.log(presets);
|
|
589
959
|
* ```
|
|
590
960
|
*
|
|
@@ -612,6 +982,50 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
612
982
|
return data;
|
|
613
983
|
});
|
|
614
984
|
}
|
|
985
|
+
/**
|
|
986
|
+
* Retrieves the video asset configuration for a given video model identifier.
|
|
987
|
+
* Validates whether the provided model ID corresponds to a video model. If it does,
|
|
988
|
+
* returns the appropriate video asset configuration based on the workflow type.
|
|
989
|
+
*
|
|
990
|
+
* @example Returned object for a model that implements image to video workflow:
|
|
991
|
+
* ```json
|
|
992
|
+
* {
|
|
993
|
+
* "workflowType": "i2v",
|
|
994
|
+
* "assets": {
|
|
995
|
+
* "referenceImage": "required",
|
|
996
|
+
* "referenceImageEnd": "optional",
|
|
997
|
+
* "referenceAudio": "forbidden",
|
|
998
|
+
* "referenceVideo": "forbidden"
|
|
999
|
+
* }
|
|
1000
|
+
* }
|
|
1001
|
+
* ```
|
|
1002
|
+
*
|
|
1003
|
+
* @param {string} modelId - The identifier of the video model to retrieve the configuration for.
|
|
1004
|
+
* @return {Object} The video asset configuration object where key is asset field and value is
|
|
1005
|
+
* either `required`, `forbidden` or `optional`. Returns `null` if no rules defined for the model.
|
|
1006
|
+
* @throws {ApiError} Throws an error if the provided model ID is not a video model.
|
|
1007
|
+
*/
|
|
1008
|
+
getVideoAssetConfig(modelId) {
|
|
1009
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1010
|
+
if (!this.isVideoModelId(modelId)) {
|
|
1011
|
+
throw new ApiClient_1.ApiError(400, {
|
|
1012
|
+
status: 'error',
|
|
1013
|
+
errorCode: 0,
|
|
1014
|
+
message: `Model ${modelId} is not a video model`
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
const workflow = (0, utils_1.getVideoWorkflowType)(modelId);
|
|
1018
|
+
if (!workflow) {
|
|
1019
|
+
return {
|
|
1020
|
+
workflowType: null
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
return {
|
|
1024
|
+
workflowType: workflow,
|
|
1025
|
+
assets: utils_1.VIDEO_WORKFLOW_ASSETS[workflow]
|
|
1026
|
+
};
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
615
1029
|
/**
|
|
616
1030
|
* Get available models and their worker counts. Normally, you would get list once you connect
|
|
617
1031
|
* to the server, but you can also call this method to get the list of available models manually.
|
|
@@ -627,11 +1041,28 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
627
1041
|
return {
|
|
628
1042
|
id: (model === null || model === void 0 ? void 0 : model.id) || sid,
|
|
629
1043
|
name: (model === null || model === void 0 ? void 0 : model.name) || sid.replace(/-/g, ' '),
|
|
630
|
-
workerCount
|
|
1044
|
+
workerCount,
|
|
1045
|
+
media: (model === null || model === void 0 ? void 0 : model.media) || 'image'
|
|
631
1046
|
};
|
|
632
1047
|
});
|
|
633
1048
|
});
|
|
634
1049
|
}
|
|
1050
|
+
getSamplers(modelId) {
|
|
1051
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1052
|
+
if ((0, validation_1.isComfyModel)(modelId)) {
|
|
1053
|
+
return Object.keys(types_1.SupportedComfySamplers);
|
|
1054
|
+
}
|
|
1055
|
+
return Object.keys(types_1.SupportedForgeSamplers);
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
getSchedulers(modelId) {
|
|
1059
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1060
|
+
if ((0, validation_1.isComfyModel)(modelId)) {
|
|
1061
|
+
return Object.keys(types_1.SupportedComfySchedulers);
|
|
1062
|
+
}
|
|
1063
|
+
return Object.keys(types_1.SupportedForgeSchedulers);
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
635
1066
|
}
|
|
636
1067
|
exports.default = ProjectsApi;
|
|
637
1068
|
//# sourceMappingURL=index.js.map
|