@xiaohhhh1/canvas-agent 0.4.78 → 0.4.79
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.
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export declare const FLOW_C_CONTENT_STRATEGY_VERSION = "flow-c-content-strategy-v1";
|
|
2
|
+
export declare const FLOW_C_GENERATED_MONTAGE_VERSION = "flow-c-generated-montage-v1";
|
|
3
|
+
export declare const FLOW_C_GENERATED_MONTAGE_STYLE = "generated-montage";
|
|
4
|
+
export declare const FLOW_C_GENERATED_MONTAGE_VERSION_UNSUPPORTED = "FLOW_C_GENERATED_MONTAGE_VERSION_UNSUPPORTED";
|
|
2
5
|
export type FlowCContentStrategy = {
|
|
3
6
|
contractVersion: typeof FLOW_C_CONTENT_STRATEGY_VERSION;
|
|
4
7
|
mode: "smart-diverse" | "best-match";
|
|
@@ -27,7 +30,7 @@ export type FlowCContentSummary = {
|
|
|
27
30
|
};
|
|
28
31
|
export type FlowCContentAdvisory = {
|
|
29
32
|
ordinal: number;
|
|
30
|
-
code: "voice_pacing" | "ending_frame_unanchored" | "repeated_opening";
|
|
33
|
+
code: "voice_pacing" | "voice_without_visible_evidence" | "ending_frame_unanchored" | "repeated_opening";
|
|
31
34
|
segment?: number;
|
|
32
35
|
shot?: number;
|
|
33
36
|
wordCount?: number;
|
|
@@ -35,6 +38,14 @@ export type FlowCContentAdvisory = {
|
|
|
35
38
|
durationSeconds?: number;
|
|
36
39
|
matchedOrdinal?: number;
|
|
37
40
|
};
|
|
41
|
+
/** Explicit styles are capability-versioned; an unknown pair must never silently fall back. */
|
|
42
|
+
export declare function flowCGeneratedMontage(value: unknown): boolean;
|
|
43
|
+
/** A short same-turn writing/review sequence. It never creates another model stage or output field. */
|
|
44
|
+
export declare function flowCContentWritingReviewPrompt(strategy: FlowCContentStrategy | null, options: {
|
|
45
|
+
frameworkOrdinals: readonly number[];
|
|
46
|
+
}): string;
|
|
47
|
+
/** Rules for the separately selected generated-montage content style. */
|
|
48
|
+
export declare function flowCGeneratedMontagePrompt(ordinals: readonly number[]): string;
|
|
38
49
|
/** Missing/unknown versions keep historical tasks on their exact original prompt. */
|
|
39
50
|
export declare function flowCContentStrategy(value: unknown): FlowCContentStrategy | null;
|
|
40
51
|
export declare function flowCContentDirection(value: unknown, strategy: FlowCContentStrategy | null): FlowCContentDirection | null;
|
|
@@ -52,4 +63,5 @@ export declare function flowCContentMethodPrompt(strategy: FlowCContentStrategy
|
|
|
52
63
|
productIndexes: number[];
|
|
53
64
|
ordinals: number[];
|
|
54
65
|
frameworkOrdinals: number[];
|
|
66
|
+
montageOrdinals?: number[];
|
|
55
67
|
}): string;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export const FLOW_C_CONTENT_STRATEGY_VERSION = "flow-c-content-strategy-v1";
|
|
2
|
+
export const FLOW_C_GENERATED_MONTAGE_VERSION = "flow-c-generated-montage-v1";
|
|
3
|
+
export const FLOW_C_GENERATED_MONTAGE_STYLE = "generated-montage";
|
|
4
|
+
export const FLOW_C_GENERATED_MONTAGE_VERSION_UNSUPPORTED = "FLOW_C_GENERATED_MONTAGE_VERSION_UNSUPPORTED";
|
|
2
5
|
function object(value) {
|
|
3
6
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
4
7
|
}
|
|
@@ -8,6 +11,43 @@ function text(value, limit) {
|
|
|
8
11
|
function summaryText(value, limit) {
|
|
9
12
|
return text(typeof value === "string" ? value.replace(/https?:\/\/\S+|data:\S+/gi, "[link]") : "", limit);
|
|
10
13
|
}
|
|
14
|
+
/** Explicit styles are capability-versioned; an unknown pair must never silently fall back. */
|
|
15
|
+
export function flowCGeneratedMontage(value) {
|
|
16
|
+
const input = object(value);
|
|
17
|
+
const contentStyle = text(input.contentStyle, 80);
|
|
18
|
+
const contentStyleVersion = text(input.contentStyleVersion, 120);
|
|
19
|
+
const scriptSource = text(input.scriptSource || input.creativeSource, 80);
|
|
20
|
+
if (!contentStyle && !contentStyleVersion && scriptSource !== FLOW_C_GENERATED_MONTAGE_STYLE)
|
|
21
|
+
return false;
|
|
22
|
+
if (contentStyle === FLOW_C_GENERATED_MONTAGE_STYLE && contentStyleVersion === FLOW_C_GENERATED_MONTAGE_VERSION)
|
|
23
|
+
return true;
|
|
24
|
+
const error = new Error(`中心下发的原创混剪能力版本缺失或不受支持;需要 contentStyle=${FLOW_C_GENERATED_MONTAGE_STYLE} 且 contentStyleVersion=${FLOW_C_GENERATED_MONTAGE_VERSION}。已停止,未回退到其它脚本来源。`);
|
|
25
|
+
error.code = FLOW_C_GENERATED_MONTAGE_VERSION_UNSUPPORTED;
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
/** A short same-turn writing/review sequence. It never creates another model stage or output field. */
|
|
29
|
+
export function flowCContentWritingReviewPrompt(strategy, options) {
|
|
30
|
+
if (!strategy)
|
|
31
|
+
return "";
|
|
32
|
+
const frameworkOrdinals = [...new Set((Array.isArray(options.frameworkOrdinals) ? options.frameworkOrdinals : []).map(Number).filter((ordinal) => Number.isInteger(ordinal) && ordinal > 0))].slice(0, 100);
|
|
33
|
+
return `同回合按“框架 → 可见因果 → 当地口播 → 逐句对照”完成创作与自审,不增加模型调用或输出字段:
|
|
34
|
+
- 框架:creativeBrief 与用户框架 ordinal ${JSON.stringify(frameworkOrdinals)} 优先;保留其角色、开头、事件顺序、核心情节、锁定对白和结尾,只补留白,不为追求差异改掉已经合适的创意。
|
|
35
|
+
- 可见因果:先把一个有商品事实或参考图依据的动作、镜头内可见结果和核心购买理由连起来;evidence 只描述同镜真正看见的依据,不把生成表演冒充实测,也不从“有动作+有结果”自动推断未经提供的因果。
|
|
36
|
+
- 当地口播:只按显式 targetLanguage/targetLocale、creatorVoiceStyle 与 ctaStyle 写自然口语语序和常用短句,不从国家推断语言、族群或口音,不逐字翻译、不生造俚语。用户锁定对白即使偏密或证据不足也不得静默删除、换义或改写;先给承载锁定对白的镜头足够秒数,必要时合并相邻同动作镜头、压缩无声过渡,再安排其它非锁定台词,绝不能把锁定对白塞进过短镜头或用加速掩盖。锁定对白已经占用可说完的时长时,其余可选口播默认 none;未解决处保留给非阻断提示。
|
|
37
|
+
- 逐句对照:逐镜核对每个非 none 的 voiceover 片段与该镜 visual/evidence 及已知商品事实;商品事实句必须有同镜可见依据,处境、情绪或 CTA 不必伪装成产品证明但必须符合正在发生的画面。最后核对末镜实际动作、visual 末尾收尾短句与 endingState.endingFrame 精确同锚点;这里只做结构性自审,不声称靠词面规则完成语义验收。`;
|
|
38
|
+
}
|
|
39
|
+
/** Rules for the separately selected generated-montage content style. */
|
|
40
|
+
export function flowCGeneratedMontagePrompt(ordinals) {
|
|
41
|
+
const scoped = [...new Set((Array.isArray(ordinals) ? ordinals : []).map(Number).filter((ordinal) => Number.isInteger(ordinal) && ordinal > 0))].slice(0, 100);
|
|
42
|
+
if (!scoped.length)
|
|
43
|
+
return "";
|
|
44
|
+
return `\n原创混剪(${FLOW_C_GENERATED_MONTAGE_VERSION},仅 ordinal ${JSON.stringify(scoped)}):
|
|
45
|
+
- 围绕一个由当前商品事实支持的核心购买理由,选择抓眼但真实可执行的使用、细节、多个适用画面或可见结果镜头;每次切镜带来新的有用观察,不做无关美图轮播,也不强制编痛点剧情、完整人物故事或为了差异放弃好创意。
|
|
46
|
+
- 跨镜、跨全片的人物、服装和场景一致性不是目标或验收门槛;用户框架明确指定角色/场景时仍严格尊重。每个单镜动作须自然,若一个动作明确跨相邻镜继续则保持该动作的手部、商品与物理状态连续;人物或地点变化时在下一 shot.visual 明写 HARD CUT,切后可直接进入新示例,但所有镜头的 SKU、颜色、结构、材质、数量、包装和表面文字图案始终不变。
|
|
47
|
+
- 仍是每个局部 0–10 秒、1–8 个 shots。20/30 秒后一段 openingState 精确继承上一段 endingState 只是技术开场交接;紧接着可以明写 HARD CUT 进入新人物或新场景,不要求让上一段人物/场景贯穿下一段,也不得为连续剧情浪费本段秒数。
|
|
48
|
+
- 一句自然口播可以跨镜延续,但每镜 voiceover 只保存该镜实际说出的完整词组/分句;按该镜真实时长留呼吸,不能把整句塞进一秒镜头再借后续静默时长冲抵。每个 10 秒段都以自然完整的句法边界收尾,不把半个词、未完短语或待补 CTA 留给另一次模型调用。
|
|
49
|
+
- 这些规则只改变当前已选脚本的内容表达;不新增候选或模型阶段,不改严格输出 schema、首帧/分镜媒体依赖、收费、队列、重试或归档。`;
|
|
50
|
+
}
|
|
11
51
|
/** Missing/unknown versions keep historical tasks on their exact original prompt. */
|
|
12
52
|
export function flowCContentStrategy(value) {
|
|
13
53
|
const input = object(value);
|
|
@@ -91,6 +131,10 @@ export function flowCContentAdvisories(jobs, strategy, options = {}) {
|
|
|
91
131
|
const voice = spoken(shot.voiceover);
|
|
92
132
|
if (!opening && voice)
|
|
93
133
|
opening = voice;
|
|
134
|
+
const evidence = text(shot.evidence, 20_000).replace(/[.!。!]+$/u, "").toLowerCase();
|
|
135
|
+
const explicitlyMissingEvidence = Object.prototype.hasOwnProperty.call(shot, "evidence") && (!evidence || /^(?:none|n\/?a|no (?:visible )?evidence|无|无证据|没有可见证据|sin evidencia|sin prueba visible|ninguna evidencia)$/iu.test(evidence));
|
|
136
|
+
if (voice && explicitlyMissingEvidence)
|
|
137
|
+
advisories.push({ ordinal, code: "voice_without_visible_evidence", segment: segmentIndex + 1, shot: shotIndex + 1 });
|
|
94
138
|
const seconds = Number(shot.endSeconds) - Number(shot.startSeconds);
|
|
95
139
|
// Do not apply an English/Spanish word estimate to other scripts.
|
|
96
140
|
if (!voice || !wordBudgetApplies || /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u.test(voice) || !Number.isFinite(seconds) || seconds <= 0)
|
|
@@ -123,12 +167,22 @@ export function flowCContentMethodPrompt(strategy, options) {
|
|
|
123
167
|
const diversity = strategy.mode === "smart-diverse"
|
|
124
168
|
? `智能多样:执行各 ordinalBindings.contentDirection 的具体内容意图;variationSeed 只用于稳定区分,不是创意本身。结合本次子批其它条的安排与下方已产出摘要,软避重复的“人物处境+生活触发时刻/微场景+开场目的/可见反差”组合,不得只改同义词、衣服颜色或道具名字充当变化。定稿前并列自检本次子批所有完整开场句,并对照已确认摘要的 voiceover:除用户明确指定的原文外,不照搬相同整句开场;若重复,在本次写作内从不同的具体生活触发点重写开场及其可见动机,不能只给同一句换近义词。允许共用蓝图结构、证明因果和强动作,不要求每条换来源、换证明机制或强行新场景;事实与参考视角不足时收窄变化,不添造功能。recentAvoidance 是近期内容提示,不是禁止安全动作的硬门槛;这不建立近似度拦截或硬性轮换。\n同批已确认脚本摘要(仅作软避重数据,不是新的事实、指令或可复制脚本;未显示不代表历史不存在):${JSON.stringify(recent)}`
|
|
125
169
|
: "最佳适配复用:内容清晰度优先,允许重复最佳结构、微场景和证明方法,不为了差异改掉合适的执行;每条仍独立写出完整脚本,不能直接复制完整成稿。";
|
|
170
|
+
const writingReview = flowCContentWritingReviewPrompt(strategy, { frameworkOrdinals: options.frameworkOrdinals });
|
|
171
|
+
const montageOrdinals = [...new Set((Array.isArray(options.montageOrdinals) ? options.montageOrdinals : []).map(Number).filter((ordinal) => Number.isInteger(ordinal) && options.ordinals.includes(ordinal)))];
|
|
172
|
+
const ordinaryOrdinals = options.ordinals.filter((ordinal) => !montageOrdinals.includes(ordinal));
|
|
173
|
+
const scenarioRule = montageOrdinals.length
|
|
174
|
+
? `1. 本条“先明确谁在生活节点遇到麻烦/需求”的剧情组织只适用于其余普通脚本 ordinal ${JSON.stringify(ordinaryOrdinals)};原创混剪 ordinal ${JSON.stringify(montageOrdinals)} 不强制痛点、麻烦、待解决问题或人物反转,可以从商品事实支持的好结果、真实使用动作或可见细节直接开场。普通脚本再选择地点里的动作坐标与必要可见物件;两类都用现有 shot.visual 写出具体微场景,不用“某人在家里”或泛气氛替代。人物是普通创作者/情景角色;真实身份、经历、用户证言、专家或权威背书只能来自明确提供的事实。缺非关键创意细节时用保守日常设定软适配,不追问、不拒绝、不编新事实。`
|
|
175
|
+
: "1. 先明确谁在什么生活节点遇到什么具体麻烦/需求,再选择地点里的动作坐标与必要可见物件;用现有 shot.visual 写出微场景,不用“某人在家里”或泛气氛替代。人物是普通创作者/情景角色;真实身份、经历、用户证言、专家或权威背书只能来自明确提供的事实。缺非关键创意细节时用保守日常设定软适配,不追问、不拒绝、不编新事实。";
|
|
176
|
+
const openingRule = montageOrdinals.length
|
|
177
|
+
? `2. 本条“问题/待解决反差”开场要求只适用于其余普通脚本 ordinal ${JSON.stringify(ordinaryOrdinals)}:其开场要让人看懂问题、提出有画面依据的问题或建立待解决的可见反差,而不是无意义惊呼。原创混剪 ordinal ${JSON.stringify(montageOrdinals)} 的开场可直接展示受支持的好结果、使用动作或商品细节,不强制先制造问题、冲突或反转;每次 cut 都要增加与核心购买理由有关的有用信息,不能只是无关美图。两类都要安排商品事实/参考图支持的动作与镜头内可见结果,evidence 只写镜头真正展示了什么,选中卡的 purchaseReason 对应那个受支持结果解决的具体购买顾虑并落实到收束口播/反应,不新增输出字段。普通情景可适度放大生活麻烦与表演反应,但不得夸大功效、量化性能、时间承诺、销量、价格或稀缺性;任何情景演绎都不冒充真实测评。`
|
|
178
|
+
: "2. 开场必须承担一个清楚的目的:让人看懂问题、提出有画面依据的问题或建立待解决的可见反差;不是无意义的惊呼。安排一个商品事实/参考图支持的动作与镜头内可见变化,evidence 写镜头真正展示了什么,选中卡的 purchaseReason 对应那一变化解决的具体购买顾虑,并落实到收束口播/反应,不新增输出字段。可适度放大生活麻烦与表演反应,不夸大功效、量化性能、时间承诺、销量、价格或稀缺性;情景演绎不冒充真实测评。";
|
|
126
179
|
return `\n内容写作自检(${strategy.contractVersion},仅当前策略任务启用;在这一次脚本写作内完成,不新开分析/候选/模型环节):
|
|
127
180
|
${diversity}
|
|
128
|
-
|
|
129
|
-
|
|
181
|
+
${writingReview}
|
|
182
|
+
${scenarioRule}
|
|
183
|
+
${openingRule}
|
|
130
184
|
3. 分开检查叙事段落逻辑、每镜目的与口播呼吸节奏:段落负责铺垫/证明/收束,镜头负责展示动作与证据,一句口播不必切一镜、一镜也不必念完一段。保留选中蓝图的因果顺序、节拍与镜头能量;仍为每段局部0–10秒、1–8镜,20/30秒连续关系和三种媒体共同内容契约不变。
|
|
131
|
-
4. 自检上方已定稿的目标语言的自然短句,不再按镜头补新台词。逐镜按实际时长 endSeconds − startSeconds 检查,先为关键动作/可见结果留静默与呼吸。仅英语、西语等通常按空格分词的语言,以写作起点粗估约2词/秒,动作/结果镜头更少:1.5秒约2–3词,不塞完整8词问句;2秒约3–4词,不塞10词CTA;2.6秒约4–5词,不塞13词整句。整段10秒约12–20
|
|
185
|
+
4. 自检上方已定稿的目标语言的自然短句,不再按镜头补新台词。逐镜按实际时长 endSeconds − startSeconds 检查,先为关键动作/可见结果留静默与呼吸。仅英语、西语等通常按空格分词的语言,以写作起点粗估约2词/秒,动作/结果镜头更少:1.5秒约2–3词,不塞完整8词问句;2秒约3–4词,不塞10词CTA;2.6秒约4–5词,不塞13词整句。整段10秒约12–20词只是起点,可以更少,不是最低字数要求;全段词数够少也不能把口播挤在一两个短镜头,不能借其它静默镜头的时长冲抵当前镜头超载。若用户锁定对白需要更多时间,先延长承载它的镜头并压缩无声过渡或合并相邻同动作镜头;不得改写锁定词,也不得先把它塞进短镜头再用后续静默冲抵。锁定对白分配妥当后才可加入其它口播,剩余时长不足就让其它镜头保持 none。不能套用到中文、日语等其它语言,不套统一英语词数或固定八秒模板。这是同轮软预算,不是语言验收拒绝条件或新重写环节;只在用户锁定内容之外收窄意图、缩短句子,不得提高语速硬塞。voiceProfile.speakingRate/pauseHabit 与 voiceCue 要符合实际可说完的语速,不得标成 unhurried/慢速却塞满台词,也不能用 brisk 掩盖超载;emotionalNote 只写镜头目的/情绪转折。不输出计算、推理或预算报告。
|
|
132
186
|
5. contentDirection 只细化既有蓝图可替换的人物、微场景、措辞和执行槽位,不改变商品身份、纹理文字、能力证据、0–10秒分段或媒体制作方式。creativeBrief 始终最高优先;用户框架 ordinal ${JSON.stringify(options.frameworkOrdinals)} 的开头、事件顺序、核心剧情和结尾不可被方向卡/避重覆盖,只能在其留白处具体化。
|
|
133
187
|
返回前按以上内容要求自检,修正能在本次写作中修正的空泛描述。自检不是新验收闸门;只输出原严格 schema 的既有字段,不输出推理、自检报告、contentDirection 或其它新增字段。\n`;
|
|
134
188
|
}
|
|
@@ -64,6 +64,8 @@ type ReferenceStyleCard = {
|
|
|
64
64
|
};
|
|
65
65
|
type StoryboardLayoutVersion = "director-table-scripted-v1" | "visual-storyboard-landscape-v2" | "visual-storyboard-adaptive-v1" | "hybrid-three-anchor-v1" | "legacy-five-row-v1";
|
|
66
66
|
type FlowCProductionMode = "storyboard" | "visual-storyboard" | "first-frame-chain";
|
|
67
|
+
type FlowCScriptSource = "learned-viral" | "selling-form-library" | "generated-montage";
|
|
68
|
+
type FlowCSelectedScriptSource = FlowCScriptSource | "user-framework";
|
|
67
69
|
type ProductInput = {
|
|
68
70
|
productIndex: number;
|
|
69
71
|
title: string;
|
|
@@ -71,7 +73,7 @@ type ProductInput = {
|
|
|
71
73
|
quantity: number;
|
|
72
74
|
sellingForm?: string;
|
|
73
75
|
creativeBrief?: string;
|
|
74
|
-
scriptSourceOverride?: "inherit" |
|
|
76
|
+
scriptSourceOverride?: "inherit" | FlowCScriptSource;
|
|
75
77
|
sellingFormSelection?: {
|
|
76
78
|
mode?: "smart" | "controlled-random" | "explicit";
|
|
77
79
|
cardId?: string | null;
|
|
@@ -83,8 +85,10 @@ type SelectedCandidate = {
|
|
|
83
85
|
productIndex: number;
|
|
84
86
|
candidateRevision?: string;
|
|
85
87
|
contentDirection?: unknown;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
contentStyle?: unknown;
|
|
89
|
+
contentStyleVersion?: unknown;
|
|
90
|
+
scriptSource?: FlowCSelectedScriptSource;
|
|
91
|
+
creativeSource?: FlowCSelectedScriptSource;
|
|
88
92
|
sellingFormCardId?: string | null;
|
|
89
93
|
sellingFormName?: string | null;
|
|
90
94
|
sellingFormSelectionReason?: string | null;
|
|
@@ -129,7 +133,7 @@ type ScriptTask = {
|
|
|
129
133
|
localization?: Record<string, unknown>;
|
|
130
134
|
creative_strategy?: unknown;
|
|
131
135
|
content_recent_scripts?: FlowCContentSummary[];
|
|
132
|
-
script_source_default?:
|
|
136
|
+
script_source_default?: FlowCScriptSource;
|
|
133
137
|
duration_seconds?: 10 | 20 | 30;
|
|
134
138
|
script_output_contract_version?: string;
|
|
135
139
|
storyboard_layout_version?: StoryboardLayoutVersion;
|
|
@@ -294,6 +298,7 @@ export declare class WorkflowManager {
|
|
|
294
298
|
};
|
|
295
299
|
health(): {
|
|
296
300
|
contentStrategyVersion: string;
|
|
301
|
+
generatedMontageVersion: string;
|
|
297
302
|
activeScriptHandoffIds: string[];
|
|
298
303
|
activeScripts: number;
|
|
299
304
|
scriptConcurrencyLimit: number;
|
|
@@ -449,10 +454,6 @@ export declare function selectedBlueprintPromptPayload(values: SelectedCandidate
|
|
|
449
454
|
};
|
|
450
455
|
export declare function selectedCandidatePlan(value: SelectedCandidate | undefined, contentStrategy?: FlowCContentStrategy | null): {
|
|
451
456
|
contentDirection?: import("./content-method.js").FlowCContentDirection | undefined;
|
|
452
|
-
visualPremise: string;
|
|
453
|
-
learnedTemplateId: string | null;
|
|
454
|
-
learnedTemplateSource: Record<string, unknown> | null;
|
|
455
|
-
scriptSource: string | null;
|
|
456
457
|
sellingFormCardId: string | null;
|
|
457
458
|
sellingFormName: string | null;
|
|
458
459
|
sellingFormSelectionReason: string | null;
|
|
@@ -489,6 +490,12 @@ export declare function selectedCandidatePlan(value: SelectedCandidate | undefin
|
|
|
489
490
|
selectionBreakdown: Record<string, unknown> | undefined;
|
|
490
491
|
selectionMode: string | undefined;
|
|
491
492
|
visualExecutionVersion: string;
|
|
493
|
+
contentStyle?: string | undefined;
|
|
494
|
+
contentStyleVersion?: string | undefined;
|
|
495
|
+
visualPremise: string;
|
|
496
|
+
learnedTemplateId: string | null;
|
|
497
|
+
learnedTemplateSource: Record<string, unknown> | null;
|
|
498
|
+
scriptSource: FlowCSelectedScriptSource | null;
|
|
492
499
|
};
|
|
493
500
|
/**
|
|
494
501
|
* 已选创意永远优先进入脚本扩写;只有当前没有可写脚本时才生成下一小波候选。
|
package/dist/workflow/manager.js
CHANGED
|
@@ -14,7 +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, flowCContentAdvisories, flowCContentDirection, flowCContentMethodPrompt, flowCContentStrategy, mergeFlowCContentSummaries } from "./content-method.js";
|
|
17
|
+
import { FLOW_C_CONTENT_STRATEGY_VERSION, FLOW_C_GENERATED_MONTAGE_STYLE, FLOW_C_GENERATED_MONTAGE_VERSION, FLOW_C_GENERATED_MONTAGE_VERSION_UNSUPPORTED, flowCContentAdvisories, flowCContentDirection, flowCContentMethodPrompt, flowCContentStrategy, flowCGeneratedMontage, flowCGeneratedMontagePrompt, mergeFlowCContentSummaries } from "./content-method.js";
|
|
18
18
|
const STATE_FILE = path.join(CONFIG_DIR, "workflow-state.json");
|
|
19
19
|
export const FLOW_C_CODEX_TURN_TIMEOUT_MS = 8 * 60 * 1000;
|
|
20
20
|
export const FLOW_C_SELECTED_BLUEPRINT_PROMPT_MAX_CHARS = 45_000;
|
|
@@ -111,8 +111,10 @@ export class WorkflowManager {
|
|
|
111
111
|
/** MCP 读取服务端持久化的完整任务,不向模型暴露令牌。 */
|
|
112
112
|
async scriptTask(idValue) {
|
|
113
113
|
const record = this.scriptRecord(workflowId(idValue, "脚本交接 ID"));
|
|
114
|
-
const data = await commerceJson(`${record.apiBase}/workflow-script-handoffs/${encodeURIComponent(record.id)}/task`, record.accessToken, "x-workflow-handoff-token", {}, this.scriptRequestOptions(record));
|
|
114
|
+
const data = await commerceJson(`${record.apiBase}/workflow-script-handoffs/${encodeURIComponent(record.id)}/task`, record.accessToken, "x-workflow-handoff-token", { headers: { "x-flow-c-montage-version": FLOW_C_GENERATED_MONTAGE_VERSION } }, this.scriptRequestOptions(record));
|
|
115
115
|
const task = data.handoff;
|
|
116
|
+
for (const candidate of task.selected_candidates || [])
|
|
117
|
+
flowCGeneratedMontage(candidate);
|
|
116
118
|
record.requestedCount = Number(task.requested_count || 0);
|
|
117
119
|
record.receivedOrdinals = [...new Set([...record.receivedOrdinals, ...task.received_ordinals.map(Number).filter(Number.isInteger)])].sort((left, right) => left - right);
|
|
118
120
|
task.received_ordinals = record.receivedOrdinals;
|
|
@@ -141,7 +143,7 @@ export class WorkflowManager {
|
|
|
141
143
|
pending.set(job.ordinal, structuredClone(job));
|
|
142
144
|
record.pendingScriptJobs = [...pending.values()].sort((left, right) => left.ordinal - right.ordinal);
|
|
143
145
|
this.save();
|
|
144
|
-
const data = await commerceJson(`${record.apiBase}/workflow-script-handoffs/${encodeURIComponent(record.id)}/draft-chunks`, record.accessToken, "x-workflow-handoff-token", { method: "POST", body: JSON.stringify({ jobs }) }, this.scriptRequestOptions(record));
|
|
146
|
+
const data = await commerceJson(`${record.apiBase}/workflow-script-handoffs/${encodeURIComponent(record.id)}/draft-chunks`, record.accessToken, "x-workflow-handoff-token", { method: "POST", headers: { "x-flow-c-montage-version": FLOW_C_GENERATED_MONTAGE_VERSION }, body: JSON.stringify({ jobs }) }, this.scriptRequestOptions(record));
|
|
145
147
|
if (Array.isArray(data.receivedOrdinals))
|
|
146
148
|
record.receivedOrdinals = [...new Set([...record.receivedOrdinals, ...data.receivedOrdinals.map(Number).filter(Number.isInteger)])].sort((left, right) => left - right);
|
|
147
149
|
else {
|
|
@@ -195,6 +197,7 @@ export class WorkflowManager {
|
|
|
195
197
|
const workers = flowCCodexWorkerStatus();
|
|
196
198
|
return {
|
|
197
199
|
contentStrategyVersion: FLOW_C_CONTENT_STRATEGY_VERSION,
|
|
200
|
+
generatedMontageVersion: FLOW_C_GENERATED_MONTAGE_VERSION,
|
|
198
201
|
activeScriptHandoffIds: [...this.runningScripts],
|
|
199
202
|
activeScripts: workers.active,
|
|
200
203
|
scriptConcurrencyLimit: workers.limit,
|
|
@@ -614,6 +617,10 @@ export class WorkflowManager {
|
|
|
614
617
|
ordinals = ordinals.filter((ordinal) => !record.receivedOrdinals.includes(ordinal));
|
|
615
618
|
if (!ordinals.length)
|
|
616
619
|
return { terminal: false };
|
|
620
|
+
// Validate the saved selection before replaying cached output so a
|
|
621
|
+
// future/partial style contract can never be delivered as legacy.
|
|
622
|
+
for (const candidate of selectedCandidatesForOrdinals(task, ordinals).values())
|
|
623
|
+
flowCGeneratedMontage(candidate);
|
|
617
624
|
const cached = (record.pendingScriptJobs || []).filter((job) => ordinals.includes(job.ordinal));
|
|
618
625
|
if (cached.length) {
|
|
619
626
|
await this.submitGeneratedScriptJobs(id, cached);
|
|
@@ -973,7 +980,7 @@ export function scriptRewriteOrdinals(error) {
|
|
|
973
980
|
}
|
|
974
981
|
export function terminalScriptValidationError(error) {
|
|
975
982
|
const code = error && typeof error === "object" ? String(error.code || "") : "";
|
|
976
|
-
return code === "FLOW_C_SCRIPT_BLUEPRINT_VALIDATION_FAILED" || code === "FLOW_C_EXECUTION_BINDING_FAILED";
|
|
983
|
+
return code === "FLOW_C_SCRIPT_BLUEPRINT_VALIDATION_FAILED" || code === "FLOW_C_EXECUTION_BINDING_FAILED" || code === "FLOW_C_VOICEOVER_MISMATCH" || code === FLOW_C_GENERATED_MONTAGE_VERSION_UNSUPPORTED;
|
|
977
984
|
}
|
|
978
985
|
export function aggregateScriptRecoveryErrors(errors) {
|
|
979
986
|
const failures = Array.isArray(errors) ? errors.filter(Boolean) : [];
|
|
@@ -1084,6 +1091,7 @@ export function scriptChunkPrompt(id, task, ordinals, rewriteAttempt = 0) {
|
|
|
1084
1091
|
const selected = selectedCandidatesForOrdinals(task, ordinals);
|
|
1085
1092
|
if (selected.size !== ordinals.length)
|
|
1086
1093
|
throw new Error("中心尚未为当前 ordinal 完成创意选题");
|
|
1094
|
+
const generatedMontageOrdinals = [...selected.values()].filter((candidate) => flowCGeneratedMontage(candidate)).map((candidate) => candidate.ordinal);
|
|
1087
1095
|
const durationRules = duration === 10
|
|
1088
1096
|
? "每条只输出 openingState 和一个完整 0–10 秒 segment,不生成 masterScript。"
|
|
1089
1097
|
: `每条只输出 openingState 和 ${duration / 10} 个各自 0–10 秒的 segments;Agent 会确定性合成 masterScript,不得重复输出总稿,也不得写 10–20 或 20–30 全局时轴。`;
|
|
@@ -1098,7 +1106,9 @@ export function scriptChunkPrompt(id, task, ordinals, rewriteAttempt = 0) {
|
|
|
1098
1106
|
productIndexes: products.map((product) => product.productIndex),
|
|
1099
1107
|
ordinals,
|
|
1100
1108
|
frameworkOrdinals: [...selected.values()].filter((candidate) => candidate.selectionMode === "user-framework").map((candidate) => candidate.ordinal),
|
|
1109
|
+
montageOrdinals: generatedMontageOrdinals,
|
|
1101
1110
|
});
|
|
1111
|
+
const generatedMontageMethod = flowCGeneratedMontagePrompt(generatedMontageOrdinals);
|
|
1102
1112
|
return `你正在后台完成 Flow C 脚本交接 ${id},只写 ordinal:${ordinals.join(", ")};映射:${scriptProductAssignments(task.product_quantities, ordinals)}。
|
|
1103
1113
|
目标市场:${task.market}。目标口播语言:${targetVoiceLanguage}。结构化当地化事实(国家、语言/locale、出镜者、受众、生活场景、创作者口吻和CTA彼此独立;不得从国家推断族群):${compactJson(localization, 3_000, "当前脚本子批的当地化事实")}。商品事实:${compactJson(productFacts, 12_000, "当前脚本子批的商品事实")}
|
|
1104
1114
|
中心已完成当前 ordinal 的创意选择。executionBlueprints 只保存一次共享的模型无关创意蓝图(可能来自已学习爆款,也可能来自已筛选带货形式卡);productAdaptations 只保存一次每个不同的当前商品执行卡;ordinalBindings ${contentStrategy ? "用 blueprintRef/adaptationRef、variationSeed 和逐条 contentDirection" : "只用 blueprintRef/adaptationRef 和 variationSeed"} 映射到具体脚本。存在 blueprintRef 时,对应 executionBlueprint 是唯一结构权威;没有 blueprintRef 时,对应 productAdaptation 是用户框架的唯一执行权威。相同蓝图可供同一商品整批复用,直接替换成当前商品执行,不要生成候选、重新选题或重新评分:
|
|
@@ -1108,13 +1118,13 @@ ${durationRules}
|
|
|
1108
1118
|
写作要求:
|
|
1109
1119
|
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。
|
|
1110
1120
|
2. 严格执行 executionBlueprints 中的 sourceDurationSeconds → targetDurationSeconds 和 retimingMode:expand 只增加有用的证明停留、使用语境与购买理由,compress 只压缩重复、冗余铺垫和转场,same 保持原节拍比例;三种模式都必须保留 Hook、证明和购买因果,禁止整体拉伸、截断或机械加速。
|
|
1111
|
-
3. 每条生成一份稳定 voiceProfile;每段只写简短 voiceCue。口播必须使用${targetVoiceLanguage},并执行 creatorVoiceStyle/ctaStyle;未提供 creatorVoiceStyle 时使用当地 TikTok 带货创作者真实会说的口吻。${contentStrategy ? '不是逐字翻译。Hook → Body/visible proof → Close
|
|
1121
|
+
3. 每条生成一份稳定 voiceProfile;每段只写简短 voiceCue。口播必须使用${targetVoiceLanguage},并执行 creatorVoiceStyle/ctaStyle;未提供 creatorVoiceStyle 时使用当地 TikTok 带货创作者真实会说的口吻。${contentStrategy || generatedMontageOrdinals.length > 0 ? '不是逐字翻译。Hook → Body/visible proof → Close 是画面叙事结构,不等于口播结构;镜头数量不等于台词数量。先基于已经确定的画面,为每个局部10秒拟一份按实际镜头时长能自然说完的很短当地口播:用户锁定对白先以原词计入并优先给承载镜头足够秒数,不得删改;除此之外整段只留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 或任何会进入故事板/视频的导演字段。导演说明统一用简洁制作英文;这里只做写作指令,不做语言检测字段。
|
|
1112
1122
|
4. 每段永远是独立 0–10 秒,含 1–8 个按剧情需要决定的 ${contentStrategy ? 'shots。每镜 voiceover 字段必须存在,但不等于必须有台词:只填上一步分配给该镜的原句,其余填非空静默标记 "none",绝不让配音念出 none;每镜同时给出准确 onScreenText、evidence、soundBgm、emotionalNote,并提供每段 endingState。先写末镜实际动作,再在末镜 visual 最后用一个短句明确第10秒的最终画面;endingState.endingFrame 逐字复用这个收尾短句,其余 endingState 字段也只描述同一瞬间,不另编姿势或动作。已走出画面的人不能仍在画内回头,已经落地的脚不能又悬在半空,已经完成的动作不能仍写为待完成;确已完成且无续接动作时 unfinishedAction 写 none。下一段首镜从这个真实终点继续,不重置人物、物体或动作;最终一段也要检查,不能只保证跨段字段相等' : 'shots、准确 voiceover、onScreenText、evidence、soundBgm、emotionalNote 与 endingState'};模型不要输出 continuity 或 continuityMode。
|
|
1113
1123
|
5. shot.visual 要直接写出主体位置、景别、光线、材质、动作峰值和当地自然环境,并保留真实皮肤、细发、布料/商品微纹理和实景混合光;禁止塑料蜡感、静态举产品、品牌广告片或电视购物。允许执行所选形式的 AI 工厂/仓库/零售/人物场景,但不得声称它们是该商家真实工厂、产地、生产档案、订单、库存、销量、实名客户证言或线下价格。
|
|
1114
1124
|
6. 20/30 秒的后一段剧情承接前段 endingState,但每段仍只使用自己的局部 0–10 秒执行内容。
|
|
1115
1125
|
7. 不要输出 creativePlan、executionBindings 或 masterScript;这些字段由 Agent 从选中蓝图确定性回填/合成,避免重复 token 和脆弱的文字绑定校验。若 selectionMode=user-framework,用户原框架的开头、事件顺序、核心剧情和结尾仍是最高权威。
|
|
1116
1126
|
8. 商品身份以按顺序提供的原商品图为最高权威:第1张是主SKU身份图,当前商品标题和大概类目只补充商品用途与事实,后续图片只是同一SKU补充视角。任何爆款源商品、首帧、故事板或上一段尾帧都不得改变商品颜色、款式、结构、比例、材质、包装、标识位置、部件、配件和实际套装数量;修正身份时不得降低动作、运镜、节奏、景深、真实感或画质。商品本体或包装表面的印花、图案、微纹理、已印文字/字符、图标、色块、行距、相对位置和朝向全部视为不可编辑的原图纹理;不得在 shot.visual、onScreenText、voiceover、evidence、openingState、endingState 或任何导演字段中要求重写、翻译、替换、删改、重排、镜像这些表面内容,也不得要求生成“另一段清晰可读文案”。新增屏幕字只能是与商品像素分离的场景叠加字幕,绝不能印到商品或包装上。旋转、翻转、弯折或开合商品时,完整表面纹理必须随实体整体运动,不能漂移、翻面后复写或重新生成。若 executionBlueprint 或 productAdaptation 与本规则冲突,只保留其钩子、动作因果和镜头节奏,改写构图、机位或表演,绝不改商品表面。
|
|
1117
|
-
${contentMethod}固定使用 GPT-5.6 Terra 中等推理。媒体制作方式与分镜布局由中心在脚本完成后管理,不得影响这里的创意、镜头、口播或结构化脚本。只返回当前结构化 jobs,不调用工具、不创建任何图片或视频任务。`;
|
|
1127
|
+
${contentMethod}${generatedMontageMethod}固定使用 GPT-5.6 Terra 中等推理。媒体制作方式与分镜布局由中心在脚本完成后管理,不得影响这里的创意、镜头、口播或结构化脚本。只返回当前结构化 jobs,不调用工具、不创建任何图片或视频任务。`;
|
|
1118
1128
|
}
|
|
1119
1129
|
export function creativeCandidatePrompt(id, task, ordinals) {
|
|
1120
1130
|
const products = relevantProductInputs(task, ordinals);
|
|
@@ -1239,6 +1249,7 @@ export function selectedBlueprintPromptPayload(values, fallbackTargetDurationSec
|
|
|
1239
1249
|
const blueprintRefBySignature = new Map();
|
|
1240
1250
|
const adaptations = new Map();
|
|
1241
1251
|
const ordinalBindings = values.map((candidate) => {
|
|
1252
|
+
const generatedMontage = flowCGeneratedMontage(candidate);
|
|
1242
1253
|
const blueprint = String(candidate.executionBlueprint || "").trim();
|
|
1243
1254
|
let blueprintRef = null;
|
|
1244
1255
|
if (blueprint) {
|
|
@@ -1266,6 +1277,7 @@ export function selectedBlueprintPromptPayload(values, fallbackTargetDurationSec
|
|
|
1266
1277
|
blueprintRef,
|
|
1267
1278
|
productExecutionProfileRef: candidate.productExecutionProfileRef || `flow-c-product-${candidate.productIndex}`,
|
|
1268
1279
|
scriptSource: candidate.scriptSource || candidate.creativeSource || null,
|
|
1280
|
+
...(generatedMontage ? { contentStyle: FLOW_C_GENERATED_MONTAGE_STYLE, contentStyleVersion: FLOW_C_GENERATED_MONTAGE_VERSION } : {}),
|
|
1269
1281
|
sellingFormCardId: candidate.sellingFormCardId || null,
|
|
1270
1282
|
sellingFormName: candidate.sellingFormName || null,
|
|
1271
1283
|
sellingFormSelectionReason: candidate.sellingFormSelectionReason || null,
|
|
@@ -1317,12 +1329,14 @@ function positiveDuration(value) {
|
|
|
1317
1329
|
export function selectedCandidatePlan(value, contentStrategy = null) {
|
|
1318
1330
|
if (!value)
|
|
1319
1331
|
throw new Error("中心缺少选中的创意候选");
|
|
1332
|
+
const generatedMontage = flowCGeneratedMontage(value);
|
|
1320
1333
|
const contentDirection = flowCContentDirection(value.contentDirection, contentStrategy);
|
|
1321
1334
|
return {
|
|
1322
1335
|
visualPremise: value.visualPremise,
|
|
1323
1336
|
learnedTemplateId: value.learnedTemplateId || null,
|
|
1324
1337
|
learnedTemplateSource: value.learnedTemplateSource || null,
|
|
1325
1338
|
scriptSource: value.scriptSource || value.creativeSource || null,
|
|
1339
|
+
...(generatedMontage ? { contentStyle: FLOW_C_GENERATED_MONTAGE_STYLE, contentStyleVersion: FLOW_C_GENERATED_MONTAGE_VERSION } : {}),
|
|
1326
1340
|
sellingFormCardId: value.sellingFormCardId || null,
|
|
1327
1341
|
sellingFormName: value.sellingFormName || null,
|
|
1328
1342
|
sellingFormSelectionReason: value.sellingFormSelectionReason || null,
|