Files
lindyson-ai/apps/api/tests/tkPromptEngine.test.ts
Juno ChowandClaude Fable 5.1 f665082dad chore: 建立产品仓库,完成开源前的敏感信息清理
前端(Vite + React)、API/worker(Fastify + BullMQ)、api-contracts 契约包、
迁移脚本、测试与自托管 docker-compose 示例。

开源准备:运维脚本与机器信息已拆到私有仓库;公司名、备案号、隐私邮箱改读
publicCompliance.local.json(仓库只保留占位模板);站点地址与客服二维码改为
VITE_ 环境变量;fixtures 中的真实用户标识已匿名化;三个 package.json 标注
Apache-2.0。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-16 02:10:46 +08:00

2257 lines
80 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest';
import {
TK_PROMPT_HOOK_IDS,
TK_PROMPT_HOOK_PATTERNS,
TK_PROMPT_LANGUAGES,
TK_PROMPT_LIVE_PRESENTER_BOUNDARY,
TK_PROMPT_NON_LIVE_PERSON_BOUNDARY,
TK_PROMPT_STYLES,
TK_PROMPT_VISUAL_TEXT_BOUNDARY,
TK_PROMPT_VIDEO_MODES,
compileTkPromptCandidateBeats,
deriveTkPromptEvidenceFlags,
eligibleTkPromptHookIds,
isTkPromptChineseNarrative,
isTkPromptTargetLanguageText,
normalizeTkPromptCandidateBeatInformation,
normalizeTkPromptCandidateCameraMovements,
normalizeTkPromptCandidateFreeText,
normalizeTkPromptCandidateStructure,
normalizeTkPromptStrategyPromotions,
normalizeTkPromptStrategyAppearanceMode,
normalizeTkPromptStrategyDiversity,
renderTkPromptExecutionPrompt,
tkPromptEngineRequestSchema,
validateTkPromptOptionCandidate,
validateTkPromptFinalExecutionPrompt,
validateTkPromptStrategySet,
type TkPromptFactPack,
type TkPromptOptionCandidate,
type TkPromptStrategy,
} from '../src/domain/tkPromptEngine.js';
const ids = {
image: '00000000-0000-4000-8000-000000000101',
image2: '00000000-0000-4000-8000-000000000103',
image3: '00000000-0000-4000-8000-000000000104',
};
describe('TK Prompt Engine 1.0 冻结合同', () => {
it('精确冻结 10 个 Style、16 个 Hook 和 3 个 Video Mode', () => {
expect(TK_PROMPT_STYLES).toEqual([
'drama',
'reversal',
'interview',
'story',
'unboxing',
'product_pov',
'comparison',
'talking_head',
'pain_point',
'scene',
]);
expect(TK_PROMPT_HOOK_IDS).toEqual([
'visual_shock',
'suspense_question',
'contrast',
'pain_strike',
'before_after',
'sound_hook',
'challenge_doubt',
'identity',
'number_benefit',
'unexpected',
'confession',
'honest_flaw',
'elimination_haul',
'cost_math',
'comment_reply',
'hype_confirm',
]);
expect(TK_PROMPT_HOOK_PATTERNS).toHaveLength(16);
expect(new Set(TK_PROMPT_HOOK_PATTERNS.map(item => item.internalExample)).size).toBe(16);
expect(TK_PROMPT_VIDEO_MODES).toEqual([
'product_closeup',
'scene_demo',
'live_presenter',
]);
expect(TK_PROMPT_VIDEO_MODES).not.toContain('graphic_montage');
});
it('5 秒只允许纯声音且图片硬上限是 9', () => {
expect(tkPromptEngineRequestSchema.safeParse({
...baseRequest(),
durationSeconds: 5,
spokenLanguage: null,
imageAssetIds: [ids.image, ids.image2],
}).success).toBe(true);
expect(tkPromptEngineRequestSchema.safeParse({
...baseRequest(),
durationSeconds: 5,
spokenLanguage: '英语',
}).success).toBe(false);
expect(tkPromptEngineRequestSchema.safeParse({
...baseRequest(),
durationSeconds: 5,
spokenLanguage: null,
verifiedOffer: { condition: '折扣10%' },
}).success).toBe(false);
expect(tkPromptEngineRequestSchema.safeParse({
...baseRequest(),
imageAssetIds: Array.from({ length: 10 }, (_, index) => (
`00000000-0000-4000-8000-${String(index + 1).padStart(12, '0')}`
)),
}).success).toBe(false);
expect(tkPromptEngineRequestSchema.parse(baseRequest()))
.not.toHaveProperty('authorizedActorAssetId');
expect(tkPromptEngineRequestSchema.safeParse({
...baseRequest(),
authorizedActorAssetId: '00000000-0000-4000-8000-000000000102',
}).success).toBe(false);
});
it('Hook 资格在策略前进行硬排除', () => {
const eligible = eligibleTkPromptHookIds({
durationSeconds: 5,
evidenceFlags: evidenceFlags(),
});
expect(eligible).toContain('visual_shock');
expect(eligible).toContain('sound_hook');
expect(eligible).not.toContain('suspense_question');
expect(eligible).not.toContain('before_after');
expect(eligible).not.toContain('number_benefit');
expect(eligible).not.toContain('comment_reply');
});
it('场景/素材自由文本不能升级为 Hook 事实证据', () => {
const flags = deriveTkPromptEvidenceFlags({
userConfirmedFacts: [],
verifiedOffer: '',
availableScenes: ['前后对比场景'],
availableAssets: ['真实评论、竞品和未拆封包装'],
});
expect(flags).toEqual(evidenceFlags());
expect(eligibleTkPromptHookIds({
durationSeconds: 10,
evidenceFlags: flags,
})).not.toContain('comment_reply');
expect(deriveTkPromptEvidenceFlags({
userConfirmedFacts: ['用户确认这是真实评论问题'],
verifiedOffer: '已验证折扣 10%',
})).toMatchObject({ realComment: true, numeric: true });
});
it('Hook 需要的证据事实必须与当前策略 allowedFactIds 相交', () => {
const factPack = makeFactPack({
facts: [...makeFactPack().facts, {
factId: 'confirmed-003',
status: 'user-confirmed',
text: '用户已提供真实评论原文问题',
sourceImageIds: [],
}],
});
const strategies = makeStrategies();
strategies[0] = { ...strategies[0]!, hookPatternId: 'comment_reply' };
expect(() => validateTkPromptStrategySet({
strategySet: { strategies },
factPack,
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), realComment: true, sealedPackage: true },
})).toThrow(/证据事实/u);
strategies[0] = {
...strategies[0]!,
allowedFactIds: [...strategies[0]!.allowedFactIds, 'confirmed-003'],
};
expect(validateTkPromptStrategySet({
strategySet: { strategies },
factPack,
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), realComment: true, sealedPackage: true },
})).toHaveLength(3);
});
it('三案每对六维差异至少四项,live_presenter 无需演员素材即可参与', () => {
const factPack = makeFactPack();
const valid = makeStrategies();
expect(validateTkPromptStrategySet({
strategySet: { strategies: valid },
factPack,
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toHaveLength(3);
expect(validateTkPromptStrategySet({
strategySet: {
strategies: valid.map((strategy, index) => index === 0
? { ...strategy, styleForm: 'presenter', style: 'talking_head', videoMode: 'live_presenter' }
: strategy),
},
factPack,
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})[0]).toMatchObject({ style: 'talking_head', videoMode: 'live_presenter' });
expect(validateTkPromptStrategySet({
strategySet: {
strategies: valid.map((strategy, index) => index === 0
? { ...strategy, styleForm: 'drama', style: 'drama', videoMode: 'scene_demo' }
: strategy),
},
factPack,
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})[0]).toMatchObject({ style: 'drama', videoMode: 'scene_demo' });
});
it.each(TK_PROMPT_LANGUAGES)('%s 的英文漂移会在生产侧收口为所选目标语言', language => {
const candidate = makeCandidate(10);
const driftText = language === '英语'
? 'Mira los detalles del producto en acción.'
: 'See the product details in action.';
candidate.beats.forEach(beat => {
beat.voiceoverTarget = driftText;
beat.voiceoverZh = language === '普通话' ? '' : '查看商品细节与操作方式。';
});
const factPack = makeFactPack({
spokenLanguage: language,
});
const normalized = normalizeTkPromptCandidateFreeText({
candidate,
strategy: makeStrategies()[0]!,
factPack,
durationSeconds: 10,
spokenLanguage: language,
});
const allowed = [factPack.productName, factPack.category, ...factPack.facts.map(fact => fact.text)];
expect(normalized.beats.every(beat => (
isTkPromptTargetLanguageText(beat.voiceoverTarget, language, allowed)
))).toBe(true);
});
it('服务端确定性锁定三策略槽多样性,保留 Provider Hook 与出镜模式', () => {
const collapsed = makeStrategies().map(strategy => ({
...strategy,
styleForm: 'scene' as const,
style: 'scene' as const,
firstFrameId: 'pain_reveal' as const,
buyingMotivationId: 'problem_relief' as const,
proofMethodId: 'visible_operation' as const,
videoMode: 'live_presenter' as const,
}));
expect(() => validateTkPromptStrategySet({
strategySet: { strategies: collapsed },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
requireLivePresenter: true,
})).toThrow(/DIVERSITY|差异/u);
const normalized = normalizeTkPromptStrategyDiversity({
strategySet: { strategies: collapsed },
});
expect(normalized.strategies.map(strategy => strategy.hookPatternId)).toEqual(
collapsed.map(strategy => strategy.hookPatternId),
);
expect(normalized.strategies.every(strategy => (
strategy.videoMode === 'live_presenter'
))).toBe(true);
expect(validateTkPromptStrategySet({
strategySet: normalized,
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
requireLivePresenter: true,
})).toHaveLength(3);
});
it('显式选择人物出镜后允许 1/2/3 案使用 live_presenter,但至少使用一案', () => {
const base = makeStrategies();
for (const liveCount of [1, 2, 3]) {
const strategies = base.map((strategy, index) => index < liveCount
? {
...strategy,
videoMode: 'live_presenter' as const,
}
: strategy);
expect(validateTkPromptStrategySet({
strategySet: { strategies },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
requireLivePresenter: true,
}).filter(strategy => strategy.videoMode === 'live_presenter')).toHaveLength(liveCount);
}
expect(() => validateTkPromptStrategySet({
strategySet: { strategies: base },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
requireLivePresenter: true,
})).toThrow(/至少一案|人物/u);
});
it('显式非主持人出镜方式由服务端锁定,主持人专属风格确定性回落', () => {
const strategies = makeStrategies();
strategies[0] = {
...strategies[0]!,
styleForm: 'presenter',
style: 'talking_head',
videoMode: 'live_presenter',
proofMethodId: 'presenter_delivery',
};
strategies[1] = { ...strategies[1]!, videoMode: 'scene_demo' };
const normalized = normalizeTkPromptStrategyAppearanceMode({
strategySet: { strategies },
appearanceMode: 'product_closeup',
durationSeconds: 10,
});
expect(normalized.strategies.every(strategy => (
strategy.videoMode === 'product_closeup'
))).toBe(true);
expect(normalized.strategies[0]).toMatchObject({
styleForm: 'scene',
style: 'scene',
proofMethodId: 'visible_operation',
});
expect(validateTkPromptStrategySet({
strategySet: normalized,
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toHaveLength(3);
const mismatched = makeStrategies();
mismatched[0] = {
...mismatched[0]!,
slot: 'creative',
objective: 'attention',
styleForm: 'drama',
style: 'scene',
};
const structurallyLocked = normalizeTkPromptStrategyAppearanceMode({
strategySet: { strategies: mismatched },
appearanceMode: 'auto',
durationSeconds: 10,
});
expect(structurallyLocked.strategies[0]).toMatchObject({
slot: 'safe',
objective: 'conversion',
styleForm: 'scene',
style: 'scene',
});
});
it('主持人风格/证明只能使用 live_presenter,策略不得携带旧演员 ID', () => {
const talkingHead = makeStrategies();
talkingHead[0] = {
...talkingHead[0]!,
styleForm: 'presenter',
style: 'talking_head',
videoMode: 'scene_demo',
};
expect(() => validateTkPromptStrategySet({
strategySet: { strategies: talkingHead },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow(/live_presenter|真人/u);
const presenterProof = makeStrategies();
presenterProof[0] = {
...presenterProof[0]!,
proofMethodId: 'presenter_delivery',
proofMethod: '主持人现场演示',
};
expect(() => validateTkPromptStrategySet({
strategySet: { strategies: presenterProof },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow(/live_presenter|真人/u);
const actorOnProduct = makeStrategies().map((strategy, index) => index === 0
? {
...strategy,
authorizedActorAssetId: '00000000-0000-4000-8000-000000000102',
}
: strategy);
expect(() => validateTkPromptStrategySet({
strategySet: { strategies: actorOnProduct },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow();
});
it('5 秒策略 openingLine 必须为空', () => {
const withSpeech = makeStrategies();
expect(() => validateTkPromptStrategySet({
strategySet: { strategies: withSpeech },
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow(/openingLine|纯声音/u);
const soundOnly = withSpeech.map(strategy => ({ ...strategy, openingLine: '' }));
expect(validateTkPromptStrategySet({
strategySet: { strategies: soundOnly },
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toHaveLength(3);
});
it('Hook 内部案例不得泄漏到策略或方案', () => {
const strategies = makeStrategies();
strategies[0] = {
...strategies[0]!,
productBridge: TK_PROMPT_HOOK_PATTERNS[0]!.internalExample,
};
expect(() => validateTkPromptStrategySet({
strategySet: { strategies },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow(/Hook/u);
const normalizedLeak = makeStrategies();
normalizedLeak[0] = {
...normalizedLeak[0]!,
productBridge: TK_PROMPT_HOOK_PATTERNS[0]!.internalExample.split('').join(' '),
};
expect(() => validateTkPromptStrategySet({
strategySet: { strategies: normalizedLeak },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow(/Hook/u);
for (const leakedHistory of [
'作为累计十亿 GMV 的顶级专家给出背书',
'拥有10年电商经验的专家推荐',
'创造十亿销售额的顶级策略师',
'world-class e-commerce expert with 10 years of experience and billions in sales',
]) {
const personaLeak = makeStrategies();
personaLeak[0] = { ...personaLeak[0]!, productBridge: leakedHistory };
expect(() => validateTkPromptStrategySet({
strategySet: { strategies: personaLeak },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow(/履历|专家/u);
}
const ordinaryProductAge = makeStrategies();
ordinaryProductAge[0] = { ...ordinaryProductAge[0]!, productBridge: '商品标注保质期10年' };
expect(validateTkPromptStrategySet({
strategySet: { strategies: ordinaryProductAge },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toHaveLength(3);
for (const disclosure of ['#AIGC', 'AI创作', '人工智能生成', 'AI-generated']) {
const aiLeak = makeStrategies();
aiLeak[0] = { ...aiLeak[0]!, productBridge: disclosure };
expect(() => validateTkPromptStrategySet({
strategySet: { strategies: aiLeak },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow(/AI/u);
}
for (const disclosure of [
'#AIGC', 'AIGC', 'AI制作', 'AI创作', '人工智能生成',
'AI-generated content', 'created by AI',
]) {
const candidate = makeCandidate(10);
candidate.whyItFits = disclosure;
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy: makeStrategies()[0]!,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/AI/u);
}
const ordinaryModelName = makeCandidate(10);
ordinaryModelName.whyItFits = 'AIGCLEAN-200 product detail';
expect(validateTkPromptOptionCandidate({
candidate: ordinaryModelName,
strategy: makeStrategies()[0]!,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
})).toBeTruthy();
const candidatePersonaLeak = makeCandidate(10);
candidatePersonaLeak.whyItFits = 'world-class e-commerce expert with 10 years of experience';
expect(() => validateTkPromptOptionCandidate({
candidate: candidatePersonaLeak,
strategy: makeStrategies()[0]!,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/履历|专家/u);
});
it('证明方法不能绕过竞品与前后素材证据门禁', () => {
const comparison = makeStrategies();
comparison[0] = {
...comparison[0]!,
proofMethodId: 'verified_comparison',
proofMethod: '使用已确认竞品做同条件对比',
};
expect(() => validateTkPromptStrategySet({
strategySet: { strategies: comparison },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow(/竞品|对比/u);
comparison[0] = {
...comparison[0]!,
allowedFactIds: [...comparison[0]!.allowedFactIds, 'confirmed-003'],
};
const competitorFactPack = makeFactPack({
facts: [...makeFactPack().facts, {
factId: 'confirmed-003',
status: 'user-confirmed',
text: '用户确认已提供竞品对比素材',
sourceImageIds: [],
}],
});
expect(validateTkPromptStrategySet({
strategySet: { strategies: comparison },
factPack: competitorFactPack,
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true, competitor: true },
})).toHaveLength(3);
const beforeAfter = makeStrategies();
beforeAfter[0] = {
...beforeAfter[0]!,
proofMethodId: 'verified_before_after',
proofMethod: '使用同场景前后素材证明',
};
expect(() => validateTkPromptStrategySet({
strategySet: { strategies: beforeAfter },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow(/前后/u);
});
it('无真实优惠时拒绝促销 CTA/发布文案,并零泄漏 forbiddenClaims', () => {
const promotionalStrategies = makeStrategies();
promotionalStrategies[0] = {
...promotionalStrategies[0]!,
ctaStrategy: '限时折扣,仅剩3件',
};
expect(() => validateTkPromptStrategySet({
strategySet: { strategies: promotionalStrategies },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow(/未验证|价格|折扣|库存|限时/u);
const strategy = makeStrategies()[0]!;
const candidate = makeCandidate(10);
candidate.whyItFits = 'Limited time discount — only 3 left.';
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/未验证|折扣|库存|限时/u);
candidate.whyItFits = 'A closer look at the product in action.';
candidate.beats[0]!.environment = '限时折扣活动现场';
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/未验证|折扣|限时/u);
candidate.beats[0]!.environment = '真实家居台面';
candidate.beats[0]!.voiceoverZh = '保证有效';
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack({ forbiddenClaims: ['保证有效'] }),
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/禁止声明/u);
const verifiedOfferPack = makeFactPack({
verifiedOffer: { condition: '折扣10%' },
});
const verifiedCandidate = makeCandidate(10);
verifiedCandidate.whyItFits = '折扣10%';
expect(validateTkPromptOptionCandidate({
candidate: verifiedCandidate,
strategy,
factPack: verifiedOfferPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toBeTruthy();
verifiedCandidate.whyItFits = '折扣10%,仅剩3件';
expect(() => validateTkPromptOptionCandidate({
candidate: verifiedCandidate,
strategy,
factPack: verifiedOfferPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/增强|不存在/u);
for (const unsupported of ['折扣10%,同时包邮', '折扣10%,另送赠品']) {
verifiedCandidate.whyItFits = unsupported;
expect(() => validateTkPromptOptionCandidate({
candidate: verifiedCandidate,
strategy,
factPack: verifiedOfferPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/增强|促销语义/u);
}
const freeShippingPack = makeFactPack({
verifiedOffer: { condition: '包邮' },
});
const freeShippingCandidate = makeCandidate(10);
freeShippingCandidate.whyItFits = '当前商品包邮';
expect(validateTkPromptOptionCandidate({
candidate: freeShippingCandidate,
strategy,
factPack: freeShippingPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toBeTruthy();
freeShippingCandidate.whyItFits = '当前商品包邮并折扣10%';
expect(() => validateTkPromptOptionCandidate({
candidate: freeShippingCandidate,
strategy,
factPack: freeShippingPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/增强|促销语义/u);
for (const [offer, allowed, enhanced] of [
['满100减10', '满100减10', '满100减50'],
['立减10', '立减10', '立减50'],
] as const) {
const amountBoundPack = makeFactPack({ verifiedOffer: { condition: offer } });
const amountBoundCandidate = makeCandidate(10);
amountBoundCandidate.whyItFits = allowed;
expect(validateTkPromptOptionCandidate({
candidate: amountBoundCandidate,
strategy,
factPack: amountBoundPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toBeTruthy();
amountBoundCandidate.whyItFits = enhanced;
expect(() => validateTkPromptOptionCandidate({
candidate: amountBoundCandidate,
strategy,
factPack: amountBoundPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/不存在的数值/u);
}
for (const unsupportedPromotion of [
'半价', '五折', '第二件半价', '特价', '买二送一',
'half price', 'buy two get one free',
]) {
const unsupportedCandidate = makeCandidate(10);
unsupportedCandidate.whyItFits = unsupportedPromotion;
expect(() => validateTkPromptOptionCandidate({
candidate: unsupportedCandidate,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/未验证|促销/u);
}
const tenPercentPack = makeFactPack({ verifiedOffer: { condition: '10% off' } });
const halfPriceRewrite = makeCandidate(10);
halfPriceRewrite.whyItFits = '第二件半价';
expect(() => validateTkPromptOptionCandidate({
candidate: halfPriceRewrite,
strategy,
factPack: tenPercentPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/不存在的数值|增强/u);
const bundlePack = makeFactPack({ verifiedOffer: { condition: '买一送一' } });
const bundleCandidate = makeCandidate(10);
bundleCandidate.whyItFits = '买一送一';
expect(validateTkPromptOptionCandidate({
candidate: bundleCandidate,
strategy,
factPack: bundlePack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toBeTruthy();
bundleCandidate.whyItFits = '买二送一';
expect(() => validateTkPromptOptionCandidate({
candidate: bundleCandidate,
strategy,
factPack: bundlePack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/不存在的数值/u);
for (const [offer, enhanced] of [
['第二件半价', '半价'],
['第二件5折', '5折'],
] as const) {
const contextualPack = makeFactPack({ verifiedOffer: { condition: offer } });
const contextualCandidate = makeCandidate(10);
contextualCandidate.whyItFits = offer;
expect(validateTkPromptOptionCandidate({
candidate: contextualCandidate,
strategy,
factPack: contextualPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toBeTruthy();
contextualCandidate.whyItFits = enhanced;
expect(() => validateTkPromptOptionCandidate({
candidate: contextualCandidate,
strategy,
factPack: contextualPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/不存在的数值/u);
}
});
it('策略 Provider 偶发扩写促销时本地确定性删除,原真实性门禁仍然生效', () => {
const promotionalStrategies = makeStrategies();
promotionalStrategies[0] = {
...promotionalStrategies[0]!,
firstFrame: '一个顾客拿起商品进行演示',
productBridge: '女性模特造型',
relevanceBeat: ids.image.split('').join(' '),
openingLine: 'Limited time discount — only 3 left.',
ctaStrategy: '限时折扣,仅剩3件,立即购买',
requiredMaterials: ['商品参考图', '优惠券截图'],
};
const factPack = makeFactPack();
const normalized = normalizeTkPromptStrategyPromotions({
strategySet: { strategies: promotionalStrategies },
factPack,
});
expect(normalized.strategies[0]).toMatchObject({
firstFrame: '首帧立即展示商品的可见结构或操作动作',
productBridge: '沿同一动作进入商品结构与操作细节',
relevanceBeat: '沿同一商品动作展示它与使用场景的关系',
openingLine: '',
ctaStrategy: '引导观众点击商品卡查看商品详情',
requiredMaterials: ['商品参考图'],
});
expect(validateTkPromptStrategySet({
strategySet: normalized,
factPack,
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toHaveLength(3);
const verifiedFactPack = makeFactPack({
verifiedOffer: { condition: '已验证折扣 10%' },
});
promotionalStrategies[0] = {
...promotionalStrategies[0]!,
openingLine: '已验证折扣 10%',
ctaStrategy: '查看已验证折扣 10%',
requiredMaterials: [],
};
const retained = normalizeTkPromptStrategyPromotions({
strategySet: { strategies: promotionalStrategies },
factPack: verifiedFactPack,
});
expect(retained.strategies[0]?.openingLine).toBe('已验证折扣 10%');
expect(retained.strategies[0]?.ctaStrategy).toBe('查看已验证折扣 10%');
});
it('商品事实百分比不被当作促销,但不能授权新的折扣数值', () => {
const strategy = makeStrategies()[0]!;
for (const factualNumber of ['过滤99%颗粒', '含50%成分']) {
const factPack = makeFactPack({
facts: [{
factId: 'visible-001',
status: 'visible',
text: factualNumber,
sourceImageIds: [ids.image],
}],
});
const candidate = makeCandidate(10);
candidate.coreInformationPoints[0]!.message = factualNumber;
candidate.beats[0]!.voiceoverTarget = factualNumber;
expect(validateTkPromptOptionCandidate({
candidate,
strategy,
factPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toBeTruthy();
}
const numericFact = '过滤99%颗粒';
const discountedPack = makeFactPack({
facts: [{
factId: 'visible-001',
status: 'visible',
text: numericFact,
sourceImageIds: [ids.image],
}],
verifiedOffer: { condition: '10% off' },
});
const valid = makeCandidate(10);
valid.coreInformationPoints[0]!.message = numericFact;
valid.beats[0]!.voiceoverTarget = `${numericFact}. 10% off.`;
expect(validateTkPromptOptionCandidate({
candidate: valid,
strategy,
factPack: discountedPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toBeTruthy();
const promotionRewrite = makeCandidate(10);
promotionRewrite.coreInformationPoints[0]!.message = numericFact;
promotionRewrite.beats[0]!.voiceoverTarget = '99% off.';
expect(() => validateTkPromptOptionCandidate({
candidate: promotionRewrite,
strategy,
factPack: discountedPack,
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/不存在的数值/u);
});
it('单方案 Provider 偶发扩写促销时逐字段中和,原真实性门禁保持', () => {
const strategy = makeStrategies()[0]!;
const factPack = makeFactPack();
const candidate = makeCandidate(10);
candidate.title = 'Limited time sale';
candidate.whyItFits = ids.image.split('').join(' ');
candidate.coreInformationPoints[0]!.message = '50% off';
candidate.beats[0]!.environment = 'Sale price display';
candidate.beats[0]!.editingCue = 'Add an on-screen text overlay';
candidate.beats[0]!.shotSize = '女性模特面部特写';
candidate.beats[0]!.lighting = '女性模特轮廓光';
candidate.beats[1]!.subject = '成年女性';
candidate.beats[1]!.action = '拿起商品进行演示';
candidate.beats[2]!.editingCue = '成年女性';
candidate.beats[3]!.environment = '拿起商品';
candidate.beats[3]!.subject = '女性模特造型';
candidate.beats[0]!.voiceoverTarget = 'Get 50% off today.';
candidate.beats[0]!.voiceoverZh = '今天五折优惠';
candidate.requiredMaterials = ['商品参考图', '优惠券截图'];
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy,
factPack,
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/未验证|文字浮层|真人/u);
const normalized = normalizeTkPromptCandidateFreeText({
candidate,
strategy,
factPack,
durationSeconds: 10,
spokenLanguage: '英语',
});
expect(normalized.requiredMaterials).toEqual(['商品参考图']);
expect(normalized.coreInformationPoints[0]?.message)
.toBe('展示已确认商品事实:商品表面有可见网格结构');
expect(normalized.whyItFits).toBe('围绕已验证商品事实展示结构与操作方式');
expect(normalized.beats[0]?.editingCue).toBe('保持动作连续');
expect(normalized.beats[0]?.shotSize).toBe('商品中近景');
expect(normalized.beats[0]?.lighting).toBe('自然清晰光线');
expect(normalized.beats[1]).toMatchObject({
subject: '商品主体及其可见结构',
action: '连续展示商品结构与操作方式',
});
expect(normalized.beats[2]?.editingCue).toBe('保持动作连续');
expect(normalized.beats[3]?.environment).toBe('清晰、简洁的商品展示场景');
expect(validateTkPromptOptionCandidate({
candidate: normalized,
strategy,
factPack,
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toBeTruthy();
});
it('英文镜头叙述确定性正规化为中文,同时保留目标语言和合法品牌型号', () => {
const candidate = makeCandidate(10);
candidate.title = 'The ultimate product demonstration';
candidate.whyItFits = 'This strategy shows why the product is useful';
candidate.coreInformationPoints[0]!.message = 'Show the ACME X200 Logo clearly';
candidate.beats.forEach(beat => {
beat.environment = 'A clean modern kitchen counter';
beat.subject = 'The ACME X200 product and its visible Logo';
beat.action = 'Push the button and show the mechanism';
beat.shotSize = 'Extreme close up product shot';
beat.lighting = 'Soft cinematic studio lighting';
beat.editingCue = 'Cut quickly when the button moves';
beat.soundDesign = 'Natural click sound and room ambience';
beat.voiceoverTarget = 'Mira los detalles del ACME X200 en acción.';
beat.voiceoverZh = '查看 ACME X200 的商品细节与操作方式。';
});
candidate.requiredMaterials = ['ACME X200 product images'];
const factPack = makeFactPack({
productName: 'ACME X200',
targetMarket: '墨西哥',
spokenLanguage: '西班牙语',
facts: [{
factId: 'visible-001',
status: 'visible',
text: 'ACME X200 正面可见 Logo 和网格结构',
sourceImageIds: [ids.image],
}, {
factId: 'confirmed-002',
status: 'user-confirmed',
text: '用户确认已提供未拆封包装素材',
sourceImageIds: [],
}],
});
const normalized = normalizeTkPromptCandidateFreeText({
candidate,
strategy: makeStrategies()[0]!,
factPack,
durationSeconds: 10,
spokenLanguage: '西班牙语',
});
const nonVoiceoverFields = [
normalized.title,
normalized.whyItFits,
...normalized.coreInformationPoints.map(point => point.message),
...normalized.beats.flatMap(beat => [
beat.environment,
beat.subject,
beat.action,
beat.shotSize,
beat.lighting,
beat.editingCue,
beat.soundDesign,
beat.voiceoverZh,
]),
...normalized.requiredMaterials,
].filter(Boolean);
expect(nonVoiceoverFields.every(value => (
isTkPromptChineseNarrative(value, ['ACME X200'])
))).toBe(true);
expect(normalized.beats.every(beat => beat.voiceoverTarget.includes('Mira los detalles')))
.toBe(true);
expect(JSON.stringify(normalized)).toContain('ACME X200');
});
it('服务端确定性锁定时长、事实、时间轴、人物、图片与口播结构', () => {
const strategy = makeStrategies()[0]!;
const factPack = makeFactPack();
const candidate = makeCandidate(10);
candidate.coreInformationPoints = [{
messageId: 'message-003',
message: '包含越权事实的描述',
factIds: ['visible-999', 'visible-001', 'visible-001'],
}, {
messageId: 'message-003',
message: '只有越权事实的描述',
factIds: ['visible-999'],
}, {
messageId: 'message-002',
message: '超出 10 秒上限的第三点',
factIds: ['visible-001'],
}];
candidate.primaryActionId = 'action-999';
candidate.beats.forEach((beat, index) => {
beat.startSeconds = index + 0.25;
beat.endSeconds = index + 0.75;
beat.actionPhase = 'start';
beat.productImageIndex = 9;
beat.presenterVisible = true;
beat.voiceoverTarget = '';
beat.voiceoverZh = '';
});
const structured = normalizeTkPromptCandidateStructure({
candidate,
strategy,
factPack,
durationSeconds: 10,
spokenLanguage: '英语',
imageCount: 1,
});
expect(structured.coreInformationPoints).toEqual([{
messageId: 'message-001',
message: '商品表面有可见网格结构',
factIds: ['visible-001'],
}, {
messageId: 'message-002',
message: '商品表面有可见网格结构',
factIds: ['visible-001'],
}]);
expect(structured.primaryActionId).toBeNull();
expect(structured.beats.map(beat => [
beat.startSeconds,
beat.endSeconds,
beat.actionPhase,
beat.productImageIndex,
beat.presenterVisible,
])).toEqual([
[0, 1, null, 1, false],
[1, 3, null, 1, false],
[3, 7, null, 1, false],
[7, 10, null, 1, false],
]);
expect(structured.beats.every(beat => beat.voiceoverTarget.length > 0)).toBe(true);
expect(structured.beats.every(beat => /[\u3400-\u9fff]/u.test(beat.voiceoverZh))).toBe(true);
const closed = normalizeTkPromptCandidateBeatInformation({
candidate: structured,
durationSeconds: 10,
});
expect(validateTkPromptOptionCandidate({
candidate: closed,
strategy,
factPack,
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toBeTruthy();
const five = makeCandidate(5);
five.primaryActionId = 'action-999';
five.beats.forEach(beat => {
beat.actionPhase = null;
beat.voiceoverTarget = 'Forbidden voice';
beat.voiceoverZh = '禁止口播';
});
const fiveStructured = normalizeTkPromptCandidateStructure({
candidate: five,
strategy,
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
spokenLanguage: null,
imageCount: 1,
});
expect(fiveStructured.primaryActionId).toBe('action-001');
expect(fiveStructured.beats.map(beat => beat.actionPhase))
.toEqual(['start', 'progress', 'complete']);
expect(fiveStructured.beats.every(beat => (
beat.voiceoverTarget === '' && beat.voiceoverZh === ''
))).toBe(true);
});
it('forbiddenClaims 会剥离中英文禁止前缀后匹配核心禁词', () => {
const strategy = makeStrategies()[0]!;
for (const [forbiddenClaim, leakedText] of [
['不要提医疗功效', '医疗功效'],
['禁止出现治愈焦虑', '治愈焦虑'],
['不得声称永久有效', '永久有效'],
['avoid medical claims', 'medical claims'],
['never say cures anxiety', 'cures anxiety'],
['do not mention permanent results', 'permanent results'],
] as const) {
const candidate = makeCandidate(10);
candidate.beats[0]!.voiceoverTarget = `Product detail: ${leakedText}`;
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack({ forbiddenClaims: [forbiddenClaim] }),
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/禁止声明/u);
}
expect(validateTkPromptOptionCandidate({
candidate: makeCandidate(10),
strategy,
factPack: makeFactPack({ forbiddenClaims: ['不要提医疗功效'] }),
durationSeconds: 10,
spokenLanguage: '英语',
})).toBeTruthy();
});
it('5 秒执行 Prompt 没有口播、中文译文和全局语言硬约束', () => {
const strategy = makeStrategies()[0]!;
const candidate = makeCandidate(5);
const validated = validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
spokenLanguage: null,
});
const prompt = renderTkPromptExecutionPrompt({
candidate: validated,
strategy,
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
spokenLanguage: null,
imageAssetIds: [ids.image, ids.image2],
});
expect(prompt).not.toContain('旁白(');
expect(prompt).not.toContain('仅供审核');
expect(prompt).toContain('@图片1=上传顺序第1张');
expect(prompt).toContain('@图片2=上传顺序第2张');
expect(prompt).not.toContain(ids.image);
expect(prompt).not.toContain(ids.image2);
expect(prompt).not.toMatch(/AUDIO LANGUAGE LOCK|音频语言硬约束/iu);
expect(prompt.length).toBeLessThanOrEqual(3_800);
});
it('5 秒 live_presenter 只生成同一虚构主持人的无声动作,不附演员素材', () => {
const strategy: TkPromptStrategy = {
...makeStrategies()[0]!,
videoMode: 'live_presenter',
};
const candidate = makeCandidate(5);
candidate.beats[0]!.presenterVisible = true;
const factPack = makeFactPack({ targetMarket: 'Japan', spokenLanguage: null });
const validated = validateTkPromptOptionCandidate({
candidate,
strategy,
factPack,
durationSeconds: 5,
spokenLanguage: null,
});
const prompt = renderTkPromptExecutionPrompt({
candidate: validated,
strategy,
factPack,
durationSeconds: 5,
spokenLanguage: null,
imageAssetIds: [ids.image],
});
expect(prompt).toContain(TK_PROMPT_LIVE_PRESENTER_BOUNDARY);
expect(prompt).toContain('主持人仅作无声动作演示');
expect(prompt).not.toContain('旁白(');
expect(prompt).not.toMatch(/authorizedActorAssetId|已绑定授权演员|@演员/u);
});
it('最终 renderer 拒绝事实包注入的素材 ID、禁用声明和未验证促销', () => {
const strategy = makeStrategies()[0]!;
const fiveCandidate = makeCandidate(5);
const renderFiveSeconds = (factPack: TkPromptFactPack) => renderTkPromptExecutionPrompt({
candidate: fiveCandidate,
strategy,
factPack,
durationSeconds: 5,
spokenLanguage: null,
imageAssetIds: [ids.image],
});
expect(() => renderFiveSeconds(makeFactPack({
spokenLanguage: null,
productName: `参考商品 ${ids.image}`,
}))).toThrow(/素材 UUID|内部标识/u);
expect(() => renderFiveSeconds(makeFactPack({
spokenLanguage: null,
demonstrableActions: [makeDemonstrableAction(`手部按下结构 ${ids.image}`)],
}))).toThrow(/素材 UUID|内部标识/u);
expect(() => renderFiveSeconds(makeFactPack({
spokenLanguage: null,
demonstrableActions: [makeDemonstrableAction('手部按下 action-001 对应结构')],
}))).toThrow(/内部标识/u);
expect(() => renderFiveSeconds(makeFactPack({
spokenLanguage: null,
productName: '治愈焦虑参考商品',
forbiddenClaims: ['治愈焦虑'],
}))).toThrow(/禁止声明/u);
expect(() => renderFiveSeconds(makeFactPack({
spokenLanguage: null,
demonstrableActions: [makeDemonstrableAction('手部按下结构并宣称治愈焦虑')],
forbiddenClaims: ['治愈焦虑'],
}))).toThrow(/禁止声明/u);
for (const productName of [
'拥有10年经验与十亿 GMV 的专家推荐商品',
TK_PROMPT_HOOK_PATTERNS[0]!.internalExample,
'参考 @图片1 商品',
]) {
expect(() => renderFiveSeconds(makeFactPack({
spokenLanguage: null,
productName,
}))).toThrow(/履历|专家|Hook|图片引用/u);
}
for (const action of [
'专家用10年经验和 GMV 背书并按下结构',
TK_PROMPT_HOOK_PATTERNS[1]!.internalExample,
'手部按下 @图片1 中的结构',
]) {
expect(() => renderFiveSeconds(makeFactPack({
spokenLanguage: null,
demonstrableActions: [makeDemonstrableAction(action)],
}))).toThrow(/履历|专家|Hook|图片引用/u);
}
expect(() => renderTkPromptExecutionPrompt({
candidate: makeCandidate(10),
strategy: { ...strategy, productBridge: '继续 @图片1 中的操作' },
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/图片引用/u);
const tenCandidate = makeCandidate(10);
expect(() => renderTkPromptExecutionPrompt({
candidate: tenCandidate,
strategy,
factPack: makeFactPack({ productName: '限时折扣10%参考商品' }),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/未验证/u);
const verifiedOfferPack = makeFactPack({
productName: '折扣10%参考商品',
verifiedOffer: { condition: '折扣10%' },
});
expect(renderTkPromptExecutionPrompt({
candidate: tenCandidate,
strategy,
factPack: verifiedOfferPack,
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toContain('【商品】折扣10%参考商品');
for (const productName of ['折扣10%并包邮参考商品', '折扣10%并送赠品参考商品']) {
expect(() => renderTkPromptExecutionPrompt({
candidate: tenCandidate,
strategy,
factPack: makeFactPack({
productName,
verifiedOffer: { condition: '折扣10%' },
}),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/增强|促销语义/u);
}
expect(() => renderTkPromptExecutionPrompt({
candidate: tenCandidate,
strategy,
factPack: makeFactPack({
productName: '包邮并折扣10%参考商品',
verifiedOffer: { condition: '包邮' },
}),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/增强|促销语义/u);
expect(renderTkPromptExecutionPrompt({
candidate: tenCandidate,
strategy,
factPack: makeFactPack({ productName: '含50%成分参考商品' }),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toContain('【商品】含50%成分参考商品');
expect(renderTkPromptExecutionPrompt({
candidate: tenCandidate,
strategy,
factPack: makeFactPack({ productName: '标注保质期10年的参考商品' }),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toContain('【商品】标注保质期10年的参考商品');
});
it('5 秒只能选择并编译审核后仍有事实支撑的稳定 actionId', () => {
const strategy = makeStrategies()[0]!;
const candidate = makeCandidate(5);
const factPack = makeFactPack({
spokenLanguage: null,
demonstrableActions: [makeDemonstrableAction('手部旋转可见结构', 'action-002')],
});
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy,
factPack,
durationSeconds: 5,
spokenLanguage: null,
})).toThrow(/主商品动作/u);
expect(() => compileTkPromptCandidateBeats({
candidate,
strategy,
factPack,
durationSeconds: 5,
})).toThrow(/失效|事实支撑/u);
candidate.primaryActionId = 'action-002';
const validated = validateTkPromptOptionCandidate({
candidate,
strategy,
factPack,
durationSeconds: 5,
spokenLanguage: null,
});
expect(renderTkPromptExecutionPrompt({
candidate: validated,
strategy,
factPack,
durationSeconds: 5,
spokenLanguage: null,
imageAssetIds: [ids.image],
})).toContain('主商品动作:手部旋转可见结构');
});
it('5 秒非人物模式不把事实包中的模特动作重新注入最终 Prompt', () => {
const strategy = makeStrategies()[0]!;
const candidate = makeCandidate(5);
const factPack = makeFactPack({
spokenLanguage: null,
demonstrableActions: [makeDemonstrableAction('女性模特穿上商品并展示')],
});
const validated = validateTkPromptOptionCandidate({
candidate,
strategy,
factPack,
durationSeconds: 5,
spokenLanguage: null,
imageAssetIds: [ids.image],
});
const prompt = renderTkPromptExecutionPrompt({
candidate: validated,
strategy,
factPack,
durationSeconds: 5,
spokenLanguage: null,
imageAssetIds: [ids.image],
});
expect(prompt).toContain('主商品动作:手部指向并展示图片中可见的商品特征');
expect(prompt).not.toMatch(/女性|模特/u);
});
it('5 秒允许否定式无人声说明,但拒绝正向人声;否定字幕规则不误伤', () => {
const strategy = makeStrategies()[0]!;
const candidate = makeCandidate(5);
candidate.beats[0]!.soundDesign = '真实卡扣声,无人声、无旁白';
candidate.warnings = ['禁止生成字幕'];
expect(validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
spokenLanguage: null,
})).toBeTruthy();
candidate.beats[0]!.soundDesign = 'Product click only; do not add voiceover.';
candidate.warnings = ['Do not show subtitles.'];
expect(validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
spokenLanguage: null,
})).toBeTruthy();
for (const safeSilence of [
'卡扣声;无解说、无配音、无语音',
'Product click only; no narration or vocals.',
'仅拟音,禁止加入旁白或对白',
]) {
candidate.beats[0]!.soundDesign = safeSilence;
expect(validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
spokenLanguage: null,
})).toBeTruthy();
}
for (const humanVoice of [
'加入人声旁白和真实卡扣声',
'加入解说',
'使用配音',
'主持人讲话',
'add narration',
'include vocals',
'use spoken words',
]) {
candidate.beats[0]!.soundDesign = humanVoice;
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
spokenLanguage: null,
})).toThrow(/人声/u);
}
candidate.beats[0]!.soundDesign = '真实卡扣声,无人声';
for (const overlay of [
'显示字幕并指向商品结构',
'屏幕显示一句文字',
'展示文案',
'出现贴纸',
'添加标签',
'show screen text',
'add a sticker',
]) {
candidate.beats[0]!.action = overlay;
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
spokenLanguage: null,
})).toThrow(/字幕|文字浮层/u);
}
candidate.beats[0]!.action = '展示商品包装原有文字和 Logo';
expect(validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
spokenLanguage: null,
})).toBeTruthy();
const crossField = makeCandidate(5);
crossField.title = 'Show';
crossField.whyItFits = 'Title-led product detail';
expect(JSON.stringify(crossField)).toMatch(/show.{0,32}title/iu);
expect(validateTkPromptOptionCandidate({
candidate: crossField,
strategy,
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
spokenLanguage: null,
})).toBeTruthy();
});
it('10 秒保留目标口播,但中文审核译文绝不进入执行 Prompt', () => {
const strategy = makeStrategies()[0]!;
const candidate = makeCandidate(10);
const validated = validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
});
const prompt = renderTkPromptExecutionPrompt({
candidate: validated,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
});
expect(prompt).toContain('旁白(English):See this detail.');
expect(prompt).not.toContain('看清这个细节。');
expect(prompt).not.toMatch(/AUDIO LANGUAGE LOCK|音频语言硬约束/iu);
});
it.each(TK_PROMPT_LANGUAGES.flatMap(language => ([10, 15] as const).flatMap(duration => (
([1, 2, 3] as const).map(imageCount => ({ language, duration, imageCount }))
))))('$language / $duration 秒 / $imageCount 图执行 Prompt 只保留目标语言口播', ({ language, duration, imageCount }) => {
const sample = LANGUAGE_SAMPLES[language];
const candidate = makeCandidate(duration);
candidate.beats.forEach((beat, index) => {
beat.voiceoverTarget = `${sample.voiceover} ${index + 1}`;
beat.voiceoverZh = language === '普通话' ? '' : `审核译文${index + 1}`;
});
const factPack = makeFactPack({
targetMarket: '墨西哥',
spokenLanguage: language,
});
const strategy = makeStrategies()[0]!;
const imageAssetIds = [ids.image, ids.image2, ids.image3].slice(0, imageCount);
const validated = validateTkPromptOptionCandidate({
candidate,
strategy,
factPack,
durationSeconds: duration,
spokenLanguage: language,
});
const prompt = renderTkPromptExecutionPrompt({
candidate: validated,
strategy,
factPack,
durationSeconds: duration,
spokenLanguage: language,
imageAssetIds,
});
expect(prompt).toContain(`旁白(${sample.label}):${sample.voiceover} 1`);
for (const beat of candidate.beats) {
expect(prompt).toContain(beat.voiceoverTarget);
if (beat.voiceoverZh) expect(prompt).not.toContain(beat.voiceoverZh);
}
for (let index = 1; index <= imageCount; index += 1) {
expect(prompt).toContain(`@图片${index}=上传顺序第${index}张`);
}
const nonVoiceoverPrompt = prompt
.split('\n')
.filter(line => !line.startsWith('旁白('))
.join('\n');
expect(isTkPromptChineseNarrative(nonVoiceoverPrompt)).toBe(true);
expect(prompt).not.toMatch(/AUDIO LANGUAGE LOCK|音频语言硬约束/iu);
expect(prompt.length).toBeLessThanOrEqual(3_800);
});
it.each(TK_PROMPT_LANGUAGES)('%s 的 live_presenter 使用目标市场虚构非名人并保持同一人物', language => {
const sample = LANGUAGE_SAMPLES[language];
const candidate = makeCandidate(10);
candidate.beats[0]!.presenterVisible = true;
candidate.beats.forEach((beat, index) => {
beat.voiceoverTarget = `${sample.voiceover} ${index + 1}`;
beat.voiceoverZh = language === '普通话' ? '' : `审核译文${index + 1}`;
});
const targetMarket = `${language}目标市场`;
const factPack = makeFactPack({ targetMarket, spokenLanguage: language });
const strategy: TkPromptStrategy = {
...makeStrategies()[0]!,
videoMode: 'live_presenter',
};
const validated = validateTkPromptOptionCandidate({
candidate,
strategy,
factPack,
durationSeconds: 10,
spokenLanguage: language,
});
const prompt = renderTkPromptExecutionPrompt({
candidate: validated,
strategy,
factPack,
durationSeconds: 10,
spokenLanguage: language,
imageAssetIds: [ids.image],
});
expect(prompt).toContain(`【目标市场】${targetMarket}`);
expect(prompt).toContain(TK_PROMPT_LIVE_PRESENTER_BOUNDARY);
expect(prompt).toContain(`口型与各镜头的${language}旁白自然对应`);
expect(prompt).toContain(`旁白(${sample.label})`);
expect(prompt).not.toMatch(/authorizedActorAssetId|已绑定授权演员|@演员/u);
});
it('策略、分镜和执行 Prompt 均拒绝虚构主持人的使用经历或测评身份', () => {
const strategies = makeStrategies();
strategies[0] = {
...strategies[0]!,
videoMode: 'live_presenter',
productBridge: '我亲自用了三个月后推荐给大家',
};
expect(() => validateTkPromptStrategySet({
strategySet: { strategies },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow(/个人使用经历|测评身份/u);
const candidate = makeCandidate(10);
candidate.beats[0]!.presenterVisible = true;
candidate.beats[0]!.voiceoverTarget = 'I personally tested this product for weeks.';
candidate.beats[0]!.voiceoverZh = '我亲自测试了这个商品数周。';
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy: { ...makeStrategies()[0]!, videoMode: 'live_presenter' },
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow(/个人使用经历|测评身份/u);
});
it('10/15 秒核心信息点上限、顺序和事实闭包是硬门禁', () => {
const strategy = makeStrategies()[0]!;
const tenSeconds = makeCandidate(10);
tenSeconds.coreInformationPoints.push({
messageId: 'message-002',
message: '同一结构可通过操作直接展示',
factIds: ['visible-001'],
});
tenSeconds.beats[1]!.coreInformationPointIds = ['message-002'];
expect(validateTkPromptOptionCandidate({
candidate: tenSeconds,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
}).coreInformationPoints).toHaveLength(2);
tenSeconds.coreInformationPoints.push({
messageId: 'message-003',
message: '第三个卖点',
factIds: ['visible-001'],
});
expect(() => validateTkPromptOptionCandidate({
candidate: tenSeconds,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/核心信息点|上限/u);
const fifteenSeconds = makeCandidate(15);
fifteenSeconds.coreInformationPoints.push(
{ messageId: 'message-002', message: '第二个信息点', factIds: ['visible-001'] },
{ messageId: 'message-003', message: '第三个信息点', factIds: ['visible-001'] },
);
fifteenSeconds.beats[1]!.coreInformationPointIds = ['message-002'];
fifteenSeconds.beats[2]!.coreInformationPointIds = ['message-003'];
expect(validateTkPromptOptionCandidate({
candidate: fifteenSeconds,
strategy,
factPack: makeFactPack(),
durationSeconds: 15,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
}).coreInformationPoints).toHaveLength(3);
fifteenSeconds.beats[0]!.factIds = [];
expect(() => validateTkPromptOptionCandidate({
candidate: fifteenSeconds,
strategy,
factPack: makeFactPack(),
durationSeconds: 15,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/闭合/u);
});
it('5 秒唯一主动作和三阶段由编译器注入公共 beats', () => {
const candidate = makeCandidate(5);
const strategy = makeStrategies()[0]!;
const factPack = makeFactPack({ spokenLanguage: null });
const validated = validateTkPromptOptionCandidate({
candidate,
strategy,
factPack,
durationSeconds: 5,
spokenLanguage: null,
imageAssetIds: [ids.image],
});
const beats = compileTkPromptCandidateBeats({
candidate: validated,
strategy,
factPack,
durationSeconds: 5,
});
expect(beats.map(beat => beat.action)).toEqual([
expect.stringContaining('主商品动作:手部指向并按下可见结构;阶段:启动'),
expect.stringContaining('阶段:连续推进'),
expect.stringContaining('阶段:完成'),
]);
expect(beats[0]!.subject).toContain('@图片1中的同一主商品');
expect(beats[0]).not.toHaveProperty('primaryActionId');
candidate.beats[1]!.actionPhase = 'complete';
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy,
factPack,
durationSeconds: 5,
spokenLanguage: null,
imageAssetIds: [ids.image],
})).toThrow(/actionPhase|阶段/u);
});
it('live_presenter 至少一拍使用同一虚构主持人,内部布尔值不泄漏到公共 beat', () => {
const strategy: TkPromptStrategy = {
...makeStrategies()[0]!,
videoMode: 'live_presenter',
};
const candidate = makeCandidate(10);
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/live_presenter|主持人/u);
candidate.beats[0]!.presenterVisible = true;
const validated = validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
});
const beats = compileTkPromptCandidateBeats({
candidate: validated,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
});
expect(beats[0]!.subject).toContain('同一位成年虚构非名人主持人');
expect(beats[0]).not.toHaveProperty('presenterVisible');
});
it('参考图整数索引和运镜连续段预算不可由自由文本绕过', () => {
const strategy = makeStrategies()[0]!;
const candidate = makeCandidate(10);
candidate.beats[0]!.productImageIndex = 2;
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/参考图/u);
candidate.beats[0]!.productImageIndex = 1;
candidate.beats[0]!.subject = '自由伪造 @图片1 引用';
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/自由文本|素材编号/u);
for (const personInstruction of [
'随机女孩出镜并拿起商品',
'一个顾客出镜拿起商品',
'店员出现并演示商品',
'妈妈露脸口播',
'学生手持商品出镜',
'a customer appears and holds the product',
'feature a shopper who demonstrates the product',
'a creator speaks to camera',
]) {
const unauthorizedPerson = makeCandidate(10);
unauthorizedPerson.beats[0]!.subject = personInstruction;
expect(() => validateTkPromptOptionCandidate({
candidate: unauthorizedPerson,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/真人|人物/u);
}
for (const facelessInstruction of [
'仅用户手部操作商品',
'无脸手部特写',
'第一视角 POV 操作商品',
'faceless POV product operation',
]) {
const faceless = makeCandidate(10);
faceless.beats[0]!.subject = facelessInstruction;
expect(validateTkPromptOptionCandidate({
candidate: faceless,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toBeTruthy();
}
const dense = makeCandidate(10);
dense.beats.map((beat, index) => {
beat.movementId = (['locked_off', 'slow_push_in', 'locked_off', 'locked_off'] as const)[index]!;
return beat;
});
expect(() => validateTkPromptOptionCandidate({
candidate: dense,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/运镜|连续段/u);
const normalizedDense = normalizeTkPromptCandidateCameraMovements({
candidate: dense,
durationSeconds: 10,
});
expect(normalizedDense.beats.map(beat => beat.movementId)).toEqual([
'locked_off',
'slow_push_in',
'slow_push_in',
'slow_push_in',
]);
expect(validateTkPromptOptionCandidate({
candidate: normalizedDense,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toBeTruthy();
const brokenInformationReference = makeCandidate(10);
brokenInformationReference.beats[0]!.coreInformationPointIds = [];
brokenInformationReference.beats[0]!.factIds = [];
brokenInformationReference.beats[3]!.coreInformationPointIds = ['message-001'];
brokenInformationReference.beats[3]!.factIds = ['visible-001'];
expect(() => validateTkPromptOptionCandidate({
candidate: brokenInformationReference,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/核心信息点|引用数量/u);
const normalizedInformation = normalizeTkPromptCandidateBeatInformation({
candidate: brokenInformationReference,
durationSeconds: 10,
});
expect(normalizedInformation.beats.map(beat => ({
points: beat.coreInformationPointIds,
facts: beat.factIds,
}))).toEqual([
{ points: ['message-001'], facts: ['visible-001'] },
{ points: ['message-001'], facts: ['visible-001'] },
{ points: ['message-001'], facts: ['visible-001'] },
{ points: [], facts: [] },
]);
expect(validateTkPromptOptionCandidate({
candidate: normalizedInformation,
strategy,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toBeTruthy();
const fiveDense = makeCandidate(5);
fiveDense.beats[1]!.movementId = 'locked_off';
expect(() => validateTkPromptOptionCandidate({
candidate: fiveDense,
strategy,
factPack: makeFactPack({ spokenLanguage: null }),
durationSeconds: 5,
spokenLanguage: null,
imageAssetIds: [ids.image],
})).toThrow(/运镜|连续段/u);
const fifteenAtBudget = makeCandidate(15);
(['locked_off', 'locked_off', 'slow_push_in', 'slow_push_in', 'macro_follow'] as const)
.forEach((movementId, index) => { fifteenAtBudget.beats[index]!.movementId = movementId; });
expect(validateTkPromptOptionCandidate({
candidate: fifteenAtBudget,
strategy,
factPack: makeFactPack(),
durationSeconds: 15,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toBeTruthy();
fifteenAtBudget.beats[3]!.movementId = 'lateral_track';
expect(() => validateTkPromptOptionCandidate({
candidate: fifteenAtBudget,
strategy,
factPack: makeFactPack(),
durationSeconds: 15,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
})).toThrow(/运镜|连续段/u);
});
it('最终 Prompt 门禁拒绝越界引用和中英文正向文字层指令', () => {
const valid = [
'【参考图固定映射】@图片1=上传顺序第1张',
'0–1秒:@图片1中的同一主商品。',
'1–3秒:@图片1中的同一主商品。',
'3–7秒:@图片1中的同一主商品。',
'7–10秒:@图片1中的同一主商品。',
TK_PROMPT_NON_LIVE_PERSON_BOUNDARY,
TK_PROMPT_VISUAL_TEXT_BOUNDARY,
].join('\n');
expect(validateTkPromptFinalExecutionPrompt({
prompt: valid,
imageCount: 1,
videoMode: 'product_closeup',
durationSeconds: 10,
})).toBe(valid);
for (const violation of [
'@图片2中的商品',
'@图片1.5中的商品',
'@视频1',
'show a $9 price sticker',
'add a speech bubble',
'显示字幕',
'屏幕显示一句文字',
'展示文案',
'出现贴纸',
'添加标签',
'随机女孩出镜并演示商品',
'一个顾客出镜拿起商品',
'a shopper appears and holds the product',
'AUDIO LANGUAGE LOCK: English only',
'generated by AI',
'#AIGC',
'AI创作',
'人工智能制作',
'拥有10年经验和十亿 GMV 的专家推荐',
TK_PROMPT_HOOK_PATTERNS[0]!.internalExample,
'@图片1中的额外自由引用',
]) {
expect(() => validateTkPromptFinalExecutionPrompt({
prompt: `${valid}\n${violation}`,
imageCount: 1,
videoMode: 'product_closeup',
durationSeconds: 10,
})).toThrow();
}
for (const safeSuffix of [
'展示商品包装原有文字和 Logo',
'仅手部操作与第一视角 POV',
'展示商品型号 AIGCLEAN-200',
]) {
expect(validateTkPromptFinalExecutionPrompt({
prompt: `${valid}\n${safeSuffix}`,
imageCount: 1,
videoMode: 'product_closeup',
durationSeconds: 10,
})).toContain(safeSuffix);
}
const soundOnlyValid = [
'【参考图固定映射】@图片1=上传顺序第1张',
'0–1秒:@图片1中的商品。',
'1–3秒:@图片1中的商品。',
'3–5秒:@图片1中的商品。',
TK_PROMPT_NON_LIVE_PERSON_BOUNDARY,
TK_PROMPT_VISUAL_TEXT_BOUNDARY,
].join('\n');
expect(validateTkPromptFinalExecutionPrompt({
prompt: `${soundOnlyValid}\n无解说、无配音、无人声`,
imageCount: 1,
videoMode: 'product_closeup',
durationSeconds: 5,
})).toContain('无解说、无配音、无人声');
for (const soundOnlyViolation of [
'加入人声旁白', '加入解说', '使用配音',
'add narration', 'include vocals', 'spoken words', '限时优惠10%',
]) {
expect(() => validateTkPromptFinalExecutionPrompt({
prompt: `${soundOnlyValid}\n${soundOnlyViolation}`,
imageCount: 1,
videoMode: 'product_closeup',
durationSeconds: 5,
})).toThrow();
}
});
it('非真人模式的人物门禁只检查视觉指令,不误伤音频中的声音特征', () => {
const candidate = makeCandidate(10);
candidate.beats[0]!.soundDesign = '女性旁白自然口播,配合轻微环境声';
const strategy = makeStrategies()[0]!;
const factPack = makeFactPack();
const validated = validateTkPromptOptionCandidate({
candidate,
strategy,
factPack,
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
});
const prompt = renderTkPromptExecutionPrompt({
candidate: validated,
strategy,
factPack,
durationSeconds: 10,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
});
expect(prompt).toContain('声音:女性旁白自然口播');
expect(() => validateTkPromptFinalExecutionPrompt({
prompt: prompt.replace('画面:', '画面:女性模特出镜展示商品;'),
imageCount: 1,
videoMode: 'scene_demo',
durationSeconds: 10,
})).toThrow(/Prompt 不得生成真人/u);
});
it('新方案不再接受发布文案产物', () => {
const candidate = { ...makeCandidate(10), publishPack: { title: '旧发布标题' } };
expect(() => validateTkPromptOptionCandidate({
candidate,
strategy: makeStrategies()[0]!,
factPack: makeFactPack(),
durationSeconds: 10,
spokenLanguage: '英语',
})).toThrow();
});
it('15 秒长字段只在总长超限时由编译器渐进压缩并保留全部硬边界', () => {
const candidate = makeCandidate(15);
candidate.title = '商品方案'.repeat(20);
candidate.beats.forEach(beat => {
beat.environment = '真实连续场景'.repeat(70);
beat.subject = '同一商品主体'.repeat(55);
beat.action = '连续展示商品动作'.repeat(45);
beat.shotSize = '商品近景'.repeat(25);
beat.lighting = '自然清晰光线'.repeat(40);
beat.editingCue = '保持动作连续'.repeat(40);
beat.soundDesign = '自然环境声与商品操作声'.repeat(35);
beat.voiceoverTarget = 'See the product detail in action. '.repeat(20);
beat.voiceoverZh = '查看商品细节与操作方式。'.repeat(30);
});
const strategy = makeStrategies()[0]!;
const factPack = makeFactPack();
const validated = validateTkPromptOptionCandidate({
candidate,
strategy,
factPack,
durationSeconds: 15,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
});
const prompt = renderTkPromptExecutionPrompt({
candidate: validated,
strategy,
factPack,
durationSeconds: 15,
spokenLanguage: '英语',
imageAssetIds: [ids.image],
});
expect(prompt.length).toBeLessThanOrEqual(3_800);
expect(prompt.match(/@图片1/gu)).toHaveLength(6);
expect(prompt).toContain(TK_PROMPT_NON_LIVE_PERSON_BOUNDARY);
expect(prompt).toContain(TK_PROMPT_VISUAL_TEXT_BOUNDARY);
});
it('Hook 证据推导逐条感知否定且不把方位“前后”当前后对比', () => {
expect(deriveTkPromptEvidenceFlags({
userConfirmedFacts: [
'没有竞品对比素材',
'无真实评论原文',
'禁止虚构真实经历',
'产品前后有两个按钮',
'无10%折扣证据',
],
verifiedOffer: '',
})).toEqual(evidenceFlags());
});
it('三案任意一对只有三维不同时硬拒绝', () => {
const strategies = makeStrategies();
strategies[1] = {
...strategies[1]!,
styleForm: strategies[0]!.styleForm,
style: strategies[0]!.style,
hookPatternId: strategies[0]!.hookPatternId,
};
expect(() => validateTkPromptStrategySet({
strategySet: { strategies },
factPack: makeFactPack(),
durationSeconds: 10,
evidenceFlags: { ...evidenceFlags(), sealedPackage: true },
})).toThrow(/六维|差异/u);
});
});
const LANGUAGE_SAMPLES = {
'英语': { label: 'English', voiceover: 'See this detail.' },
'西班牙语': { label: 'Spanish', voiceover: 'Mira este detalle.' },
'葡萄牙语': { label: 'Portuguese', voiceover: 'Veja este detalhe.' },
'法语': { label: 'French', voiceover: 'Regardez ce détail.' },
'印尼语': { label: 'Indonesian', voiceover: 'Lihat detail ini.' },
'意大利语': { label: 'Italian', voiceover: 'Guarda questo dettaglio.' },
'日语': { label: 'Japanese', voiceover: 'この細部を見てください。' },
'马来语': { label: 'Malay', voiceover: 'Lihat butiran ini.' },
'泰语': { label: 'Thai', voiceover: 'ดูรายละเอียดนี้' },
'越南语': { label: 'Vietnamese', voiceover: 'Hãy xem chi tiết này.' },
'普通话': { label: 'Mandarin Chinese', voiceover: '看看这个细节。' },
'德语': { label: 'German', voiceover: 'Sieh dir dieses Detail an.' },
'荷兰语': { label: 'Dutch', voiceover: 'Bekijk dit detail.' },
'波兰语': { label: 'Polish', voiceover: 'Spójrz na ten szczegół.' },
} as const;
function baseRequest() {
return {
imageAssetIds: [ids.image],
durationSeconds: 10,
targetMarket: 'United States',
spokenLanguage: '英语',
userConfirmedFacts: [],
forbiddenClaims: [],
evidenceFlags: evidenceFlags(),
verifiedOffer: null,
};
}
function evidenceFlags() {
return {
beforeAfter: false,
competitor: false,
numeric: false,
realComment: false,
popularity: false,
honestExperience: false,
sealedPackage: false,
};
}
function makeFactPack(
overrides: Partial<TkPromptFactPack> = {},
): TkPromptFactPack {
return {
productName: '参考商品',
category: '家居用品',
confidence: 'high',
facts: [{
factId: 'visible-001',
status: 'visible',
text: '商品表面有可见网格结构',
sourceImageIds: [ids.image],
}, {
factId: 'confirmed-002',
status: 'user-confirmed',
text: '用户确认已提供未拆封包装素材',
sourceImageIds: [],
}],
internalHypotheses: [],
demonstrableActions: [makeDemonstrableAction('手部指向并按下可见结构')],
forbiddenClaims: [],
availableMaterials: ['product-images'],
targetMarket: 'United States',
spokenLanguage: '英语',
verifiedOffer: null,
...overrides,
};
}
function makeDemonstrableAction(text: string, actionId = 'action-001') {
return {
actionId,
text,
supportingFactIds: ['visible-001'],
sourceImageAssetIds: [ids.image],
};
}
function makeStrategies(): TkPromptStrategy[] {
return [
{
slot: 'safe',
labelZh: '最稳',
objective: 'conversion',
styleForm: 'scene',
style: 'scene',
videoMode: 'scene_demo',
hookPatternId: 'pain_strike',
firstFrameId: 'pain_reveal',
firstFrame: '首帧呈现真实不便',
openingAction: '手部立即指向可见结构',
openingLine: 'This detail fixes the awkward moment.',
relevanceBeat: '真实台面中的不便与商品动作连续发生',
productBridge: '沿同一动作进入商品结构自证',
buyingMotivationId: 'problem_relief',
buyingMotivation: '缓解真实使用不便',
proofMethodId: 'visible_operation',
proofMethod: '现场完成一次可见操作',
ctaStrategy: '邀请查看当前商品详情',
allowedFactIds: ['visible-001', 'confirmed-002'],
requiredMaterials: [],
},
{
slot: 'native',
labelZh: '最原生',
objective: 'trust',
styleForm: 'product',
style: 'product_pov',
videoMode: 'product_closeup',
hookPatternId: 'sound_hook',
firstFrameId: 'sound_sync',
firstFrame: '商品操作拟音与微距动作同步',
openingAction: '手部按下可见结构并触发卡扣声',
openingLine: 'Listen to this tiny detail.',
relevanceBeat: '熟悉的操作声音对应真实使用瞬间',
productBridge: '音画同步落到结构特写',
buyingMotivationId: 'confidence',
buyingMotivation: '通过可见细节建立使用信心',
proofMethodId: 'structure_closeup',
proofMethod: '微距展示结构和操作关系',
ctaStrategy: '邀请用户近看商品细节',
allowedFactIds: ['visible-001'],
requiredMaterials: [],
},
{
slot: 'creative',
labelZh: '最抓眼',
objective: 'attention',
styleForm: 'product',
style: 'unboxing',
videoMode: 'product_closeup',
hookPatternId: 'visual_shock',
firstFrameId: 'unboxing_reveal',
firstFrame: '首帧以开启动作揭晓商品结构',
openingAction: '手部打开完整封装并连续移向结构',
openingLine: 'Wait until you see this part.',
relevanceBeat: '揭晓动作立刻说明结构与使用场景的关系',
productBridge: '继续同一开启动作完成场景证明',
buyingMotivationId: 'curiosity',
buyingMotivation: '通过结构揭晓满足好奇心',
proofMethodId: 'context_demo',
proofMethod: '在真实台面完成场景演示',
ctaStrategy: '邀请查看商品完整结构',
allowedFactIds: ['visible-001', 'confirmed-002'],
requiredMaterials: ['已确认封装'],
},
];
}
function makeCandidate(duration: 5 | 10 | 15): TkPromptOptionCandidate {
const timeline = duration === 5
? [[0, 1, 'stop'], [1, 3, 'relevance'], [3, 5, 'proof']] as const
: duration === 10
? [[0, 1, 'stop'], [1, 3, 'relevance'], [3, 7, 'proof'], [7, 10, 'cta']] as const
: [[0, 1, 'stop'], [1, 3, 'relevance'], [3, 7, 'proof'], [7, 11, 'resolution'], [11, 15, 'cta']] as const;
return {
title: '商品结构自证',
whyItFits: '使用可见结构和真实操作建立信任',
coreInformationPoints: [{
messageId: 'message-001',
message: '商品表面有可见网格结构',
factIds: ['visible-001'],
}],
primaryActionId: duration === 5 ? 'action-001' : null,
beats: timeline.map(([startSeconds, endSeconds], index) => ({
startSeconds,
endSeconds,
environment: '真实家居台面',
subject: '商品可见结构与操作手部',
action: '手部指向并按下可见结构',
shotSize: '微距特写',
coreInformationPointIds: ['message-001'],
actionPhase: duration === 5 ? (['start', 'progress', 'complete'] as const)[index]! : null,
productImageIndex: 1,
presenterVisible: false,
movementId: 'slow_push_in',
lighting: '柔和真实室内光',
editingCue: '在手指按下时切镜',
soundDesign: '真实按键卡扣声与轻微环境声',
voiceoverTarget: duration === 5 ? '' : 'See this detail.',
voiceoverZh: duration === 5 ? '' : '看清这个细节。',
factIds: ['visible-001'],
})),
assumptions: [],
requiredMaterials: [],
warnings: [],
};
}