@sogni-ai/sogni-client 4.0.0-alpha.5 → 4.0.0-alpha.50
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 +345 -0
- package/README.md +295 -58
- package/dist/Account/index.d.ts +18 -16
- package/dist/Account/index.js +42 -21
- 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.d.ts +2 -1
- package/dist/Projects/createJobRequestMessage.js +173 -14
- package/dist/Projects/createJobRequestMessage.js.map +1 -1
- package/dist/Projects/index.d.ts +114 -11
- package/dist/Projects/index.js +504 -47
- package/dist/Projects/index.js.map +1 -1
- package/dist/Projects/types/ComfySamplerParams.d.ts +0 -0
- package/dist/Projects/types/ComfySamplerParams.js +2 -0
- package/dist/Projects/types/ComfySamplerParams.js.map +1 -0
- package/dist/Projects/types/EstimationResponse.d.ts +2 -0
- package/dist/Projects/types/ModelOptions.d.ts +31 -0
- package/dist/Projects/types/ModelOptions.js +56 -0
- package/dist/Projects/types/ModelOptions.js.map +1 -0
- package/dist/Projects/types/ModelTiersRaw.d.ts +67 -0
- package/dist/Projects/types/ModelTiersRaw.js +15 -0
- package/dist/Projects/types/ModelTiersRaw.js.map +1 -0
- package/dist/Projects/types/events.d.ts +5 -1
- package/dist/Projects/types/index.d.ts +201 -42
- package/dist/Projects/types/index.js +8 -0
- package/dist/Projects/types/index.js.map +1 -1
- package/dist/Projects/utils/index.d.ts +20 -0
- package/dist/Projects/utils/index.js +82 -0
- package/dist/Projects/utils/index.js.map +1 -0
- package/dist/Projects/utils/samplers.d.ts +6 -0
- package/dist/Projects/utils/samplers.js +39 -0
- package/dist/Projects/utils/samplers.js.map +1 -0
- package/dist/Projects/utils/scheduler.d.ts +6 -0
- package/dist/Projects/utils/scheduler.js +30 -0
- package/dist/Projects/utils/scheduler.js.map +1 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.js +8 -3
- 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/{utils.js → utils/index.js} +1 -1
- package/dist/lib/utils/index.js.map +1 -0
- package/dist/lib/validation.d.ts +31 -2
- package/dist/lib/validation.js +80 -13
- package/dist/lib/validation.js.map +1 -1
- package/package.json +4 -4
- package/src/Account/index.ts +39 -20
- 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 +234 -34
- package/src/Projects/index.ts +533 -51
- package/src/Projects/types/ComfySamplerParams.ts +0 -0
- package/src/Projects/types/EstimationResponse.ts +2 -0
- package/src/Projects/types/ModelOptions.ts +92 -0
- package/src/Projects/types/ModelTiersRaw.ts +86 -0
- package/src/Projects/types/events.ts +6 -0
- package/src/Projects/types/index.ts +235 -45
- package/src/Projects/utils/index.ts +77 -0
- package/src/Projects/utils/samplers.ts +36 -0
- package/src/Projects/utils/scheduler.ts +27 -0
- package/src/index.ts +36 -9
- 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 +90 -17
- package/dist/Projects/types/SamplerParams.d.ts +0 -15
- package/dist/Projects/types/SamplerParams.js +0 -21
- package/dist/Projects/types/SamplerParams.js.map +0 -1
- package/dist/Projects/types/SchedulerParams.d.ts +0 -13
- package/dist/Projects/types/SchedulerParams.js +0 -19
- package/dist/Projects/types/SchedulerParams.js.map +0 -1
- package/dist/Projects/utils.d.ts +0 -2
- package/dist/Projects/utils.js +0 -14
- package/dist/Projects/utils.js.map +0 -1
- package/dist/lib/utils.js.map +0 -1
- package/src/Projects/types/SamplerParams.ts +0 -19
- package/src/Projects/types/SchedulerParams.ts +0 -17
- package/src/Projects/utils.ts +0 -12
- /package/dist/lib/{utils.d.ts → utils/index.d.ts} +0 -0
- /package/src/lib/{utils.ts → utils/index.ts} +0 -0
package/src/Projects/Project.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Job, { JobData } from './Job';
|
|
2
2
|
import DataEntity, { EntityEvents } from '../lib/DataEntity';
|
|
3
|
-
import { ProjectParams } from './types';
|
|
3
|
+
import { isImageParams, ProjectParams } from './types';
|
|
4
4
|
import cloneDeep from 'lodash/cloneDeep';
|
|
5
5
|
import ErrorData from '../types/ErrorData';
|
|
6
6
|
import getUUID from '../lib/getUUID';
|
|
@@ -8,8 +8,8 @@ import { RawJob, RawProject } from './types/RawProject';
|
|
|
8
8
|
import ProjectsApi from './index';
|
|
9
9
|
import { Logger } from '../lib/DefaultLogger';
|
|
10
10
|
|
|
11
|
-
// If project is not finished and had no updates for
|
|
12
|
-
const PROJECT_TIMEOUT = 60 * 1000;
|
|
11
|
+
// If project is not finished and had no updates for 2 minutes, force refresh
|
|
12
|
+
const PROJECT_TIMEOUT = 2 * 60 * 1000;
|
|
13
13
|
const MAX_FAILED_SYNC_ATTEMPTS = 3;
|
|
14
14
|
|
|
15
15
|
export type ProjectStatus =
|
|
@@ -39,6 +39,11 @@ export interface ProjectData {
|
|
|
39
39
|
params: ProjectParams;
|
|
40
40
|
queuePosition: number;
|
|
41
41
|
status: ProjectStatus;
|
|
42
|
+
/**
|
|
43
|
+
* Estimated completion time of the project (for long-running projects like video generation).
|
|
44
|
+
* Is equal to maximum job ETA
|
|
45
|
+
*/
|
|
46
|
+
eta?: Date;
|
|
42
47
|
error?: ErrorData;
|
|
43
48
|
}
|
|
44
49
|
/** @inline */
|
|
@@ -93,10 +98,22 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
93
98
|
return this.data.params;
|
|
94
99
|
}
|
|
95
100
|
|
|
101
|
+
get type() {
|
|
102
|
+
return this.params.type;
|
|
103
|
+
}
|
|
104
|
+
|
|
96
105
|
get status() {
|
|
97
106
|
return this.data.status;
|
|
98
107
|
}
|
|
99
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Estimated time of completion in seconds (for long-running projects like video generation).
|
|
111
|
+
* Updated by ComfyUI workers during inference.
|
|
112
|
+
*/
|
|
113
|
+
get eta() {
|
|
114
|
+
return this.data.eta;
|
|
115
|
+
}
|
|
116
|
+
|
|
100
117
|
get finished() {
|
|
101
118
|
return ['completed', 'failed', 'canceled'].includes(this.status);
|
|
102
119
|
}
|
|
@@ -110,10 +127,10 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
110
127
|
*/
|
|
111
128
|
get progress() {
|
|
112
129
|
// Worker can reduce the number of steps in the job, so we need to calculate the progress based on the actual number of steps
|
|
113
|
-
const stepsPerJob = this.jobs.length ? this.jobs[0].stepCount : this.data.params.steps;
|
|
114
|
-
const jobCount = this.data.params.
|
|
130
|
+
const stepsPerJob = this.jobs.length ? this.jobs[0].stepCount : (this.data.params.steps ?? 0);
|
|
131
|
+
const jobCount = this.data.params.numberOfMedia;
|
|
115
132
|
const stepsDone = this._jobs.reduce((acc, job) => acc + job.step, 0);
|
|
116
|
-
return Math.round((stepsDone / (stepsPerJob * jobCount)) * 100);
|
|
133
|
+
return Math.round((stepsDone / ((stepsPerJob ?? 1) * jobCount)) * 100);
|
|
117
134
|
}
|
|
118
135
|
|
|
119
136
|
get queuePosition() {
|
|
@@ -156,10 +173,10 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
156
173
|
}
|
|
157
174
|
|
|
158
175
|
return new Promise((resolve, reject) => {
|
|
159
|
-
this.
|
|
176
|
+
this.once('completed', (images) => {
|
|
160
177
|
resolve(images);
|
|
161
178
|
});
|
|
162
|
-
this.
|
|
179
|
+
this.once('failed', (error) => {
|
|
163
180
|
reject(error);
|
|
164
181
|
});
|
|
165
182
|
});
|
|
@@ -192,7 +209,7 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
192
209
|
this._timeout = null;
|
|
193
210
|
}
|
|
194
211
|
if (keys.includes('status') || keys.includes('jobs')) {
|
|
195
|
-
const allJobsStarted = this.jobs.length >= this.params.
|
|
212
|
+
const allJobsStarted = this.jobs.length >= this.params.numberOfMedia;
|
|
196
213
|
const allJobsDone = this.jobs.every((job) => job.finished);
|
|
197
214
|
if (this.data.status === 'completed' && allJobsStarted && allJobsDone) {
|
|
198
215
|
return this.emit('completed', this.resultUrls);
|
|
@@ -203,6 +220,16 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
203
220
|
}
|
|
204
221
|
}
|
|
205
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Refresh the lastUpdated timestamp to prevent timeout.
|
|
225
|
+
* Used when receiving socket events that indicate the project is still active
|
|
226
|
+
* (e.g., jobETA events during long-running video generation).
|
|
227
|
+
* @internal
|
|
228
|
+
*/
|
|
229
|
+
_keepAlive() {
|
|
230
|
+
this.lastUpdated = new Date();
|
|
231
|
+
}
|
|
232
|
+
|
|
206
233
|
/**
|
|
207
234
|
* This is internal method to add a job to the project. Do not call this directly.
|
|
208
235
|
* @internal
|
|
@@ -231,7 +258,11 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
231
258
|
private _checkForTimeout() {
|
|
232
259
|
if (this.lastUpdated.getTime() + PROJECT_TIMEOUT < Date.now()) {
|
|
233
260
|
this._syncToServer().catch((error) => {
|
|
234
|
-
|
|
261
|
+
// 404 errors are expected when project is still initializing and not yet available via REST API
|
|
262
|
+
// Only log non-404 errors to avoid confusing users
|
|
263
|
+
if (error.status !== 404) {
|
|
264
|
+
this._logger.error(error);
|
|
265
|
+
}
|
|
235
266
|
this._failedSyncAttempts++;
|
|
236
267
|
if (this._failedSyncAttempts >= MAX_FAILED_SYNC_ATTEMPTS) {
|
|
237
268
|
this._logger.error(
|
|
@@ -298,11 +329,13 @@ class Project extends DataEntity<ProjectData, ProjectEventMap> {
|
|
|
298
329
|
const delta: Partial<ProjectData> = {
|
|
299
330
|
params: {
|
|
300
331
|
...this.data.params,
|
|
301
|
-
|
|
302
|
-
steps: data.stepCount
|
|
303
|
-
numberOfPreviews: data.previewCount
|
|
332
|
+
numberOfMedia: data.imageCount,
|
|
333
|
+
steps: data.stepCount
|
|
304
334
|
}
|
|
305
335
|
};
|
|
336
|
+
if (delta.params && isImageParams(delta.params)) {
|
|
337
|
+
delta.params.numberOfPreviews = data.previewCount;
|
|
338
|
+
}
|
|
306
339
|
if (PROJECT_STATUS_MAP[data.status]) {
|
|
307
340
|
delta.status = PROJECT_STATUS_MAP[data.status];
|
|
308
341
|
}
|
|
@@ -1,11 +1,70 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ImageProjectParams,
|
|
3
|
+
isImageParams,
|
|
4
|
+
isVideoParams,
|
|
5
|
+
ProjectParams,
|
|
6
|
+
VideoProjectParams
|
|
7
|
+
} from './types';
|
|
2
8
|
import { ControlNetParams, ControlNetParamsRaw } from './types/ControlNetParams';
|
|
3
9
|
import {
|
|
4
10
|
validateNumber,
|
|
5
11
|
validateCustomImageSize,
|
|
12
|
+
validateVideoSize,
|
|
13
|
+
validateTeacacheThreshold,
|
|
14
|
+
isComfyModel,
|
|
15
|
+
validateVideoDuration,
|
|
6
16
|
validateSampler,
|
|
7
17
|
validateScheduler
|
|
8
18
|
} from '../lib/validation';
|
|
19
|
+
import { getVideoWorkflowType, isVideoModel, VIDEO_WORKFLOW_ASSETS } from './utils';
|
|
20
|
+
import { ApiError } from '../ApiClient';
|
|
21
|
+
import { ImageModelOptions, ModelOptions, VideoModelOptions } from './types/ModelOptions';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validate that the provided assets match the workflow requirements.
|
|
25
|
+
* Throws an error if required assets are missing or forbidden assets are provided.
|
|
26
|
+
*/
|
|
27
|
+
function validateVideoWorkflowAssets(params: VideoProjectParams): void {
|
|
28
|
+
const workflowType = getVideoWorkflowType(params.modelId);
|
|
29
|
+
if (!workflowType) return;
|
|
30
|
+
|
|
31
|
+
const requirements = VIDEO_WORKFLOW_ASSETS[workflowType];
|
|
32
|
+
if (!requirements) return;
|
|
33
|
+
|
|
34
|
+
// Special case for i2v: at least ONE of referenceImage or referenceImageEnd required
|
|
35
|
+
if (workflowType === 'i2v') {
|
|
36
|
+
if (!params.referenceImage && !params.referenceImageEnd) {
|
|
37
|
+
throw new ApiError(400, {
|
|
38
|
+
status: 'error',
|
|
39
|
+
errorCode: 0,
|
|
40
|
+
message:
|
|
41
|
+
'i2v workflow requires at least one of referenceImage or referenceImageEnd. Please provide this asset.'
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check for missing required assets and forbidden assets
|
|
47
|
+
for (const [asset, requirement] of Object.entries(requirements)) {
|
|
48
|
+
const assetKey = asset as keyof VideoProjectParams;
|
|
49
|
+
const hasAsset = !!params[assetKey];
|
|
50
|
+
|
|
51
|
+
if (requirement === 'required' && !hasAsset) {
|
|
52
|
+
throw new ApiError(400, {
|
|
53
|
+
status: 'error',
|
|
54
|
+
errorCode: 0,
|
|
55
|
+
message: `${workflowType} workflow requires ${assetKey}. Please provide this asset.`
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (requirement === 'forbidden' && hasAsset) {
|
|
60
|
+
throw new ApiError(400, {
|
|
61
|
+
status: 'error',
|
|
62
|
+
errorCode: 0,
|
|
63
|
+
message: `${workflowType} workflow does not support ${assetKey}. Please remove this asset.`
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
9
68
|
|
|
10
69
|
// Mac worker can't process the data if some of the fields are missing, so we need to provide a default template
|
|
11
70
|
function getTemplate() {
|
|
@@ -118,49 +177,190 @@ function getControlNet(params: ControlNetParams): ControlNetParamsRaw[] {
|
|
|
118
177
|
return [cn];
|
|
119
178
|
}
|
|
120
179
|
|
|
121
|
-
function
|
|
180
|
+
function applyImageParams(
|
|
181
|
+
inputKeyframe: Record<string, any>,
|
|
182
|
+
params: ImageProjectParams,
|
|
183
|
+
options: ImageModelOptions
|
|
184
|
+
) {
|
|
185
|
+
const keyFrame: Record<string, any> = {
|
|
186
|
+
...inputKeyframe,
|
|
187
|
+
sizePreset: params.sizePreset,
|
|
188
|
+
hasContextImage1: !!params.contextImages?.[0],
|
|
189
|
+
hasContextImage2: !!params.contextImages?.[1],
|
|
190
|
+
hasContextImage3: !!params.contextImages?.[2],
|
|
191
|
+
hasContextImage4: !!params.contextImages?.[3],
|
|
192
|
+
hasContextImage5: !!params.contextImages?.[4],
|
|
193
|
+
hasContextImage6: !!params.contextImages?.[5]
|
|
194
|
+
};
|
|
195
|
+
// Sampler/scheduler handling: SDK validates and passes through as-is.
|
|
196
|
+
// sogni-socket normalizes values for both ComfyUI and Forge workers.
|
|
197
|
+
if (isComfyModel(params.modelId)) {
|
|
198
|
+
// ComfyUI models use comfySampler/comfyScheduler fields
|
|
199
|
+
keyFrame.comfySampler = validateSampler(params.sampler, options);
|
|
200
|
+
keyFrame.comfyScheduler = validateScheduler(params.scheduler, options);
|
|
201
|
+
} else {
|
|
202
|
+
// Legacy Forge models use scheduler/timeStepSpacing fields
|
|
203
|
+
keyFrame.scheduler = validateSampler(params.sampler, options);
|
|
204
|
+
keyFrame.timeStepSpacing = validateScheduler(params.scheduler, options);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (params.startingImage) {
|
|
208
|
+
keyFrame.hasStartingImage = true;
|
|
209
|
+
keyFrame.strengthIsEnabled = true;
|
|
210
|
+
keyFrame.strength = 1 - (Number(params.startingImageStrength) || 0.5);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (params.controlNet) {
|
|
214
|
+
keyFrame.currentControlNetsJob = getControlNet(params.controlNet);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Set sizePreset to 'custom' if width/height are provided but sizePreset is not set
|
|
218
|
+
let effectiveSizePreset = params.sizePreset;
|
|
219
|
+
if (params.width && params.height && !params.sizePreset) {
|
|
220
|
+
effectiveSizePreset = 'custom';
|
|
221
|
+
}
|
|
222
|
+
keyFrame.sizePreset = effectiveSizePreset;
|
|
223
|
+
|
|
224
|
+
if (effectiveSizePreset === 'custom' && params.width && params.height) {
|
|
225
|
+
keyFrame.width = validateCustomImageSize(params.width);
|
|
226
|
+
keyFrame.height = validateCustomImageSize(params.height);
|
|
227
|
+
}
|
|
228
|
+
return keyFrame;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function applyVideoParams(
|
|
232
|
+
inputKeyframe: Record<string, any>,
|
|
233
|
+
params: VideoProjectParams,
|
|
234
|
+
options: VideoModelOptions
|
|
235
|
+
) {
|
|
236
|
+
if (!isVideoModel(params.modelId)) {
|
|
237
|
+
throw new ApiError(400, {
|
|
238
|
+
status: 'error',
|
|
239
|
+
errorCode: 0,
|
|
240
|
+
message: 'Video generation is only supported for video models.'
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
validateVideoWorkflowAssets(params);
|
|
244
|
+
const keyFrame: Record<string, any> = { ...inputKeyframe };
|
|
245
|
+
if (params.referenceImage) {
|
|
246
|
+
keyFrame.hasReferenceImage = true;
|
|
247
|
+
}
|
|
248
|
+
if (params.referenceImageEnd) {
|
|
249
|
+
keyFrame.hasReferenceImageEnd = true;
|
|
250
|
+
}
|
|
251
|
+
if (params.referenceAudio) {
|
|
252
|
+
keyFrame.hasReferenceAudio = true;
|
|
253
|
+
}
|
|
254
|
+
if (params.referenceVideo) {
|
|
255
|
+
keyFrame.hasReferenceVideo = true;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Video generation parameters
|
|
259
|
+
if (params.frames !== undefined) {
|
|
260
|
+
keyFrame.frames = params.frames;
|
|
261
|
+
}
|
|
262
|
+
if (params.duration !== undefined) {
|
|
263
|
+
const duration = validateVideoDuration(params.duration);
|
|
264
|
+
keyFrame.frames = duration * 16 + 1;
|
|
265
|
+
}
|
|
266
|
+
if (params.fps !== undefined) {
|
|
267
|
+
keyFrame.fps = params.fps;
|
|
268
|
+
}
|
|
269
|
+
if (params.shift !== undefined) {
|
|
270
|
+
keyFrame.shift = params.shift;
|
|
271
|
+
}
|
|
272
|
+
if (params.teacacheThreshold !== undefined) {
|
|
273
|
+
const validatedThreshold = validateTeacacheThreshold(params.teacacheThreshold);
|
|
274
|
+
if (validatedThreshold !== undefined) {
|
|
275
|
+
keyFrame.teacacheThreshold = validatedThreshold;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// S2V audio parameters
|
|
280
|
+
if (params.audioStart !== undefined) {
|
|
281
|
+
keyFrame.audioStart = params.audioStart;
|
|
282
|
+
}
|
|
283
|
+
if (params.audioDuration !== undefined) {
|
|
284
|
+
keyFrame.audioDuration = params.audioDuration;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Animate video parameters (for animate-move, animate-replace)
|
|
288
|
+
if (params.videoStart !== undefined) {
|
|
289
|
+
keyFrame.videoStart = params.videoStart;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Validate and set video dimensions (minimum 480px for Wan 2.2 models)
|
|
293
|
+
if (params.width && params.height) {
|
|
294
|
+
keyFrame.width = validateVideoSize(params.width, 'width');
|
|
295
|
+
keyFrame.height = validateVideoSize(params.height, 'height');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
keyFrame.comfySampler = validateSampler(params.sampler, options);
|
|
299
|
+
keyFrame.comfyScheduler = validateScheduler(params.scheduler, options);
|
|
300
|
+
|
|
301
|
+
return keyFrame;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function createJobRequestMessage(id: string, params: ProjectParams, options: ModelOptions) {
|
|
122
305
|
const template = getTemplate();
|
|
306
|
+
// Base keyFrame with common params
|
|
307
|
+
let keyFrame: Record<string, any> = {
|
|
308
|
+
...template.keyFrames[0],
|
|
309
|
+
steps: params.steps,
|
|
310
|
+
guidanceScale: params.guidance,
|
|
311
|
+
modelID: params.modelId,
|
|
312
|
+
negativePrompt: params.negativePrompt,
|
|
313
|
+
seed: params.seed,
|
|
314
|
+
positivePrompt: params.positivePrompt,
|
|
315
|
+
stylePrompt: params.stylePrompt
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
switch (params.type) {
|
|
319
|
+
case 'image':
|
|
320
|
+
if (options.type !== 'image') {
|
|
321
|
+
throw new ApiError(400, {
|
|
322
|
+
status: 'error',
|
|
323
|
+
errorCode: 0,
|
|
324
|
+
message:
|
|
325
|
+
'Invalid model type. Model does not support image generation. Please use a different model.'
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
keyFrame = applyImageParams(keyFrame, params, options);
|
|
329
|
+
break;
|
|
330
|
+
case 'video':
|
|
331
|
+
if (options.type !== 'video') {
|
|
332
|
+
throw new ApiError(400, {
|
|
333
|
+
status: 'error',
|
|
334
|
+
errorCode: 0,
|
|
335
|
+
message:
|
|
336
|
+
'Invalid model type. Model does not support video generation. Please use a different model.'
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
keyFrame = applyVideoParams(keyFrame, params, options);
|
|
340
|
+
break;
|
|
341
|
+
default:
|
|
342
|
+
throw new ApiError(400, {
|
|
343
|
+
status: 'error',
|
|
344
|
+
errorCode: 0,
|
|
345
|
+
message: 'Invalid project type. Must be "image" or "video".'
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
123
349
|
const jobRequest: Record<string, any> = {
|
|
124
350
|
...template,
|
|
125
|
-
keyFrames: [
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
scheduler: validateSampler(params.sampler),
|
|
129
|
-
timeStepSpacing: validateScheduler(params.scheduler),
|
|
130
|
-
steps: params.steps,
|
|
131
|
-
guidanceScale: params.guidance,
|
|
132
|
-
modelID: params.modelId,
|
|
133
|
-
negativePrompt: params.negativePrompt,
|
|
134
|
-
seed: params.seed,
|
|
135
|
-
positivePrompt: params.positivePrompt,
|
|
136
|
-
stylePrompt: params.stylePrompt,
|
|
137
|
-
hasStartingImage: !!params.startingImage,
|
|
138
|
-
hasContextImage1: !!params.contextImages?.[0],
|
|
139
|
-
hasContextImage2: !!params.contextImages?.[1],
|
|
140
|
-
strengthIsEnabled: !!params.startingImage,
|
|
141
|
-
strength: !!params.startingImage
|
|
142
|
-
? 1 - (Number(params.startingImageStrength) || 0.5)
|
|
143
|
-
: undefined,
|
|
144
|
-
sizePreset: params.sizePreset
|
|
145
|
-
}
|
|
146
|
-
],
|
|
147
|
-
previews: params.numberOfPreviews || 0,
|
|
148
|
-
numberOfImages: params.numberOfImages,
|
|
351
|
+
keyFrames: [keyFrame],
|
|
352
|
+
previews: isImageParams(params) ? params.numberOfPreviews || 0 : 0,
|
|
353
|
+
numberOfImages: params.numberOfMedia || 1,
|
|
149
354
|
jobID: id,
|
|
150
355
|
disableSafety: !!params.disableNSFWFilter,
|
|
151
356
|
tokenType: params.tokenType,
|
|
152
|
-
outputFormat: params.outputFormat || 'png'
|
|
357
|
+
outputFormat: params.outputFormat || (isVideoParams(params) ? 'mp4' : 'png')
|
|
153
358
|
};
|
|
359
|
+
|
|
154
360
|
if (params.network) {
|
|
155
361
|
jobRequest.network = params.network;
|
|
156
362
|
}
|
|
157
|
-
|
|
158
|
-
jobRequest.keyFrames[0].currentControlNetsJob = getControlNet(params.controlNet);
|
|
159
|
-
}
|
|
160
|
-
if (params.sizePreset === 'custom') {
|
|
161
|
-
jobRequest.keyFrames[0].width = validateCustomImageSize(params.width);
|
|
162
|
-
jobRequest.keyFrames[0].height = validateCustomImageSize(params.height);
|
|
163
|
-
}
|
|
363
|
+
|
|
164
364
|
return jobRequest;
|
|
165
365
|
}
|
|
166
366
|
|