@sogni-ai/sogni-client 4.2.0-alpha.2 → 4.2.0-alpha.21
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 +148 -0
- package/CLAUDE.md +25 -3
- package/README.md +411 -136
- package/dist/Account/index.d.ts +4 -2
- package/dist/Account/index.js +27 -23
- package/dist/Account/index.js.map +1 -1
- package/dist/Account/types.d.ts +7 -0
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.d.ts +3 -1
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js +26 -2
- package/dist/ApiClient/WebSocketClient/BrowserWebSocketClient/index.js.map +1 -1
- package/dist/ApiClient/WebSocketClient/eventSubscriptions.d.ts +33 -0
- package/dist/ApiClient/WebSocketClient/eventSubscriptions.js +39 -0
- package/dist/ApiClient/WebSocketClient/eventSubscriptions.js.map +1 -0
- package/dist/ApiClient/WebSocketClient/events.d.ts +24 -7
- package/dist/ApiClient/WebSocketClient/index.d.ts +5 -1
- package/dist/ApiClient/WebSocketClient/index.js +24 -1
- package/dist/ApiClient/WebSocketClient/index.js.map +1 -1
- package/dist/ApiClient/WebSocketClient/messages.d.ts +2 -0
- package/dist/ApiClient/WebSocketClient/types.d.ts +2 -0
- package/dist/ApiClient/index.d.ts +6 -1
- package/dist/ApiClient/index.js +7 -3
- package/dist/ApiClient/index.js.map +1 -1
- package/dist/Chat/ChatTools.d.ts +5 -49
- package/dist/Chat/ChatTools.js +311 -88
- package/dist/Chat/ChatTools.js.map +1 -1
- package/dist/Chat/index.d.ts +11 -2
- package/dist/Chat/index.js +78 -4
- package/dist/Chat/index.js.map +1 -1
- package/dist/Chat/modelRouting.d.ts +100 -0
- package/dist/Chat/modelRouting.js +441 -0
- package/dist/Chat/modelRouting.js.map +1 -0
- package/dist/Chat/sogniHostedTools.generated.json +529 -0
- package/dist/Chat/tools.d.ts +9 -55
- package/dist/Chat/tools.js +72 -228
- package/dist/Chat/tools.js.map +1 -1
- package/dist/Chat/types.d.ts +91 -2
- package/dist/CreativeWorkflows/index.d.ts +23 -0
- package/dist/CreativeWorkflows/index.js +274 -0
- package/dist/CreativeWorkflows/index.js.map +1 -0
- package/dist/CreativeWorkflows/types.d.ts +106 -0
- package/dist/CreativeWorkflows/types.js +3 -0
- package/dist/CreativeWorkflows/types.js.map +1 -0
- package/dist/Projects/Job.d.ts +6 -0
- package/dist/Projects/Job.js +60 -5
- package/dist/Projects/Job.js.map +1 -1
- package/dist/Projects/Project.js +15 -3
- package/dist/Projects/Project.js.map +1 -1
- package/dist/Projects/createJobRequestMessage.js +140 -6
- package/dist/Projects/createJobRequestMessage.js.map +1 -1
- package/dist/Projects/index.d.ts +10 -1
- package/dist/Projects/index.js +197 -58
- package/dist/Projects/index.js.map +1 -1
- package/dist/Projects/types/ModelOptions.d.ts +3 -3
- package/dist/Projects/types/ModelOptions.js +12 -5
- package/dist/Projects/types/ModelOptions.js.map +1 -1
- package/dist/Projects/types/ModelTiersRaw.d.ts +7 -7
- package/dist/Projects/types/RawProject.d.ts +2 -0
- package/dist/Projects/types/events.d.ts +5 -4
- package/dist/Projects/types/index.d.ts +77 -7
- package/dist/Projects/types/index.js.map +1 -1
- package/dist/Projects/utils/index.d.ts +8 -1
- package/dist/Projects/utils/index.js +22 -8
- package/dist/Projects/utils/index.js.map +1 -1
- package/dist/index.d.ts +28 -3
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/RestClient.d.ts +4 -1
- package/dist/lib/RestClient.js +17 -9
- package/dist/lib/RestClient.js.map +1 -1
- package/dist/lib/mediaValidation.d.ts +16 -0
- package/dist/lib/mediaValidation.js +280 -0
- package/dist/lib/mediaValidation.js.map +1 -0
- package/dist/lib/validation.d.ts +6 -1
- package/dist/lib/validation.js +28 -2
- package/dist/lib/validation.js.map +1 -1
- package/llms-full.txt +372 -133
- package/llms.txt +197 -86
- package/package.json +13 -4
- package/src/Account/index.ts +22 -2
- package/src/Account/types.ts +7 -0
- package/src/ApiClient/WebSocketClient/BrowserWebSocketClient/index.ts +47 -3
- package/src/ApiClient/WebSocketClient/eventSubscriptions.ts +92 -0
- package/src/ApiClient/WebSocketClient/events.ts +25 -7
- package/src/ApiClient/WebSocketClient/index.ts +33 -1
- package/src/ApiClient/WebSocketClient/messages.ts +2 -0
- package/src/ApiClient/WebSocketClient/types.ts +2 -0
- package/src/ApiClient/index.ts +32 -2
- package/src/Chat/ChatTools.ts +395 -95
- package/src/Chat/index.ts +149 -5
- package/src/Chat/modelRouting.ts +602 -0
- package/src/Chat/sogniHostedTools.generated.json +529 -0
- package/src/Chat/tools.ts +98 -245
- package/src/Chat/types.ts +100 -2
- package/src/CreativeWorkflows/index.ts +290 -0
- package/src/CreativeWorkflows/types.ts +134 -0
- package/src/Projects/Job.ts +76 -5
- package/src/Projects/Project.ts +13 -3
- package/src/Projects/createJobRequestMessage.ts +152 -13
- package/src/Projects/index.ts +230 -52
- package/src/Projects/types/ModelOptions.ts +15 -8
- package/src/Projects/types/ModelTiersRaw.ts +7 -7
- package/src/Projects/types/RawProject.ts +2 -0
- package/src/Projects/types/events.ts +5 -4
- package/src/Projects/types/index.ts +86 -6
- package/src/Projects/utils/index.ts +24 -8
- package/src/index.ts +93 -0
- package/src/lib/RestClient.ts +15 -5
- package/src/lib/mediaValidation.ts +367 -0
- package/src/lib/validation.ts +38 -2
package/src/Projects/index.ts
CHANGED
|
@@ -70,6 +70,29 @@ function getFileContentType(file: File | Buffer | Blob): string | undefined {
|
|
|
70
70
|
if (file instanceof Blob && 'type' in file && file.type) {
|
|
71
71
|
return file.type;
|
|
72
72
|
}
|
|
73
|
+
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(file)) {
|
|
74
|
+
if (file.length >= 12) {
|
|
75
|
+
if (file[0] === 0xff && file[1] === 0xd8 && file[2] === 0xff) return 'image/jpeg';
|
|
76
|
+
if (file[0] === 0x89 && file[1] === 0x50 && file[2] === 0x4e && file[3] === 0x47) {
|
|
77
|
+
return 'image/png';
|
|
78
|
+
}
|
|
79
|
+
if (file.toString('ascii', 0, 4) === 'RIFF' && file.toString('ascii', 8, 12) === 'WEBP') {
|
|
80
|
+
return 'image/webp';
|
|
81
|
+
}
|
|
82
|
+
if (file.toString('ascii', 0, 3) === 'GIF') return 'image/gif';
|
|
83
|
+
if (file.toString('ascii', 4, 8) === 'ftyp') {
|
|
84
|
+
const brand = file.toString('ascii', 8, 12).toLowerCase();
|
|
85
|
+
if (brand.includes('m4a') || brand.includes('m4b')) return 'audio/mp4';
|
|
86
|
+
if (brand.includes('qt')) return 'video/quicktime';
|
|
87
|
+
return 'video/mp4';
|
|
88
|
+
}
|
|
89
|
+
if (file.toString('ascii', 0, 4) === 'RIFF' && file.toString('ascii', 8, 12) === 'WAVE') {
|
|
90
|
+
return 'audio/wav';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (file.length >= 3 && file.toString('ascii', 0, 3) === 'ID3') return 'audio/mpeg';
|
|
94
|
+
if (file.length >= 2 && file[0] === 0xff && (file[1] & 0xe0) === 0xe0) return 'audio/mpeg';
|
|
95
|
+
}
|
|
73
96
|
return undefined;
|
|
74
97
|
}
|
|
75
98
|
|
|
@@ -282,15 +305,17 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
282
305
|
}
|
|
283
306
|
|
|
284
307
|
private async handleJobProgress(data: JobProgressData) {
|
|
285
|
-
|
|
308
|
+
const event: JobEvent = {
|
|
286
309
|
type: 'progress',
|
|
287
310
|
projectId: data.jobID,
|
|
288
311
|
jobId: data.imgID,
|
|
289
|
-
step: data.step,
|
|
290
|
-
stepCount: data.stepCount
|
|
291
|
-
|
|
312
|
+
...(typeof data.step === 'number' ? { step: data.step } : {}),
|
|
313
|
+
...(typeof data.stepCount === 'number' ? { stepCount: data.stepCount } : {}),
|
|
314
|
+
...(typeof data.progress === 'number' ? { progress: data.progress } : {})
|
|
315
|
+
};
|
|
316
|
+
this.emit('job', event);
|
|
292
317
|
|
|
293
|
-
if (data.hasImage) {
|
|
318
|
+
if (data.hasImage === true) {
|
|
294
319
|
this.downloadUrl({
|
|
295
320
|
jobId: data.jobID,
|
|
296
321
|
imageId: data.imgID,
|
|
@@ -318,7 +343,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
318
343
|
private async handleJobResult(data: JobResultData) {
|
|
319
344
|
const project = this.projects.find((p) => p.id === data.jobID);
|
|
320
345
|
const passNSFWCheck = !data.triggeredNSFWFilter || !project || project.params.disableNSFWFilter;
|
|
321
|
-
let downloadUrl = data.resultUrl || null; // Use
|
|
346
|
+
let downloadUrl = data.resultUrl || data.videoUrl || data.videoFile || null; // Use result URL from event if provided
|
|
322
347
|
|
|
323
348
|
// If no resultUrl provided and NSFW check passes, generate download URL
|
|
324
349
|
if (!downloadUrl && passNSFWCheck && !data.userCanceled) {
|
|
@@ -350,16 +375,25 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
350
375
|
}
|
|
351
376
|
|
|
352
377
|
// Update the job directly with the result URL to prevent duplicate API calls
|
|
378
|
+
let performedStepCount = data.performedStepCount;
|
|
379
|
+
let seed = data.lastSeed !== undefined ? Number(data.lastSeed) : undefined;
|
|
353
380
|
if (project) {
|
|
354
381
|
const job = project.job(data.imgID);
|
|
355
382
|
if (job) {
|
|
383
|
+
performedStepCount =
|
|
384
|
+
typeof performedStepCount === 'number'
|
|
385
|
+
? performedStepCount
|
|
386
|
+
: job.stepCount > 0
|
|
387
|
+
? job.stepCount
|
|
388
|
+
: job.step;
|
|
389
|
+
seed = typeof seed === 'number' && Number.isFinite(seed) ? seed : job.seed;
|
|
356
390
|
job._update({
|
|
357
391
|
status: data.userCanceled ? 'canceled' : 'completed',
|
|
358
|
-
step:
|
|
359
|
-
seed
|
|
392
|
+
step: performedStepCount,
|
|
393
|
+
seed,
|
|
360
394
|
resultUrl: downloadUrl,
|
|
361
|
-
isNSFW: data.triggeredNSFWFilter,
|
|
362
|
-
userCanceled: data.userCanceled
|
|
395
|
+
isNSFW: Boolean(data.triggeredNSFWFilter),
|
|
396
|
+
userCanceled: Boolean(data.userCanceled)
|
|
363
397
|
});
|
|
364
398
|
}
|
|
365
399
|
}
|
|
@@ -369,11 +403,11 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
369
403
|
type: 'completed',
|
|
370
404
|
projectId: data.jobID,
|
|
371
405
|
jobId: data.imgID,
|
|
372
|
-
steps:
|
|
373
|
-
seed
|
|
406
|
+
...(typeof performedStepCount === 'number' ? { steps: performedStepCount } : {}),
|
|
407
|
+
...(typeof seed === 'number' && Number.isFinite(seed) ? { seed } : {}),
|
|
374
408
|
resultUrl: downloadUrl,
|
|
375
|
-
isNSFW: data.triggeredNSFWFilter,
|
|
376
|
-
userCanceled: data.userCanceled
|
|
409
|
+
isNSFW: Boolean(data.triggeredNSFWFilter),
|
|
410
|
+
userCanceled: Boolean(data.userCanceled)
|
|
377
411
|
});
|
|
378
412
|
}
|
|
379
413
|
|
|
@@ -483,12 +517,27 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
483
517
|
});
|
|
484
518
|
break;
|
|
485
519
|
case 'progress':
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
520
|
+
{
|
|
521
|
+
const delta: {
|
|
522
|
+
status: 'processing';
|
|
523
|
+
step?: number;
|
|
524
|
+
stepCount?: number;
|
|
525
|
+
externalProgress?: number;
|
|
526
|
+
} = {
|
|
527
|
+
status: 'processing'
|
|
528
|
+
};
|
|
529
|
+
if (typeof event.step === 'number') {
|
|
530
|
+
// Just in case event comes out of order
|
|
531
|
+
delta.step = Math.max(event.step, job.step);
|
|
532
|
+
}
|
|
533
|
+
if (typeof event.stepCount === 'number') {
|
|
534
|
+
delta.stepCount = event.stepCount;
|
|
535
|
+
}
|
|
536
|
+
if (typeof event.progress === 'number') {
|
|
537
|
+
delta.externalProgress = event.progress;
|
|
538
|
+
}
|
|
539
|
+
job._update(delta);
|
|
540
|
+
}
|
|
492
541
|
if (project.status !== 'processing') {
|
|
493
542
|
project._update({ status: 'processing' });
|
|
494
543
|
}
|
|
@@ -515,13 +564,29 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
515
564
|
job._update({ previewUrl: event.url });
|
|
516
565
|
break;
|
|
517
566
|
case 'completed': {
|
|
518
|
-
|
|
567
|
+
const delta: {
|
|
568
|
+
status: 'completed' | 'canceled';
|
|
569
|
+
resultUrl: string | null;
|
|
570
|
+
isNSFW: boolean;
|
|
571
|
+
userCanceled: boolean;
|
|
572
|
+
step?: number;
|
|
573
|
+
seed?: number;
|
|
574
|
+
} = {
|
|
519
575
|
status: event.userCanceled ? 'canceled' : 'completed',
|
|
520
|
-
step: event.steps,
|
|
521
|
-
seed: event.seed,
|
|
522
576
|
resultUrl: event.resultUrl,
|
|
523
577
|
isNSFW: event.isNSFW,
|
|
524
578
|
userCanceled: event.userCanceled
|
|
579
|
+
};
|
|
580
|
+
if (typeof event.steps === 'number') {
|
|
581
|
+
delta.step = event.steps;
|
|
582
|
+
} else if (job.stepCount > 0) {
|
|
583
|
+
delta.step = job.stepCount;
|
|
584
|
+
}
|
|
585
|
+
if (typeof event.seed === 'number' && Number.isFinite(event.seed)) {
|
|
586
|
+
delta.seed = event.seed;
|
|
587
|
+
}
|
|
588
|
+
job._update({
|
|
589
|
+
...delta
|
|
525
590
|
});
|
|
526
591
|
break;
|
|
527
592
|
}
|
|
@@ -592,7 +657,11 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
592
657
|
async create(data: ProjectParams): Promise<Project> {
|
|
593
658
|
const project = new Project({ ...data }, { api: this, logger: this.client.logger });
|
|
594
659
|
const modelOptions = await this.getModelOptions(data.modelId);
|
|
595
|
-
const
|
|
660
|
+
const requestParams = {
|
|
661
|
+
...data,
|
|
662
|
+
appSource: data.appSource || this.client.appSource
|
|
663
|
+
} as ProjectParams;
|
|
664
|
+
const request = createJobRequestMessage(project.id, requestParams, modelOptions);
|
|
596
665
|
|
|
597
666
|
switch (data.type) {
|
|
598
667
|
case 'image':
|
|
@@ -600,6 +669,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
600
669
|
break;
|
|
601
670
|
case 'video':
|
|
602
671
|
await this._processVideoAssets(project, data);
|
|
672
|
+
this._annotateVideoAssetContentTypes(request, data);
|
|
603
673
|
break;
|
|
604
674
|
case 'audio':
|
|
605
675
|
// No assets to upload for audio
|
|
@@ -621,7 +691,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
621
691
|
await this.uploadCNImage(project.id, data.controlNet.image);
|
|
622
692
|
}
|
|
623
693
|
|
|
624
|
-
// Context images (Flux.2 Dev supports up to 6; Qwen Image Edit Plus supports up to 3; Flux Kontext supports up to 2)
|
|
694
|
+
// Context images (GPT Image 2 supports up to 16; Flux.2 Dev supports up to 6; Qwen Image Edit Plus supports up to 3; Flux Kontext supports up to 2)
|
|
625
695
|
if (data.contextImages?.length) {
|
|
626
696
|
const maxContextImages = getMaxContextImages(data.modelId);
|
|
627
697
|
if (data.contextImages.length > maxContextImages) {
|
|
@@ -634,7 +704,11 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
634
704
|
await Promise.all(
|
|
635
705
|
data.contextImages.map((image, index) => {
|
|
636
706
|
if (image && image !== true) {
|
|
637
|
-
return this.uploadContextImage(
|
|
707
|
+
return this.uploadContextImage(
|
|
708
|
+
project.id,
|
|
709
|
+
index as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15,
|
|
710
|
+
image
|
|
711
|
+
);
|
|
638
712
|
}
|
|
639
713
|
})
|
|
640
714
|
);
|
|
@@ -659,6 +733,29 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
659
733
|
}
|
|
660
734
|
}
|
|
661
735
|
|
|
736
|
+
private _annotateVideoAssetContentTypes(request: Record<string, any>, data: VideoProjectParams) {
|
|
737
|
+
const keyFrame = request.keyFrames?.[0];
|
|
738
|
+
if (!keyFrame) return;
|
|
739
|
+
|
|
740
|
+
if (data.referenceImage && data.referenceImage !== true) {
|
|
741
|
+
keyFrame.referenceImageContentType = getFileContentType(data.referenceImage);
|
|
742
|
+
}
|
|
743
|
+
if (data.referenceImageEnd && data.referenceImageEnd !== true) {
|
|
744
|
+
keyFrame.referenceImageEndContentType = getFileContentType(data.referenceImageEnd);
|
|
745
|
+
}
|
|
746
|
+
if (data.referenceAudio && data.referenceAudio !== true) {
|
|
747
|
+
keyFrame.referenceAudioContentType = getFileContentType(data.referenceAudio);
|
|
748
|
+
}
|
|
749
|
+
if (data.referenceAudioIdentity && data.referenceAudioIdentity !== true) {
|
|
750
|
+
const contentType = getFileContentType(data.referenceAudioIdentity);
|
|
751
|
+
keyFrame.referenceAudioIdentityContentType = contentType;
|
|
752
|
+
keyFrame.referenceAudioContentType ??= contentType;
|
|
753
|
+
}
|
|
754
|
+
if (data.referenceVideo && data.referenceVideo !== true) {
|
|
755
|
+
keyFrame.referenceVideoContentType = getFileContentType(data.referenceVideo);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
662
759
|
/**
|
|
663
760
|
* Get project by id, this API returns project data from the server only if the project is
|
|
664
761
|
* completed or failed. If the project is still processing, it will throw 404 error.
|
|
@@ -703,16 +800,36 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
703
800
|
}
|
|
704
801
|
}
|
|
705
802
|
|
|
803
|
+
/**
|
|
804
|
+
* Notify the socket server to cancel a project this client has timed out waiting for.
|
|
805
|
+
* This preserves the local timeout failure state while still using the normal artist
|
|
806
|
+
* cancellation protocol so the server aborts worker/vendor-side work.
|
|
807
|
+
* @internal
|
|
808
|
+
*/
|
|
809
|
+
async _notifyProjectTimedOut(projectId: string) {
|
|
810
|
+
await this.client.socket.send('jobError', {
|
|
811
|
+
jobID: projectId,
|
|
812
|
+
error: 'artistCanceled',
|
|
813
|
+
error_message: 'artistCanceled',
|
|
814
|
+
isFromWorker: false
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
706
818
|
private async uploadGuideImage(projectId: string, file: File | Buffer | Blob) {
|
|
707
819
|
const imageId = getUUID();
|
|
820
|
+
const contentType = getFileContentType(file);
|
|
708
821
|
const presignedUrl = await this.uploadUrl({
|
|
709
822
|
imageId,
|
|
710
823
|
jobId: projectId,
|
|
711
|
-
type: 'startingImage'
|
|
824
|
+
type: 'startingImage',
|
|
825
|
+
contentType
|
|
712
826
|
});
|
|
827
|
+
const headers: Record<string, string> = {};
|
|
828
|
+
if (contentType) headers['Content-Type'] = contentType;
|
|
713
829
|
const res = await fetch(presignedUrl, {
|
|
714
830
|
method: 'PUT',
|
|
715
|
-
body: toFetchBody(file)
|
|
831
|
+
body: toFetchBody(file),
|
|
832
|
+
headers
|
|
716
833
|
});
|
|
717
834
|
if (!res.ok) {
|
|
718
835
|
throw new ApiError(res.status, {
|
|
@@ -726,14 +843,19 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
726
843
|
|
|
727
844
|
private async uploadCNImage(projectId: string, file: File | Buffer | Blob) {
|
|
728
845
|
const imageId = getUUID();
|
|
846
|
+
const contentType = getFileContentType(file);
|
|
729
847
|
const presignedUrl = await this.uploadUrl({
|
|
730
848
|
imageId,
|
|
731
849
|
jobId: projectId,
|
|
732
|
-
type: 'cnImage'
|
|
850
|
+
type: 'cnImage',
|
|
851
|
+
contentType
|
|
733
852
|
});
|
|
853
|
+
const headers: Record<string, string> = {};
|
|
854
|
+
if (contentType) headers['Content-Type'] = contentType;
|
|
734
855
|
const res = await fetch(presignedUrl, {
|
|
735
856
|
method: 'PUT',
|
|
736
|
-
body: toFetchBody(file)
|
|
857
|
+
body: toFetchBody(file),
|
|
858
|
+
headers
|
|
737
859
|
});
|
|
738
860
|
if (!res.ok) {
|
|
739
861
|
throw new ApiError(res.status, {
|
|
@@ -747,20 +869,41 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
747
869
|
|
|
748
870
|
private async uploadContextImage(
|
|
749
871
|
projectId: string,
|
|
750
|
-
index: 0 | 1 | 2 | 3 | 4 | 5,
|
|
872
|
+
index: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15,
|
|
751
873
|
file: File | Buffer | Blob
|
|
752
874
|
) {
|
|
753
875
|
const imageId = getUUID();
|
|
754
|
-
const imageIndex = (index + 1) as
|
|
876
|
+
const imageIndex = (index + 1) as
|
|
877
|
+
| 1
|
|
878
|
+
| 2
|
|
879
|
+
| 3
|
|
880
|
+
| 4
|
|
881
|
+
| 5
|
|
882
|
+
| 6
|
|
883
|
+
| 7
|
|
884
|
+
| 8
|
|
885
|
+
| 9
|
|
886
|
+
| 10
|
|
887
|
+
| 11
|
|
888
|
+
| 12
|
|
889
|
+
| 13
|
|
890
|
+
| 14
|
|
891
|
+
| 15
|
|
892
|
+
| 16;
|
|
893
|
+
const contentType = getFileContentType(file);
|
|
755
894
|
const presignedUrl = await this.uploadUrl({
|
|
756
895
|
imageId,
|
|
757
896
|
jobId: projectId,
|
|
758
|
-
type: `contextImage${imageIndex}
|
|
897
|
+
type: `contextImage${imageIndex}`,
|
|
898
|
+
contentType
|
|
759
899
|
});
|
|
760
900
|
const body = toFetchBody(file);
|
|
901
|
+
const headers: Record<string, string> = {};
|
|
902
|
+
if (contentType) headers['Content-Type'] = contentType;
|
|
761
903
|
const res = await fetch(presignedUrl, {
|
|
762
904
|
method: 'PUT',
|
|
763
|
-
body
|
|
905
|
+
body,
|
|
906
|
+
headers
|
|
764
907
|
});
|
|
765
908
|
if (!res.ok) {
|
|
766
909
|
throw new ApiError(res.status, {
|
|
@@ -782,14 +925,19 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
782
925
|
*/
|
|
783
926
|
private async uploadReferenceImage(projectId: string, file: File | Buffer | Blob) {
|
|
784
927
|
const imageId = getUUID();
|
|
928
|
+
const contentType = getFileContentType(file);
|
|
785
929
|
const presignedUrl = await this.uploadUrl({
|
|
786
930
|
imageId,
|
|
787
931
|
jobId: projectId,
|
|
788
|
-
type: 'referenceImage'
|
|
932
|
+
type: 'referenceImage',
|
|
933
|
+
contentType
|
|
789
934
|
});
|
|
935
|
+
const headers: Record<string, string> = {};
|
|
936
|
+
if (contentType) headers['Content-Type'] = contentType;
|
|
790
937
|
const res = await fetch(presignedUrl, {
|
|
791
938
|
method: 'PUT',
|
|
792
|
-
body: toFetchBody(file)
|
|
939
|
+
body: toFetchBody(file),
|
|
940
|
+
headers
|
|
793
941
|
});
|
|
794
942
|
if (!res.ok) {
|
|
795
943
|
throw new ApiError(res.status, {
|
|
@@ -807,14 +955,19 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
807
955
|
*/
|
|
808
956
|
private async uploadReferenceImageEnd(projectId: string, file: File | Buffer | Blob) {
|
|
809
957
|
const imageId = getUUID();
|
|
958
|
+
const contentType = getFileContentType(file);
|
|
810
959
|
const presignedUrl = await this.uploadUrl({
|
|
811
960
|
imageId,
|
|
812
961
|
jobId: projectId,
|
|
813
|
-
type: 'referenceImageEnd'
|
|
962
|
+
type: 'referenceImageEnd',
|
|
963
|
+
contentType
|
|
814
964
|
});
|
|
965
|
+
const headers: Record<string, string> = {};
|
|
966
|
+
if (contentType) headers['Content-Type'] = contentType;
|
|
815
967
|
const res = await fetch(presignedUrl, {
|
|
816
968
|
method: 'PUT',
|
|
817
|
-
body: toFetchBody(file)
|
|
969
|
+
body: toFetchBody(file),
|
|
970
|
+
headers
|
|
818
971
|
});
|
|
819
972
|
if (!res.ok) {
|
|
820
973
|
throw new ApiError(res.status, {
|
|
@@ -836,7 +989,8 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
836
989
|
const contentType = getFileContentType(file);
|
|
837
990
|
const presignedUrl = await this.mediaUploadUrl({
|
|
838
991
|
jobId: projectId,
|
|
839
|
-
type: 'referenceAudio'
|
|
992
|
+
type: 'referenceAudio',
|
|
993
|
+
contentType
|
|
840
994
|
});
|
|
841
995
|
const headers: Record<string, string> = {};
|
|
842
996
|
if (contentType) {
|
|
@@ -865,7 +1019,8 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
865
1019
|
const contentType = getFileContentType(file);
|
|
866
1020
|
const presignedUrl = await this.mediaUploadUrl({
|
|
867
1021
|
jobId: projectId,
|
|
868
|
-
type: 'referenceVideo'
|
|
1022
|
+
type: 'referenceVideo',
|
|
1023
|
+
contentType
|
|
869
1024
|
});
|
|
870
1025
|
const headers: Record<string, string> = {};
|
|
871
1026
|
if (contentType) {
|
|
@@ -906,7 +1061,9 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
906
1061
|
sizePreset,
|
|
907
1062
|
guidance,
|
|
908
1063
|
sampler,
|
|
909
|
-
contextImages
|
|
1064
|
+
contextImages,
|
|
1065
|
+
gptImageQuality,
|
|
1066
|
+
outputFormat
|
|
910
1067
|
}: EstimateRequest): Promise<CostEstimation> {
|
|
911
1068
|
let apiVersion = 2;
|
|
912
1069
|
const modelOptions = await this.getModelOptions(model);
|
|
@@ -932,14 +1089,18 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
932
1089
|
} else {
|
|
933
1090
|
pathParams.push(0, 0);
|
|
934
1091
|
}
|
|
935
|
-
if (sampler) {
|
|
1092
|
+
if (sampler || contextImages !== undefined) {
|
|
936
1093
|
apiVersion = 3;
|
|
937
1094
|
pathParams.push(guidance || 0);
|
|
938
|
-
pathParams.push(validateSampler(sampler, modelOptions)!);
|
|
1095
|
+
pathParams.push(sampler ? validateSampler(sampler, modelOptions)! : '_');
|
|
939
1096
|
pathParams.push(contextImages || 0);
|
|
940
1097
|
}
|
|
1098
|
+
const queryParams = new URLSearchParams();
|
|
1099
|
+
if (gptImageQuality) queryParams.set('gptImageQuality', gptImageQuality);
|
|
1100
|
+
if (outputFormat) queryParams.set('outputFormat', outputFormat);
|
|
1101
|
+
const query = queryParams.toString();
|
|
941
1102
|
const r = await this.client.socket.get<EstimationResponse>(
|
|
942
|
-
`/api/v${apiVersion}/job/estimate/${pathParams.join('/')}`
|
|
1103
|
+
`/api/v${apiVersion}/job/estimate/${pathParams.join('/')}${query ? `?${query}` : ''}`
|
|
943
1104
|
);
|
|
944
1105
|
return {
|
|
945
1106
|
token: r.quote.project.costInToken,
|
|
@@ -978,6 +1139,7 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
978
1139
|
* - frames: The total number of frames in the video.
|
|
979
1140
|
* - fps: The frames per second for the video.
|
|
980
1141
|
* - steps: Number of steps.
|
|
1142
|
+
* - hasVideoInput: Whether to price a Seedance estimate with video input.
|
|
981
1143
|
* @return {Promise<Object>} Returns an object containing the estimated costs for the video in different units:
|
|
982
1144
|
* - token: Cost in tokens.
|
|
983
1145
|
* - usd: Cost in USD.
|
|
@@ -985,21 +1147,37 @@ class ProjectsApi extends ApiGroup<ProjectApiEvents> {
|
|
|
985
1147
|
* - sogni: Cost in Sogni.
|
|
986
1148
|
*/
|
|
987
1149
|
async estimateVideoCost(params: VideoEstimateRequest) {
|
|
988
|
-
const
|
|
1150
|
+
const frames = params.frames
|
|
1151
|
+
? params.frames
|
|
1152
|
+
: calculateVideoFrames(params.model, params.duration, params.fps);
|
|
1153
|
+
const numberOfMedia = params.numberOfMedia ?? 1;
|
|
1154
|
+
const pathParams: Array<string | number> = [
|
|
989
1155
|
params.tokenType,
|
|
990
1156
|
params.model,
|
|
991
1157
|
params.width,
|
|
992
1158
|
params.height,
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
: calculateVideoFrames(params.model, params.duration, params.fps),
|
|
996
|
-
params.fps,
|
|
997
|
-
params.steps,
|
|
998
|
-
params.numberOfMedia
|
|
1159
|
+
frames,
|
|
1160
|
+
params.fps
|
|
999
1161
|
];
|
|
1162
|
+
if (params.steps !== undefined && params.steps !== null) {
|
|
1163
|
+
pathParams.push(params.steps);
|
|
1164
|
+
pathParams.push(numberOfMedia);
|
|
1165
|
+
} else if (numberOfMedia !== 1) {
|
|
1166
|
+
pathParams.push(0);
|
|
1167
|
+
pathParams.push(numberOfMedia);
|
|
1168
|
+
}
|
|
1000
1169
|
const path = pathParams.map((p) => encodeURIComponent(p)).join('/');
|
|
1170
|
+
const query = new URLSearchParams();
|
|
1171
|
+
const hasVideoInput =
|
|
1172
|
+
params.hasVideoInput === true ||
|
|
1173
|
+
Boolean(params.referenceVideo) ||
|
|
1174
|
+
(Array.isArray(params.referenceVideoUrls) && params.referenceVideoUrls.length > 0);
|
|
1175
|
+
if (hasVideoInput) {
|
|
1176
|
+
query.set('hasVideoInput', '1');
|
|
1177
|
+
}
|
|
1178
|
+
const queryString = query.toString();
|
|
1001
1179
|
const r = await this.client.socket.get<EstimationResponse>(
|
|
1002
|
-
`/api/v1/job-video/estimate/${path}`
|
|
1180
|
+
`/api/v1/job-video/estimate/${path}${queryString ? `?${queryString}` : ''}`
|
|
1003
1181
|
);
|
|
1004
1182
|
return {
|
|
1005
1183
|
token: r.quote.project.costInToken,
|
|
@@ -29,9 +29,9 @@ export interface ImageModelOptions {
|
|
|
29
29
|
|
|
30
30
|
export interface VideoModelOptions {
|
|
31
31
|
type: 'video';
|
|
32
|
-
steps
|
|
33
|
-
guidance
|
|
34
|
-
fps
|
|
32
|
+
steps?: NumRange;
|
|
33
|
+
guidance?: NumRange;
|
|
34
|
+
fps?: Options<number>;
|
|
35
35
|
sampler: Options<string>;
|
|
36
36
|
scheduler: Options<string>;
|
|
37
37
|
}
|
|
@@ -98,14 +98,21 @@ export function mapComfyImageTier(tier: ComfyImageTier): ImageModelOptions {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
export function mapVideoTier(tier: VideoTier): VideoModelOptions {
|
|
101
|
-
|
|
101
|
+
const options: VideoModelOptions = {
|
|
102
102
|
type: 'video',
|
|
103
|
-
steps: mapRange(tier.steps),
|
|
104
|
-
guidance: mapRange(tier.guidance),
|
|
105
103
|
scheduler: mapOptions(tier.comfyScheduler, schedulerValueToAlias),
|
|
106
|
-
sampler: mapOptions(tier.comfySampler, samplerValueToAlias)
|
|
107
|
-
fps: tier.fps
|
|
104
|
+
sampler: mapOptions(tier.comfySampler, samplerValueToAlias)
|
|
108
105
|
};
|
|
106
|
+
if (tier.steps) {
|
|
107
|
+
options.steps = mapRange(tier.steps);
|
|
108
|
+
}
|
|
109
|
+
if (tier.guidance) {
|
|
110
|
+
options.guidance = mapRange(tier.guidance);
|
|
111
|
+
}
|
|
112
|
+
if (tier.fps) {
|
|
113
|
+
options.fps = tier.fps;
|
|
114
|
+
}
|
|
115
|
+
return options;
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
export function mapAudioTier(tier: AudioTier): AudioModelOptions {
|
|
@@ -56,14 +56,14 @@ export interface VideoTier {
|
|
|
56
56
|
audioDuration?: DurationDefaults;
|
|
57
57
|
audioStart?: DurationDefaults;
|
|
58
58
|
benchmark: Benchmark;
|
|
59
|
-
comfySampler
|
|
60
|
-
comfyScheduler
|
|
61
|
-
fps
|
|
62
|
-
frames
|
|
63
|
-
guidance
|
|
59
|
+
comfySampler?: StringDefaults;
|
|
60
|
+
comfyScheduler?: StringDefaults;
|
|
61
|
+
fps?: NumericOptions;
|
|
62
|
+
frames?: NumericDefaults;
|
|
63
|
+
guidance?: NumericDefaults;
|
|
64
64
|
height: NumericDefaults;
|
|
65
|
-
shift
|
|
66
|
-
steps
|
|
65
|
+
shift?: NumericDefaults;
|
|
66
|
+
steps?: NumericDefaults;
|
|
67
67
|
type: 'video';
|
|
68
68
|
videoStart?: DurationDefaults;
|
|
69
69
|
width: NumericDefaults;
|
|
@@ -44,8 +44,9 @@ export interface JobStarted extends JobEventBase {
|
|
|
44
44
|
|
|
45
45
|
export interface JobProgress extends JobEventBase {
|
|
46
46
|
type: 'progress';
|
|
47
|
-
step
|
|
48
|
-
stepCount
|
|
47
|
+
step?: number;
|
|
48
|
+
stepCount?: number;
|
|
49
|
+
progress?: number;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export interface JobETA extends JobEventBase {
|
|
@@ -60,8 +61,8 @@ export interface JobPreview extends JobEventBase {
|
|
|
60
61
|
|
|
61
62
|
export interface JobCompleted extends JobEventBase {
|
|
62
63
|
type: 'completed';
|
|
63
|
-
steps
|
|
64
|
-
seed
|
|
64
|
+
steps?: number;
|
|
65
|
+
seed?: number;
|
|
65
66
|
/**
|
|
66
67
|
* URL to the result image, could be null if the job was canceled or triggered NSFW filter while
|
|
67
68
|
* it was not disabled by the user
|