@sogni-ai/sogni-client 4.0.0-alpha.9 → 4.0.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/CHANGELOG.md +268 -0
- package/README.md +262 -27
- 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 +38 -10
- package/dist/Projects/Project.js.map +1 -1
- package/dist/Projects/createJobRequestMessage.js +170 -13
- package/dist/Projects/createJobRequestMessage.js.map +1 -1
- package/dist/Projects/index.d.ts +112 -11
- package/dist/Projects/index.js +473 -45
- 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 +205 -38
- 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 +95 -10
- 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 +46 -13
- package/src/Projects/createJobRequestMessage.ts +222 -35
- package/src/Projects/index.ts +504 -49
- 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 +243 -39
- 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 -12
- 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
|
+
}
|
|
170
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
|
+
}
|
|
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,23 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
292
396
|
project._update({ status: 'processing' });
|
|
293
397
|
}
|
|
294
398
|
break;
|
|
399
|
+
case 'jobETA': {
|
|
400
|
+
// ETA updates keep the project alive (refreshes lastUpdated) and store the ETA value.
|
|
401
|
+
// This is critical for long-running jobs like video generation that can take several
|
|
402
|
+
// minutes and may not send frequent progress updates.
|
|
403
|
+
// We always call _keepAlive() to ensure lastUpdated is refreshed, preventing premature timeouts.
|
|
404
|
+
project._keepAlive();
|
|
405
|
+
const newEta = new Date(Date.now() + event.etaSeconds * 1000);
|
|
406
|
+
if (((_b = job.eta) === null || _b === void 0 ? void 0 : _b.getTime()) !== (newEta === null || newEta === void 0 ? void 0 : newEta.getTime())) {
|
|
407
|
+
job._update({ eta: newEta });
|
|
408
|
+
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);
|
|
409
|
+
const projectETA = maxEta ? new Date(maxEta) : undefined;
|
|
410
|
+
if (((_c = project.eta) === null || _c === void 0 ? void 0 : _c.getTime()) !== (projectETA === null || projectETA === void 0 ? void 0 : projectETA.getTime())) {
|
|
411
|
+
project._update({ eta: projectETA });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
295
416
|
case 'preview':
|
|
296
417
|
job._update({ previewUrl: event.url });
|
|
297
418
|
break;
|
|
@@ -308,6 +429,17 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
308
429
|
}
|
|
309
430
|
case 'error':
|
|
310
431
|
job._update({ status: 'failed', error: event.error });
|
|
432
|
+
// Check if project should also fail when a job fails
|
|
433
|
+
// For video jobs (single image) or when all jobs have failed, propagate to project
|
|
434
|
+
const allJobsStarted = project.jobs.length >= project.params.numberOfMedia;
|
|
435
|
+
const allJobsFailed = allJobsStarted && project.jobs.every((j) => j.status === 'failed');
|
|
436
|
+
const isSingleJobProject = project.params.numberOfMedia === 1;
|
|
437
|
+
if (isSingleJobProject || allJobsFailed) {
|
|
438
|
+
project._update({
|
|
439
|
+
status: 'failed',
|
|
440
|
+
error: event.error
|
|
441
|
+
});
|
|
442
|
+
}
|
|
311
443
|
break;
|
|
312
444
|
}
|
|
313
445
|
}
|
|
@@ -328,41 +460,67 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
328
460
|
return Promise.resolve(this._availableModels);
|
|
329
461
|
}
|
|
330
462
|
return new Promise((resolve, reject) => {
|
|
463
|
+
let settled = false;
|
|
331
464
|
const timeoutId = setTimeout(() => {
|
|
332
|
-
|
|
465
|
+
if (!settled) {
|
|
466
|
+
settled = true;
|
|
467
|
+
this.off('availableModels', handler);
|
|
468
|
+
reject(new Error('Timeout waiting for models'));
|
|
469
|
+
}
|
|
333
470
|
}, timeout);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
471
|
+
const handler = (models) => {
|
|
472
|
+
// Only resolve when we get a non-empty models list
|
|
473
|
+
// Empty arrays may be emitted during disconnects/reconnects
|
|
474
|
+
if (models.length && !settled) {
|
|
475
|
+
settled = true;
|
|
476
|
+
clearTimeout(timeoutId);
|
|
477
|
+
this.off('availableModels', handler);
|
|
337
478
|
resolve(models);
|
|
338
479
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
});
|
|
480
|
+
};
|
|
481
|
+
this.on('availableModels', handler);
|
|
343
482
|
});
|
|
344
483
|
}
|
|
345
484
|
/**
|
|
346
485
|
* Send new project request to the network. Returns project instance which can be used to track
|
|
347
|
-
* progress and get resulting images.
|
|
486
|
+
* progress and get resulting images or videos.
|
|
348
487
|
* @param data
|
|
349
488
|
*/
|
|
350
489
|
create(data) {
|
|
351
490
|
return __awaiter(this, void 0, void 0, function* () {
|
|
352
|
-
var _a, _b;
|
|
353
491
|
const project = new Project_1.default(Object.assign({}, data), { api: this, logger: this.client.logger });
|
|
492
|
+
const request = (0, createJobRequestMessage_1.default)(project.id, data);
|
|
493
|
+
switch (data.type) {
|
|
494
|
+
case 'image':
|
|
495
|
+
yield this._processImageAssets(project, data);
|
|
496
|
+
break;
|
|
497
|
+
case 'video':
|
|
498
|
+
yield this._processVideoAssets(project, data);
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
yield this.client.socket.send('jobRequest', request);
|
|
502
|
+
this.projects.push(project);
|
|
503
|
+
return project;
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
_processImageAssets(project, data) {
|
|
507
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
508
|
+
var _a, _b;
|
|
509
|
+
//Guide image
|
|
354
510
|
if (data.startingImage && data.startingImage !== true) {
|
|
355
511
|
yield this.uploadGuideImage(project.id, data.startingImage);
|
|
356
512
|
}
|
|
513
|
+
// ControlNet image
|
|
357
514
|
if (((_a = data.controlNet) === null || _a === void 0 ? void 0 : _a.image) && data.controlNet.image !== true) {
|
|
358
515
|
yield this.uploadCNImage(project.id, data.controlNet.image);
|
|
359
516
|
}
|
|
517
|
+
// Context images (Flux.2 Dev, Qwen Image Edit Plus support up to 3; Flux Kontext supports up to 2)
|
|
360
518
|
if ((_b = data.contextImages) === null || _b === void 0 ? void 0 : _b.length) {
|
|
361
|
-
if (data.contextImages.length >
|
|
519
|
+
if (data.contextImages.length > 3) {
|
|
362
520
|
throw new ApiClient_1.ApiError(500, {
|
|
363
521
|
status: 'error',
|
|
364
522
|
errorCode: 0,
|
|
365
|
-
message: `Up to
|
|
523
|
+
message: `Up to 3 context images are supported`
|
|
366
524
|
});
|
|
367
525
|
}
|
|
368
526
|
yield Promise.all(data.contextImages.map((image, index) => {
|
|
@@ -371,10 +529,22 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
371
529
|
}
|
|
372
530
|
}));
|
|
373
531
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
_processVideoAssets(project, data) {
|
|
535
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
536
|
+
if ((data === null || data === void 0 ? void 0 : data.referenceImage) && data.referenceImage !== true) {
|
|
537
|
+
yield this.uploadReferenceImage(project.id, data.referenceImage);
|
|
538
|
+
}
|
|
539
|
+
if ((data === null || data === void 0 ? void 0 : data.referenceImageEnd) && data.referenceImageEnd !== true) {
|
|
540
|
+
yield this.uploadReferenceImageEnd(project.id, data.referenceImageEnd);
|
|
541
|
+
}
|
|
542
|
+
if ((data === null || data === void 0 ? void 0 : data.referenceAudio) && data.referenceAudio !== true) {
|
|
543
|
+
yield this.uploadReferenceAudio(project.id, data.referenceAudio);
|
|
544
|
+
}
|
|
545
|
+
if ((data === null || data === void 0 ? void 0 : data.referenceVideo) && data.referenceVideo !== true) {
|
|
546
|
+
yield this.uploadReferenceVideo(project.id, data.referenceVideo);
|
|
547
|
+
}
|
|
378
548
|
});
|
|
379
549
|
}
|
|
380
550
|
/**
|
|
@@ -425,13 +595,13 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
425
595
|
return __awaiter(this, void 0, void 0, function* () {
|
|
426
596
|
const imageId = (0, getUUID_1.default)();
|
|
427
597
|
const presignedUrl = yield this.uploadUrl({
|
|
428
|
-
imageId
|
|
598
|
+
imageId,
|
|
429
599
|
jobId: projectId,
|
|
430
600
|
type: 'startingImage'
|
|
431
601
|
});
|
|
432
602
|
const res = yield fetch(presignedUrl, {
|
|
433
603
|
method: 'PUT',
|
|
434
|
-
body: file
|
|
604
|
+
body: toFetchBody(file)
|
|
435
605
|
});
|
|
436
606
|
if (!res.ok) {
|
|
437
607
|
throw new ApiClient_1.ApiError(res.status, {
|
|
@@ -447,13 +617,13 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
447
617
|
return __awaiter(this, void 0, void 0, function* () {
|
|
448
618
|
const imageId = (0, getUUID_1.default)();
|
|
449
619
|
const presignedUrl = yield this.uploadUrl({
|
|
450
|
-
imageId
|
|
620
|
+
imageId,
|
|
451
621
|
jobId: projectId,
|
|
452
622
|
type: 'cnImage'
|
|
453
623
|
});
|
|
454
624
|
const res = yield fetch(presignedUrl, {
|
|
455
625
|
method: 'PUT',
|
|
456
|
-
body: file
|
|
626
|
+
body: toFetchBody(file)
|
|
457
627
|
});
|
|
458
628
|
if (!res.ok) {
|
|
459
629
|
throw new ApiClient_1.ApiError(res.status, {
|
|
@@ -474,9 +644,10 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
474
644
|
jobId: projectId,
|
|
475
645
|
type: `contextImage${imageIndex}`
|
|
476
646
|
});
|
|
647
|
+
const body = toFetchBody(file);
|
|
477
648
|
const res = yield fetch(presignedUrl, {
|
|
478
649
|
method: 'PUT',
|
|
479
|
-
body
|
|
650
|
+
body
|
|
480
651
|
});
|
|
481
652
|
if (!res.ok) {
|
|
482
653
|
throw new ApiClient_1.ApiError(res.status, {
|
|
@@ -488,14 +659,132 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
488
659
|
return imageId;
|
|
489
660
|
});
|
|
490
661
|
}
|
|
662
|
+
// ============================================
|
|
663
|
+
// VIDEO WORKFLOW UPLOADS (WAN 2.2)
|
|
664
|
+
// ============================================
|
|
665
|
+
/**
|
|
666
|
+
* Upload reference image for WAN video workflows
|
|
667
|
+
* @internal
|
|
668
|
+
*/
|
|
669
|
+
uploadReferenceImage(projectId, file) {
|
|
670
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
671
|
+
const imageId = (0, getUUID_1.default)();
|
|
672
|
+
const presignedUrl = yield this.uploadUrl({
|
|
673
|
+
imageId,
|
|
674
|
+
jobId: projectId,
|
|
675
|
+
type: 'referenceImage'
|
|
676
|
+
});
|
|
677
|
+
const res = yield fetch(presignedUrl, {
|
|
678
|
+
method: 'PUT',
|
|
679
|
+
body: toFetchBody(file)
|
|
680
|
+
});
|
|
681
|
+
if (!res.ok) {
|
|
682
|
+
throw new ApiClient_1.ApiError(res.status, {
|
|
683
|
+
status: 'error',
|
|
684
|
+
errorCode: 0,
|
|
685
|
+
message: 'Failed to upload reference image'
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
return imageId;
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Upload reference image end for i2v interpolation
|
|
693
|
+
* @internal
|
|
694
|
+
*/
|
|
695
|
+
uploadReferenceImageEnd(projectId, file) {
|
|
696
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
697
|
+
const imageId = (0, getUUID_1.default)();
|
|
698
|
+
const presignedUrl = yield this.uploadUrl({
|
|
699
|
+
imageId,
|
|
700
|
+
jobId: projectId,
|
|
701
|
+
type: 'referenceImageEnd'
|
|
702
|
+
});
|
|
703
|
+
const res = yield fetch(presignedUrl, {
|
|
704
|
+
method: 'PUT',
|
|
705
|
+
body: toFetchBody(file)
|
|
706
|
+
});
|
|
707
|
+
if (!res.ok) {
|
|
708
|
+
throw new ApiClient_1.ApiError(res.status, {
|
|
709
|
+
status: 'error',
|
|
710
|
+
errorCode: 0,
|
|
711
|
+
message: 'Failed to upload reference image end'
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
return imageId;
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Upload reference audio for s2v workflows
|
|
719
|
+
* Supported formats: mp3, m4a, wav
|
|
720
|
+
* @internal
|
|
721
|
+
*/
|
|
722
|
+
uploadReferenceAudio(projectId, file) {
|
|
723
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
724
|
+
const contentType = getFileContentType(file);
|
|
725
|
+
const presignedUrl = yield this.mediaUploadUrl({
|
|
726
|
+
jobId: projectId,
|
|
727
|
+
type: 'referenceAudio'
|
|
728
|
+
});
|
|
729
|
+
const headers = {};
|
|
730
|
+
if (contentType) {
|
|
731
|
+
headers['Content-Type'] = contentType;
|
|
732
|
+
}
|
|
733
|
+
const res = yield fetch(presignedUrl, {
|
|
734
|
+
method: 'PUT',
|
|
735
|
+
body: toFetchBody(file),
|
|
736
|
+
headers
|
|
737
|
+
});
|
|
738
|
+
if (!res.ok) {
|
|
739
|
+
throw new ApiClient_1.ApiError(res.status, {
|
|
740
|
+
status: 'error',
|
|
741
|
+
errorCode: 0,
|
|
742
|
+
message: 'Failed to upload reference audio'
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
}
|
|
491
747
|
/**
|
|
492
|
-
*
|
|
748
|
+
* Upload reference video for animate workflows
|
|
749
|
+
* Supported formats: mp4, mov
|
|
750
|
+
* @internal
|
|
751
|
+
*/
|
|
752
|
+
uploadReferenceVideo(projectId, file) {
|
|
753
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
754
|
+
const contentType = getFileContentType(file);
|
|
755
|
+
const presignedUrl = yield this.mediaUploadUrl({
|
|
756
|
+
jobId: projectId,
|
|
757
|
+
type: 'referenceVideo'
|
|
758
|
+
});
|
|
759
|
+
const headers = {};
|
|
760
|
+
if (contentType) {
|
|
761
|
+
headers['Content-Type'] = contentType;
|
|
762
|
+
}
|
|
763
|
+
const res = yield fetch(presignedUrl, {
|
|
764
|
+
method: 'PUT',
|
|
765
|
+
body: toFetchBody(file),
|
|
766
|
+
headers
|
|
767
|
+
});
|
|
768
|
+
if (!res.ok) {
|
|
769
|
+
throw new ApiClient_1.ApiError(res.status, {
|
|
770
|
+
status: 'error',
|
|
771
|
+
errorCode: 0,
|
|
772
|
+
message: 'Failed to upload reference video'
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
// ============================================
|
|
778
|
+
// COST ESTIMATION
|
|
779
|
+
// ============================================
|
|
780
|
+
/**
|
|
781
|
+
* Estimate image project cost
|
|
493
782
|
*/
|
|
494
783
|
estimateCost(_a) {
|
|
495
784
|
return __awaiter(this, arguments, void 0, function* ({ network, tokenType, model, imageCount, stepCount, previewCount, cnEnabled, startingImageStrength, width, height, sizePreset, guidance, sampler, contextImages }) {
|
|
496
785
|
let apiVersion = 2;
|
|
497
786
|
const pathParams = [
|
|
498
|
-
tokenType || '
|
|
787
|
+
tokenType || 'spark',
|
|
499
788
|
network,
|
|
500
789
|
model,
|
|
501
790
|
imageCount,
|
|
@@ -521,18 +810,25 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
521
810
|
if (sampler) {
|
|
522
811
|
apiVersion = 3;
|
|
523
812
|
pathParams.push(guidance || 0);
|
|
524
|
-
pathParams.push(
|
|
813
|
+
pathParams.push((0, validation_1.validateSampler)(model, sampler));
|
|
525
814
|
pathParams.push(contextImages || 0);
|
|
526
815
|
}
|
|
527
816
|
const r = yield this.client.socket.get(`/api/v${apiVersion}/job/estimate/${pathParams.join('/')}`);
|
|
528
817
|
return {
|
|
529
818
|
token: r.quote.project.costInToken,
|
|
530
|
-
usd: r.quote.project.costInUSD
|
|
819
|
+
usd: r.quote.project.costInUSD,
|
|
820
|
+
spark: r.quote.project.costInSpark,
|
|
821
|
+
sogni: r.quote.project.costInSogni
|
|
531
822
|
};
|
|
532
823
|
});
|
|
533
824
|
}
|
|
825
|
+
/**
|
|
826
|
+
* Estimate image enhancement cost
|
|
827
|
+
* @param strength
|
|
828
|
+
* @param tokenType
|
|
829
|
+
*/
|
|
534
830
|
estimateEnhancementCost(strength_1) {
|
|
535
|
-
return __awaiter(this, arguments, void 0, function* (strength, tokenType = '
|
|
831
|
+
return __awaiter(this, arguments, void 0, function* (strength, tokenType = 'spark') {
|
|
536
832
|
return this.estimateCost({
|
|
537
833
|
network: Job_1.enhancementDefaults.network,
|
|
538
834
|
tokenType,
|
|
@@ -545,10 +841,51 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
545
841
|
});
|
|
546
842
|
});
|
|
547
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Estimates the cost of generating a video based on the provided parameters.
|
|
846
|
+
*
|
|
847
|
+
* @param {VideoEstimateRequest} params - The parameters required for video cost estimation. This includes:
|
|
848
|
+
* - tokenType: The type of token to be used for generation.
|
|
849
|
+
* - model: The model to be used for video generation.
|
|
850
|
+
* - width: The width of the video in pixels.
|
|
851
|
+
* - height: The height of the video in pixels.
|
|
852
|
+
* - frames: The total number of frames in the video.
|
|
853
|
+
* - fps: The frames per second for the video.
|
|
854
|
+
* - steps: Number of steps.
|
|
855
|
+
* @return {Promise<Object>} Returns an object containing the estimated costs for the video in different units:
|
|
856
|
+
* - token: Cost in tokens.
|
|
857
|
+
* - usd: Cost in USD.
|
|
858
|
+
* - spark: Cost in Spark.
|
|
859
|
+
* - sogni: Cost in Sogni.
|
|
860
|
+
*/
|
|
861
|
+
estimateVideoCost(params) {
|
|
862
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
863
|
+
const pathParams = [
|
|
864
|
+
params.tokenType,
|
|
865
|
+
params.model,
|
|
866
|
+
params.width,
|
|
867
|
+
params.height,
|
|
868
|
+
params.frames ? params.frames : params.duration * 16 + 1,
|
|
869
|
+
params.fps,
|
|
870
|
+
params.steps,
|
|
871
|
+
params.numberOfMedia
|
|
872
|
+
];
|
|
873
|
+
const path = pathParams.map((p) => encodeURIComponent(p)).join('/');
|
|
874
|
+
const r = yield this.client.socket.get(`/api/v1/job-video/estimate/${path}`);
|
|
875
|
+
return {
|
|
876
|
+
token: r.quote.project.costInToken,
|
|
877
|
+
usd: r.quote.project.costInUSD,
|
|
878
|
+
spark: r.quote.project.costInSpark,
|
|
879
|
+
sogni: r.quote.project.costInSogni
|
|
880
|
+
};
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
// ============================================
|
|
884
|
+
// URL HELPERS
|
|
885
|
+
// ============================================
|
|
548
886
|
/**
|
|
549
887
|
* Get upload URL for image
|
|
550
888
|
* @internal
|
|
551
|
-
* @param params
|
|
552
889
|
*/
|
|
553
890
|
uploadUrl(params) {
|
|
554
891
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -559,14 +896,44 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
559
896
|
/**
|
|
560
897
|
* Get download URL for image
|
|
561
898
|
* @internal
|
|
562
|
-
* @param params
|
|
563
899
|
*/
|
|
564
900
|
downloadUrl(params) {
|
|
565
901
|
return __awaiter(this, void 0, void 0, function* () {
|
|
902
|
+
var _a;
|
|
566
903
|
const r = yield this.client.rest.get(`/v1/image/downloadUrl`, params);
|
|
904
|
+
if (!((_a = r === null || r === void 0 ? void 0 : r.data) === null || _a === void 0 ? void 0 : _a.downloadUrl)) {
|
|
905
|
+
throw new Error(`API returned no downloadUrl: ${JSON.stringify(r)}`);
|
|
906
|
+
}
|
|
907
|
+
return r.data.downloadUrl;
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Get upload URL for media (video/audio)
|
|
912
|
+
* @internal
|
|
913
|
+
*/
|
|
914
|
+
mediaUploadUrl(params) {
|
|
915
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
916
|
+
const r = yield this.client.rest.get(`/v1/media/uploadUrl`, params);
|
|
917
|
+
return r.data.uploadUrl;
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Get download URL for media (video/audio)
|
|
922
|
+
* @internal
|
|
923
|
+
*/
|
|
924
|
+
mediaDownloadUrl(params) {
|
|
925
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
926
|
+
var _a;
|
|
927
|
+
const r = yield this.client.rest.get(`/v1/media/downloadUrl`, params);
|
|
928
|
+
if (!((_a = r === null || r === void 0 ? void 0 : r.data) === null || _a === void 0 ? void 0 : _a.downloadUrl)) {
|
|
929
|
+
throw new Error(`API returned no downloadUrl: ${JSON.stringify(r)}`);
|
|
930
|
+
}
|
|
567
931
|
return r.data.downloadUrl;
|
|
568
932
|
});
|
|
569
933
|
}
|
|
934
|
+
// ============================================
|
|
935
|
+
// MODEL/PRESET HELPERS
|
|
936
|
+
// ============================================
|
|
570
937
|
getSupportedModels() {
|
|
571
938
|
return __awaiter(this, arguments, void 0, function* (forceRefresh = false) {
|
|
572
939
|
if (this._supportedModels.data &&
|
|
@@ -584,7 +951,7 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
584
951
|
*
|
|
585
952
|
* @example
|
|
586
953
|
* ```ts
|
|
587
|
-
* const presets = await
|
|
954
|
+
* const presets = await sogni.projects.getSizePresets('fast', 'flux1-schnell-fp8');
|
|
588
955
|
* console.log(presets);
|
|
589
956
|
* ```
|
|
590
957
|
*
|
|
@@ -612,6 +979,50 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
612
979
|
return data;
|
|
613
980
|
});
|
|
614
981
|
}
|
|
982
|
+
/**
|
|
983
|
+
* Retrieves the video asset configuration for a given video model identifier.
|
|
984
|
+
* Validates whether the provided model ID corresponds to a video model. If it does,
|
|
985
|
+
* returns the appropriate video asset configuration based on the workflow type.
|
|
986
|
+
*
|
|
987
|
+
* @example Returned object for a model that implements image to video workflow:
|
|
988
|
+
* ```json
|
|
989
|
+
* {
|
|
990
|
+
* "workflowType": "i2v",
|
|
991
|
+
* "assets": {
|
|
992
|
+
* "referenceImage": "required",
|
|
993
|
+
* "referenceImageEnd": "optional",
|
|
994
|
+
* "referenceAudio": "forbidden",
|
|
995
|
+
* "referenceVideo": "forbidden"
|
|
996
|
+
* }
|
|
997
|
+
* }
|
|
998
|
+
* ```
|
|
999
|
+
*
|
|
1000
|
+
* @param {string} modelId - The identifier of the video model to retrieve the configuration for.
|
|
1001
|
+
* @return {Object} The video asset configuration object where key is asset field and value is
|
|
1002
|
+
* either `required`, `forbidden` or `optional`. Returns `null` if no rules defined for the model.
|
|
1003
|
+
* @throws {ApiError} Throws an error if the provided model ID is not a video model.
|
|
1004
|
+
*/
|
|
1005
|
+
getVideoAssetConfig(modelId) {
|
|
1006
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1007
|
+
if (!this.isVideoModelId(modelId)) {
|
|
1008
|
+
throw new ApiClient_1.ApiError(400, {
|
|
1009
|
+
status: 'error',
|
|
1010
|
+
errorCode: 0,
|
|
1011
|
+
message: `Model ${modelId} is not a video model`
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
const workflow = (0, utils_1.getVideoWorkflowType)(modelId);
|
|
1015
|
+
if (!workflow) {
|
|
1016
|
+
return {
|
|
1017
|
+
workflowType: null
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
return {
|
|
1021
|
+
workflowType: workflow,
|
|
1022
|
+
assets: utils_1.VIDEO_WORKFLOW_ASSETS[workflow]
|
|
1023
|
+
};
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
615
1026
|
/**
|
|
616
1027
|
* Get available models and their worker counts. Normally, you would get list once you connect
|
|
617
1028
|
* to the server, but you can also call this method to get the list of available models manually.
|
|
@@ -627,11 +1038,28 @@ class ProjectsApi extends ApiGroup_1.default {
|
|
|
627
1038
|
return {
|
|
628
1039
|
id: (model === null || model === void 0 ? void 0 : model.id) || sid,
|
|
629
1040
|
name: (model === null || model === void 0 ? void 0 : model.name) || sid.replace(/-/g, ' '),
|
|
630
|
-
workerCount
|
|
1041
|
+
workerCount,
|
|
1042
|
+
media: (model === null || model === void 0 ? void 0 : model.media) || 'image'
|
|
631
1043
|
};
|
|
632
1044
|
});
|
|
633
1045
|
});
|
|
634
1046
|
}
|
|
1047
|
+
getSamplers(modelId) {
|
|
1048
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1049
|
+
if ((0, validation_1.isComfyModel)(modelId)) {
|
|
1050
|
+
return Object.keys(types_1.SupportedComfySamplers);
|
|
1051
|
+
}
|
|
1052
|
+
return Object.keys(types_1.SupportedForgeSamplers);
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
getSchedulers(modelId) {
|
|
1056
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1057
|
+
if ((0, validation_1.isComfyModel)(modelId)) {
|
|
1058
|
+
return Object.keys(types_1.SupportedComfySchedulers);
|
|
1059
|
+
}
|
|
1060
|
+
return Object.keys(types_1.SupportedForgeSchedulers);
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
635
1063
|
}
|
|
636
1064
|
exports.default = ProjectsApi;
|
|
637
1065
|
//# sourceMappingURL=index.js.map
|