@xiaohhhh1/canvas-agent 0.4.84 → 0.4.85
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/dist/workflow/manager.d.ts +30 -1
- package/dist/workflow/manager.js +147 -21
- package/package.json +1 -1
|
@@ -203,7 +203,7 @@ type DraftJob = {
|
|
|
203
203
|
type ScriptChunkResult = {
|
|
204
204
|
error?: string;
|
|
205
205
|
terminal: boolean;
|
|
206
|
-
terminalKind?: "contract" | "transport" | "timeout" | "turn" | "delivery" | "review";
|
|
206
|
+
terminalKind?: "contract" | "transport" | "timeout" | "turn" | "delivery" | "review" | "isolation";
|
|
207
207
|
affectedOrdinals?: number[];
|
|
208
208
|
replanOrdinals?: number[];
|
|
209
209
|
};
|
|
@@ -409,6 +409,8 @@ export declare class WorkflowManager {
|
|
|
409
409
|
private pumpScriptQueue;
|
|
410
410
|
private finishDownloadDirectorySelection;
|
|
411
411
|
private runScript;
|
|
412
|
+
/** A finished lane refills immediately; a fatal result closes admission but drains every admitted lane. */
|
|
413
|
+
private runScriptChunksRolling;
|
|
412
414
|
/** Analyze each product once; image 1 owns identity and later images may only corroborate visible evidence. */
|
|
413
415
|
private ensureProductExecutionProfiles;
|
|
414
416
|
private runProductExecutionProfile;
|
|
@@ -474,6 +476,8 @@ export declare function recordCreativeReplanAttempts(attempts: Map<number, numbe
|
|
|
474
476
|
export declare function isFlowCPromptPayloadTooLarge(error: unknown): error is Error & {
|
|
475
477
|
code: "FLOW_C_PROMPT_PAYLOAD_TOO_LARGE";
|
|
476
478
|
};
|
|
479
|
+
/** Preflight the real immutable prompt; only a size error permits subdividing it. */
|
|
480
|
+
export declare function promptSafeScriptChunks(id: string, task: ScriptTask, ordinals: number[]): number[][];
|
|
477
481
|
export declare function productExecutionProfilePrompt(product: ProductInput, contractVersion?: string): string;
|
|
478
482
|
export declare function scriptChunkPrompt(id: string, task: ScriptTask, ordinals: number[], rewriteAttempt?: number): string;
|
|
479
483
|
export declare function creativeCandidatePrompt(id: string, task: ScriptTask, ordinals: number[]): string;
|
|
@@ -495,6 +499,31 @@ export declare function selectedBlueprintPromptPayload(values: SelectedCandidate
|
|
|
495
499
|
variationSeed: string;
|
|
496
500
|
}[];
|
|
497
501
|
};
|
|
502
|
+
/** Avoid encoding a complete JSON blueprint as an escaped JSON string again. */
|
|
503
|
+
export declare function unescapeBlueprintPromptPayload(payload: ReturnType<typeof selectedBlueprintPromptPayload>): {
|
|
504
|
+
executionBlueprints: ({
|
|
505
|
+
blueprintRef: string;
|
|
506
|
+
executionBlueprint: string;
|
|
507
|
+
sourceDurationSeconds: number | null;
|
|
508
|
+
targetDurationSeconds: number | null;
|
|
509
|
+
retimingMode: "compress" | "expand" | "same" | null;
|
|
510
|
+
} | {
|
|
511
|
+
executionBlueprint: object;
|
|
512
|
+
blueprintRef: string;
|
|
513
|
+
sourceDurationSeconds: number | null;
|
|
514
|
+
targetDurationSeconds: number | null;
|
|
515
|
+
retimingMode: "compress" | "expand" | "same" | null;
|
|
516
|
+
})[];
|
|
517
|
+
productAdaptations: Record<string, unknown>[];
|
|
518
|
+
ordinalBindings: {
|
|
519
|
+
contentDirection?: import("./content-method.js").FlowCContentDirection | undefined;
|
|
520
|
+
ordinal: number;
|
|
521
|
+
productIndex: number;
|
|
522
|
+
blueprintRef: string | null;
|
|
523
|
+
adaptationRef: string;
|
|
524
|
+
variationSeed: string;
|
|
525
|
+
}[];
|
|
526
|
+
};
|
|
498
527
|
export declare function selectedCandidatePlan(value: SelectedCandidate | undefined, contentStrategy?: FlowCContentStrategy | null): {
|
|
499
528
|
contentDirection?: import("./content-method.js").FlowCContentDirection | undefined;
|
|
500
529
|
sellingFormCardId: string | null;
|
package/dist/workflow/manager.js
CHANGED
|
@@ -368,23 +368,31 @@ export class WorkflowManager {
|
|
|
368
368
|
record.activeChunks = 0;
|
|
369
369
|
record.updatedAt = now();
|
|
370
370
|
this.save();
|
|
371
|
+
const admission = { stopped: false };
|
|
371
372
|
const pipelineResults = await Promise.all(wave.chunks.map(async (ordinals) => {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
373
|
+
try {
|
|
374
|
+
const candidateResult = await this.runCandidateChunk(id, task, ordinals, workspace.workspacePath);
|
|
375
|
+
if (candidateResult.terminal)
|
|
376
|
+
admission.stopped = true;
|
|
377
|
+
if (candidateResult.error || admission.stopped)
|
|
378
|
+
return [candidateResult];
|
|
379
|
+
const selectedTask = await this.scriptTask(id);
|
|
380
|
+
const scriptOrdinals = immediateScriptOrdinals(selectedTask, this.scriptRecord(id).receivedOrdinals, ordinals);
|
|
381
|
+
if (!scriptOrdinals.length || admission.stopped)
|
|
382
|
+
return [candidateResult];
|
|
383
|
+
const scriptResults = await this.runScriptChunksRolling(id, selectedTask, scriptOrdinals, workspace.workspacePath, 1, admission);
|
|
384
|
+
return [candidateResult, ...scriptResults];
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
admission.stopped = true;
|
|
388
|
+
return [{ terminal: true, terminalKind: "isolation", affectedOrdinals: ordinals, error: error instanceof Error ? error.message : String(error) }];
|
|
389
|
+
}
|
|
382
390
|
}));
|
|
383
391
|
const results = pipelineResults.flat();
|
|
384
392
|
task = await this.scriptTask(id);
|
|
385
393
|
const terminalFailure = terminalScriptChunkFailure(results.filter((result) => result.terminalKind !== "review"), this.scriptRecord(id).receivedOrdinals);
|
|
386
394
|
if (terminalFailure) {
|
|
387
|
-
if (terminalFailure.terminalKind === "delivery")
|
|
395
|
+
if (terminalFailure.terminalKind === "delivery" || terminalFailure.terminalKind === "isolation")
|
|
388
396
|
throw new Error(terminalFailure.error);
|
|
389
397
|
if (terminalFailure.terminalKind === "transport")
|
|
390
398
|
throw new Error(`创意或脚本阶段的本机 Codex 进程连续异常且自动恢复未成功,已停止当前任务且未提交缺失脚本(${terminalFailure.error})`);
|
|
@@ -428,15 +436,15 @@ export class WorkflowManager {
|
|
|
428
436
|
const heldBefore = voicePacingHeldOrdinals(record).length;
|
|
429
437
|
const chunkSize = chunkSizes[chunkSizeIndex];
|
|
430
438
|
record.chunkSize = chunkSize;
|
|
431
|
-
record.
|
|
432
|
-
record.message = `创意已选 ${selectedBefore.length}/${task.requested_count};正在立即写 ${wave.chunks.length} 个对应脚本子批(已回传 ${before}/${task.requested_count})`;
|
|
439
|
+
record.message = `创意已选 ${selectedBefore.length}/${task.requested_count};正在按完整蓝图大小分组并滚动写作(已回传 ${before}/${task.requested_count})`;
|
|
433
440
|
record.updatedAt = now();
|
|
434
441
|
this.save();
|
|
435
|
-
const
|
|
442
|
+
const readyOrdinals = immediateScriptOrdinals(task, settledOrdinals, missingOrdinals(task.requested_count, settledOrdinals));
|
|
443
|
+
const results = await this.runScriptChunksRolling(id, task, readyOrdinals, workspace.workspacePath);
|
|
436
444
|
task = await this.scriptTask(id);
|
|
437
445
|
const terminalFailure = terminalScriptChunkFailure(results.filter((result) => result.terminalKind !== "review"), this.scriptRecord(id).receivedOrdinals);
|
|
438
446
|
if (terminalFailure) {
|
|
439
|
-
if (terminalFailure.terminalKind === "delivery")
|
|
447
|
+
if (terminalFailure.terminalKind === "delivery" || terminalFailure.terminalKind === "isolation")
|
|
440
448
|
throw new Error(terminalFailure.error);
|
|
441
449
|
if (terminalFailure.terminalKind === "transport")
|
|
442
450
|
throw new Error(`本机 Codex 脚本引擎连续异常且自动恢复未成功,已停止当前任务且未提交缺失脚本。诊断已保存在本机 Agent 日志中(${terminalFailure.error})`);
|
|
@@ -514,6 +522,82 @@ export class WorkflowManager {
|
|
|
514
522
|
this.scheduleScript(id);
|
|
515
523
|
}
|
|
516
524
|
}
|
|
525
|
+
/** A finished lane refills immediately; a fatal result closes admission but drains every admitted lane. */
|
|
526
|
+
async runScriptChunksRolling(id, initialTask, ordinals, cwd, concurrency = FLOW_C_CODEX_WORKER_CONCURRENCY, admission = { stopped: false }) {
|
|
527
|
+
const durationSeconds = Number(initialTask.duration_seconds || 10);
|
|
528
|
+
const chunkSizes = flowCScriptChunkSizes(durationSeconds);
|
|
529
|
+
const queue = flowCScriptChunks(durationSeconds, [...new Set(ordinals)], chunkSizes[0]);
|
|
530
|
+
const results = [];
|
|
531
|
+
let task = initialTask;
|
|
532
|
+
const settled = () => new Set([...this.scriptRecord(id).receivedOrdinals, ...voicePacingHeldOrdinals(this.scriptRecord(id))]);
|
|
533
|
+
const stopWithError = (error, affectedOrdinals) => {
|
|
534
|
+
admission.stopped = true;
|
|
535
|
+
results.push({ terminal: true, terminalKind: "isolation", affectedOrdinals, error: error instanceof Error ? error.message : String(error) });
|
|
536
|
+
};
|
|
537
|
+
const worker = async () => {
|
|
538
|
+
while (!admission.stopped && queue.length) {
|
|
539
|
+
// Ownership is acquired synchronously, before the first await. A
|
|
540
|
+
// queue entry is never visible to another lane while in flight.
|
|
541
|
+
let current = queue.shift().filter((ordinal) => !settled().has(ordinal));
|
|
542
|
+
if (!current.length)
|
|
543
|
+
continue;
|
|
544
|
+
try {
|
|
545
|
+
const record = this.scriptRecord(id);
|
|
546
|
+
const selected = selectedCandidatesForOrdinals(task, current);
|
|
547
|
+
const recoverable = new Set([
|
|
548
|
+
...(record.pendingScriptJobs || []).map((job) => job.ordinal),
|
|
549
|
+
...(record.voicePacingReviewJobs || []).filter((job) => String(job.expectedCandidateRevision || "") === String(selected.get(job.ordinal)?.candidateRevision || "")).map((job) => job.ordinal),
|
|
550
|
+
]);
|
|
551
|
+
// Exact saved delivery/repair must not be gated by a new
|
|
552
|
+
// writing prompt. Isolate it so an overlarge sibling cannot
|
|
553
|
+
// prevent its recovery or force its regeneration.
|
|
554
|
+
const retained = current.filter((ordinal) => recoverable.has(ordinal));
|
|
555
|
+
if (retained.length && current.length > 1) {
|
|
556
|
+
const fresh = current.filter((ordinal) => !recoverable.has(ordinal));
|
|
557
|
+
queue.unshift(...retained.map((ordinal) => [ordinal]), ...(fresh.length ? [fresh] : []));
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
if (!retained.length) {
|
|
561
|
+
const promptTask = scriptPromptTask(task, record);
|
|
562
|
+
const planned = promptSafeScriptChunks(id, promptTask, current);
|
|
563
|
+
current = planned[0];
|
|
564
|
+
queue.unshift(...planned.slice(1));
|
|
565
|
+
}
|
|
566
|
+
if (admission.stopped)
|
|
567
|
+
break;
|
|
568
|
+
record.chunkSize = current.length;
|
|
569
|
+
record.attempts += 1;
|
|
570
|
+
record.message = `正在滚动写作,本次 ${current.length} 条;完成即补下一组(已回传 ${record.receivedOrdinals.length}/${task.requested_count})`;
|
|
571
|
+
record.updatedAt = now();
|
|
572
|
+
this.save();
|
|
573
|
+
const result = await this.runScriptChunk(id, task, current, cwd);
|
|
574
|
+
results.push(result);
|
|
575
|
+
// Stop before the ACK refresh can yield. Other in-flight
|
|
576
|
+
// writes still finish and keep their exact pending/ACK state.
|
|
577
|
+
if (result.terminal && result.terminalKind !== "review" || scriptCreativeReplanOrdinals([result]).length)
|
|
578
|
+
admission.stopped = true;
|
|
579
|
+
task = await this.scriptTask(id);
|
|
580
|
+
const remaining = current.filter((ordinal) => !settled().has(ordinal));
|
|
581
|
+
if (admission.stopped || !remaining.length)
|
|
582
|
+
continue;
|
|
583
|
+
const nextSize = chunkSizes.find((size) => size < current.length);
|
|
584
|
+
if (!nextSize) {
|
|
585
|
+
stopWithError(new Error(`本机 Codex 已隔离到 ordinal ${remaining.join(", ")} 单条仍未回传(${result.error || "未返回可用的结构化脚本"}),请点击重试`), remaining);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
// Only this failed entry descends. Successful siblings never
|
|
589
|
+
// reset it to a larger size or retry its in-flight ordinals.
|
|
590
|
+
queue.unshift(...flowCScriptChunks(durationSeconds, remaining, nextSize));
|
|
591
|
+
}
|
|
592
|
+
catch (error) {
|
|
593
|
+
stopWithError(error, current);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
const count = Math.max(1, Math.min(FLOW_C_CODEX_WORKER_CONCURRENCY, Math.floor(concurrency) || 1));
|
|
598
|
+
await Promise.all(Array.from({ length: count }, () => worker()));
|
|
599
|
+
return results;
|
|
600
|
+
}
|
|
517
601
|
/** Analyze each product once; image 1 owns identity and later images may only corroborate visible evidence. */
|
|
518
602
|
async ensureProductExecutionProfiles(id, task, cwd) {
|
|
519
603
|
const record = this.scriptRecord(id);
|
|
@@ -700,9 +784,7 @@ export class WorkflowManager {
|
|
|
700
784
|
const segmentSeconds = flowCTaskSegmentSeconds(task);
|
|
701
785
|
let prompt;
|
|
702
786
|
try {
|
|
703
|
-
const promptTask =
|
|
704
|
-
? { ...task, received_ordinals: activeRecord.receivedOrdinals, content_recent_scripts: mergeFlowCContentSummaries([...(Array.isArray(task.content_recent_scripts) ? task.content_recent_scripts : []), ...(activeRecord.contentSummaries || [])], [], activeRecord.receivedOrdinals) }
|
|
705
|
-
: task;
|
|
787
|
+
const promptTask = scriptPromptTask(task, activeRecord);
|
|
706
788
|
prompt = scriptChunkPrompt(id, promptTask, ordinals, rewriteAttempt);
|
|
707
789
|
}
|
|
708
790
|
catch (error) {
|
|
@@ -1394,6 +1476,29 @@ class FlowCPromptPayloadTooLargeError extends Error {
|
|
|
1394
1476
|
export function isFlowCPromptPayloadTooLarge(error) {
|
|
1395
1477
|
return Boolean(error && typeof error === "object" && error.code === "FLOW_C_PROMPT_PAYLOAD_TOO_LARGE");
|
|
1396
1478
|
}
|
|
1479
|
+
function scriptPromptTask(task, record) {
|
|
1480
|
+
return flowCContentStrategy(task.creative_strategy)
|
|
1481
|
+
? { ...task, received_ordinals: record.receivedOrdinals, content_recent_scripts: mergeFlowCContentSummaries([...(Array.isArray(task.content_recent_scripts) ? task.content_recent_scripts : []), ...(record.contentSummaries || [])], [], record.receivedOrdinals) }
|
|
1482
|
+
: task;
|
|
1483
|
+
}
|
|
1484
|
+
/** Preflight the real immutable prompt; only a size error permits subdividing it. */
|
|
1485
|
+
export function promptSafeScriptChunks(id, task, ordinals) {
|
|
1486
|
+
if (!ordinals.length)
|
|
1487
|
+
return [];
|
|
1488
|
+
try {
|
|
1489
|
+
scriptChunkPrompt(id, task, ordinals);
|
|
1490
|
+
return [[...ordinals]];
|
|
1491
|
+
}
|
|
1492
|
+
catch (error) {
|
|
1493
|
+
if (!isFlowCPromptPayloadTooLarge(error))
|
|
1494
|
+
throw error;
|
|
1495
|
+
if (ordinals.length === 1)
|
|
1496
|
+
throw new Error(`ordinal ${ordinals[0]} 的完整蓝图单条仍超限,已停止写作并保留任务与已完成脚本。${error.message}`);
|
|
1497
|
+
const durationSeconds = Number(task.duration_seconds || 10);
|
|
1498
|
+
const smallerSize = flowCScriptChunkSizes(durationSeconds).find((size) => size < ordinals.length) || 1;
|
|
1499
|
+
return flowCScriptChunks(durationSeconds, ordinals, smallerSize).flatMap((chunk) => promptSafeScriptChunks(id, task, chunk));
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1397
1502
|
export function productExecutionProfilePrompt(product, contractVersion = FLOW_C_PRODUCT_EXECUTION_PROFILE_CONTRACT_VERSION) {
|
|
1398
1503
|
const referenceCount = contractVersion === FLOW_C_PRODUCT_EXECUTION_PROFILE_CONTRACT_VERSION
|
|
1399
1504
|
? Math.max(1, Math.min(5, product.productImageUrlsInExactOrder?.length || 0))
|
|
@@ -1488,14 +1593,20 @@ export function scriptChunkPrompt(id, task, ordinals, rewriteAttempt = 0) {
|
|
|
1488
1593
|
segmentSeconds,
|
|
1489
1594
|
});
|
|
1490
1595
|
const generatedMontageMethod = flowCGeneratedMontagePrompt(generatedMontageOrdinals, segmentSeconds);
|
|
1596
|
+
const conciseExecution = Boolean(contentStrategy || generatedMontageOrdinals.length);
|
|
1597
|
+
const selectedPayload = selectedBlueprintPromptPayload([...selected.values()], duration, contentStrategy);
|
|
1598
|
+
const blueprintPayload = conciseExecution ? unescapeBlueprintPromptPayload(selectedPayload) : selectedPayload;
|
|
1599
|
+
const executionWriting = conciseExecution
|
|
1600
|
+
? '\n执行稿只写实际拍摄/生成需要的信息,不写创作理由、评分或模板讲解。visual 用简洁制作英文保留必要的主体位置、景别、运镜、光线、材质、动作与可见结果;已在共享设定确定且本镜未变化的内容不反复铺陈。evidence 简短说明本镜具体可见的证明或结果,不复述整段 visual;emotionalNote、voiceCue 用准确短语,不写情绪分析段落。镜头数量由所选蓝图的因果、节奏和目标时长决定,不为凑字段加镜,不为缩短文字删减有效镜头、动作、商品依据或用户锁定的原文对白。\n'
|
|
1601
|
+
: '';
|
|
1491
1602
|
return `你正在后台完成 Flow C 脚本交接 ${id},只写 ordinal:${ordinals.join(", ")};映射:${scriptProductAssignments(task.product_quantities, ordinals)}。
|
|
1492
1603
|
目标市场:${task.market}。目标口播语言:${targetVoiceLanguage}。结构化当地化事实(国家、语言/locale、出镜者、受众、生活场景、创作者口吻和CTA彼此独立;不得从国家推断族群):${compactJson(localization, 3_000, "当前脚本子批的当地化事实")}。商品事实:${compactJson(productFacts, 12_000, "当前脚本子批的商品事实")}
|
|
1493
1604
|
中心已完成当前 ordinal 的创意选择。executionBlueprints 只保存一次共享的模型无关创意蓝图(可能来自已学习爆款,也可能来自已筛选带货形式卡);productAdaptations 只保存一次每个不同的当前商品执行卡;ordinalBindings ${contentStrategy ? "用 blueprintRef/adaptationRef、variationSeed 和逐条 contentDirection" : "只用 blueprintRef/adaptationRef 和 variationSeed"} 映射到具体脚本。存在 blueprintRef 时,对应 executionBlueprint 是唯一结构权威;没有 blueprintRef 时,对应 productAdaptation 是用户框架的唯一执行权威。相同蓝图可供同一商品整批复用,直接替换成当前商品执行,不要生成候选、重新选题或重新评分:
|
|
1494
|
-
${compactJson(
|
|
1605
|
+
${compactJson(blueprintPayload, FLOW_C_SELECTED_BLUEPRINT_PROMPT_MAX_CHARS, "当前脚本子批的共享蓝图与商品适配")}
|
|
1495
1606
|
${rewriteInstruction}
|
|
1496
1607
|
${durationRules}
|
|
1497
1608
|
写作要求:
|
|
1498
|
-
1. visualHook、conflict、escalation/turn、productIntervention、visibleProof、purchaseReason、callbackMotivation 与所有 shots 必须执行选中卡的 visualPremise、firstFrame、spectacleEscalation、productProofAction、purchaseReason、truthBoundary 以及七项视觉执行字段;保留所选蓝图的因果时间线、节拍比例、镜头/剪辑节奏、动作压力、证明方式和购买逻辑,只替换当前商品、人物、场景与当地口播槽位,不得稀释构图或换成普通模板。爆款来源的旧商品身份/动作必须替换;形式卡只提供抽象创意结构,绝不能反向改变现有首帧、分段、${videoModelName}或参考图制作方式。形式库中的工厂、仓库、街访、使用者体验、主理人、探店、补货、对比、促销和耐用形式都可以用 AI 人物与场景正常演绎;缺少现实素材或事实证据不是脚本失败条件,必须按 executionBlueprint.executionAdaptation 保留所选形式,同时不把演绎的角色、地点、订单、库存、销量、价格、身份或经历写成已核验的现实事实。若 productAdaptation.templateMatch.actionCompatibility 的 openingAction、proofAction 或 spectacleAction 为 false,对应 productAdaptation 的 firstFrame、productProofAction 或 spectacleEscalation 对商品动作内容具有绝对优先级,必须删除其旧商品动作,不得折中混用。productProofAction 必须同时写清一个商品图或执行档案能支持的可见动作及其镜头内可见结果;purchaseReason 只解释该结果为何值得目标用户购买,不能变成泛泛 CTA。
|
|
1609
|
+
1. ${conciseExecution ? "所有 structured shots" : "visualHook、conflict、escalation/turn、productIntervention、visibleProof、purchaseReason、callbackMotivation 与所有 shots"} 必须执行选中卡的 visualPremise、firstFrame、spectacleEscalation、productProofAction、purchaseReason、truthBoundary 以及七项视觉执行字段;保留所选蓝图的因果时间线、节拍比例、镜头/剪辑节奏、动作压力、证明方式和购买逻辑,只替换当前商品、人物、场景与当地口播槽位,不得稀释构图或换成普通模板。爆款来源的旧商品身份/动作必须替换;形式卡只提供抽象创意结构,绝不能反向改变现有首帧、分段、${videoModelName}或参考图制作方式。形式库中的工厂、仓库、街访、使用者体验、主理人、探店、补货、对比、促销和耐用形式都可以用 AI 人物与场景正常演绎;缺少现实素材或事实证据不是脚本失败条件,必须按 executionBlueprint.executionAdaptation 保留所选形式,同时不把演绎的角色、地点、订单、库存、销量、价格、身份或经历写成已核验的现实事实。若 productAdaptation.templateMatch.actionCompatibility 的 openingAction、proofAction 或 spectacleAction 为 false,对应 productAdaptation 的 firstFrame、productProofAction 或 spectacleEscalation 对商品动作内容具有绝对优先级,必须删除其旧商品动作,不得折中混用。productProofAction 必须同时写清一个商品图或执行档案能支持的可见动作及其镜头内可见结果;purchaseReason 只解释该结果为何值得目标用户购买,不能变成泛泛 CTA。
|
|
1499
1610
|
2. 严格执行 executionBlueprints 中的 sourceDurationSeconds → targetDurationSeconds 和 retimingMode:expand 只增加有用的证明停留、使用语境与购买理由,compress 只压缩重复、冗余铺垫和转场,same 保持原节拍比例;三种模式都必须保留 Hook、证明和购买因果,禁止整体拉伸、截断或机械加速。
|
|
1500
1611
|
3. 每条生成一份稳定 voiceProfile;每段只写简短 voiceCue。口播必须使用${targetVoiceLanguage},并执行 creatorVoiceStyle/ctaStyle;未提供 creatorVoiceStyle 时使用当地 TikTok 带货创作者真实会说的口吻。${contentStrategy || generatedMontageOrdinals.length > 0 ? `不是逐字翻译。Hook → Body/visible proof → Close 是画面叙事结构,不等于口播结构;镜头数量不等于台词数量。先基于已经确定的画面,为每个局部${segmentSeconds}秒拟一份按实际镜头时长能自然说完的很短当地口播:用户锁定对白先以原词计入并优先给承载镜头足够秒数,不得删改;除此之外整段只留1–2个必要短句。再按完整词组或自然分句分配到真正展示相关动作/判断的镜头,允许一句在连续相关 cuts 间自然延续,但每镜 voiceover 只存该镜实际说出的片段,不逐镜复述 visual。允许符合当地带货口吻的自然偏快语速,偶尔密一点无需重写;不要夸张急促、吞字或失去可懂度。连续相关镜头可承接同一句口播;只有明显超出实际可用时长时才删除模型自行增加的赘句,不为填满有声镜硬塞台词;其它镜头默认 voiceover="none",只在 soundBgm 保留动作现场声。结尾的一个简短自然 CTA 并入上述已定短句;若锁定对白占满可用口播时长就不再追加 CTA。用户锁定的对白、原框架和目标语言优先;上述拟稿与分配在同一次写作内完成,不输出中间声音轨。` : '不是逐字翻译,整条内容按 Hook → Body/visible proof → Close 组织,短句口语化且与当前镜头可见动作同步,结尾只给一个简短自然 CTA。'}只使用少量可信的 localSceneProfile/audienceContext 日常细节;presenterContext 没填写时使用普通创作者,不得把国家代码、语言或地区自动等同于人物族群。商品事实没有提供时不得编造价格、折扣、销量、库存或稀缺性。商品标题只用于理解商品类型与事实,所有输出不得主动写出或念出品牌名、品牌广告语、包装文案,也不得要求模型把品牌字样变得更清晰;商品外观与已有标识的位置、比例、颜色和版式只服从第1张产品参考图。TikTok/TikTok Shop 只用于定义这条写作规则,不得把平台名写入 voiceover、onScreenText、visual、soundBgm、emotionalNote、openingState、endingState 或任何会进入故事板/视频的导演字段。导演说明统一用简洁制作英文;这里只做写作指令,不做语言检测字段。
|
|
1501
1612
|
4. 每段永远是独立 0–${segmentSeconds} 秒,含 1–8 个按剧情需要决定的 ${contentStrategy ? `shots。每镜 voiceover 字段必须存在,但不等于必须有台词:只填上一步分配给该镜的原句,其余填非空静默标记 "none",绝不让配音念出 none;每镜同时给出准确 onScreenText、evidence、soundBgm、emotionalNote,并提供每段 endingState。先写末镜实际动作,再在末镜 visual 最后用一个短句明确第${segmentSeconds}秒的最终画面;endingState.endingFrame 逐字复用这个收尾短句,其余 endingState 字段也只描述同一瞬间,不另编姿势或动作。已走出画面的人不能仍在画内回头,已经落地的脚不能又悬在半空,已经完成的动作不能仍写为待完成;确已完成且无续接动作时 unfinishedAction 写 none。下一段首镜从这个真实终点继续,不重置人物、物体或动作;最终一段也要检查,不能只保证跨段字段相等` : 'shots、准确 voiceover、onScreenText、evidence、soundBgm、emotionalNote 与 endingState'};模型不要输出 continuity 或 continuityMode。
|
|
@@ -1503,7 +1614,7 @@ ${durationRules}
|
|
|
1503
1614
|
6. 多段视频的后一段剧情承接前段 endingState,但每段仍只使用自己的局部 0–${segmentSeconds} 秒执行内容。
|
|
1504
1615
|
7. 不要输出 creativePlan、executionBindings 或 masterScript;这些字段由 Agent 从选中蓝图确定性回填/合成,避免重复 token 和脆弱的文字绑定校验。若 selectionMode=user-framework,用户原框架的开头、事件顺序、核心剧情和结尾仍是最高权威。
|
|
1505
1616
|
8. 商品身份以按顺序提供的原商品图为最高权威:第1张是主SKU身份图,当前商品标题和大概类目只补充商品用途与事实,后续图片只是同一SKU补充视角。任何爆款源商品、首帧、故事板或上一段尾帧都不得改变商品颜色、款式、结构、比例、材质、包装、标识位置、部件、配件和实际套装数量;修正身份时不得降低动作、运镜、节奏、景深、真实感或画质。商品本体或包装表面的印花、图案、微纹理、已印文字/字符、图标、色块、行距、相对位置和朝向全部视为不可编辑的原图纹理;不得在 shot.visual、onScreenText、voiceover、evidence、openingState、endingState 或任何导演字段中要求重写、翻译、替换、删改、重排、镜像这些表面内容,也不得要求生成“另一段清晰可读文案”。新增屏幕字只能是与商品像素分离的场景叠加字幕,绝不能印到商品或包装上。旋转、翻转、弯折或开合商品时,完整表面纹理必须随实体整体运动,不能漂移、翻面后复写或重新生成。若 executionBlueprint 或 productAdaptation 与本规则冲突,只保留其钩子、动作因果和镜头节奏,改写构图、机位或表演,绝不改商品表面。
|
|
1506
|
-
${contentMethod}${generatedMontageMethod}固定使用 GPT-5.6 Terra 中等推理。媒体制作方式与分镜布局由中心在脚本完成后管理,不得影响这里的创意、镜头、口播或结构化脚本。只返回当前结构化 jobs,不调用工具、不创建任何图片或视频任务。`;
|
|
1617
|
+
${contentMethod}${generatedMontageMethod}${executionWriting}固定使用 GPT-5.6 Terra 中等推理。媒体制作方式与分镜布局由中心在脚本完成后管理,不得影响这里的创意、镜头、口播或结构化脚本。只返回当前结构化 jobs,不调用工具、不创建任何图片或视频任务。`;
|
|
1507
1618
|
}
|
|
1508
1619
|
export function creativeCandidatePrompt(id, task, ordinals) {
|
|
1509
1620
|
const products = relevantProductInputs(task, ordinals);
|
|
@@ -1701,6 +1812,21 @@ export function selectedBlueprintPromptPayload(values, fallbackTargetDurationSec
|
|
|
1701
1812
|
ordinalBindings,
|
|
1702
1813
|
};
|
|
1703
1814
|
}
|
|
1815
|
+
/** Avoid encoding a complete JSON blueprint as an escaped JSON string again. */
|
|
1816
|
+
export function unescapeBlueprintPromptPayload(payload) {
|
|
1817
|
+
return {
|
|
1818
|
+
...payload,
|
|
1819
|
+
executionBlueprints: payload.executionBlueprints.map((entry) => {
|
|
1820
|
+
try {
|
|
1821
|
+
const blueprint = JSON.parse(entry.executionBlueprint);
|
|
1822
|
+
if (blueprint && typeof blueprint === "object" && !Array.isArray(blueprint))
|
|
1823
|
+
return { ...entry, executionBlueprint: blueprint };
|
|
1824
|
+
}
|
|
1825
|
+
catch { /* Plain-text and legacy blueprints keep their exact content. */ }
|
|
1826
|
+
return entry;
|
|
1827
|
+
}),
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1704
1830
|
function positiveDuration(value) {
|
|
1705
1831
|
const duration = Number(value);
|
|
1706
1832
|
return Number.isFinite(duration) && duration > 0 ? Math.round(duration * 1000) / 1000 : null;
|