@sogni-ai/sogni-client 4.0.0-alpha.21 → 4.0.0-alpha.22
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 +16 -0
- package/README.md +26 -15
- package/dist/Account/index.d.ts +15 -15
- package/dist/Account/index.js +15 -15
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js +0 -4
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js.map +1 -1
- package/dist/ApiClient/WebSocketClient/events.d.ts +10 -0
- package/dist/ApiClient/WebSocketClient/index.js +12 -2
- package/dist/ApiClient/WebSocketClient/index.js.map +1 -1
- package/dist/ApiClient/index.js +1 -1
- package/dist/ApiClient/index.js.map +1 -1
- package/dist/Projects/Job.d.ts +12 -3
- package/dist/Projects/Job.js +50 -16
- package/dist/Projects/Job.js.map +1 -1
- package/dist/Projects/Project.d.ts +1 -0
- package/dist/Projects/Project.js +10 -3
- package/dist/Projects/Project.js.map +1 -1
- package/dist/Projects/createJobRequestMessage.js +105 -12
- package/dist/Projects/createJobRequestMessage.js.map +1 -1
- package/dist/Projects/index.d.ts +74 -5
- package/dist/Projects/index.js +337 -33
- package/dist/Projects/index.js.map +1 -1
- package/dist/Projects/types/events.d.ts +5 -1
- package/dist/Projects/types/index.d.ts +113 -28
- package/dist/Projects/types/index.js +8 -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 +2 -2
- 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/package.json +1 -1
- package/src/Account/index.ts +15 -15
- package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/index.ts +0 -4
- package/src/ApiClient/WebSocketClient/events.ts +11 -0
- package/src/ApiClient/WebSocketClient/index.ts +12 -2
- package/src/ApiClient/index.ts +1 -1
- package/src/Projects/Job.ts +50 -16
- package/src/Projects/Project.ts +12 -6
- package/src/Projects/createJobRequestMessage.ts +143 -33
- package/src/Projects/index.ts +351 -33
- package/src/Projects/types/events.ts +6 -0
- package/src/Projects/types/index.ts +141 -30
- package/src/Projects/utils.ts +66 -1
- package/src/index.ts +16 -4
- package/src/lib/AuthManager/TokenAuthManager.ts +0 -2
package/src/Projects/index.ts
CHANGED
|
@@ -4,13 +4,17 @@ import {
|
|
|
4
4
|
EnhancementStrength,
|
|
5
5
|
EstimateRequest,
|
|
6
6
|
ImageUrlParams,
|
|
7
|
+
MediaUrlParams,
|
|
7
8
|
CostEstimation,
|
|
8
9
|
ProjectParams,
|
|
9
10
|
SizePreset,
|
|
10
|
-
SupportedModel
|
|
11
|
+
SupportedModel,
|
|
12
|
+
ImageProjectParams,
|
|
13
|
+
VideoProjectParams
|
|
11
14
|
} from './types';
|
|
12
15
|
import {
|
|
13
16
|
JobErrorData,
|
|
17
|
+
JobETAData,
|
|
14
18
|
JobProgressData,
|
|
15
19
|
JobResultData,
|
|
16
20
|
JobStateData,
|
|
@@ -27,7 +31,12 @@ import ErrorData from '../types/ErrorData';
|
|
|
27
31
|
import { SupernetType } from '../ApiClient/WebSocketClient/types';
|
|
28
32
|
import Cache from '../lib/Cache';
|
|
29
33
|
import { enhancementDefaults } from './Job';
|
|
30
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
getEnhacementStrength,
|
|
36
|
+
getVideoWorkflowType,
|
|
37
|
+
isVideoModel,
|
|
38
|
+
VIDEO_WORKFLOW_ASSETS
|
|
39
|
+
} from './utils';
|
|
31
40
|
import { TokenType } from '../types/token';
|
|
32
41
|
import { validateSampler } from '../lib/validation';
|
|
33
42
|
|
|
@@ -35,6 +44,35 @@ const sizePresetCache = new Cache<SizePreset[]>(10 * 60 * 1000);
|
|
|
35
44
|
const GARBAGE_COLLECT_TIMEOUT = 30000;
|
|
36
45
|
const MODELS_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 24 hours
|
|
37
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Detect content type from a file object.
|
|
49
|
+
* For File objects in browser, uses the type property.
|
|
50
|
+
* Returns undefined if content type cannot be detected.
|
|
51
|
+
*/
|
|
52
|
+
function getFileContentType(file: File | Buffer | Blob): string | undefined {
|
|
53
|
+
if (file instanceof Blob && 'type' in file && file.type) {
|
|
54
|
+
return file.type;
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Convert file to a format compatible with fetch body.
|
|
61
|
+
* Converts Node.js Buffer to Blob for cross-platform compatibility.
|
|
62
|
+
*/
|
|
63
|
+
function toFetchBody(file: File | Buffer | Blob) {
|
|
64
|
+
// Node.js Buffer is not supported in browsers, so we can skip this conversion
|
|
65
|
+
if (typeof Buffer === 'undefined') {
|
|
66
|
+
return file;
|
|
67
|
+
}
|
|
68
|
+
if (Buffer.isBuffer(file)) {
|
|
69
|
+
// Copy Buffer data to a new ArrayBuffer to ensure type compatibility
|
|
70
|
+
const arrayBuffer = file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength);
|
|
71
|
+
return new Blob([arrayBuffer as ArrayBuffer]);
|
|
72
|
+
}
|
|
73
|
+
return file;
|
|
74
|
+
}
|
|
75
|
+
|
|
38
76
|
function mapErrorCodes(code: string): number {
|
|
39
77
|
switch (code) {
|
|
40
78
|
case 'serverRestarting':
|
|
@@ -64,6 +102,20 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
64
102
|
return this._availableModels;
|
|
65
103
|
}
|
|
66
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Check if a model produces video output using the cached models list.
|
|
107
|
+
* Uses the `media` property from the models API when available,
|
|
108
|
+
* falls back to model ID prefix check if models aren't loaded yet.
|
|
109
|
+
*/
|
|
110
|
+
isVideoModelId(modelId: string): boolean {
|
|
111
|
+
const model = this._supportedModels.data?.find((m) => m.id === modelId);
|
|
112
|
+
if (model) {
|
|
113
|
+
return model.media === 'video';
|
|
114
|
+
}
|
|
115
|
+
// Fallback to prefix check if models not loaded
|
|
116
|
+
return isVideoModel(modelId);
|
|
117
|
+
}
|
|
118
|
+
|
|
67
119
|
constructor(config: ApiConfig) {
|
|
68
120
|
super(config);
|
|
69
121
|
// Listen to server events and emit them as project and job events
|
|
@@ -71,6 +123,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
71
123
|
this.client.socket.on('swarmModels', this.handleSwarmModels.bind(this));
|
|
72
124
|
this.client.socket.on('jobState', this.handleJobState.bind(this));
|
|
73
125
|
this.client.socket.on('jobProgress', this.handleJobProgress.bind(this));
|
|
126
|
+
this.client.socket.on('jobETA', this.handleJobETA.bind(this));
|
|
74
127
|
this.client.socket.on('jobError', this.handleJobError.bind(this));
|
|
75
128
|
this.client.socket.on('jobResult', this.handleJobResult.bind(this));
|
|
76
129
|
// Listen to the server disconnect event
|
|
@@ -99,7 +152,8 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
99
152
|
this._availableModels = Object.entries(data).map(([id, workerCount]) => ({
|
|
100
153
|
id,
|
|
101
154
|
name: modelIndex[id]?.name || id.replace(/-/g, ' '),
|
|
102
|
-
workerCount
|
|
155
|
+
workerCount,
|
|
156
|
+
media: modelIndex[id]?.media || 'image'
|
|
103
157
|
}));
|
|
104
158
|
this.emit('availableModels', this._availableModels);
|
|
105
159
|
}
|
|
@@ -167,6 +221,15 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
167
221
|
}
|
|
168
222
|
}
|
|
169
223
|
|
|
224
|
+
private async handleJobETA(data: JobETAData) {
|
|
225
|
+
this.emit('job', {
|
|
226
|
+
type: 'jobETA',
|
|
227
|
+
projectId: data.jobID,
|
|
228
|
+
jobId: data.imgID || '',
|
|
229
|
+
etaSeconds: data.etaSeconds
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
170
233
|
private async handleJobResult(data: JobResultData) {
|
|
171
234
|
const project = this.projects.find((p) => p.id === data.jobID);
|
|
172
235
|
const passNSFWCheck = !data.triggeredNSFWFilter || !project || project.params.disableNSFWFilter;
|
|
@@ -174,11 +237,21 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
174
237
|
// If NSFW filter is triggered, image will be only available for download if user explicitly
|
|
175
238
|
// disabled the filter for this project
|
|
176
239
|
if (passNSFWCheck && !data.userCanceled) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
240
|
+
// Use media endpoint for video models, image endpoint for image models
|
|
241
|
+
const isVideo = project && this.isVideoModelId(project.params.modelId);
|
|
242
|
+
if (isVideo) {
|
|
243
|
+
downloadUrl = await this.mediaDownloadUrl({
|
|
244
|
+
jobId: data.jobID,
|
|
245
|
+
id: data.imgID,
|
|
246
|
+
type: 'complete'
|
|
247
|
+
});
|
|
248
|
+
} else {
|
|
249
|
+
downloadUrl = await this.downloadUrl({
|
|
250
|
+
jobId: data.jobID,
|
|
251
|
+
imageId: data.imgID,
|
|
252
|
+
type: 'complete'
|
|
253
|
+
});
|
|
254
|
+
}
|
|
182
255
|
}
|
|
183
256
|
|
|
184
257
|
this.emit('job', {
|
|
@@ -297,7 +370,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
297
370
|
case 'progress':
|
|
298
371
|
job._update({
|
|
299
372
|
status: 'processing',
|
|
300
|
-
//
|
|
373
|
+
// Just in case event comes out of order
|
|
301
374
|
step: Math.max(event.step, job.step),
|
|
302
375
|
stepCount: event.stepCount
|
|
303
376
|
});
|
|
@@ -305,6 +378,10 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
305
378
|
project._update({ status: 'processing' });
|
|
306
379
|
}
|
|
307
380
|
break;
|
|
381
|
+
case 'jobETA':
|
|
382
|
+
// ETA updates don't change job state, just pass through to listeners
|
|
383
|
+
// The event is already emitted, no need to update job data
|
|
384
|
+
break;
|
|
308
385
|
case 'preview':
|
|
309
386
|
job._update({ previewUrl: event.url });
|
|
310
387
|
break;
|
|
@@ -321,6 +398,17 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
321
398
|
}
|
|
322
399
|
case 'error':
|
|
323
400
|
job._update({ status: 'failed', error: event.error });
|
|
401
|
+
// Check if project should also fail when a job fails
|
|
402
|
+
// For video jobs (single image) or when all jobs have failed, propagate to project
|
|
403
|
+
const allJobsStarted = project.jobs.length >= project.params.numberOfMedia;
|
|
404
|
+
const allJobsFailed = allJobsStarted && project.jobs.every((j) => j.status === 'failed');
|
|
405
|
+
const isSingleJobProject = project.params.numberOfMedia === 1;
|
|
406
|
+
if (isSingleJobProject || allJobsFailed) {
|
|
407
|
+
project._update({
|
|
408
|
+
status: 'failed',
|
|
409
|
+
error: event.error
|
|
410
|
+
});
|
|
411
|
+
}
|
|
324
412
|
break;
|
|
325
413
|
}
|
|
326
414
|
}
|
|
@@ -343,33 +431,63 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
343
431
|
return Promise.resolve(this._availableModels);
|
|
344
432
|
}
|
|
345
433
|
return new Promise((resolve, reject) => {
|
|
434
|
+
let settled = false;
|
|
346
435
|
const timeoutId = setTimeout(() => {
|
|
347
|
-
|
|
436
|
+
if (!settled) {
|
|
437
|
+
settled = true;
|
|
438
|
+
this.off('availableModels', handler);
|
|
439
|
+
reject(new Error('Timeout waiting for models'));
|
|
440
|
+
}
|
|
348
441
|
}, timeout);
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
442
|
+
|
|
443
|
+
const handler = (models: AvailableModel[]) => {
|
|
444
|
+
// Only resolve when we get a non-empty models list
|
|
445
|
+
// Empty arrays may be emitted during disconnects/reconnects
|
|
446
|
+
if (models.length && !settled) {
|
|
447
|
+
settled = true;
|
|
448
|
+
clearTimeout(timeoutId);
|
|
449
|
+
this.off('availableModels', handler);
|
|
352
450
|
resolve(models);
|
|
353
|
-
} else {
|
|
354
|
-
reject(new Error('No models available'));
|
|
355
451
|
}
|
|
356
|
-
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
this.on('availableModels', handler);
|
|
357
455
|
});
|
|
358
456
|
}
|
|
359
457
|
|
|
360
458
|
/**
|
|
361
459
|
* Send new project request to the network. Returns project instance which can be used to track
|
|
362
|
-
* progress and get resulting images.
|
|
460
|
+
* progress and get resulting images or videos.
|
|
363
461
|
* @param data
|
|
364
462
|
*/
|
|
365
463
|
async create(data: ProjectParams): Promise<Project> {
|
|
366
464
|
const project = new Project({ ...data }, { api: this, logger: this.client.logger });
|
|
465
|
+
const request = createJobRequestMessage(project.id, data);
|
|
466
|
+
switch (data.type) {
|
|
467
|
+
case 'image':
|
|
468
|
+
await this._processImageAssets(project, data);
|
|
469
|
+
break;
|
|
470
|
+
case 'video':
|
|
471
|
+
await this._processVideoAssets(project, data);
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
await this.client.socket.send('jobRequest', request);
|
|
475
|
+
this.projects.push(project);
|
|
476
|
+
return project;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private async _processImageAssets(project: Project, data: ImageProjectParams) {
|
|
480
|
+
//Guide image
|
|
367
481
|
if (data.startingImage && data.startingImage !== true) {
|
|
368
482
|
await this.uploadGuideImage(project.id, data.startingImage);
|
|
369
483
|
}
|
|
484
|
+
|
|
485
|
+
// ControlNet image
|
|
370
486
|
if (data.controlNet?.image && data.controlNet.image !== true) {
|
|
371
487
|
await this.uploadCNImage(project.id, data.controlNet.image);
|
|
372
488
|
}
|
|
489
|
+
|
|
490
|
+
// Context images (Flux Kontext)
|
|
373
491
|
if (data.contextImages?.length) {
|
|
374
492
|
if (data.contextImages.length > 2) {
|
|
375
493
|
throw new ApiError(500, {
|
|
@@ -386,10 +504,21 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
386
504
|
})
|
|
387
505
|
);
|
|
388
506
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
private async _processVideoAssets(project: Project, data: VideoProjectParams) {
|
|
510
|
+
if (data?.referenceImage && data.referenceImage !== true) {
|
|
511
|
+
await this.uploadReferenceImage(project.id, data.referenceImage);
|
|
512
|
+
}
|
|
513
|
+
if (data?.referenceImageEnd && data.referenceImageEnd !== true) {
|
|
514
|
+
await this.uploadReferenceImageEnd(project.id, data.referenceImageEnd);
|
|
515
|
+
}
|
|
516
|
+
if (data?.referenceAudio && data.referenceAudio !== true) {
|
|
517
|
+
await this.uploadReferenceAudio(project.id, data.referenceAudio);
|
|
518
|
+
}
|
|
519
|
+
if (data?.referenceVideo && data.referenceVideo !== true) {
|
|
520
|
+
await this.uploadReferenceVideo(project.id, data.referenceVideo);
|
|
521
|
+
}
|
|
393
522
|
}
|
|
394
523
|
|
|
395
524
|
/**
|
|
@@ -424,7 +553,6 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
424
553
|
}
|
|
425
554
|
// Remove project from the list to stop tracking it
|
|
426
555
|
this.projects = this.projects.filter((p) => p.id !== projectId);
|
|
427
|
-
|
|
428
556
|
// Cancel all jobs in the project
|
|
429
557
|
project.jobs.forEach((job) => {
|
|
430
558
|
if (!job.finished) {
|
|
@@ -440,13 +568,13 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
440
568
|
private async uploadGuideImage(projectId: string, file: File | Buffer | Blob) {
|
|
441
569
|
const imageId = getUUID();
|
|
442
570
|
const presignedUrl = await this.uploadUrl({
|
|
443
|
-
imageId
|
|
571
|
+
imageId,
|
|
444
572
|
jobId: projectId,
|
|
445
573
|
type: 'startingImage'
|
|
446
574
|
});
|
|
447
575
|
const res = await fetch(presignedUrl, {
|
|
448
576
|
method: 'PUT',
|
|
449
|
-
body: file
|
|
577
|
+
body: toFetchBody(file)
|
|
450
578
|
});
|
|
451
579
|
if (!res.ok) {
|
|
452
580
|
throw new ApiError(res.status, {
|
|
@@ -461,13 +589,13 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
461
589
|
private async uploadCNImage(projectId: string, file: File | Buffer | Blob) {
|
|
462
590
|
const imageId = getUUID();
|
|
463
591
|
const presignedUrl = await this.uploadUrl({
|
|
464
|
-
imageId
|
|
592
|
+
imageId,
|
|
465
593
|
jobId: projectId,
|
|
466
594
|
type: 'cnImage'
|
|
467
595
|
});
|
|
468
596
|
const res = await fetch(presignedUrl, {
|
|
469
597
|
method: 'PUT',
|
|
470
|
-
body: file
|
|
598
|
+
body: toFetchBody(file)
|
|
471
599
|
});
|
|
472
600
|
if (!res.ok) {
|
|
473
601
|
throw new ApiError(res.status, {
|
|
@@ -489,7 +617,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
489
617
|
});
|
|
490
618
|
const res = await fetch(presignedUrl, {
|
|
491
619
|
method: 'PUT',
|
|
492
|
-
body: file
|
|
620
|
+
body: toFetchBody(file)
|
|
493
621
|
});
|
|
494
622
|
if (!res.ok) {
|
|
495
623
|
throw new ApiError(res.status, {
|
|
@@ -501,6 +629,122 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
501
629
|
return imageId;
|
|
502
630
|
}
|
|
503
631
|
|
|
632
|
+
// ============================================
|
|
633
|
+
// VIDEO WORKFLOW UPLOADS (WAN 2.2)
|
|
634
|
+
// ============================================
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Upload reference image for WAN video workflows
|
|
638
|
+
* @internal
|
|
639
|
+
*/
|
|
640
|
+
private async uploadReferenceImage(projectId: string, file: File | Buffer | Blob) {
|
|
641
|
+
const imageId = getUUID();
|
|
642
|
+
const presignedUrl = await this.uploadUrl({
|
|
643
|
+
imageId,
|
|
644
|
+
jobId: projectId,
|
|
645
|
+
type: 'referenceImage'
|
|
646
|
+
});
|
|
647
|
+
const res = await fetch(presignedUrl, {
|
|
648
|
+
method: 'PUT',
|
|
649
|
+
body: toFetchBody(file)
|
|
650
|
+
});
|
|
651
|
+
if (!res.ok) {
|
|
652
|
+
throw new ApiError(res.status, {
|
|
653
|
+
status: 'error',
|
|
654
|
+
errorCode: 0,
|
|
655
|
+
message: 'Failed to upload reference image'
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
return imageId;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Upload reference image end for i2v interpolation
|
|
663
|
+
* @internal
|
|
664
|
+
*/
|
|
665
|
+
private async uploadReferenceImageEnd(projectId: string, file: File | Buffer | Blob) {
|
|
666
|
+
const imageId = getUUID();
|
|
667
|
+
const presignedUrl = await this.uploadUrl({
|
|
668
|
+
imageId,
|
|
669
|
+
jobId: projectId,
|
|
670
|
+
type: 'referenceImageEnd'
|
|
671
|
+
});
|
|
672
|
+
const res = await fetch(presignedUrl, {
|
|
673
|
+
method: 'PUT',
|
|
674
|
+
body: toFetchBody(file)
|
|
675
|
+
});
|
|
676
|
+
if (!res.ok) {
|
|
677
|
+
throw new ApiError(res.status, {
|
|
678
|
+
status: 'error',
|
|
679
|
+
errorCode: 0,
|
|
680
|
+
message: 'Failed to upload reference image end'
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
return imageId;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Upload reference audio for s2v workflows
|
|
688
|
+
* Supported formats: mp3, m4a, wav
|
|
689
|
+
* @internal
|
|
690
|
+
*/
|
|
691
|
+
private async uploadReferenceAudio(projectId: string, file: File | Buffer | Blob) {
|
|
692
|
+
const contentType = getFileContentType(file);
|
|
693
|
+
const presignedUrl = await this.mediaUploadUrl({
|
|
694
|
+
jobId: projectId,
|
|
695
|
+
type: 'referenceAudio'
|
|
696
|
+
});
|
|
697
|
+
const headers: Record<string, string> = {};
|
|
698
|
+
if (contentType) {
|
|
699
|
+
headers['Content-Type'] = contentType;
|
|
700
|
+
}
|
|
701
|
+
const res = await fetch(presignedUrl, {
|
|
702
|
+
method: 'PUT',
|
|
703
|
+
body: toFetchBody(file),
|
|
704
|
+
headers
|
|
705
|
+
});
|
|
706
|
+
if (!res.ok) {
|
|
707
|
+
throw new ApiError(res.status, {
|
|
708
|
+
status: 'error',
|
|
709
|
+
errorCode: 0,
|
|
710
|
+
message: 'Failed to upload reference audio'
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Upload reference video for animate workflows
|
|
717
|
+
* Supported formats: mp4, mov
|
|
718
|
+
* @internal
|
|
719
|
+
*/
|
|
720
|
+
private async uploadReferenceVideo(projectId: string, file: File | Buffer | Blob) {
|
|
721
|
+
const contentType = getFileContentType(file);
|
|
722
|
+
const presignedUrl = await this.mediaUploadUrl({
|
|
723
|
+
jobId: projectId,
|
|
724
|
+
type: 'referenceVideo'
|
|
725
|
+
});
|
|
726
|
+
const headers: Record<string, string> = {};
|
|
727
|
+
if (contentType) {
|
|
728
|
+
headers['Content-Type'] = contentType;
|
|
729
|
+
}
|
|
730
|
+
const res = await fetch(presignedUrl, {
|
|
731
|
+
method: 'PUT',
|
|
732
|
+
body: toFetchBody(file),
|
|
733
|
+
headers
|
|
734
|
+
});
|
|
735
|
+
if (!res.ok) {
|
|
736
|
+
throw new ApiError(res.status, {
|
|
737
|
+
status: 'error',
|
|
738
|
+
errorCode: 0,
|
|
739
|
+
message: 'Failed to upload reference video'
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// ============================================
|
|
745
|
+
// COST ESTIMATION
|
|
746
|
+
// ============================================
|
|
747
|
+
|
|
504
748
|
/**
|
|
505
749
|
* Estimate project cost
|
|
506
750
|
*/
|
|
@@ -522,7 +766,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
522
766
|
}: EstimateRequest): Promise<CostEstimation> {
|
|
523
767
|
let apiVersion = 2;
|
|
524
768
|
const pathParams = [
|
|
525
|
-
tokenType || '
|
|
769
|
+
tokenType || 'spark',
|
|
526
770
|
network,
|
|
527
771
|
model,
|
|
528
772
|
imageCount,
|
|
@@ -560,7 +804,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
560
804
|
};
|
|
561
805
|
}
|
|
562
806
|
|
|
563
|
-
async estimateEnhancementCost(strength: EnhancementStrength, tokenType: TokenType = '
|
|
807
|
+
async estimateEnhancementCost(strength: EnhancementStrength, tokenType: TokenType = 'spark') {
|
|
564
808
|
return this.estimateCost({
|
|
565
809
|
network: enhancementDefaults.network,
|
|
566
810
|
tokenType,
|
|
@@ -573,10 +817,13 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
573
817
|
});
|
|
574
818
|
}
|
|
575
819
|
|
|
820
|
+
// ============================================
|
|
821
|
+
// URL HELPERS
|
|
822
|
+
// ============================================
|
|
823
|
+
|
|
576
824
|
/**
|
|
577
825
|
* Get upload URL for image
|
|
578
826
|
* @internal
|
|
579
|
-
* @param params
|
|
580
827
|
*/
|
|
581
828
|
async uploadUrl(params: ImageUrlParams) {
|
|
582
829
|
const r = await this.client.rest.get<ApiResponse<{ uploadUrl: string }>>(
|
|
@@ -589,7 +836,6 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
589
836
|
/**
|
|
590
837
|
* Get download URL for image
|
|
591
838
|
* @internal
|
|
592
|
-
* @param params
|
|
593
839
|
*/
|
|
594
840
|
async downloadUrl(params: ImageUrlParams) {
|
|
595
841
|
const r = await this.client.rest.get<ApiResponse<{ downloadUrl: string }>>(
|
|
@@ -599,6 +845,34 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
599
845
|
return r.data.downloadUrl;
|
|
600
846
|
}
|
|
601
847
|
|
|
848
|
+
/**
|
|
849
|
+
* Get upload URL for media (video/audio)
|
|
850
|
+
* @internal
|
|
851
|
+
*/
|
|
852
|
+
async mediaUploadUrl(params: MediaUrlParams) {
|
|
853
|
+
const r = await this.client.rest.get<ApiResponse<{ uploadUrl: string }>>(
|
|
854
|
+
`/v1/media/uploadUrl`,
|
|
855
|
+
params
|
|
856
|
+
);
|
|
857
|
+
return r.data.uploadUrl;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Get download URL for media (video/audio)
|
|
862
|
+
* @internal
|
|
863
|
+
*/
|
|
864
|
+
async mediaDownloadUrl(params: MediaUrlParams) {
|
|
865
|
+
const r = await this.client.rest.get<ApiResponse<{ downloadUrl: string }>>(
|
|
866
|
+
`/v1/media/downloadUrl`,
|
|
867
|
+
params
|
|
868
|
+
);
|
|
869
|
+
return r.data.downloadUrl;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// ============================================
|
|
873
|
+
// MODEL/PRESET HELPERS
|
|
874
|
+
// ============================================
|
|
875
|
+
|
|
602
876
|
async getSupportedModels(forceRefresh = false) {
|
|
603
877
|
if (
|
|
604
878
|
this._supportedModels.data &&
|
|
@@ -617,7 +891,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
617
891
|
*
|
|
618
892
|
* @example
|
|
619
893
|
* ```ts
|
|
620
|
-
* const presets = await
|
|
894
|
+
* const presets = await sogni.projects.getSizePresets('fast', 'flux1-schnell-fp8');
|
|
621
895
|
* console.log(presets);
|
|
622
896
|
* ```
|
|
623
897
|
*
|
|
@@ -646,6 +920,49 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
646
920
|
return data;
|
|
647
921
|
}
|
|
648
922
|
|
|
923
|
+
/**
|
|
924
|
+
* Retrieves the video asset configuration for a given video model identifier.
|
|
925
|
+
* Validates whether the provided model ID corresponds to a video model. If it does,
|
|
926
|
+
* returns the appropriate video asset configuration based on the workflow type.
|
|
927
|
+
*
|
|
928
|
+
* @example Returned object for a model that implements image to video workflow:
|
|
929
|
+
* ```json
|
|
930
|
+
* {
|
|
931
|
+
* "workflowType": "i2v",
|
|
932
|
+
* "assets": {
|
|
933
|
+
* "referenceImage": "required",
|
|
934
|
+
* "referenceImageEnd": "optional",
|
|
935
|
+
* "referenceAudio": "forbidden",
|
|
936
|
+
* "referenceVideo": "forbidden"
|
|
937
|
+
* }
|
|
938
|
+
* }
|
|
939
|
+
* ```
|
|
940
|
+
*
|
|
941
|
+
* @param {string} modelId - The identifier of the video model to retrieve the configuration for.
|
|
942
|
+
* @return {Object} The video asset configuration object where key is asset field and value is
|
|
943
|
+
* either `required`, `forbidden` or `optional`. Returns `null` if no rules defined for the model.
|
|
944
|
+
* @throws {ApiError} Throws an error if the provided model ID is not a video model.
|
|
945
|
+
*/
|
|
946
|
+
async getVideoAssetConfig(modelId: string) {
|
|
947
|
+
if (!this.isVideoModelId(modelId)) {
|
|
948
|
+
throw new ApiError(400, {
|
|
949
|
+
status: 'error',
|
|
950
|
+
errorCode: 0,
|
|
951
|
+
message: `Model ${modelId} is not a video model`
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
const workflow = getVideoWorkflowType(modelId);
|
|
955
|
+
if (!workflow) {
|
|
956
|
+
return {
|
|
957
|
+
workflowType: null
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
return {
|
|
961
|
+
workflowType: workflow,
|
|
962
|
+
assets: VIDEO_WORKFLOW_ASSETS[workflow]
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
|
|
649
966
|
/**
|
|
650
967
|
* Get available models and their worker counts. Normally, you would get list once you connect
|
|
651
968
|
* to the server, but you can also call this method to get the list of available models manually.
|
|
@@ -662,7 +979,8 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
662
979
|
return {
|
|
663
980
|
id: model?.id || sid,
|
|
664
981
|
name: model?.name || sid.replace(/-/g, ' '),
|
|
665
|
-
workerCount
|
|
982
|
+
workerCount,
|
|
983
|
+
media: model?.media || 'image'
|
|
666
984
|
};
|
|
667
985
|
});
|
|
668
986
|
}
|
|
@@ -48,6 +48,11 @@ export interface JobProgress extends JobEventBase {
|
|
|
48
48
|
stepCount: number;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export interface JobETA extends JobEventBase {
|
|
52
|
+
type: 'jobETA';
|
|
53
|
+
etaSeconds: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
51
56
|
export interface JobPreview extends JobEventBase {
|
|
52
57
|
type: 'preview';
|
|
53
58
|
url: string;
|
|
@@ -75,6 +80,7 @@ export type JobEvent =
|
|
|
75
80
|
| JobInitiating
|
|
76
81
|
| JobStarted
|
|
77
82
|
| JobProgress
|
|
83
|
+
| JobETA
|
|
78
84
|
| JobPreview
|
|
79
85
|
| JobCompleted
|
|
80
86
|
| JobError;
|