@xiaohhhh1/canvas-agent 0.4.76 → 0.4.77
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.
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare const FLOW_C_CONTENT_STRATEGY_VERSION = "flow-c-content-strategy-v1";
|
|
2
|
+
export type FlowCContentStrategy = {
|
|
3
|
+
contractVersion: typeof FLOW_C_CONTENT_STRATEGY_VERSION;
|
|
4
|
+
mode: "smart-diverse" | "best-match";
|
|
5
|
+
seed: string;
|
|
6
|
+
};
|
|
7
|
+
export type FlowCContentDirection = {
|
|
8
|
+
contractVersion: typeof FLOW_C_CONTENT_STRATEGY_VERSION;
|
|
9
|
+
mode: FlowCContentStrategy["mode"];
|
|
10
|
+
directionKey: string;
|
|
11
|
+
intentLabel: string;
|
|
12
|
+
brief: string;
|
|
13
|
+
selectionReason: string;
|
|
14
|
+
variationSeed: string;
|
|
15
|
+
recentAvoidance?: {
|
|
16
|
+
sameProductSelections: number;
|
|
17
|
+
matchedDirectionUses: number;
|
|
18
|
+
policy: "soft-penalty-only";
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export type FlowCContentSummary = {
|
|
22
|
+
ordinal: number;
|
|
23
|
+
productIndex: number;
|
|
24
|
+
opening: string;
|
|
25
|
+
proof: string;
|
|
26
|
+
voiceover: string;
|
|
27
|
+
};
|
|
28
|
+
/** Missing/unknown versions keep historical tasks on their exact original prompt. */
|
|
29
|
+
export declare function flowCContentStrategy(value: unknown): FlowCContentStrategy | null;
|
|
30
|
+
export declare function flowCContentDirection(value: unknown, strategy: FlowCContentStrategy | null): FlowCContentDirection | null;
|
|
31
|
+
/** Summarize actual rendered output, never the seed-labelled candidate fingerprint. */
|
|
32
|
+
export declare function summarizeFlowCContentJob(value: unknown): FlowCContentSummary | null;
|
|
33
|
+
/** Only centrally acknowledged ordinals are durable avoidance evidence. */
|
|
34
|
+
export declare function mergeFlowCContentSummaries(values: unknown, jobs: unknown, acceptedOrdinals: readonly number[]): FlowCContentSummary[];
|
|
35
|
+
export declare function flowCContentMethodPrompt(strategy: FlowCContentStrategy | null, options: {
|
|
36
|
+
recentScripts?: unknown;
|
|
37
|
+
productIndexes: number[];
|
|
38
|
+
ordinals: number[];
|
|
39
|
+
frameworkOrdinals: number[];
|
|
40
|
+
}): string;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const FLOW_C_CONTENT_STRATEGY_VERSION = "flow-c-content-strategy-v1";
|
|
2
|
+
function object(value) {
|
|
3
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
4
|
+
}
|
|
5
|
+
function text(value, limit) {
|
|
6
|
+
return String(value ?? "").normalize("NFKC").trim().replace(/\s+/g, " ").slice(0, limit);
|
|
7
|
+
}
|
|
8
|
+
function summaryText(value, limit) {
|
|
9
|
+
return text(typeof value === "string" ? value.replace(/https?:\/\/\S+|data:\S+/gi, "[link]") : "", limit);
|
|
10
|
+
}
|
|
11
|
+
/** Missing/unknown versions keep historical tasks on their exact original prompt. */
|
|
12
|
+
export function flowCContentStrategy(value) {
|
|
13
|
+
const input = object(value);
|
|
14
|
+
if (input.contractVersion !== FLOW_C_CONTENT_STRATEGY_VERSION || (input.mode !== "smart-diverse" && input.mode !== "best-match"))
|
|
15
|
+
return null;
|
|
16
|
+
const seed = text(input.seed, 1000);
|
|
17
|
+
return seed ? { contractVersion: FLOW_C_CONTENT_STRATEGY_VERSION, mode: input.mode, seed } : null;
|
|
18
|
+
}
|
|
19
|
+
export function flowCContentDirection(value, strategy) {
|
|
20
|
+
const input = object(value);
|
|
21
|
+
if (!strategy || input.contractVersion !== strategy.contractVersion || input.mode !== strategy.mode)
|
|
22
|
+
return null;
|
|
23
|
+
const directionKey = text(input.directionKey, 100);
|
|
24
|
+
const variationSeed = text(input.variationSeed, 100);
|
|
25
|
+
if (!/^flow-c-direction:[a-f0-9]{32}$/.test(directionKey) || !/^[a-f0-9]{24}$/.test(variationSeed))
|
|
26
|
+
return null;
|
|
27
|
+
const avoidance = object(input.recentAvoidance);
|
|
28
|
+
const count = (value) => Math.min(1000, Math.max(0, Math.floor(Number(value) || 0)));
|
|
29
|
+
const recentAvoidance = input.recentAvoidance && typeof input.recentAvoidance === "object" && !Array.isArray(input.recentAvoidance)
|
|
30
|
+
? { sameProductSelections: count(avoidance.sameProductSelections), matchedDirectionUses: count(avoidance.matchedDirectionUses), policy: "soft-penalty-only" }
|
|
31
|
+
: undefined;
|
|
32
|
+
return {
|
|
33
|
+
contractVersion: strategy.contractVersion,
|
|
34
|
+
mode: strategy.mode,
|
|
35
|
+
directionKey,
|
|
36
|
+
intentLabel: text(input.intentLabel, 120),
|
|
37
|
+
brief: text(input.brief, 2400),
|
|
38
|
+
selectionReason: text(input.selectionReason, 700),
|
|
39
|
+
variationSeed,
|
|
40
|
+
...(recentAvoidance ? { recentAvoidance } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function summary(value) {
|
|
44
|
+
const input = object(value);
|
|
45
|
+
if (!Number.isInteger(input.ordinal) || Number(input.ordinal) < 1 || !Number.isInteger(input.productIndex) || Number(input.productIndex) < 0)
|
|
46
|
+
return null;
|
|
47
|
+
const result = { ordinal: Number(input.ordinal), productIndex: Number(input.productIndex), opening: summaryText(input.opening, 240), proof: summaryText(input.proof, 240), voiceover: summaryText(input.voiceover, 160) };
|
|
48
|
+
return result.opening || result.proof || result.voiceover ? result : null;
|
|
49
|
+
}
|
|
50
|
+
/** Summarize actual rendered output, never the seed-labelled candidate fingerprint. */
|
|
51
|
+
export function summarizeFlowCContentJob(value) {
|
|
52
|
+
const job = object(value);
|
|
53
|
+
const script = typeof job.script === "string" ? job.script : "";
|
|
54
|
+
const fields = (name) => [...script.matchAll(new RegExp(`(?:^|\\|)\\s*${name}:\\s*([^|\\r\\n]*)`, "gm"))]
|
|
55
|
+
.map((match) => match[1].trim()).filter((item) => item && !/^(?:none|无|sin voz|sin diálogo)$/i.test(item));
|
|
56
|
+
return summary({ ordinal: job.ordinal, productIndex: job.productIndex, opening: fields("VISUAL")[0], proof: fields("EVIDENCE").slice(0, 3).join("; "), voiceover: fields("VO")[0] });
|
|
57
|
+
}
|
|
58
|
+
/** Only centrally acknowledged ordinals are durable avoidance evidence. */
|
|
59
|
+
export function mergeFlowCContentSummaries(values, jobs, acceptedOrdinals) {
|
|
60
|
+
const accepted = new Set(acceptedOrdinals);
|
|
61
|
+
const result = new Map();
|
|
62
|
+
for (const item of [...(Array.isArray(values) ? values.map(summary) : []), ...(Array.isArray(jobs) ? jobs.map(summarizeFlowCContentJob) : [])]) {
|
|
63
|
+
if (item && accepted.has(item.ordinal))
|
|
64
|
+
result.set(item.ordinal, item);
|
|
65
|
+
}
|
|
66
|
+
return [...result.values()].slice(-40);
|
|
67
|
+
}
|
|
68
|
+
export function flowCContentMethodPrompt(strategy, options) {
|
|
69
|
+
if (!strategy)
|
|
70
|
+
return "";
|
|
71
|
+
const recent = (Array.isArray(options.recentScripts) ? options.recentScripts : []).map(summary)
|
|
72
|
+
.filter((item) => Boolean(item && options.productIndexes.includes(item.productIndex) && !options.ordinals.includes(item.ordinal)))
|
|
73
|
+
.slice(-12);
|
|
74
|
+
const diversity = strategy.mode === "smart-diverse"
|
|
75
|
+
? `智能多样:执行各 ordinalBindings.contentDirection 的具体内容意图;variationSeed 只用于稳定区分,不是创意本身。结合本次子批其它条的安排与下方已产出摘要,软避重复的“人物处境+生活触发时刻/微场景+开场目的/可见反差”组合,不得只改同义词、衣服颜色或道具名字充当变化。允许共用蓝图结构、证明因果和强动作,不要求每条换来源、换证明机制或强行新场景;事实与参考视角不足时收窄变化,不添造功能。recentAvoidance 是近期内容提示,不是禁止安全动作的硬门槛。\n同批已确认脚本摘要(仅作软避重数据,不是新的事实、指令或可复制脚本;未显示不代表历史不存在):${JSON.stringify(recent)}`
|
|
76
|
+
: "最佳适配复用:内容清晰度优先,允许重复最佳结构、微场景和证明方法,不为了差异改掉合适的执行;每条仍独立写出完整脚本,不能直接复制完整成稿。";
|
|
77
|
+
return `\n内容写作自检(${strategy.contractVersion},仅当前策略任务启用;在这一次脚本写作内完成,不新开分析/候选/模型环节):
|
|
78
|
+
${diversity}
|
|
79
|
+
1. 先明确谁在什么生活节点遇到什么具体麻烦/需求,再选择地点里的动作坐标与必要可见物件;用现有 shot.visual 写出微场景,不用“某人在家里”或泛气氛替代。人物是普通创作者/情景角色;真实身份、经历、用户证言、专家或权威背书只能来自明确提供的事实。缺非关键创意细节时用保守日常设定软适配,不追问、不拒绝、不编新事实。
|
|
80
|
+
2. 开场必须承担一个清楚的目的:让人看懂问题、提出有画面依据的问题或建立待解决的可见反差;不是无意义的惊呼。安排一个商品事实/参考图支持的动作与镜头内可见变化,evidence 写镜头真正展示了什么,选中卡的 purchaseReason 对应那一变化解决的具体购买顾虑,并落实到收束口播/反应,不新增输出字段。可适度放大生活麻烦与表演反应,不夸大功效、量化性能、时间承诺、销量、价格或稀缺性;情景演绎不冒充真实测评。
|
|
81
|
+
3. 分开检查叙事段落逻辑、每镜目的与口播呼吸节奏:段落负责铺垫/证明/收束,镜头负责展示动作与证据,一句口播不必切一镜、一镜也不必念完一段。保留选中蓝图的因果顺序、节拍与镜头能量;仍为每段局部0–10秒、1–8镜,20/30秒连续关系和三种媒体共同内容契约不变。
|
|
82
|
+
4. 自检上方已定稿的目标语言的自然短句,不再按镜头补新台词。逐镜按实际时长 endSeconds − startSeconds 检查,先为关键动作/可见结果留静默与呼吸。仅英语、西语等通常按空格分词的语言,以写作起点粗估约2词/秒,动作/结果镜头更少:1.5秒约2–3词,不塞完整8词问句;整段10秒约12–20词只是起点,可以更少,不是最低字数要求。不能套用到中文、日语等其它语言,不套统一英语词数或固定八秒模板。这是同轮软预算,不是语言验收拒绝条件或新重写环节;只在用户锁定内容之外收窄意图、缩短句子,不得提高语速硬塞。voiceProfile.speakingRate/pauseHabit 与 voiceCue 要符合实际可说完的语速,不得标成 unhurried/慢速却塞满台词;emotionalNote 只写镜头目的/情绪转折。不输出计算、推理或预算报告。
|
|
83
|
+
5. contentDirection 只细化既有蓝图可替换的人物、微场景、措辞和执行槽位,不改变商品身份、纹理文字、能力证据、0–10秒分段或媒体制作方式。creativeBrief 始终最高优先;用户框架 ordinal ${JSON.stringify(options.frameworkOrdinals)} 的开头、事件顺序、核心剧情和结尾不可被方向卡/避重覆盖,只能在其留白处具体化。
|
|
84
|
+
返回前按以上内容要求自检,修正能在本次写作中修正的空泛描述。自检不是新验收闸门;只输出原严格 schema 的既有字段,不输出推理、自检报告、contentDirection 或其它新增字段。\n`;
|
|
85
|
+
}
|
|
@@ -2,6 +2,7 @@ import type { AgentEmit } from "../agent/types.js";
|
|
|
2
2
|
import { type CanvasAgentConfig } from "../config.js";
|
|
3
3
|
import { type FlowCProductExecutionProfile } from "./product-profile.js";
|
|
4
4
|
import { type CommerceRequestDiagnostic } from "./commerce-http.js";
|
|
5
|
+
import { type FlowCContentStrategy, type FlowCContentSummary } from "./content-method.js";
|
|
5
6
|
export declare const FLOW_C_CODEX_TURN_TIMEOUT_MS: number;
|
|
6
7
|
export declare const FLOW_C_SELECTED_BLUEPRINT_PROMPT_MAX_CHARS = 36000;
|
|
7
8
|
export declare const FLOW_C_SCRIPT_REWRITE_MAX_ATTEMPTS = 2;
|
|
@@ -26,6 +27,8 @@ type ScriptRecord = {
|
|
|
26
27
|
activeChunks?: number;
|
|
27
28
|
productProfiles?: FlowCProductExecutionProfile[];
|
|
28
29
|
pendingScriptJobs?: DraftJob[];
|
|
30
|
+
contentStrategy?: FlowCContentStrategy;
|
|
31
|
+
contentSummaries?: FlowCContentSummary[];
|
|
29
32
|
lastFailure?: CommerceRequestDiagnostic;
|
|
30
33
|
retryRequested?: boolean;
|
|
31
34
|
priorityAt?: string;
|
|
@@ -78,6 +81,7 @@ type SelectedCandidate = {
|
|
|
78
81
|
ordinal: number;
|
|
79
82
|
productIndex: number;
|
|
80
83
|
candidateRevision?: string;
|
|
84
|
+
contentDirection?: unknown;
|
|
81
85
|
scriptSource?: string;
|
|
82
86
|
creativeSource?: string;
|
|
83
87
|
sellingFormCardId?: string | null;
|
|
@@ -122,6 +126,8 @@ type ScriptTask = {
|
|
|
122
126
|
market: string;
|
|
123
127
|
target_language?: string | null;
|
|
124
128
|
localization?: Record<string, unknown>;
|
|
129
|
+
creative_strategy?: unknown;
|
|
130
|
+
content_recent_scripts?: FlowCContentSummary[];
|
|
125
131
|
script_source_default?: string;
|
|
126
132
|
duration_seconds?: 10 | 20 | 30;
|
|
127
133
|
script_output_contract_version?: string;
|
|
@@ -271,6 +277,7 @@ export declare class WorkflowManager {
|
|
|
271
277
|
}[];
|
|
272
278
|
};
|
|
273
279
|
health(): {
|
|
280
|
+
contentStrategyVersion: string;
|
|
274
281
|
activeScriptHandoffIds: string[];
|
|
275
282
|
activeScripts: number;
|
|
276
283
|
scriptConcurrencyLimit: number;
|
|
@@ -404,7 +411,7 @@ export declare function isFlowCPromptPayloadTooLarge(error: unknown): error is E
|
|
|
404
411
|
export declare function productExecutionProfilePrompt(product: ProductInput, contractVersion?: string): string;
|
|
405
412
|
export declare function scriptChunkPrompt(id: string, task: ScriptTask, ordinals: number[], rewriteAttempt?: number): string;
|
|
406
413
|
export declare function creativeCandidatePrompt(id: string, task: ScriptTask, ordinals: number[]): string;
|
|
407
|
-
export declare function selectedBlueprintPromptPayload(values: SelectedCandidate[], fallbackTargetDurationSeconds?: number): {
|
|
414
|
+
export declare function selectedBlueprintPromptPayload(values: SelectedCandidate[], fallbackTargetDurationSeconds?: number, contentStrategy?: FlowCContentStrategy | null): {
|
|
408
415
|
executionBlueprints: {
|
|
409
416
|
blueprintRef: string;
|
|
410
417
|
executionBlueprint: string;
|
|
@@ -414,6 +421,7 @@ export declare function selectedBlueprintPromptPayload(values: SelectedCandidate
|
|
|
414
421
|
}[];
|
|
415
422
|
productAdaptations: Record<string, unknown>[];
|
|
416
423
|
ordinalBindings: {
|
|
424
|
+
contentDirection?: import("./content-method.js").FlowCContentDirection | undefined;
|
|
417
425
|
ordinal: number;
|
|
418
426
|
productIndex: number;
|
|
419
427
|
blueprintRef: string | null;
|
|
@@ -421,6 +429,49 @@ export declare function selectedBlueprintPromptPayload(values: SelectedCandidate
|
|
|
421
429
|
variationSeed: string;
|
|
422
430
|
}[];
|
|
423
431
|
};
|
|
432
|
+
export declare function selectedCandidatePlan(value: SelectedCandidate | undefined, contentStrategy?: FlowCContentStrategy | null): {
|
|
433
|
+
contentDirection?: import("./content-method.js").FlowCContentDirection | undefined;
|
|
434
|
+
visualPremise: string;
|
|
435
|
+
learnedTemplateId: string | null;
|
|
436
|
+
learnedTemplateSource: Record<string, unknown> | null;
|
|
437
|
+
scriptSource: string | null;
|
|
438
|
+
sellingFormCardId: string | null;
|
|
439
|
+
sellingFormName: string | null;
|
|
440
|
+
sellingFormSelectionReason: string | null;
|
|
441
|
+
sellingFormLibraryVersion: string | null;
|
|
442
|
+
sellingFormSelectionVersion: string | null;
|
|
443
|
+
productIdentityProfile: Record<string, unknown>;
|
|
444
|
+
productExecutionProfileRef: string;
|
|
445
|
+
mutationAxes: string[];
|
|
446
|
+
culturalAnchors: Record<string, unknown>[];
|
|
447
|
+
spectacleEscalation: string;
|
|
448
|
+
productProofAction: string;
|
|
449
|
+
purchaseReason: string;
|
|
450
|
+
truthBoundary: string;
|
|
451
|
+
riskFlags: string[];
|
|
452
|
+
firstFrame: string;
|
|
453
|
+
visualHook: string;
|
|
454
|
+
conflict: string;
|
|
455
|
+
escalation: string;
|
|
456
|
+
turn: string;
|
|
457
|
+
productIntervention: string;
|
|
458
|
+
visibleProof: string;
|
|
459
|
+
callbackMotivation: string;
|
|
460
|
+
differentiationKey: string | undefined;
|
|
461
|
+
firstFrameFocus: string;
|
|
462
|
+
visualContrast: string;
|
|
463
|
+
sensoryTexture: string;
|
|
464
|
+
motionPeak: string;
|
|
465
|
+
compositionLighting: string;
|
|
466
|
+
localNativeDetail: string;
|
|
467
|
+
antiFlatness: string;
|
|
468
|
+
creativeFingerprint: Record<string, string>;
|
|
469
|
+
fingerprintKey: string | undefined;
|
|
470
|
+
selectionScore: number | undefined;
|
|
471
|
+
selectionBreakdown: Record<string, unknown> | undefined;
|
|
472
|
+
selectionMode: string | undefined;
|
|
473
|
+
visualExecutionVersion: string;
|
|
474
|
+
};
|
|
424
475
|
/**
|
|
425
476
|
* 已选创意永远优先进入脚本扩写;只有当前没有可写脚本时才生成下一小波候选。
|
|
426
477
|
* 每次最多占用受控 worker 数量的子批,避免把整批候选提前塞满 worker 队列。
|
package/dist/workflow/manager.js
CHANGED
|
@@ -14,6 +14,7 @@ import { FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION, flowCCreativeCandidateOutpu
|
|
|
14
14
|
import { FLOW_C_LEGACY_PRODUCT_EXECUTION_PROFILE_CONTRACT_VERSION, FLOW_C_PRODUCT_EXECUTION_PROFILE_CONTRACT_VERSION, flowCProductExecutionProfileOutputSchema, parseFlowCProductExecutionProfileOutput } from "./product-profile.js";
|
|
15
15
|
import { flowCScriptOutputSchema, parseFlowCScriptOutput } from "./script-output.js";
|
|
16
16
|
import { commerceJson, CommerceRequestError } from "./commerce-http.js";
|
|
17
|
+
import { FLOW_C_CONTENT_STRATEGY_VERSION, flowCContentDirection, flowCContentMethodPrompt, flowCContentStrategy, mergeFlowCContentSummaries } from "./content-method.js";
|
|
17
18
|
const STATE_FILE = path.join(CONFIG_DIR, "workflow-state.json");
|
|
18
19
|
export const FLOW_C_CODEX_TURN_TIMEOUT_MS = 8 * 60 * 1000;
|
|
19
20
|
export const FLOW_C_SELECTED_BLUEPRINT_PROMPT_MAX_CHARS = 36_000;
|
|
@@ -115,6 +116,12 @@ export class WorkflowManager {
|
|
|
115
116
|
record.receivedOrdinals = [...new Set([...record.receivedOrdinals, ...task.received_ordinals.map(Number).filter(Number.isInteger)])].sort((left, right) => left - right);
|
|
116
117
|
task.received_ordinals = record.receivedOrdinals;
|
|
117
118
|
record.productProfiles = mergeProductProfiles(record.productProfiles, task.product_execution_profiles);
|
|
119
|
+
const contentStrategy = flowCContentStrategy(task.creative_strategy);
|
|
120
|
+
if (contentStrategy) {
|
|
121
|
+
record.contentStrategy = contentStrategy;
|
|
122
|
+
record.contentSummaries = mergeFlowCContentSummaries([...(record.contentSummaries || []), ...(Array.isArray(task.content_recent_scripts) ? task.content_recent_scripts : [])], record.pendingScriptJobs, record.receivedOrdinals);
|
|
123
|
+
task.content_recent_scripts = record.contentSummaries;
|
|
124
|
+
}
|
|
118
125
|
record.pendingScriptJobs = (record.pendingScriptJobs || []).filter((job) => !record.receivedOrdinals.includes(job.ordinal));
|
|
119
126
|
record.expiresAt = task.expires_at;
|
|
120
127
|
record.updatedAt = now();
|
|
@@ -144,6 +151,8 @@ export class WorkflowManager {
|
|
|
144
151
|
record.receivedOrdinals = [...accepted].sort((a, b) => a - b);
|
|
145
152
|
}
|
|
146
153
|
record.requestedCount = Number(data.requestedCount || record.requestedCount);
|
|
154
|
+
if (flowCContentStrategy(record.contentStrategy))
|
|
155
|
+
record.contentSummaries = mergeFlowCContentSummaries(record.contentSummaries, jobs, record.receivedOrdinals);
|
|
147
156
|
record.pendingScriptJobs = (record.pendingScriptJobs || []).filter((job) => !record.receivedOrdinals.includes(job.ordinal));
|
|
148
157
|
record.status = data.status === "ready" ? "complete" : "running";
|
|
149
158
|
record.message = data.status === "ready" ? `全部 ${record.requestedCount} 条高质量脚本已回传` : `已回传 ${data.received}/${data.requestedCount} 条脚本`;
|
|
@@ -182,6 +191,7 @@ export class WorkflowManager {
|
|
|
182
191
|
const records = Object.values(this.state.scripts);
|
|
183
192
|
const workers = flowCCodexWorkerStatus();
|
|
184
193
|
return {
|
|
194
|
+
contentStrategyVersion: FLOW_C_CONTENT_STRATEGY_VERSION,
|
|
185
195
|
activeScriptHandoffIds: [...this.runningScripts],
|
|
186
196
|
activeScripts: workers.active,
|
|
187
197
|
scriptConcurrencyLimit: workers.limit,
|
|
@@ -611,7 +621,10 @@ export class WorkflowManager {
|
|
|
611
621
|
const durationSeconds = Number(task.duration_seconds || 10);
|
|
612
622
|
let prompt;
|
|
613
623
|
try {
|
|
614
|
-
|
|
624
|
+
const promptTask = flowCContentStrategy(task.creative_strategy)
|
|
625
|
+
? { ...task, received_ordinals: record.receivedOrdinals, content_recent_scripts: mergeFlowCContentSummaries([...(Array.isArray(task.content_recent_scripts) ? task.content_recent_scripts : []), ...(record.contentSummaries || [])], [], record.receivedOrdinals) }
|
|
626
|
+
: task;
|
|
627
|
+
prompt = scriptChunkPrompt(id, promptTask, ordinals, rewriteAttempt);
|
|
615
628
|
}
|
|
616
629
|
catch (error) {
|
|
617
630
|
if (isFlowCPromptPayloadTooLarge(error))
|
|
@@ -640,7 +653,7 @@ export class WorkflowManager {
|
|
|
640
653
|
...job,
|
|
641
654
|
sellingFormId: selected.get(job.ordinal)?.sellingFormCardId || job.sellingFormId,
|
|
642
655
|
expectedCandidateRevision: selected.get(job.ordinal)?.candidateRevision,
|
|
643
|
-
creativePlan: { ...(job.creativePlan || {}), ...selectedCandidatePlan(selected.get(job.ordinal)) },
|
|
656
|
+
creativePlan: { ...(job.creativePlan || {}), ...selectedCandidatePlan(selected.get(job.ordinal), flowCContentStrategy(task.creative_strategy)) },
|
|
644
657
|
}));
|
|
645
658
|
this.emitScriptStage(id, ordinals, "parse", Date.now() - parseStartedAt);
|
|
646
659
|
const persistStartedAt = Date.now();
|
|
@@ -1060,22 +1073,29 @@ export function scriptChunkPrompt(id, task, ordinals, rewriteAttempt = 0) {
|
|
|
1060
1073
|
: "";
|
|
1061
1074
|
const localization = compactTaskLocalization(task);
|
|
1062
1075
|
const targetVoiceLanguage = String(localization.targetLanguage || task.target_language || localization.targetLocale || "").trim() || `目标市场 ${task.market} 的自然当地语言`;
|
|
1076
|
+
const contentStrategy = flowCContentStrategy(task.creative_strategy);
|
|
1077
|
+
const contentMethod = flowCContentMethodPrompt(contentStrategy, {
|
|
1078
|
+
recentScripts: mergeFlowCContentSummaries(task.content_recent_scripts, [], task.received_ordinals),
|
|
1079
|
+
productIndexes: products.map((product) => product.productIndex),
|
|
1080
|
+
ordinals,
|
|
1081
|
+
frameworkOrdinals: [...selected.values()].filter((candidate) => candidate.selectionMode === "user-framework").map((candidate) => candidate.ordinal),
|
|
1082
|
+
});
|
|
1063
1083
|
return `你正在后台完成 Flow C 脚本交接 ${id},只写 ordinal:${ordinals.join(", ")};映射:${scriptProductAssignments(task.product_quantities, ordinals)}。
|
|
1064
1084
|
目标市场:${task.market}。目标口播语言:${targetVoiceLanguage}。结构化当地化事实(国家、语言/locale、出镜者、受众、生活场景、创作者口吻和CTA彼此独立;不得从国家推断族群):${compactJson(localization, 3_000, "当前脚本子批的当地化事实")}。商品事实:${compactJson(productFacts, 12_000, "当前脚本子批的商品事实")}
|
|
1065
|
-
中心已完成当前 ordinal 的创意选择。executionBlueprints 只保存一次共享的模型无关创意蓝图(可能来自已学习爆款,也可能来自已筛选带货形式卡);productAdaptations 只保存一次每个不同的当前商品执行卡;ordinalBindings 只用 blueprintRef/adaptationRef 和 variationSeed 映射到具体脚本。存在 blueprintRef 时,对应 executionBlueprint 是唯一结构权威;没有 blueprintRef 时,对应 productAdaptation 是用户框架的唯一执行权威。相同蓝图可供同一商品整批复用,直接替换成当前商品执行,不要生成候选、重新选题或重新评分:
|
|
1066
|
-
${compactJson(selectedBlueprintPromptPayload([...selected.values()], duration), FLOW_C_SELECTED_BLUEPRINT_PROMPT_MAX_CHARS, "当前脚本子批的共享蓝图与商品适配")}
|
|
1085
|
+
中心已完成当前 ordinal 的创意选择。executionBlueprints 只保存一次共享的模型无关创意蓝图(可能来自已学习爆款,也可能来自已筛选带货形式卡);productAdaptations 只保存一次每个不同的当前商品执行卡;ordinalBindings ${contentStrategy ? "用 blueprintRef/adaptationRef、variationSeed 和逐条 contentDirection" : "只用 blueprintRef/adaptationRef 和 variationSeed"} 映射到具体脚本。存在 blueprintRef 时,对应 executionBlueprint 是唯一结构权威;没有 blueprintRef 时,对应 productAdaptation 是用户框架的唯一执行权威。相同蓝图可供同一商品整批复用,直接替换成当前商品执行,不要生成候选、重新选题或重新评分:
|
|
1086
|
+
${compactJson(selectedBlueprintPromptPayload([...selected.values()], duration, contentStrategy), FLOW_C_SELECTED_BLUEPRINT_PROMPT_MAX_CHARS, "当前脚本子批的共享蓝图与商品适配")}
|
|
1067
1087
|
${rewriteInstruction}
|
|
1068
1088
|
${durationRules}
|
|
1069
1089
|
写作要求:
|
|
1070
1090
|
1. visualHook、conflict、escalation/turn、productIntervention、visibleProof、purchaseReason、callbackMotivation 与所有 shots 必须执行选中卡的 visualPremise、firstFrame、spectacleEscalation、productProofAction、purchaseReason、truthBoundary 以及七项视觉执行字段;保留所选蓝图的因果时间线、节拍比例、镜头/剪辑节奏、动作压力、证明方式和购买逻辑,只替换当前商品、人物、场景与当地口播槽位,不得稀释构图或换成普通模板。爆款来源的旧商品身份/动作必须替换;形式卡只提供抽象创意结构,绝不能反向改变现有首帧、分段、Omni或参考图制作方式。形式库中的工厂、仓库、街访、使用者体验、主理人、探店、补货、对比、促销和耐用形式都可以用 AI 人物与场景正常演绎;缺少现实素材或事实证据不是脚本失败条件,必须按 executionBlueprint.executionAdaptation 保留所选形式,同时不把演绎的角色、地点、订单、库存、销量、价格、身份或经历写成已核验的现实事实。若 productAdaptation.templateMatch.actionCompatibility 的 openingAction、proofAction 或 spectacleAction 为 false,对应 productAdaptation 的 firstFrame、productProofAction 或 spectacleEscalation 对商品动作内容具有绝对优先级,必须删除其旧商品动作,不得折中混用。productProofAction 必须同时写清一个商品图或执行档案能支持的可见动作及其镜头内可见结果;purchaseReason 只解释该结果为何值得目标用户购买,不能变成泛泛 CTA。
|
|
1071
1091
|
2. 严格执行 executionBlueprints 中的 sourceDurationSeconds → targetDurationSeconds 和 retimingMode:expand 只增加有用的证明停留、使用语境与购买理由,compress 只压缩重复、冗余铺垫和转场,same 保持原节拍比例;三种模式都必须保留 Hook、证明和购买因果,禁止整体拉伸、截断或机械加速。
|
|
1072
|
-
3. 每条生成一份稳定 voiceProfile;每段只写简短 voiceCue。口播必须使用${targetVoiceLanguage},并执行 creatorVoiceStyle/ctaStyle;未提供 creatorVoiceStyle 时使用当地 TikTok
|
|
1073
|
-
4. 每段永远是独立 0–10 秒,含 1–8 个按剧情需要决定的 shots、准确 voiceover、onScreenText、evidence、soundBgm、emotionalNote 与 endingState;模型不要输出 continuity 或 continuityMode。
|
|
1092
|
+
3. 每条生成一份稳定 voiceProfile;每段只写简短 voiceCue。口播必须使用${targetVoiceLanguage},并执行 creatorVoiceStyle/ctaStyle;未提供 creatorVoiceStyle 时使用当地 TikTok 带货创作者真实会说的口吻。${contentStrategy ? '不是逐字翻译。Hook → Body/visible proof → Close 是画面叙事结构,不等于口播结构;镜头数量不等于台词数量。除用户明确锁定的对白外,先为整个局部10秒定稿1–2个能自然说完的很短口语句,再分配到真正需要补充人物动机或购买判断的镜头,不逐镜复述 visual;其它镜头默认 voiceover="none",只在 soundBgm 保留动作现场声。结尾的一个简短自然 CTA 并入已定稿短句,不另外追加一句。用户锁定的对白、原框架和目标语言优先,不擅自删改;上述定稿与分配在同一次写作内完成,不输出中间声音轨。' : '不是逐字翻译,整条内容按 Hook → Body/visible proof → Close 组织,短句口语化且与当前镜头可见动作同步,结尾只给一个简短自然 CTA。'}只使用少量可信的 localSceneProfile/audienceContext 日常细节;presenterContext 没填写时使用普通创作者,不得把国家代码、语言或地区自动等同于人物族群。商品事实没有提供时不得编造价格、折扣、销量、库存或稀缺性。商品标题只用于理解商品类型与事实,所有输出不得主动写出或念出品牌名、品牌广告语、包装文案,也不得要求模型把品牌字样变得更清晰;商品外观与已有标识的位置、比例、颜色和版式只服从第1张产品参考图。TikTok/TikTok Shop 只用于定义这条写作规则,不得把平台名写入 voiceover、onScreenText、visual、soundBgm、emotionalNote、openingState、endingState 或任何会进入故事板/视频的导演字段。导演说明统一用简洁制作英文;这里只做写作指令,不做语言检测字段。
|
|
1093
|
+
4. 每段永远是独立 0–10 秒,含 1–8 个按剧情需要决定的 ${contentStrategy ? 'shots。每镜 voiceover 字段必须存在,但不等于必须有台词:只填上一步分配给该镜的原句,其余填非空静默标记 "none",绝不让配音念出 none;每镜同时给出准确 onScreenText、evidence、soundBgm、emotionalNote,并提供每段 endingState' : 'shots、准确 voiceover、onScreenText、evidence、soundBgm、emotionalNote 与 endingState'};模型不要输出 continuity 或 continuityMode。
|
|
1074
1094
|
5. shot.visual 要直接写出主体位置、景别、光线、材质、动作峰值和当地自然环境,并保留真实皮肤、细发、布料/商品微纹理和实景混合光;禁止塑料蜡感、静态举产品、品牌广告片或电视购物。允许执行所选形式的 AI 工厂/仓库/零售/人物场景,但不得声称它们是该商家真实工厂、产地、生产档案、订单、库存、销量、实名客户证言或线下价格。
|
|
1075
1095
|
6. 20/30 秒的后一段剧情承接前段 endingState,但每段仍只使用自己的局部 0–10 秒执行内容。
|
|
1076
1096
|
7. 不要输出 creativePlan、executionBindings 或 masterScript;这些字段由 Agent 从选中蓝图确定性回填/合成,避免重复 token 和脆弱的文字绑定校验。若 selectionMode=user-framework,用户原框架的开头、事件顺序、核心剧情和结尾仍是最高权威。
|
|
1077
1097
|
8. 商品身份以按顺序提供的原商品图为最高权威:第1张是主SKU身份图,当前商品标题和大概类目只补充商品用途与事实,后续图片只是同一SKU补充视角。任何爆款源商品、首帧、故事板或上一段尾帧都不得改变商品颜色、款式、结构、比例、材质、包装、标识位置、部件、配件和实际套装数量;修正身份时不得降低动作、运镜、节奏、景深、真实感或画质。商品本体或包装表面的印花、图案、微纹理、已印文字/字符、图标、色块、行距、相对位置和朝向全部视为不可编辑的原图纹理;不得在 shot.visual、onScreenText、voiceover、evidence、openingState、endingState 或任何导演字段中要求重写、翻译、替换、删改、重排、镜像这些表面内容,也不得要求生成“另一段清晰可读文案”。新增屏幕字只能是与商品像素分离的场景叠加字幕,绝不能印到商品或包装上。旋转、翻转、弯折或开合商品时,完整表面纹理必须随实体整体运动,不能漂移、翻面后复写或重新生成。若 executionBlueprint 或 productAdaptation 与本规则冲突,只保留其钩子、动作因果和镜头节奏,改写构图、机位或表演,绝不改商品表面。
|
|
1078
|
-
固定使用 GPT-5.6 Terra 中等推理。媒体制作方式与分镜布局由中心在脚本完成后管理,不得影响这里的创意、镜头、口播或结构化脚本。只返回当前结构化 jobs,不调用工具、不创建任何图片或视频任务。`;
|
|
1098
|
+
${contentMethod}固定使用 GPT-5.6 Terra 中等推理。媒体制作方式与分镜布局由中心在脚本完成后管理,不得影响这里的创意、镜头、口播或结构化脚本。只返回当前结构化 jobs,不调用工具、不创建任何图片或视频任务。`;
|
|
1079
1099
|
}
|
|
1080
1100
|
export function creativeCandidatePrompt(id, task, ordinals) {
|
|
1081
1101
|
const products = relevantProductInputs(task, ordinals);
|
|
@@ -1195,7 +1215,7 @@ function selectedCandidatesForOrdinals(task, ordinals) {
|
|
|
1195
1215
|
const expected = new Set(ordinals);
|
|
1196
1216
|
return new Map((task.selected_candidates || []).filter((candidate) => expected.has(Number(candidate.ordinal))).map((candidate) => [Number(candidate.ordinal), candidate]));
|
|
1197
1217
|
}
|
|
1198
|
-
export function selectedBlueprintPromptPayload(values, fallbackTargetDurationSeconds) {
|
|
1218
|
+
export function selectedBlueprintPromptPayload(values, fallbackTargetDurationSeconds, contentStrategy = null) {
|
|
1199
1219
|
const blueprints = new Map();
|
|
1200
1220
|
const blueprintRefBySignature = new Map();
|
|
1201
1221
|
const adaptations = new Map();
|
|
@@ -1255,12 +1275,14 @@ export function selectedBlueprintPromptPayload(values, fallbackTargetDurationSec
|
|
|
1255
1275
|
const adaptationRef = `product-${candidate.productIndex}-${createHash("sha256").update(signature).digest("hex").slice(0, 16)}`;
|
|
1256
1276
|
if (!adaptations.has(adaptationRef))
|
|
1257
1277
|
adaptations.set(adaptationRef, { adaptationRef, ...adaptation });
|
|
1278
|
+
const contentDirection = flowCContentDirection(candidate.contentDirection, contentStrategy);
|
|
1258
1279
|
return {
|
|
1259
1280
|
ordinal: candidate.ordinal,
|
|
1260
1281
|
productIndex: candidate.productIndex,
|
|
1261
1282
|
blueprintRef,
|
|
1262
1283
|
adaptationRef,
|
|
1263
|
-
variationSeed: candidate.fingerprintKey || `ordinal-${candidate.ordinal}`,
|
|
1284
|
+
variationSeed: contentDirection?.variationSeed || candidate.fingerprintKey || `ordinal-${candidate.ordinal}`,
|
|
1285
|
+
...(contentDirection ? { contentDirection } : {}),
|
|
1264
1286
|
};
|
|
1265
1287
|
});
|
|
1266
1288
|
return {
|
|
@@ -1273,9 +1295,10 @@ function positiveDuration(value) {
|
|
|
1273
1295
|
const duration = Number(value);
|
|
1274
1296
|
return Number.isFinite(duration) && duration > 0 ? Math.round(duration * 1000) / 1000 : null;
|
|
1275
1297
|
}
|
|
1276
|
-
function selectedCandidatePlan(value) {
|
|
1298
|
+
export function selectedCandidatePlan(value, contentStrategy = null) {
|
|
1277
1299
|
if (!value)
|
|
1278
1300
|
throw new Error("中心缺少选中的创意候选");
|
|
1301
|
+
const contentDirection = flowCContentDirection(value.contentDirection, contentStrategy);
|
|
1279
1302
|
return {
|
|
1280
1303
|
visualPremise: value.visualPremise,
|
|
1281
1304
|
learnedTemplateId: value.learnedTemplateId || null,
|
|
@@ -1317,6 +1340,7 @@ function selectedCandidatePlan(value) {
|
|
|
1317
1340
|
selectionBreakdown: value.selectionBreakdown,
|
|
1318
1341
|
selectionMode: value.selectionMode,
|
|
1319
1342
|
visualExecutionVersion: "flow-c-visual-execution-v1",
|
|
1343
|
+
...(contentDirection ? { contentDirection } : {}),
|
|
1320
1344
|
};
|
|
1321
1345
|
}
|
|
1322
1346
|
function chunkNumbers(values, size) {
|