@sogni-ai/sogni-creative-agent-skill 2.1.0 → 2.1.2
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/README.md +23 -3
- package/SKILL.md +16 -9
- package/generated/creative-agent-runtime.mjs +1138 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +10 -6
- package/scripts/check-creative-agent-runtime.mjs +57 -0
- package/skill-package.json +1 -1
- package/sogni-agent.mjs +267 -699
- package/version.mjs +1 -1
|
@@ -0,0 +1,1138 @@
|
|
|
1
|
+
// Generated by sogni-creative-agent/scripts/sync-skill-runtime.mjs.
|
|
2
|
+
// Do not edit manually in sogni-creative-agent-skill.
|
|
3
|
+
|
|
4
|
+
function isLtxWorkflow(workflow) {
|
|
5
|
+
return workflow === 't2v' || workflow === 'i2v' || workflow === 'ia2v' || workflow === 'a2v' || workflow === 'v2v';
|
|
6
|
+
}
|
|
7
|
+
export const SKILL_RUNTIME_VERSION = '2026-05-04.1';
|
|
8
|
+
export const SEEDANCE_STORYBOARD_REFERENCE_PROMPT = 'Turn the video storyboard in @Image1 into a video by following the thumbnails and script for each segment in the image.';
|
|
9
|
+
export const LTX23_WORKFLOW_MODELS = Object.freeze({
|
|
10
|
+
t2v: 'ltx23-22b-fp8_t2v_distilled',
|
|
11
|
+
i2v: 'ltx23-22b-fp8_i2v_distilled',
|
|
12
|
+
ia2v: 'ltx23-22b-fp8_ia2v_distilled',
|
|
13
|
+
a2v: 'ltx23-22b-fp8_a2v_distilled',
|
|
14
|
+
v2v: 'ltx23-22b-fp8_v2v_distilled'
|
|
15
|
+
});
|
|
16
|
+
export const SEEDANCE_WORKFLOW_MODELS = Object.freeze({
|
|
17
|
+
t2v: 'seedance-2-0',
|
|
18
|
+
t2vFast: 'seedance-2-0-fast',
|
|
19
|
+
ia2v: 'seedance-2-0',
|
|
20
|
+
v2v: 'seedance-2-0'
|
|
21
|
+
});
|
|
22
|
+
export const VIDEO_MODEL_REGISTRY = Object.freeze({
|
|
23
|
+
[LTX23_WORKFLOW_MODELS.t2v]: {
|
|
24
|
+
workflow: 't2v',
|
|
25
|
+
family: 'ltx23',
|
|
26
|
+
defaultWidth: 1920,
|
|
27
|
+
defaultHeight: 1088,
|
|
28
|
+
minDimension: 640,
|
|
29
|
+
maxDimension: 2048,
|
|
30
|
+
dimensionMultiple: 64,
|
|
31
|
+
steps: 8,
|
|
32
|
+
guidance: 1.0,
|
|
33
|
+
fps: 24,
|
|
34
|
+
frameStep: 8,
|
|
35
|
+
minFrames: 25,
|
|
36
|
+
maxFrames: 505,
|
|
37
|
+
sampler: 'euler_ancestral',
|
|
38
|
+
scheduler: 'simple',
|
|
39
|
+
supportsNativeAudio: true
|
|
40
|
+
},
|
|
41
|
+
[LTX23_WORKFLOW_MODELS.i2v]: {
|
|
42
|
+
workflow: 'i2v',
|
|
43
|
+
family: 'ltx23',
|
|
44
|
+
defaultWidth: 1920,
|
|
45
|
+
defaultHeight: 1088,
|
|
46
|
+
minDimension: 640,
|
|
47
|
+
maxDimension: 2048,
|
|
48
|
+
dimensionMultiple: 64,
|
|
49
|
+
steps: 8,
|
|
50
|
+
guidance: 1.0,
|
|
51
|
+
fps: 24,
|
|
52
|
+
frameStep: 8,
|
|
53
|
+
minFrames: 25,
|
|
54
|
+
maxFrames: 505,
|
|
55
|
+
sampler: 'euler_ancestral',
|
|
56
|
+
scheduler: 'simple',
|
|
57
|
+
supportsNativeAudio: true
|
|
58
|
+
},
|
|
59
|
+
[LTX23_WORKFLOW_MODELS.ia2v]: {
|
|
60
|
+
workflow: 'ia2v',
|
|
61
|
+
family: 'ltx23',
|
|
62
|
+
defaultWidth: 1920,
|
|
63
|
+
defaultHeight: 1088,
|
|
64
|
+
minDimension: 640,
|
|
65
|
+
maxDimension: 2048,
|
|
66
|
+
dimensionMultiple: 64,
|
|
67
|
+
steps: 8,
|
|
68
|
+
guidance: 1.0,
|
|
69
|
+
fps: 24,
|
|
70
|
+
frameStep: 8,
|
|
71
|
+
minFrames: 25,
|
|
72
|
+
maxFrames: 505,
|
|
73
|
+
sampler: 'euler_ancestral',
|
|
74
|
+
scheduler: 'simple'
|
|
75
|
+
},
|
|
76
|
+
[LTX23_WORKFLOW_MODELS.a2v]: {
|
|
77
|
+
workflow: 'a2v',
|
|
78
|
+
family: 'ltx23',
|
|
79
|
+
defaultWidth: 1920,
|
|
80
|
+
defaultHeight: 1088,
|
|
81
|
+
minDimension: 640,
|
|
82
|
+
maxDimension: 2048,
|
|
83
|
+
dimensionMultiple: 64,
|
|
84
|
+
steps: 8,
|
|
85
|
+
guidance: 1.0,
|
|
86
|
+
fps: 24,
|
|
87
|
+
frameStep: 8,
|
|
88
|
+
minFrames: 25,
|
|
89
|
+
maxFrames: 505,
|
|
90
|
+
sampler: 'euler_ancestral',
|
|
91
|
+
scheduler: 'simple'
|
|
92
|
+
},
|
|
93
|
+
[LTX23_WORKFLOW_MODELS.v2v]: {
|
|
94
|
+
workflow: 'v2v',
|
|
95
|
+
family: 'ltx23',
|
|
96
|
+
defaultWidth: 1920,
|
|
97
|
+
defaultHeight: 1088,
|
|
98
|
+
minDimension: 640,
|
|
99
|
+
maxDimension: 2048,
|
|
100
|
+
dimensionMultiple: 64,
|
|
101
|
+
steps: 8,
|
|
102
|
+
guidance: 1.0,
|
|
103
|
+
fps: 25,
|
|
104
|
+
frameStep: 8,
|
|
105
|
+
minFrames: 25,
|
|
106
|
+
maxFrames: 505,
|
|
107
|
+
sampler: 'euler_ancestral',
|
|
108
|
+
scheduler: 'simple'
|
|
109
|
+
},
|
|
110
|
+
'wan_v2.2-14b-fp8_t2v_lightx2v': {
|
|
111
|
+
workflow: 't2v',
|
|
112
|
+
family: 'wan22',
|
|
113
|
+
defaultWidth: 640,
|
|
114
|
+
defaultHeight: 640,
|
|
115
|
+
minDimension: 480,
|
|
116
|
+
maxDimension: 1536,
|
|
117
|
+
dimensionMultiple: 16,
|
|
118
|
+
steps: 4,
|
|
119
|
+
guidance: 1.0,
|
|
120
|
+
fps: 32,
|
|
121
|
+
internalFps: 16,
|
|
122
|
+
frameStep: 1,
|
|
123
|
+
minFrames: 17,
|
|
124
|
+
maxFrames: 161,
|
|
125
|
+
sampler: 'euler',
|
|
126
|
+
scheduler: 'simple',
|
|
127
|
+
shift: 5.0
|
|
128
|
+
},
|
|
129
|
+
'wan_v2.2-14b-fp8_i2v_lightx2v': {
|
|
130
|
+
workflow: 'i2v',
|
|
131
|
+
family: 'wan22',
|
|
132
|
+
defaultWidth: 832,
|
|
133
|
+
defaultHeight: 480,
|
|
134
|
+
minDimension: 480,
|
|
135
|
+
maxDimension: 1536,
|
|
136
|
+
dimensionMultiple: 16,
|
|
137
|
+
steps: 4,
|
|
138
|
+
guidance: 1.0,
|
|
139
|
+
fps: 32,
|
|
140
|
+
internalFps: 16,
|
|
141
|
+
frameStep: 1,
|
|
142
|
+
minFrames: 17,
|
|
143
|
+
maxFrames: 321,
|
|
144
|
+
sampler: 'euler',
|
|
145
|
+
scheduler: 'simple',
|
|
146
|
+
shift: 8.0
|
|
147
|
+
},
|
|
148
|
+
'wan_v2.2-14b-fp8_s2v_lightx2v': {
|
|
149
|
+
workflow: 's2v',
|
|
150
|
+
family: 'wan22',
|
|
151
|
+
defaultWidth: 832,
|
|
152
|
+
defaultHeight: 480,
|
|
153
|
+
minDimension: 480,
|
|
154
|
+
maxDimension: 1536,
|
|
155
|
+
dimensionMultiple: 16,
|
|
156
|
+
steps: 4,
|
|
157
|
+
guidance: 1.0,
|
|
158
|
+
fps: 32,
|
|
159
|
+
internalFps: 16,
|
|
160
|
+
frameStep: 1,
|
|
161
|
+
minFrames: 17,
|
|
162
|
+
maxFrames: 321,
|
|
163
|
+
sampler: 'uni_pc',
|
|
164
|
+
scheduler: 'simple',
|
|
165
|
+
shift: 8.0
|
|
166
|
+
},
|
|
167
|
+
'wan_v2.2-14b-fp8_animate-move_lightx2v': {
|
|
168
|
+
workflow: 'animate-move',
|
|
169
|
+
family: 'wan22',
|
|
170
|
+
defaultWidth: 832,
|
|
171
|
+
defaultHeight: 480,
|
|
172
|
+
minDimension: 480,
|
|
173
|
+
maxDimension: 1536,
|
|
174
|
+
dimensionMultiple: 16,
|
|
175
|
+
steps: 4,
|
|
176
|
+
guidance: 1.0,
|
|
177
|
+
fps: 32,
|
|
178
|
+
internalFps: 16,
|
|
179
|
+
frameStep: 1,
|
|
180
|
+
minFrames: 17,
|
|
181
|
+
maxFrames: 321,
|
|
182
|
+
sampler: 'euler',
|
|
183
|
+
scheduler: 'simple',
|
|
184
|
+
shift: 8.0
|
|
185
|
+
},
|
|
186
|
+
'wan_v2.2-14b-fp8_animate-replace_lightx2v': {
|
|
187
|
+
workflow: 'animate-replace',
|
|
188
|
+
family: 'wan22',
|
|
189
|
+
defaultWidth: 832,
|
|
190
|
+
defaultHeight: 480,
|
|
191
|
+
minDimension: 480,
|
|
192
|
+
maxDimension: 1536,
|
|
193
|
+
dimensionMultiple: 16,
|
|
194
|
+
steps: 4,
|
|
195
|
+
guidance: 1.0,
|
|
196
|
+
fps: 32,
|
|
197
|
+
internalFps: 16,
|
|
198
|
+
frameStep: 1,
|
|
199
|
+
minFrames: 17,
|
|
200
|
+
maxFrames: 321,
|
|
201
|
+
sampler: 'euler',
|
|
202
|
+
scheduler: 'simple',
|
|
203
|
+
shift: 8.0
|
|
204
|
+
},
|
|
205
|
+
[SEEDANCE_WORKFLOW_MODELS.t2v]: {
|
|
206
|
+
workflow: 't2v',
|
|
207
|
+
family: 'seedance2',
|
|
208
|
+
defaultWidth: 1920,
|
|
209
|
+
defaultHeight: 1088,
|
|
210
|
+
minDimension: 1,
|
|
211
|
+
maxDimension: 99999,
|
|
212
|
+
dimensionMultiple: 1,
|
|
213
|
+
fps: 24,
|
|
214
|
+
frameStep: 1,
|
|
215
|
+
minFrames: 97,
|
|
216
|
+
maxFrames: 361,
|
|
217
|
+
supportsNativeAudio: true
|
|
218
|
+
},
|
|
219
|
+
[SEEDANCE_WORKFLOW_MODELS.t2vFast]: {
|
|
220
|
+
workflow: 't2v',
|
|
221
|
+
family: 'seedance2',
|
|
222
|
+
defaultWidth: 1280,
|
|
223
|
+
defaultHeight: 720,
|
|
224
|
+
minDimension: 1,
|
|
225
|
+
maxDimension: 1280,
|
|
226
|
+
dimensionMultiple: 1,
|
|
227
|
+
fps: 24,
|
|
228
|
+
frameStep: 1,
|
|
229
|
+
minFrames: 97,
|
|
230
|
+
maxFrames: 361,
|
|
231
|
+
supportsNativeAudio: true
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
export const EXPANDED_VIDEO_MODEL_REGISTRY = (() => {
|
|
235
|
+
const registry = { ...VIDEO_MODEL_REGISTRY };
|
|
236
|
+
for (const workflow of ['t2v', 'i2v', 'ia2v', 'a2v', 'v2v']) {
|
|
237
|
+
const ltx2Distilled = 'ltx2-19b-fp8_' + workflow + '_distilled';
|
|
238
|
+
const ltx2Quality = 'ltx2-19b-fp8_' + workflow;
|
|
239
|
+
const base = registry[LTX23_WORKFLOW_MODELS[workflow]];
|
|
240
|
+
if (!base)
|
|
241
|
+
continue;
|
|
242
|
+
registry[ltx2Distilled] = {
|
|
243
|
+
...base,
|
|
244
|
+
family: 'ltx2',
|
|
245
|
+
defaultWidth: 768,
|
|
246
|
+
defaultHeight: 768,
|
|
247
|
+
minDimension: 480,
|
|
248
|
+
maxDimension: 1536,
|
|
249
|
+
steps: 8,
|
|
250
|
+
supportsNativeAudio: workflow === 't2v' || workflow === 'i2v'
|
|
251
|
+
};
|
|
252
|
+
registry[ltx2Quality] = {
|
|
253
|
+
...registry[ltx2Distilled],
|
|
254
|
+
steps: 20
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return Object.freeze(registry);
|
|
258
|
+
})();
|
|
259
|
+
export const VIDEO_WORKFLOW_DEFAULT_MODELS = Object.freeze({
|
|
260
|
+
t2v: LTX23_WORKFLOW_MODELS.t2v,
|
|
261
|
+
i2v: 'wan_v2.2-14b-fp8_i2v_lightx2v',
|
|
262
|
+
s2v: 'wan_v2.2-14b-fp8_s2v_lightx2v',
|
|
263
|
+
ia2v: LTX23_WORKFLOW_MODELS.ia2v,
|
|
264
|
+
a2v: LTX23_WORKFLOW_MODELS.a2v,
|
|
265
|
+
'animate-move': 'wan_v2.2-14b-fp8_animate-move_lightx2v',
|
|
266
|
+
'animate-replace': 'wan_v2.2-14b-fp8_animate-replace_lightx2v',
|
|
267
|
+
v2v: LTX23_WORKFLOW_MODELS.v2v
|
|
268
|
+
});
|
|
269
|
+
export const VIDEO_MODEL_ALIASES = Object.freeze({
|
|
270
|
+
ltx23: LTX23_WORKFLOW_MODELS.t2v,
|
|
271
|
+
'ltx23-t2v': LTX23_WORKFLOW_MODELS.t2v,
|
|
272
|
+
'ltx23-i2v': LTX23_WORKFLOW_MODELS.i2v,
|
|
273
|
+
'ltx23-ia2v': LTX23_WORKFLOW_MODELS.ia2v,
|
|
274
|
+
'ltx23-a2v': LTX23_WORKFLOW_MODELS.a2v,
|
|
275
|
+
'ltx23-v2v': LTX23_WORKFLOW_MODELS.v2v,
|
|
276
|
+
wan22: 'wan_v2.2-14b-fp8_t2v_lightx2v',
|
|
277
|
+
'wan22-t2v': 'wan_v2.2-14b-fp8_t2v_lightx2v',
|
|
278
|
+
'wan22-i2v': 'wan_v2.2-14b-fp8_i2v_lightx2v',
|
|
279
|
+
'wan22-s2v': 'wan_v2.2-14b-fp8_s2v_lightx2v',
|
|
280
|
+
'wan22-animate-move': 'wan_v2.2-14b-fp8_animate-move_lightx2v',
|
|
281
|
+
'wan22-animate-replace': 'wan_v2.2-14b-fp8_animate-replace_lightx2v',
|
|
282
|
+
seedance2: SEEDANCE_WORKFLOW_MODELS.t2v,
|
|
283
|
+
'seedance2-t2v': SEEDANCE_WORKFLOW_MODELS.t2v,
|
|
284
|
+
'seedance2-fast': SEEDANCE_WORKFLOW_MODELS.t2vFast,
|
|
285
|
+
'seedance2-fast-t2v': SEEDANCE_WORKFLOW_MODELS.t2vFast,
|
|
286
|
+
'seedance2-ia2v': SEEDANCE_WORKFLOW_MODELS.ia2v,
|
|
287
|
+
'seedance2-v2v': SEEDANCE_WORKFLOW_MODELS.v2v
|
|
288
|
+
});
|
|
289
|
+
export const QUALITY_TIERS = Object.freeze({
|
|
290
|
+
fast: {
|
|
291
|
+
model: 'z_image_turbo_bf16',
|
|
292
|
+
steps: 8,
|
|
293
|
+
shortSide: null,
|
|
294
|
+
video: { steps: 8, shortSide: null }
|
|
295
|
+
},
|
|
296
|
+
hq: {
|
|
297
|
+
model: 'z_image_turbo_bf16',
|
|
298
|
+
steps: null,
|
|
299
|
+
shortSide: 768,
|
|
300
|
+
video: { steps: 8, shortSide: 1088 }
|
|
301
|
+
},
|
|
302
|
+
pro: {
|
|
303
|
+
model: 'flux2_dev_fp8',
|
|
304
|
+
steps: 40,
|
|
305
|
+
shortSide: 1024,
|
|
306
|
+
video: { steps: 20, shortSide: 1920 }
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
export function isLtx2Model(modelId) {
|
|
310
|
+
return modelId?.startsWith('ltx2-') || modelId?.startsWith('ltx23-') || false;
|
|
311
|
+
}
|
|
312
|
+
export function isWanModel(modelId) {
|
|
313
|
+
return modelId?.startsWith('wan_') || false;
|
|
314
|
+
}
|
|
315
|
+
export function isSeedanceModel(modelId) {
|
|
316
|
+
return modelId?.startsWith('seedance-2-0') || false;
|
|
317
|
+
}
|
|
318
|
+
export function resolveVideoControlNetStrength(name, explicitStrength) {
|
|
319
|
+
if (explicitStrength !== null && explicitStrength !== undefined)
|
|
320
|
+
return explicitStrength;
|
|
321
|
+
return name === 'detailer' ? 1.0 : 0.85;
|
|
322
|
+
}
|
|
323
|
+
export function resolveVideoModelAlias(modelId, workflow) {
|
|
324
|
+
if (!modelId)
|
|
325
|
+
return modelId;
|
|
326
|
+
const key = String(modelId).trim().toLowerCase();
|
|
327
|
+
if (key === 'ltx23' && isLtxWorkflow(workflow)) {
|
|
328
|
+
return LTX23_WORKFLOW_MODELS[workflow];
|
|
329
|
+
}
|
|
330
|
+
if (key === 'wan22' && workflow) {
|
|
331
|
+
return VIDEO_WORKFLOW_DEFAULT_MODELS[workflow] || VIDEO_MODEL_ALIASES.wan22;
|
|
332
|
+
}
|
|
333
|
+
if (key === 'seedance2' &&
|
|
334
|
+
(workflow === 't2v' || workflow === 'ia2v' || workflow === 'v2v')) {
|
|
335
|
+
return SEEDANCE_WORKFLOW_MODELS[workflow];
|
|
336
|
+
}
|
|
337
|
+
return VIDEO_MODEL_ALIASES[key] || modelId;
|
|
338
|
+
}
|
|
339
|
+
export function getBuiltinVideoModelConfig(modelId) {
|
|
340
|
+
if (!modelId)
|
|
341
|
+
return null;
|
|
342
|
+
const id = resolveVideoModelAlias(modelId);
|
|
343
|
+
if (!id)
|
|
344
|
+
return null;
|
|
345
|
+
if (EXPANDED_VIDEO_MODEL_REGISTRY[id])
|
|
346
|
+
return EXPANDED_VIDEO_MODEL_REGISTRY[id];
|
|
347
|
+
const workflow = inferVideoWorkflowFromModel(id);
|
|
348
|
+
if (!workflow)
|
|
349
|
+
return null;
|
|
350
|
+
if (id.startsWith('ltx23-') && isLtxWorkflow(workflow)) {
|
|
351
|
+
return EXPANDED_VIDEO_MODEL_REGISTRY[LTX23_WORKFLOW_MODELS[workflow]] || null;
|
|
352
|
+
}
|
|
353
|
+
if (id.startsWith('ltx2-')) {
|
|
354
|
+
return {
|
|
355
|
+
workflow,
|
|
356
|
+
family: 'ltx2',
|
|
357
|
+
defaultWidth: 768,
|
|
358
|
+
defaultHeight: 768,
|
|
359
|
+
minDimension: 480,
|
|
360
|
+
maxDimension: 1536,
|
|
361
|
+
dimensionMultiple: 64,
|
|
362
|
+
steps: id.includes('distilled') ? 8 : 20,
|
|
363
|
+
guidance: 1.0,
|
|
364
|
+
fps: workflow === 'v2v' ? 25 : 24,
|
|
365
|
+
frameStep: 8,
|
|
366
|
+
minFrames: 25,
|
|
367
|
+
maxFrames: 321,
|
|
368
|
+
sampler: 'euler_ancestral',
|
|
369
|
+
scheduler: 'simple'
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (isWanModel(id)) {
|
|
373
|
+
return {
|
|
374
|
+
workflow,
|
|
375
|
+
family: 'wan22',
|
|
376
|
+
defaultWidth: workflow === 't2v' ? 640 : 832,
|
|
377
|
+
defaultHeight: workflow === 't2v' ? 640 : 480,
|
|
378
|
+
minDimension: 480,
|
|
379
|
+
maxDimension: 1536,
|
|
380
|
+
dimensionMultiple: 16,
|
|
381
|
+
steps: id.includes('lightx2v') ? 4 : 20,
|
|
382
|
+
guidance: 1.0,
|
|
383
|
+
fps: 32,
|
|
384
|
+
internalFps: 16,
|
|
385
|
+
frameStep: 1,
|
|
386
|
+
minFrames: 17,
|
|
387
|
+
maxFrames: workflow === 't2v' ? 161 : 321,
|
|
388
|
+
sampler: workflow === 's2v' ? 'uni_pc' : 'euler',
|
|
389
|
+
scheduler: 'simple',
|
|
390
|
+
shift: workflow === 't2v' ? 5.0 : 8.0
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
export function normalizeVideoWorkflow(value) {
|
|
396
|
+
if (!value)
|
|
397
|
+
return null;
|
|
398
|
+
const normalized = value.toLowerCase();
|
|
399
|
+
if (normalized === 't2v' || normalized === 'text-to-video')
|
|
400
|
+
return 't2v';
|
|
401
|
+
if (normalized === 'i2v' || normalized === 'image-to-video')
|
|
402
|
+
return 'i2v';
|
|
403
|
+
if (normalized === 's2v' || normalized === 'sound-to-video')
|
|
404
|
+
return 's2v';
|
|
405
|
+
if (normalized === 'ia2v' || normalized === 'image-audio-to-video' || normalized === 'image+audio-to-video')
|
|
406
|
+
return 'ia2v';
|
|
407
|
+
if (normalized === 'a2v' || normalized === 'audio-to-video')
|
|
408
|
+
return 'a2v';
|
|
409
|
+
if (normalized === 'animate-move' || normalized === 'animate_move')
|
|
410
|
+
return 'animate-move';
|
|
411
|
+
if (normalized === 'animate-replace' || normalized === 'animate_replace')
|
|
412
|
+
return 'animate-replace';
|
|
413
|
+
if (normalized === 'v2v' || normalized === 'video-to-video')
|
|
414
|
+
return 'v2v';
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
export function inferVideoWorkflowFromModel(modelId) {
|
|
418
|
+
if (!modelId)
|
|
419
|
+
return null;
|
|
420
|
+
const resolvedModelId = resolveVideoModelAlias(modelId);
|
|
421
|
+
if (!resolvedModelId)
|
|
422
|
+
return null;
|
|
423
|
+
const id = resolvedModelId.toLowerCase();
|
|
424
|
+
if (id.includes('animate-move'))
|
|
425
|
+
return 'animate-move';
|
|
426
|
+
if (id.includes('animate-replace'))
|
|
427
|
+
return 'animate-replace';
|
|
428
|
+
if (id.includes('_v2v'))
|
|
429
|
+
return 'v2v';
|
|
430
|
+
if (id.includes('_ia2v'))
|
|
431
|
+
return 'ia2v';
|
|
432
|
+
if (id.includes('_a2v'))
|
|
433
|
+
return 'a2v';
|
|
434
|
+
if (id.includes('_t2v') || id.includes('-t2v'))
|
|
435
|
+
return 't2v';
|
|
436
|
+
if (id.includes('_i2v') || id.includes('-i2v'))
|
|
437
|
+
return 'i2v';
|
|
438
|
+
if (id.includes('_s2v') || id.includes('-s2v'))
|
|
439
|
+
return 's2v';
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
export function promptExplicitlyDisablesSpeech(prompt) {
|
|
443
|
+
return /\b(no dialogue|no speech|without dialogue|without speech|silent|no voiceover|no voice-over)\b/i.test(prompt || '');
|
|
444
|
+
}
|
|
445
|
+
export function containsQuotedDialogue(prompt) {
|
|
446
|
+
return /"[^"]{1,400}"/.test(prompt || '');
|
|
447
|
+
}
|
|
448
|
+
export function promptMentionsSpeech(prompt) {
|
|
449
|
+
if (!prompt || promptExplicitlyDisablesSpeech(prompt))
|
|
450
|
+
return false;
|
|
451
|
+
return /\b(dialogue|speaks?|speaking|says?|said|asks?|asked|whispers?|shouts?|yells?|narrates?|narration|voiceover|voice-over|conversation|monologue|interview|talking|tells? (?:a )?story)\b/i.test(prompt);
|
|
452
|
+
}
|
|
453
|
+
export function promptMentionsAudio(prompt) {
|
|
454
|
+
if (!prompt)
|
|
455
|
+
return false;
|
|
456
|
+
return /\b(audio|sound|sounds|ambient sound|music|song|singing|sings|voice|voices|dialogue|speech|voiceover|voice-over|narration|foley)\b/i.test(prompt);
|
|
457
|
+
}
|
|
458
|
+
export function promptLooksLikeLongFormStory(prompt) {
|
|
459
|
+
return /\b(story|screenplay|script|scene|episode|short film|commercial|storyboard|chapter|narrative)\b/i.test(prompt || '');
|
|
460
|
+
}
|
|
461
|
+
export function promptLooksLikeLipSync(prompt) {
|
|
462
|
+
return /\b(lip[- ]?sync|lipsync|talking head|mouth movement|sync(?:hronize)? (?:the )?(?:lips|mouth|speech)|face speaks|sing along)\b/i.test(prompt || '');
|
|
463
|
+
}
|
|
464
|
+
export function promptNeedsLtxNativeAudio(prompt) {
|
|
465
|
+
return !promptExplicitlyDisablesSpeech(prompt) && (containsQuotedDialogue(prompt) ||
|
|
466
|
+
promptMentionsSpeech(prompt) ||
|
|
467
|
+
promptMentionsAudio(prompt) ||
|
|
468
|
+
promptLooksLikeLongFormStory(prompt));
|
|
469
|
+
}
|
|
470
|
+
export function normalizeScreenplayDialogueQuotes(prompt) {
|
|
471
|
+
if (!prompt)
|
|
472
|
+
return prompt;
|
|
473
|
+
return prompt
|
|
474
|
+
.replace(/^(\s*[A-Za-z][A-Za-z0-9 _.-]{0,48}:\s*)'([^'\n]{1,300})'/gm, '$1"$2"')
|
|
475
|
+
.replace(/([\s(])'([^'\n]{1,180})'(?=[\s).,!?:;]|$)/g, '$1"$2"');
|
|
476
|
+
}
|
|
477
|
+
export function extractQuotedDialogueSegments(prompt) {
|
|
478
|
+
const matches = [];
|
|
479
|
+
const pattern = /"([^"]{1,800})"/g;
|
|
480
|
+
let match;
|
|
481
|
+
while ((match = pattern.exec(prompt || '')) !== null) {
|
|
482
|
+
matches.push(match[1]);
|
|
483
|
+
}
|
|
484
|
+
return matches;
|
|
485
|
+
}
|
|
486
|
+
export function countWords(text) {
|
|
487
|
+
const words = String(text || '').trim().match(/\b[\w'-]+\b/g);
|
|
488
|
+
return words ? words.length : 0;
|
|
489
|
+
}
|
|
490
|
+
export function quotedDialogueWordCount(prompt) {
|
|
491
|
+
return extractQuotedDialogueSegments(prompt).reduce((sum, segment) => sum + countWords(segment), 0);
|
|
492
|
+
}
|
|
493
|
+
export function suggestedDurationForDialogue(prompt, currentDuration) {
|
|
494
|
+
const words = quotedDialogueWordCount(prompt);
|
|
495
|
+
const baseDuration = currentDuration ?? 0;
|
|
496
|
+
if (words <= 0)
|
|
497
|
+
return baseDuration;
|
|
498
|
+
const speechSeconds = Math.ceil(words / 2.5) + 2;
|
|
499
|
+
return Math.max(baseDuration, Math.min(20, speechSeconds));
|
|
500
|
+
}
|
|
501
|
+
export function formatAudioIdPrompt(prompt, voiceName) {
|
|
502
|
+
if (!prompt)
|
|
503
|
+
return prompt;
|
|
504
|
+
if (/\[VISUAL\]|\[SPEECH\]|\[SOUNDS\]/i.test(prompt))
|
|
505
|
+
return prompt;
|
|
506
|
+
const dialogue = extractQuotedDialogueSegments(prompt);
|
|
507
|
+
const speechLines = dialogue.length > 0
|
|
508
|
+
? dialogue.map((line, index) => (voiceName || 'SPEAKER_' + (index + 1)) + ': "' + line + '"').join('\n')
|
|
509
|
+
: 'No spoken dialogue unless exact quoted words are present in the visual prompt.';
|
|
510
|
+
return [
|
|
511
|
+
'[VISUAL]',
|
|
512
|
+
prompt.trim(),
|
|
513
|
+
'',
|
|
514
|
+
'[SPEECH]',
|
|
515
|
+
speechLines,
|
|
516
|
+
'',
|
|
517
|
+
'[SOUNDS]',
|
|
518
|
+
'Use natural ambient sound that matches the scene unless the prompt specifies silence.'
|
|
519
|
+
].join('\n');
|
|
520
|
+
}
|
|
521
|
+
export function getVideoPromptGuardrailPlan({ prompt, duration, frames, fps, durationExplicit, referenceAudioIdentity, voiceName } = {}) {
|
|
522
|
+
let nextPrompt = prompt || '';
|
|
523
|
+
let nextDuration = duration ?? 0;
|
|
524
|
+
const warnings = [];
|
|
525
|
+
const normalizedPrompt = normalizeScreenplayDialogueQuotes(nextPrompt);
|
|
526
|
+
if (normalizedPrompt !== nextPrompt) {
|
|
527
|
+
nextPrompt = normalizedPrompt;
|
|
528
|
+
warnings.push({
|
|
529
|
+
type: 'normalized-dialogue-quotes',
|
|
530
|
+
message: 'Normalized screenplay dialogue to double quotes for video prompting.'
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (promptMentionsSpeech(nextPrompt) && !containsQuotedDialogue(nextPrompt)) {
|
|
534
|
+
warnings.push({
|
|
535
|
+
type: 'missing-quoted-dialogue',
|
|
536
|
+
message: 'Warning: video prompt mentions speech/dialogue but has no exact spoken words in double quotes. LTX native audio works best with concrete quoted dialogue.'
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
if (!frames && !durationExplicit) {
|
|
540
|
+
const suggested = suggestedDurationForDialogue(nextPrompt, nextDuration);
|
|
541
|
+
if (suggested > nextDuration) {
|
|
542
|
+
warnings.push({
|
|
543
|
+
type: 'duration-extended-for-dialogue',
|
|
544
|
+
message: 'Auto-extended video duration from ' + nextDuration + 's to ' + suggested + 's to fit quoted dialogue.'
|
|
545
|
+
});
|
|
546
|
+
nextDuration = suggested;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
const dialogueWords = quotedDialogueWordCount(nextPrompt);
|
|
551
|
+
const hardBudget = Math.floor((frames ? frames / (fps ?? 24) : nextDuration) * 3.75);
|
|
552
|
+
if (dialogueWords > hardBudget) {
|
|
553
|
+
warnings.push({
|
|
554
|
+
type: 'dialogue-over-budget',
|
|
555
|
+
message: 'Warning: quoted dialogue has about ' + dialogueWords + ' words, which may not fit in ' + (frames ? frames + ' frames' : nextDuration + 's') + '.'
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (referenceAudioIdentity) {
|
|
560
|
+
nextPrompt = formatAudioIdPrompt(nextPrompt, voiceName || 'SPEAKER');
|
|
561
|
+
}
|
|
562
|
+
return {
|
|
563
|
+
prompt: nextPrompt,
|
|
564
|
+
duration: nextDuration,
|
|
565
|
+
warnings
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
export function inferVideoWorkflowFromAssets(opts = {}) {
|
|
569
|
+
if (opts.refVideo && opts.videoControlNetName)
|
|
570
|
+
return 'v2v';
|
|
571
|
+
if (opts.refVideo)
|
|
572
|
+
return 'animate-move';
|
|
573
|
+
if (opts.refAudio && !opts.refImage && !opts.refImageEnd)
|
|
574
|
+
return 'a2v';
|
|
575
|
+
if (opts.refAudio && opts.refImage)
|
|
576
|
+
return promptLooksLikeLipSync(opts.prompt) ? 's2v' : 'ia2v';
|
|
577
|
+
if (opts.refAudio)
|
|
578
|
+
return 's2v';
|
|
579
|
+
if (opts.refImage || opts.refImageEnd)
|
|
580
|
+
return 'i2v';
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
export function workflowRequiresImage(workflow) {
|
|
584
|
+
return workflow === 'i2v' || workflow === 's2v' || workflow === 'ia2v' || workflow === 'animate-move' || workflow === 'animate-replace';
|
|
585
|
+
}
|
|
586
|
+
export function getModelDefaults(modelId, config) {
|
|
587
|
+
if (!modelId)
|
|
588
|
+
return null;
|
|
589
|
+
const normalizedModelId = resolveVideoModelAlias(modelId) || modelId;
|
|
590
|
+
const builtin = getBuiltinVideoModelConfig(normalizedModelId);
|
|
591
|
+
const entry = config?.modelDefaults?.[normalizedModelId] || config?.modelDefaults?.[modelId];
|
|
592
|
+
if (!entry || typeof entry !== 'object')
|
|
593
|
+
return builtin;
|
|
594
|
+
return { ...(builtin || {}), ...entry };
|
|
595
|
+
}
|
|
596
|
+
export function selectDefaultVideoModel(workflow, opts = {}, config) {
|
|
597
|
+
if (!workflow)
|
|
598
|
+
return null;
|
|
599
|
+
const configured = config?.videoModels?.[workflow];
|
|
600
|
+
if (configured)
|
|
601
|
+
return resolveVideoModelAlias(configured, workflow) || null;
|
|
602
|
+
if (workflow === 'ia2v')
|
|
603
|
+
return LTX23_WORKFLOW_MODELS.ia2v;
|
|
604
|
+
if (workflow === 'a2v')
|
|
605
|
+
return LTX23_WORKFLOW_MODELS.a2v;
|
|
606
|
+
if (workflow === 'v2v')
|
|
607
|
+
return LTX23_WORKFLOW_MODELS.v2v;
|
|
608
|
+
if (workflow === 't2v')
|
|
609
|
+
return LTX23_WORKFLOW_MODELS.t2v;
|
|
610
|
+
if (workflow === 'i2v' && (opts.referenceAudioIdentity || promptNeedsLtxNativeAudio(opts.prompt) || opts.quality === 'hq' || opts.quality === 'pro')) {
|
|
611
|
+
return LTX23_WORKFLOW_MODELS.i2v;
|
|
612
|
+
}
|
|
613
|
+
return VIDEO_WORKFLOW_DEFAULT_MODELS[workflow] || null;
|
|
614
|
+
}
|
|
615
|
+
export function dimensionsWithShortSide(width, height, shortSide) {
|
|
616
|
+
const w = Number(width);
|
|
617
|
+
const h = Number(height);
|
|
618
|
+
const s = Number(shortSide);
|
|
619
|
+
if (!Number.isFinite(w) || !Number.isFinite(h) || !Number.isFinite(s) || w <= 0 || h <= 0 || s <= 0) {
|
|
620
|
+
return { width, height };
|
|
621
|
+
}
|
|
622
|
+
const currentShort = Math.min(w, h);
|
|
623
|
+
const scale = s / currentShort;
|
|
624
|
+
return {
|
|
625
|
+
width: Math.round(w * scale),
|
|
626
|
+
height: Math.round(h * scale)
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
function hasPortraitResolutionHint(text) {
|
|
630
|
+
return /\b(portrait|vertical|reels?|tiktok|shorts?|story)\b/i.test(text);
|
|
631
|
+
}
|
|
632
|
+
function hasLandscapeResolutionHint(text) {
|
|
633
|
+
return /\b(landscape|horizontal|wide(?:screen)?|cinematic|youtube)\b/i.test(text);
|
|
634
|
+
}
|
|
635
|
+
function normalizeRequestedPixelDimension(value) {
|
|
636
|
+
return Number.isInteger(value) && value > 0 && value <= 10000 ? value : null;
|
|
637
|
+
}
|
|
638
|
+
function makeLandscapeOrPortraitDimensions(landscapeWidth, landscapeHeight, text) {
|
|
639
|
+
return hasPortraitResolutionHint(text)
|
|
640
|
+
? { width: landscapeHeight, height: landscapeWidth }
|
|
641
|
+
: { width: landscapeWidth, height: landscapeHeight };
|
|
642
|
+
}
|
|
643
|
+
export function inferNamedVideoResolutionShortSideFromText(text) {
|
|
644
|
+
const compact = text.replace(/,/g, '');
|
|
645
|
+
if (/\b4\s*k\b|\buhd\b/i.test(compact))
|
|
646
|
+
return 2160;
|
|
647
|
+
if (/\b8\s*k\b/i.test(compact))
|
|
648
|
+
return 4320;
|
|
649
|
+
const namedResolution = compact.match(/\b(480|720|1080|1440|2160)\s*p\b/i);
|
|
650
|
+
return namedResolution ? Number(namedResolution[1]) : null;
|
|
651
|
+
}
|
|
652
|
+
export function inferExplicitPixelDimensionsFromText(text) {
|
|
653
|
+
const compact = text.replace(/,/g, '');
|
|
654
|
+
const exactPair = compact.match(/\b(\d{1,5})\s*(x|by)\s*(\d{1,5})\s*(px|pixels?)?\b/i);
|
|
655
|
+
if (exactPair) {
|
|
656
|
+
const width = normalizeRequestedPixelDimension(Number(exactPair[1]));
|
|
657
|
+
const height = normalizeRequestedPixelDimension(Number(exactPair[3]));
|
|
658
|
+
const hasPixelUnit = Boolean(exactPair[4]);
|
|
659
|
+
if (width && height && (hasPixelUnit || Math.max(width, height) >= 100)) {
|
|
660
|
+
return { width, height };
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
const widthThenHeight = compact.match(/\b(?:width|wide|w)\s*(?:is|=|:)?\s*(\d{1,5})\s*(?:px|pixels?)?\b[\s\S]{0,80}\b(?:height|tall|high|h)\s*(?:is|=|:)?\s*(\d{1,5})\s*(?:px|pixels?)?\b/i);
|
|
664
|
+
if (widthThenHeight) {
|
|
665
|
+
const width = normalizeRequestedPixelDimension(Number(widthThenHeight[1]));
|
|
666
|
+
const height = normalizeRequestedPixelDimension(Number(widthThenHeight[2]));
|
|
667
|
+
if (width && height)
|
|
668
|
+
return { width, height };
|
|
669
|
+
}
|
|
670
|
+
const heightThenWidth = compact.match(/\b(?:height|tall|high|h)\s*(?:is|=|:)?\s*(\d{1,5})\s*(?:px|pixels?)?\b[\s\S]{0,80}\b(?:width|wide|w)\s*(?:is|=|:)?\s*(\d{1,5})\s*(?:px|pixels?)?\b/i);
|
|
671
|
+
if (heightThenWidth) {
|
|
672
|
+
const height = normalizeRequestedPixelDimension(Number(heightThenWidth[1]));
|
|
673
|
+
const width = normalizeRequestedPixelDimension(Number(heightThenWidth[2]));
|
|
674
|
+
if (width && height)
|
|
675
|
+
return { width, height };
|
|
676
|
+
}
|
|
677
|
+
if (/\b4\s*k\b|\buhd\b/i.test(compact)) {
|
|
678
|
+
return hasPortraitResolutionHint(compact) || hasLandscapeResolutionHint(compact)
|
|
679
|
+
? makeLandscapeOrPortraitDimensions(3840, 2160, compact)
|
|
680
|
+
: null;
|
|
681
|
+
}
|
|
682
|
+
if (/\b8\s*k\b/i.test(compact)) {
|
|
683
|
+
return hasPortraitResolutionHint(compact) || hasLandscapeResolutionHint(compact)
|
|
684
|
+
? makeLandscapeOrPortraitDimensions(7680, 4320, compact)
|
|
685
|
+
: null;
|
|
686
|
+
}
|
|
687
|
+
const namedResolution = compact.match(/\b(480|720|1080|1440|2160)\s*p\b/i);
|
|
688
|
+
if (namedResolution) {
|
|
689
|
+
if (!hasPortraitResolutionHint(compact) && !hasLandscapeResolutionHint(compact)) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
const shortSide = Number(namedResolution[1]);
|
|
693
|
+
const landscapeByShortSide = {
|
|
694
|
+
480: { width: 854, height: 480 },
|
|
695
|
+
720: { width: 1280, height: 720 },
|
|
696
|
+
1080: { width: 1920, height: 1080 },
|
|
697
|
+
1440: { width: 2560, height: 1440 },
|
|
698
|
+
2160: { width: 3840, height: 2160 },
|
|
699
|
+
};
|
|
700
|
+
const dimensions = landscapeByShortSide[shortSide];
|
|
701
|
+
if (dimensions) {
|
|
702
|
+
return makeLandscapeOrPortraitDimensions(dimensions.width, dimensions.height, compact);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
export function inferExplicitAspectRatioFromText(text) {
|
|
708
|
+
const match = text.match(/\b(\d{1,4}(?:\.\d+)?)\s*:\s*(\d{1,4}(?:\.\d+)?)\b/);
|
|
709
|
+
if (!match)
|
|
710
|
+
return null;
|
|
711
|
+
const width = Number(match[1]);
|
|
712
|
+
const height = Number(match[2]);
|
|
713
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
const index = match.index ?? 0;
|
|
717
|
+
const context = text.slice(Math.max(0, index - 48), Math.min(text.length, index + match[0].length + 48));
|
|
718
|
+
if (/\b(?:ratio|aspect|format|portrait|landscape|vertical|horizontal|widescreen|frame|video|output)\b/i.test(context)) {
|
|
719
|
+
return { width, height, text: match[0] };
|
|
720
|
+
}
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
export function inferRequestedTotalVideoDurationSeconds(text) {
|
|
724
|
+
const durations = [];
|
|
725
|
+
for (const match of text.matchAll(/\b(\d{1,3}(?:\.\d+)?)\s*(?:minutes?|mins?)\b/gi)) {
|
|
726
|
+
const minutes = Number(match[1]);
|
|
727
|
+
if (Number.isFinite(minutes) && minutes > 0)
|
|
728
|
+
durations.push(Math.ceil(minutes * 60));
|
|
729
|
+
}
|
|
730
|
+
for (const match of text.matchAll(/\b(\d{1,3})\s*(?:s|sec|secs|seconds?)\b/gi)) {
|
|
731
|
+
const seconds = Number(match[1]);
|
|
732
|
+
if (Number.isFinite(seconds) && seconds > 0)
|
|
733
|
+
durations.push(seconds);
|
|
734
|
+
}
|
|
735
|
+
return durations.length > 0 ? Math.max(...durations) : null;
|
|
736
|
+
}
|
|
737
|
+
export function textProvidesLiteralVideoPrompt(text) {
|
|
738
|
+
return /\b(?:full|exact|literal)\s+prompt\b/i.test(text)
|
|
739
|
+
|| /\bdo\s+not\s+(?:modify|change|rewrite|alter|enhance|expand|improve)\s+(?:this\s+|the\s+)?prompt\b/i.test(text)
|
|
740
|
+
|| /\bprompt\s+to\s+use\b/i.test(text)
|
|
741
|
+
|| /\bprompt\s+(?:exactly|verbatim|as[-\s]?is)\b/i.test(text)
|
|
742
|
+
|| /\buse\s+(?:this|the)\s+(?:full|exact|literal)\s+prompt\b/i.test(text)
|
|
743
|
+
|| /\buse\s+(?:this|the)\s+prompt\s+(?:exactly|verbatim|as[-\s]?is)\b/i.test(text);
|
|
744
|
+
}
|
|
745
|
+
function stripPromptWrapper(text) {
|
|
746
|
+
let prompt = text.trim();
|
|
747
|
+
const fenced = prompt.match(/^```(?:[\w-]+)?\s*\n?([\s\S]*?)\n?```\s*$/);
|
|
748
|
+
if (fenced?.[1]?.trim()) {
|
|
749
|
+
prompt = fenced[1].trim();
|
|
750
|
+
}
|
|
751
|
+
const quotePairs = [
|
|
752
|
+
['"', '"'],
|
|
753
|
+
["'", "'"],
|
|
754
|
+
];
|
|
755
|
+
for (const [open, close] of quotePairs) {
|
|
756
|
+
if (prompt.startsWith(open) && prompt.endsWith(close)) {
|
|
757
|
+
const unwrapped = prompt.slice(open.length, -close.length).trim();
|
|
758
|
+
if (unwrapped)
|
|
759
|
+
return unwrapped;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return prompt;
|
|
763
|
+
}
|
|
764
|
+
export function extractLiteralVideoPrompt(text) {
|
|
765
|
+
const markers = [
|
|
766
|
+
/\b(?:use|using|with)\s+(?:this|the)\s+(?:full\s+|exact\s+|literal\s+)?prompt\s+(?:exactly|verbatim|as[-\s]?is)\s*:?\s*/gi,
|
|
767
|
+
/\b(?:use|using|with)\s+(?:this|the)\s+(?:full|exact|literal)\s+prompt\s*:?\s*/gi,
|
|
768
|
+
/\b(?:full|exact|literal)\s+prompt\s*:?\s*/gi,
|
|
769
|
+
/\bprompt\s+to\s+use\s*:?\s*/gi,
|
|
770
|
+
];
|
|
771
|
+
let markerEnd = -1;
|
|
772
|
+
for (const marker of markers) {
|
|
773
|
+
marker.lastIndex = 0;
|
|
774
|
+
while (marker.exec(text) !== null) {
|
|
775
|
+
markerEnd = Math.max(markerEnd, marker.lastIndex);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (markerEnd < 0 && /\bdo\s+not\s+(?:modify|change|rewrite|alter|enhance|expand|improve)\s+(?:this\s+|the\s+)?prompt\b/i.test(text)) {
|
|
779
|
+
const promptLabel = /\bprompt\s*:\s*/gi;
|
|
780
|
+
while (promptLabel.exec(text) !== null) {
|
|
781
|
+
markerEnd = Math.max(markerEnd, promptLabel.lastIndex);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (markerEnd < 0)
|
|
785
|
+
return null;
|
|
786
|
+
const extracted = stripPromptWrapper(text
|
|
787
|
+
.slice(markerEnd)
|
|
788
|
+
.replace(/^\s*[:-]\s*/, ''));
|
|
789
|
+
return extracted.length >= 3 ? extracted : null;
|
|
790
|
+
}
|
|
791
|
+
export function textMentionsStoryboardReference(text) {
|
|
792
|
+
return /\b(?:story\s*board|storyboard|video\s+sequence|sequence\s+sheet|shot\s+sheet|thumbnail\s+sequence|panel\s+sequence)\b/i.test(text);
|
|
793
|
+
}
|
|
794
|
+
export function textProvidesVideoScriptOrDetailedPrompt(text) {
|
|
795
|
+
const normalized = text.trim();
|
|
796
|
+
if (!normalized)
|
|
797
|
+
return false;
|
|
798
|
+
if (textProvidesLiteralVideoPrompt(normalized))
|
|
799
|
+
return true;
|
|
800
|
+
if (/\[\s*\d{1,2}(?:[:.]\d{2})?\s*(?:-|to)\s*\d{1,2}(?:[:.]\d{2})?\s*\]/i.test(normalized))
|
|
801
|
+
return true;
|
|
802
|
+
if (/^\s*(?:style|shot|scene|segment|camera|motion|audio|vo|v\.o\.|voiceover|sfx|fx|music|dialogue)\s*:/im.test(normalized))
|
|
803
|
+
return true;
|
|
804
|
+
if (/^\s*(?:scene|shot|segment)\s*\d{1,2}\b/im.test(normalized))
|
|
805
|
+
return true;
|
|
806
|
+
if ((normalized.match(/^\s*(?:vo|v\.o\.|sfx|fx|music|camera)\s*:/gim) || []).length >= 2)
|
|
807
|
+
return true;
|
|
808
|
+
if ((normalized.match(/\b(?:VO|SFX|camera|style|dialogue|shot|segment)\s*:/g) || []).length >= 3)
|
|
809
|
+
return true;
|
|
810
|
+
const quotedPhrases = normalized.match(/"[^"]{8,}"/g) || [];
|
|
811
|
+
if (quotedPhrases.length >= 2)
|
|
812
|
+
return true;
|
|
813
|
+
const commandStripped = normalized
|
|
814
|
+
.replace(/\b(?:generate|create|make|render|turn|use|using|with|this|the|uploaded|attached|provided|image|photo|picture|reference|seedance|video|seconds?|secs?|s)\b/gi, ' ')
|
|
815
|
+
.replace(/\b\d{1,2}\b/g, ' ')
|
|
816
|
+
.replace(/\s+/g, ' ')
|
|
817
|
+
.trim();
|
|
818
|
+
const wordCount = commandStripped ? commandStripped.split(/\s+/).length : 0;
|
|
819
|
+
return wordCount >= 55;
|
|
820
|
+
}
|
|
821
|
+
export function seedanceStoryboardFallbackAllowedForText(text) {
|
|
822
|
+
return !textProvidesVideoScriptOrDetailedPrompt(text);
|
|
823
|
+
}
|
|
824
|
+
function normalizeDurationSeconds(value) {
|
|
825
|
+
const raw = typeof value === 'string'
|
|
826
|
+
? Number(value.trim().match(/^(\d{1,3}(?:\.\d+)?)\s*(?:s|sec|secs|seconds?)?$/i)?.[1])
|
|
827
|
+
: typeof value === 'number'
|
|
828
|
+
? value
|
|
829
|
+
: NaN;
|
|
830
|
+
if (!Number.isFinite(raw) || raw <= 0 || raw > 600)
|
|
831
|
+
return null;
|
|
832
|
+
return Math.ceil(raw);
|
|
833
|
+
}
|
|
834
|
+
function greatestCommonDivisor(a, b) {
|
|
835
|
+
let x = Math.abs(Math.trunc(a));
|
|
836
|
+
let y = Math.abs(Math.trunc(b));
|
|
837
|
+
while (y > 0) {
|
|
838
|
+
const next = x % y;
|
|
839
|
+
x = y;
|
|
840
|
+
y = next;
|
|
841
|
+
}
|
|
842
|
+
return x || 1;
|
|
843
|
+
}
|
|
844
|
+
function formatAspectRatio(width, height) {
|
|
845
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) {
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
const ratio = width / height;
|
|
849
|
+
if (ratio < 0.25 || ratio > 4)
|
|
850
|
+
return null;
|
|
851
|
+
const divisor = greatestCommonDivisor(width, height);
|
|
852
|
+
return `${Math.trunc(width) / divisor}:${Math.trunc(height) / divisor}`;
|
|
853
|
+
}
|
|
854
|
+
function normalizeAspectRatio(value) {
|
|
855
|
+
if (typeof value !== 'string')
|
|
856
|
+
return null;
|
|
857
|
+
const normalized = value.trim().replace(/[x/]/gi, ':').toLowerCase();
|
|
858
|
+
if (!normalized || normalized === 'null' || normalized === 'unknown' || normalized === 'ambiguous') {
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
if (/^(?:portrait|vertical)$/.test(normalized))
|
|
862
|
+
return '9:16';
|
|
863
|
+
if (/^(?:landscape|horizontal|widescreen)$/.test(normalized))
|
|
864
|
+
return '16:9';
|
|
865
|
+
if (/^square$/.test(normalized))
|
|
866
|
+
return '1:1';
|
|
867
|
+
const match = normalized.match(/(\d{1,5})\s*:\s*(\d{1,5})/);
|
|
868
|
+
if (!match)
|
|
869
|
+
return null;
|
|
870
|
+
return formatAspectRatio(Number(match[1]), Number(match[2]));
|
|
871
|
+
}
|
|
872
|
+
function textExplicitlyRequestsMultipleVideoRenders(text) {
|
|
873
|
+
return /\b(?:\d{1,2}|multiple|several|many)\s+(?:separate\s+|different\s+|distinct\s+)?(?:videos?|clips?|versions?|variations?)\b/i.test(text);
|
|
874
|
+
}
|
|
875
|
+
function textExplicitlyRequestsGeneratedImageStage(text) {
|
|
876
|
+
return /\b(?:generate|create|make|render|produce|design|build)\b\s+(?:an?\s+|the\s+)?(?:\d{1,2}\s+)?(?:new|different|distinct|separate|alternate|transformed|scene\s+)*(?:images?|photos?|pictures?|portraits?|keyframes?|frames?|versions?|variations?|variants?)\b/i.test(text);
|
|
877
|
+
}
|
|
878
|
+
function textRequestsAdjacentImageTransitions(text) {
|
|
879
|
+
const lower = text.toLowerCase();
|
|
880
|
+
const mentionsTransition = /\b(?:transition|transitions|transitioning|link|links|linking|connect|connecting|morph)\b/.test(lower)
|
|
881
|
+
|| /\bbetween\s+(?:the\s+)?(?:images?|photos?|pictures?|keyframes?|frames?|versions?|variations?|scenes?)\b/.test(lower)
|
|
882
|
+
|| /(?:->)/.test(text);
|
|
883
|
+
if (!mentionsTransition)
|
|
884
|
+
return false;
|
|
885
|
+
const mentionsGeneratedSequence = /\b(?:versions?|variations?|images?|photos?|pictures?|keyframes?|frames?|scenes?)\b/.test(lower)
|
|
886
|
+
|| /(?:->)/.test(text);
|
|
887
|
+
const mentionsVideoOutput = /\b(?:video|videos|clips?|segments?|stitch|stitched|stitching|montage)\b/.test(lower);
|
|
888
|
+
return mentionsGeneratedSequence && mentionsVideoOutput;
|
|
889
|
+
}
|
|
890
|
+
export function planSeedanceStoryboardFallback(input) {
|
|
891
|
+
const userIntentText = input.userIntentText;
|
|
892
|
+
const providesLiteralPrompt = input.providesLiteralPrompt
|
|
893
|
+
?? textProvidesLiteralVideoPrompt(userIntentText);
|
|
894
|
+
if (providesLiteralPrompt)
|
|
895
|
+
return null;
|
|
896
|
+
if (!seedanceStoryboardFallbackAllowedForText(userIntentText))
|
|
897
|
+
return null;
|
|
898
|
+
if (input.uploadedImageCount !== 1)
|
|
899
|
+
return null;
|
|
900
|
+
if ((input.uploadedVideoCount ?? 0) > 0 || (input.uploadedAudioCount ?? 0) > 0)
|
|
901
|
+
return null;
|
|
902
|
+
if (textExplicitlyRequestsMultipleVideoRenders(userIntentText))
|
|
903
|
+
return null;
|
|
904
|
+
if (textExplicitlyRequestsGeneratedImageStage(userIntentText))
|
|
905
|
+
return null;
|
|
906
|
+
if (textRequestsAdjacentImageTransitions(userIntentText))
|
|
907
|
+
return null;
|
|
908
|
+
const reason = textMentionsStoryboardReference(userIntentText)
|
|
909
|
+
? 'text_mentions_storyboard'
|
|
910
|
+
: input.storyboardDetected
|
|
911
|
+
? 'vision_detected_storyboard'
|
|
912
|
+
: null;
|
|
913
|
+
if (!reason)
|
|
914
|
+
return null;
|
|
915
|
+
const requestedDuration = input.requestedDurationSeconds
|
|
916
|
+
?? inferRequestedTotalVideoDurationSeconds(userIntentText);
|
|
917
|
+
const storyboardDuration = normalizeDurationSeconds(input.storyboardDurationSeconds);
|
|
918
|
+
const storyboardAspectRatio = normalizeAspectRatio(input.storyboardAspectRatio);
|
|
919
|
+
const defaultDuration = input.defaultDurationSeconds ?? 5;
|
|
920
|
+
const maxDuration = input.maxDurationSeconds ?? 15;
|
|
921
|
+
const minDuration = input.minDurationSeconds ?? 4;
|
|
922
|
+
const intendedDuration = requestedDuration !== null && requestedDuration !== undefined
|
|
923
|
+
? requestedDuration
|
|
924
|
+
: storyboardDuration ?? defaultDuration;
|
|
925
|
+
return {
|
|
926
|
+
prompt: SEEDANCE_STORYBOARD_REFERENCE_PROMPT,
|
|
927
|
+
duration: Math.max(minDuration, Math.min(maxDuration, intendedDuration)),
|
|
928
|
+
referenceImageIndices: input.referenceImageIndices ?? [-1],
|
|
929
|
+
skipPromptProcessing: true,
|
|
930
|
+
expandPrompt: false,
|
|
931
|
+
reason,
|
|
932
|
+
...(storyboardAspectRatio ? { aspectRatio: storyboardAspectRatio } : {}),
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
function valuePresent(value) {
|
|
936
|
+
return value !== undefined && value !== null && value !== '';
|
|
937
|
+
}
|
|
938
|
+
export function dimensionsForAspectRatio(width, height, aspectRatio) {
|
|
939
|
+
const normalized = normalizeAspectRatio(aspectRatio);
|
|
940
|
+
if (!normalized)
|
|
941
|
+
return null;
|
|
942
|
+
const [ratioWText, ratioHText] = normalized.split(':');
|
|
943
|
+
const ratioW = Number(ratioWText);
|
|
944
|
+
const ratioH = Number(ratioHText);
|
|
945
|
+
if (!Number.isFinite(ratioW) || !Number.isFinite(ratioH) || ratioW <= 0 || ratioH <= 0) {
|
|
946
|
+
return null;
|
|
947
|
+
}
|
|
948
|
+
const shortSide = Math.min(width, height);
|
|
949
|
+
if (!Number.isFinite(shortSide) || shortSide <= 0)
|
|
950
|
+
return null;
|
|
951
|
+
if (ratioW >= ratioH) {
|
|
952
|
+
return {
|
|
953
|
+
width: Math.round(shortSide * ratioW / ratioH),
|
|
954
|
+
height: Math.round(shortSide),
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
return {
|
|
958
|
+
width: Math.round(shortSide),
|
|
959
|
+
height: Math.round(shortSide * ratioH / ratioW),
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
export function isSeedanceModelSelection(modelId) {
|
|
963
|
+
if (!modelId)
|
|
964
|
+
return false;
|
|
965
|
+
return (isSeedanceModel(modelId) ||
|
|
966
|
+
isSeedanceModel(resolveVideoModelAlias(modelId, 't2v')) ||
|
|
967
|
+
isSeedanceModel(resolveVideoModelAlias(modelId, 'ia2v')) ||
|
|
968
|
+
isSeedanceModel(resolveVideoModelAlias(modelId, 'v2v')));
|
|
969
|
+
}
|
|
970
|
+
export function planCliVideoBrain(input) {
|
|
971
|
+
const plan = { warnings: [] };
|
|
972
|
+
if (!input.video || !input.prompt?.trim())
|
|
973
|
+
return plan;
|
|
974
|
+
const cliSet = input.cliSet ?? {};
|
|
975
|
+
const text = input.prompt;
|
|
976
|
+
const normalizedWorkflow = normalizeVideoWorkflow(input.workflow);
|
|
977
|
+
const literalPrompt = extractLiteralVideoPrompt(text);
|
|
978
|
+
if (literalPrompt) {
|
|
979
|
+
plan.literalPrompt = true;
|
|
980
|
+
plan.prompt = literalPrompt;
|
|
981
|
+
}
|
|
982
|
+
const inferredDuration = inferRequestedTotalVideoDurationSeconds(text);
|
|
983
|
+
if (!cliSet.duration && !cliSet.frames && inferredDuration !== null) {
|
|
984
|
+
plan.duration = inferredDuration;
|
|
985
|
+
}
|
|
986
|
+
if (!cliSet.width && !cliSet.height) {
|
|
987
|
+
const exactDimensions = inferExplicitPixelDimensionsFromText(text);
|
|
988
|
+
if (exactDimensions) {
|
|
989
|
+
plan.width = exactDimensions.width;
|
|
990
|
+
plan.height = exactDimensions.height;
|
|
991
|
+
plan.dimensionSource = 'exact';
|
|
992
|
+
}
|
|
993
|
+
else if (!cliSet.targetResolution) {
|
|
994
|
+
const shortSide = inferNamedVideoResolutionShortSideFromText(text);
|
|
995
|
+
if (shortSide !== null) {
|
|
996
|
+
plan.targetResolution = shortSide;
|
|
997
|
+
}
|
|
998
|
+
else {
|
|
999
|
+
const aspectRatio = inferExplicitAspectRatioFromText(text);
|
|
1000
|
+
if (aspectRatio) {
|
|
1001
|
+
const dimensions = dimensionsForAspectRatio(input.width ?? 1920, input.height ?? 1088, aspectRatio.text);
|
|
1002
|
+
if (dimensions) {
|
|
1003
|
+
plan.width = dimensions.width;
|
|
1004
|
+
plan.height = dimensions.height;
|
|
1005
|
+
plan.dimensionSource = 'aspect';
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const uploadedImageCount = (valuePresent(input.refImage) ? 1 : 0) +
|
|
1012
|
+
(valuePresent(input.refImageEnd) ? 1 : 0);
|
|
1013
|
+
const storyboard = plan.literalPrompt
|
|
1014
|
+
? null
|
|
1015
|
+
: planSeedanceStoryboardFallback({
|
|
1016
|
+
userIntentText: text,
|
|
1017
|
+
uploadedImageCount,
|
|
1018
|
+
uploadedVideoCount: valuePresent(input.refVideo) ? 1 : 0,
|
|
1019
|
+
uploadedAudioCount: valuePresent(input.refAudio) ? 1 : 0,
|
|
1020
|
+
requestedDurationSeconds: cliSet.duration ? input.duration : plan.duration ?? inferredDuration,
|
|
1021
|
+
defaultDurationSeconds: input.duration ?? 5,
|
|
1022
|
+
storyboardAspectRatio: inferExplicitAspectRatioFromText(text)?.text,
|
|
1023
|
+
});
|
|
1024
|
+
if (storyboard) {
|
|
1025
|
+
const explicitNonSeedanceModel = cliSet.model && input.model && !isSeedanceModelSelection(input.model);
|
|
1026
|
+
const workflowAllowsStoryboard = !normalizedWorkflow || normalizedWorkflow === 't2v';
|
|
1027
|
+
if (!explicitNonSeedanceModel && workflowAllowsStoryboard) {
|
|
1028
|
+
plan.storyboard = storyboard;
|
|
1029
|
+
plan.prompt = storyboard.prompt;
|
|
1030
|
+
if (!cliSet.model)
|
|
1031
|
+
plan.model = SEEDANCE_WORKFLOW_MODELS.t2v;
|
|
1032
|
+
if (!cliSet.workflow)
|
|
1033
|
+
plan.workflow = 't2v';
|
|
1034
|
+
if (!cliSet.duration && !cliSet.frames)
|
|
1035
|
+
plan.duration = storyboard.duration;
|
|
1036
|
+
if (storyboard.aspectRatio && !cliSet.width && !cliSet.height && plan.width === undefined && plan.height === undefined) {
|
|
1037
|
+
const dimensions = dimensionsForAspectRatio(input.width ?? 1920, input.height ?? 1088, storyboard.aspectRatio);
|
|
1038
|
+
if (dimensions) {
|
|
1039
|
+
plan.width = dimensions.width;
|
|
1040
|
+
plan.height = dimensions.height;
|
|
1041
|
+
plan.dimensionSource = 'aspect';
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return plan;
|
|
1047
|
+
}
|
|
1048
|
+
export function inferDefaultVideoSteps(modelId) {
|
|
1049
|
+
const id = (modelId || '').toLowerCase();
|
|
1050
|
+
if (isSeedanceModel(id))
|
|
1051
|
+
return undefined;
|
|
1052
|
+
if (isLtx2Model(id) && id.includes('distilled'))
|
|
1053
|
+
return 8;
|
|
1054
|
+
if (id.includes('lightx2v'))
|
|
1055
|
+
return 4;
|
|
1056
|
+
if (id.includes('lightning') || id.includes('turbo') || id.includes('lcm'))
|
|
1057
|
+
return 4;
|
|
1058
|
+
if (isLtx2Model(id))
|
|
1059
|
+
return 20;
|
|
1060
|
+
return 20;
|
|
1061
|
+
}
|
|
1062
|
+
export function resolveVideoSteps(modelId, modelDefaults, explicitSteps) {
|
|
1063
|
+
if (typeof explicitSteps === 'number' && Number.isFinite(explicitSteps))
|
|
1064
|
+
return explicitSteps;
|
|
1065
|
+
const defaultSteps = modelDefaults?.steps;
|
|
1066
|
+
if (typeof defaultSteps === 'number' && Number.isFinite(defaultSteps))
|
|
1067
|
+
return defaultSteps;
|
|
1068
|
+
return inferDefaultVideoSteps(modelId);
|
|
1069
|
+
}
|
|
1070
|
+
const GRID_PATTERNS = [
|
|
1071
|
+
/\b(?:different|various|varying|multiple|several|many|diverse|assorted|all|range of|variety of|array of|series of|set of|collection of)\s+(?:facial\s+)?(?:expressions?|poses?|angles?|versions?|variations?|looks?|smiles?|moods?|emotions?|faces?|views?|shots?|styles?|options?|takes?|scenes?|settings?|environments?|worlds?)\b/gi,
|
|
1072
|
+
/\b\d+\s+(?:completely\s+|totally\s+|very\s+)?(?:different|unique|distinct|varying|varied|separate|individual)?\s*(?:facial\s+)?(?:expressions?|poses?|angles?|versions?|variations?|looks?|smiles?|moods?|emotions?|faces?|views?|shots?|styles?|options?|takes?|images?|photos?|pictures?|portraits?|copies|duplicates|scenes?|settings?|environments?|worlds?)\b/gi,
|
|
1073
|
+
/\b(?:grid|collage|montage|composite|triptych|diptych|side[- ]by[- ]side|side[- ]to[- ]side|split[- ]?screen|photo[- ]?sheet|contact[- ]?sheet|mood[- ]?board|lineup|line[- ]?up|tile[ds]?|tiling|rows?\s+(?:of|and)\s+columns?|columns?\s+(?:of|and)\s+rows?)\b/gi,
|
|
1074
|
+
/\beach\s+(?:with|showing|featuring|displaying|having|in)\s+(?:a\s+)?(?:different|unique|distinct|its own)\b/gi,
|
|
1075
|
+
/\beach\s+(?:one|version|variation|image|copy)\b/gi,
|
|
1076
|
+
/\b(?:show|display|create|generate|make|render|produce)\s+(?:multiple|different|various|several|all)\b/gi,
|
|
1077
|
+
/\b(?:switch(?:ing)?|mix(?:ing)?)\s+up\b/gi,
|
|
1078
|
+
/\b\d+\s+of\s+(?:them|these|those)\b/gi,
|
|
1079
|
+
/\b(?:multiple|several|many)\s+(?:copies|duplicates|instances|repeats)\b/gi,
|
|
1080
|
+
/\b\d+\s+(?:versions?|variations?|renditions?|interpretations?|depictions?|iterations?)\b/gi,
|
|
1081
|
+
/\b(?:repeated|repeating|repeat)\s+\d*\s*(?:times?)?\b/gi,
|
|
1082
|
+
/\b(?:put|place|fit|arrange)\s+(?:them|these|those|it)\s+(?:all\s+)?(?:together|into one|in one)\b/gi,
|
|
1083
|
+
/\ball\s+(?:together|in\s+one\s+(?:image|frame|picture|photo))\b/gi
|
|
1084
|
+
];
|
|
1085
|
+
const SINGULARIZE_PATTERNS = [
|
|
1086
|
+
[
|
|
1087
|
+
/\b(create|generate|make|render|produce)\s+\d+\s+(?:completely\s+|totally\s+|very\s+)?(?:different|unique|distinct|varying|varied|separate|individual)?\s*(scenes?|settings?|environments?|worlds?)\s+(featuring|with|of)\b/gi,
|
|
1088
|
+
'$1 a single scene $3'
|
|
1089
|
+
],
|
|
1090
|
+
[
|
|
1091
|
+
/\b(create|generate|make|render|produce)\s+\d+\s+(?:completely\s+|totally\s+|very\s+)?(?:different|unique|distinct|varying|varied|separate|individual)?\s*(images?|photos?|pictures?|portraits?)\s+(featuring|with|of)\b/gi,
|
|
1092
|
+
'$1 a single image $3'
|
|
1093
|
+
],
|
|
1094
|
+
[
|
|
1095
|
+
/\b(?:in|across)\s+(?:different|various|varying|multiple|several|many|diverse|assorted)\s+(?:scenes?|settings?|environments?|worlds?)\s*:/gi,
|
|
1096
|
+
'in this setting:'
|
|
1097
|
+
],
|
|
1098
|
+
[
|
|
1099
|
+
/\bfor\s+all\s+(?:scenes?|settings?|environments?|worlds?|images?|photos?|pictures?|portraits?)\b/gi,
|
|
1100
|
+
'for the image'
|
|
1101
|
+
]
|
|
1102
|
+
];
|
|
1103
|
+
function countPatternReplacement(match, ...args) {
|
|
1104
|
+
const offset = typeof args[args.length - 2] === 'number' ? args[args.length - 2] : 0;
|
|
1105
|
+
const source = typeof args[args.length - 1] === 'string' ? args[args.length - 1] : '';
|
|
1106
|
+
const before = source.slice(Math.max(0, offset - 8), offset);
|
|
1107
|
+
const after = source.slice(offset + match.length, offset + match.length + 8);
|
|
1108
|
+
const touchesAspectOrDimension = /(?:\d\s*[:/x×]\s*|\bby\s*)$/i.test(before) ||
|
|
1109
|
+
/^\s*(?::|\/|x|×|\bby\b)\s*\d/i.test(after);
|
|
1110
|
+
return touchesAspectOrDimension ? match : '';
|
|
1111
|
+
}
|
|
1112
|
+
export function sanitizeBatchPrompt(prompt) {
|
|
1113
|
+
const groups = [];
|
|
1114
|
+
const placeholderPrefix = '\x00DP';
|
|
1115
|
+
const placeholder = (i) => placeholderPrefix + i + '\x00';
|
|
1116
|
+
let shielded = prompt.replace(/\{[^{}]+\}/g, (match) => {
|
|
1117
|
+
if (match.includes('|')) {
|
|
1118
|
+
groups.push(match);
|
|
1119
|
+
return placeholder(groups.length - 1);
|
|
1120
|
+
}
|
|
1121
|
+
return match;
|
|
1122
|
+
});
|
|
1123
|
+
for (const [pattern, replacement] of SINGULARIZE_PATTERNS) {
|
|
1124
|
+
pattern.lastIndex = 0;
|
|
1125
|
+
shielded = shielded.replace(pattern, replacement);
|
|
1126
|
+
}
|
|
1127
|
+
for (const pattern of GRID_PATTERNS) {
|
|
1128
|
+
pattern.lastIndex = 0;
|
|
1129
|
+
shielded = shielded.replace(pattern, countPatternReplacement);
|
|
1130
|
+
}
|
|
1131
|
+
shielded = shielded.replace(/\s{2,}/g, ' ').replace(/\s+([.,;!?])/g, '$1').trim();
|
|
1132
|
+
shielded = shielded.replace(/^[,.\s]+/, '').replace(/[,.\s]+$/, '').trim();
|
|
1133
|
+
let result = shielded;
|
|
1134
|
+
for (let i = 0; i < groups.length; i++) {
|
|
1135
|
+
result = result.replace(placeholder(i), groups[i]);
|
|
1136
|
+
}
|
|
1137
|
+
return result;
|
|
1138
|
+
}
|