@xiaohhhh1/canvas-agent 0.4.61 → 0.4.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/video-intelligence/local-analysis.d.ts +3 -1
- package/dist/video-intelligence/local-analysis.js +10 -6
- package/dist/workflow/creative-candidates.d.ts +1 -1
- package/dist/workflow/creative-candidates.js +3 -1
- package/dist/workflow/manager.d.ts +9 -0
- package/dist/workflow/manager.js +11 -7
- package/package.json +1 -1
|
@@ -30,11 +30,13 @@ type VideoArchiveUpload = {
|
|
|
30
30
|
publicUrl: string;
|
|
31
31
|
headers?: Record<string, string>;
|
|
32
32
|
maxBytes?: number;
|
|
33
|
+
alreadyUploaded?: boolean;
|
|
34
|
+
downloadUrl?: string | null;
|
|
33
35
|
};
|
|
34
36
|
export declare function shouldUploadVideoArchive(input: {
|
|
35
37
|
kind: "uploaded-mp4" | "tiktok";
|
|
36
38
|
url: string;
|
|
37
|
-
}, publicUrl: string): boolean;
|
|
39
|
+
}, publicUrl: string, alreadyUploaded?: boolean): boolean;
|
|
38
40
|
export declare class LocalVideoIntelligence {
|
|
39
41
|
private readonly fastmoss;
|
|
40
42
|
constructor(fastmoss: FastMossIntegration);
|
|
@@ -8,9 +8,10 @@ import { videoEvidenceTimestamps } from "../integrations/fastmoss.js";
|
|
|
8
8
|
const ANALYSIS_TIMEOUT_MS = 10 * 60_000;
|
|
9
9
|
const TRANSCRIPT_LANGUAGES = "en.*,es.*,zh.*,pt.*,fr.*,de.*,vi.*,th.*,id.*,ms.*,ja.*,ko.*";
|
|
10
10
|
const LOCAL_ASR_MODEL = "onnx-community/whisper-base";
|
|
11
|
+
const VIDEO_INTELLIGENCE_SCHEMA_VERSION = "commerce-video-intelligence-v5";
|
|
11
12
|
let localAsrPipeline;
|
|
12
|
-
export function shouldUploadVideoArchive(input, publicUrl) {
|
|
13
|
-
return input.kind !== "uploaded-mp4" || input.url !== publicUrl;
|
|
13
|
+
export function shouldUploadVideoArchive(input, publicUrl, alreadyUploaded = false) {
|
|
14
|
+
return !alreadyUploaded && (input.kind !== "uploaded-mp4" || input.url !== publicUrl);
|
|
14
15
|
}
|
|
15
16
|
export class LocalVideoIntelligence {
|
|
16
17
|
fastmoss;
|
|
@@ -18,7 +19,10 @@ export class LocalVideoIntelligence {
|
|
|
18
19
|
this.fastmoss = fastmoss;
|
|
19
20
|
}
|
|
20
21
|
async archive(source, upload) {
|
|
21
|
-
const
|
|
22
|
+
const verifiedInput = verifiedVideoLearningSource(source);
|
|
23
|
+
const input = upload?.downloadUrl
|
|
24
|
+
? { ...verifiedInput, url: verifiedHttpsUrl(upload.downloadUrl, "站内下载地址无效") }
|
|
25
|
+
: verifiedInput;
|
|
22
26
|
const uploadUrl = verifiedHttpsUrl(upload?.uploadUrl, "站内上传地址无效");
|
|
23
27
|
const publicUrl = verifiedHttpsUrl(upload?.publicUrl, "站内视频地址无效");
|
|
24
28
|
const maxBytes = Math.min(500 * 1024 * 1024, Math.max(1, Number(upload?.maxBytes || 100 * 1024 * 1024)));
|
|
@@ -28,7 +32,7 @@ export class LocalVideoIntelligence {
|
|
|
28
32
|
? await downloadUploadedMp4(input.url, workDir, maxBytes)
|
|
29
33
|
: await downloadVideoArchive(input.url, workDir, maxBytes);
|
|
30
34
|
const bytes = await readFile(downloaded.file);
|
|
31
|
-
if (shouldUploadVideoArchive(input, publicUrl)) {
|
|
35
|
+
if (shouldUploadVideoArchive(input, publicUrl, upload.alreadyUploaded)) {
|
|
32
36
|
const response = await fetch(uploadUrl, {
|
|
33
37
|
method: "PUT",
|
|
34
38
|
headers: { "Content-Type": "video/mp4" },
|
|
@@ -153,11 +157,11 @@ export function localVideoAnalysisPrompt(source, durationMs, transcript, attachm
|
|
|
153
157
|
durationMs,
|
|
154
158
|
})}\n\n` +
|
|
155
159
|
`带时间码的口播字幕:\n${transcript}\n\n` +
|
|
156
|
-
`只返回一个 JSON 对象,不要 Markdown。字段必须是:schemaVersion="
|
|
160
|
+
`只返回一个 JSON 对象,不要 Markdown。字段必须是:schemaVersion="${VIDEO_INTELLIGENCE_SCHEMA_VERSION}";durationMs;language;summary;` +
|
|
157
161
|
`sellingFormat{primary,label,secondary,rationale,evidence[{startMs,endMs,observation}],confidence};` +
|
|
158
162
|
`hook{startMs,endMs,visual,spokenText,onScreenText,patternInterrupt,openLoop};productFirstSeenMs;` +
|
|
159
163
|
`segments[{startMs,endMs,role,visual,spokenText,onScreenText,audio,editing,confidence}](至少2段);` +
|
|
160
|
-
`creativeMutationProfile{coreVisualPremise,mutationAxes[至少2项],culturalSignals[{market,signal,narrativeRole,aestheticRole,transferConditions,avoidStereotype,evidence[{startMs,endMs,observation}]}],firstFrameComposition,spectacleMechanism,productIntegration,transferableRule,culturalLimitations,doNotCopy,evidence[{startMs,endMs,observation}],confidence};` +
|
|
164
|
+
`creativeMutationProfile{coreVisualPremise,mutationAxes[至少2项],culturalSignals[{market,signal,narrativeRole,aestheticRole,transferConditions,avoidStereotype,evidence[{startMs,endMs,observation}]}],firstFrameComposition,spectacleMechanism,productIntegration,captureProfile{subjectDominance,depthAndDensity,colorLightContrast,materialTexture,cameraEnergy,captureImperfections},transferableRule,culturalLimitations,doNotCopy,evidence[{startMs,endMs,observation}],confidence};` +
|
|
161
165
|
`mechanisms[{type,label,mechanism,whyItMayWork,evidence[{startMs,endMs,observation}],confidence,replicationRisk}](type 仅 hook/conflict/proof/pacing/trust/product-reveal/offer-framing/cta/audio/visual-grammar/environment/visual-mutation/culture-code/spectacle);` +
|
|
162
166
|
`viralHypotheses[{hypothesis,supportingMetrics,supportingEvidence[{startMs,endMs,observation}],confounders,confidence}];` +
|
|
163
167
|
`nonReplicableFactors;complianceRisks;originalityGuidance{preserveMechanisms,mustRewrite,forbiddenCopying}。`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
type JsonSchema = Record<string, unknown>;
|
|
2
|
-
export declare const FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION = "flow-c-creative-candidates-
|
|
2
|
+
export declare const FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION = "flow-c-creative-candidates-v4";
|
|
3
3
|
export declare function flowCCreativeCandidateOutputSchema(count: number): JsonSchema;
|
|
4
4
|
export declare function parseFlowCCreativeCandidateOutput(value: string, expectedOrdinals: number[]): Record<string, unknown>[];
|
|
5
5
|
export {};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { assertStrictResponseSchema } from './script-output.js';
|
|
2
|
-
export const FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION = 'flow-c-creative-candidates-
|
|
2
|
+
export const FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION = 'flow-c-creative-candidates-v4';
|
|
3
3
|
const text = { type: 'string', minLength: 1 };
|
|
4
|
+
const nullableText = { anyOf: [text, { type: 'null' }] };
|
|
4
5
|
function object(properties) {
|
|
5
6
|
return { type: 'object', properties, required: Object.keys(properties), additionalProperties: false };
|
|
6
7
|
}
|
|
7
8
|
function candidateSchema() {
|
|
8
9
|
return object({
|
|
10
|
+
learnedTemplateId: nullableText,
|
|
9
11
|
visualPremise: text,
|
|
10
12
|
mutationAxes: { type: 'array', minItems: 2, maxItems: 4, items: text },
|
|
11
13
|
culturalAnchors: {
|
|
@@ -21,6 +21,10 @@ type ScriptRecord = {
|
|
|
21
21
|
};
|
|
22
22
|
type ReferenceStyleCard = {
|
|
23
23
|
id?: unknown;
|
|
24
|
+
templateKind?: unknown;
|
|
25
|
+
templateProfile?: unknown;
|
|
26
|
+
modelFamily?: unknown;
|
|
27
|
+
sourceDurationSeconds?: unknown;
|
|
24
28
|
visualPremisePattern?: unknown;
|
|
25
29
|
mutationAxes?: unknown;
|
|
26
30
|
culturePattern?: unknown;
|
|
@@ -31,6 +35,10 @@ type ReferenceStyleCard = {
|
|
|
31
35
|
rhythmPattern?: unknown;
|
|
32
36
|
capturePattern?: unknown;
|
|
33
37
|
voiceTone?: unknown;
|
|
38
|
+
targetDurationPolicy?: unknown;
|
|
39
|
+
retimingRule?: unknown;
|
|
40
|
+
segmentRule?: unknown;
|
|
41
|
+
omniAdaptation?: unknown;
|
|
34
42
|
sourceEvidenceTier?: unknown;
|
|
35
43
|
truthBoundaries?: unknown;
|
|
36
44
|
applicability?: {
|
|
@@ -51,6 +59,7 @@ type ProductInput = {
|
|
|
51
59
|
type SelectedCandidate = {
|
|
52
60
|
ordinal: number;
|
|
53
61
|
productIndex: number;
|
|
62
|
+
learnedTemplateId?: string | null;
|
|
54
63
|
visualPremise: string;
|
|
55
64
|
mutationAxes: string[];
|
|
56
65
|
culturalAnchors: Array<Record<string, unknown>>;
|
package/dist/workflow/manager.js
CHANGED
|
@@ -655,13 +655,14 @@ ${durationRules}
|
|
|
655
655
|
}
|
|
656
656
|
export function creativeCandidatePrompt(id, task, ordinals) {
|
|
657
657
|
const products = relevantProductInputs(task, ordinals);
|
|
658
|
-
const styleCards = relevantStyleCards(task.reference_style_cards || [], products).map(compactReferenceStyleCard).filter(Boolean).slice(0, 6);
|
|
658
|
+
const styleCards = relevantStyleCards(task.reference_style_cards || [], products, task.market).map(compactReferenceStyleCard).filter(Boolean).slice(0, 6);
|
|
659
659
|
const ledger = (task.creative_fingerprint_ledger || []).slice(-60);
|
|
660
660
|
return `Flow C creative-candidate stage for handoff ${id}. Set contractVersion exactly to ${FLOW_C_CREATIVE_CANDIDATE_CONTRACT_VERSION}. Return exactly one group for each ordinal: ${ordinals.join(", ")}; mapping: ${scriptProductAssignments(task.product_quantities, ordinals)}.
|
|
661
|
-
Market: ${task.market}. Product facts: ${compactJson(products, 12_000)}
|
|
661
|
+
Market: ${task.market}. Target output duration: ${Number(task.duration_seconds || 10)} seconds. Product facts: ${compactJson(products, 12_000)}
|
|
662
662
|
Optional learned style abstractions: ${compactJson(styleCards, 5_000)}
|
|
663
663
|
Already accepted fingerprint ledger: ${compactJson(ledger, 8_000)}
|
|
664
664
|
For each ordinal return exactly 6 short, auditable candidate cards. These are visible structured alternatives, not hidden reasoning. Vary the overall creative skeleton through open dimensions such as relationship/identity, scene mismatch, scale/quantity, group reaction, viewpoint, sensory texture and proof action; do not rotate a fixed template list.
|
|
665
|
+
Set learnedTemplateId to the id of one supplied learned-video-structure card when its product/market fit is credible; otherwise set it to null. A fitting learned template has priority over free ideation. Preserve its hook-to-proof-to-purchase-reason causal structure, but re-time it to the current target duration instead of stretching, truncating or copying the source timeline. The template is model-neutral evidence: never output Seedance wording or source-specific people, dialogue, wardrobe, set or exact shots. The same learnedTemplateId may be used for multiple ordinals when useful, but each adapted candidate must still have truthful product-specific content, native VO intent and a meaningfully changed opening/proof execution. If the product has a non-empty user-authored creativeBrief, this candidate stage is bypassed entirely and no learned template may influence the script.
|
|
665
666
|
Every card must be surprising but physically filmable and product-relevant. productProofAction must state one truthful intended-use action and the observable result that remains visible in the same scene. purchaseReason must state one concrete reason the supported result matters to a real target buyer; it is not a generic CTA and must not invent price, discount, scarcity, sales, inventory or unsupported quantified performance. productProofAction, purchaseReason and truthBoundary must follow supplied facts. For food, baby, medical/efficacy and personal-hygiene products, reject unhygienic display, fear marketing, dangerous use and unsupported promises.
|
|
666
667
|
Every card must also contain a concrete visual execution card, not mood adjectives: firstFrameFocus names one dominant subject and its approximate 35%-85% frame share; visualContrast defines one readable scale/color/light/relationship contrast; sensoryTexture names truthful category-specific material evidence; motionPeak names the single peak physical instant; compositionLighting specifies foreground/midground/background, practical light direction and one plausible exposure/optical imperfection; localNativeDetail names observable local behavior/environment/aesthetic density with the same evidence discipline as culturalAnchors; antiFlatness names the exact generic catalogue treatment this concept must avoid. The firstFrame must pass a screenshot test: even without dialogue, a still image must show the unusual relationship, product relevance and depth hierarchy.
|
|
667
668
|
Cultural anchors are optional (0–3). source must be learned-style-card, product-market-evidence, or conservative-everyday-detail. A learned-style-card anchor must cite the supplied card id in evidence. A product-market-evidence anchor must cite a concrete supplied product/market fact; if none exists, use conservative-everyday-detail and explicitly say it is ordinary context, not a custom claim. Never use flags, national costumes, royalty/public figures, religion, stereotypes or cultural mockery as shortcut hooks. Locality belongs in consumption context, natural interaction, language rhythm and aesthetic density.
|
|
@@ -670,7 +671,7 @@ creativeFingerprint must use five concise semantic labels: characterRelation, sc
|
|
|
670
671
|
function compactReferenceStyleCard(value) {
|
|
671
672
|
if (!value || typeof value !== "object")
|
|
672
673
|
return "";
|
|
673
|
-
const fields = ["id", "visualPremisePattern", "mutationAxes", "culturePattern", "spectaclePattern", "productIntegrationPattern", "openingPattern", "proofPattern", "rhythmPattern", "capturePattern", "voiceTone", "sourceEvidenceTier"];
|
|
674
|
+
const fields = ["id", "templateKind", "templateProfile", "modelFamily", "sourceDurationSeconds", "visualPremisePattern", "mutationAxes", "culturePattern", "spectaclePattern", "productIntegrationPattern", "openingPattern", "proofPattern", "rhythmPattern", "capturePattern", "voiceTone", "targetDurationPolicy", "retimingRule", "segmentRule", "omniAdaptation", "sourceEvidenceTier"];
|
|
674
675
|
const compact = Object.fromEntries(fields.flatMap((field) => {
|
|
675
676
|
const text = String(value[field] || "").trim().replace(/\s+/g, " ").slice(0, 400);
|
|
676
677
|
return text ? [[field, text]] : [];
|
|
@@ -681,13 +682,15 @@ function compactReferenceStyleCard(value) {
|
|
|
681
682
|
Object.assign(compact, { applicability: { categories, markets } });
|
|
682
683
|
return Object.keys(compact).length ? compact : null;
|
|
683
684
|
}
|
|
684
|
-
function relevantStyleCards(cards, products) {
|
|
685
|
+
function relevantStyleCards(cards, products, market = "") {
|
|
685
686
|
const categories = new Set(products.map((product) => String(product.category || "").trim().toLowerCase()).filter(Boolean));
|
|
686
|
-
if (!categories.size)
|
|
687
|
-
return cards.slice(0, 3);
|
|
688
687
|
const matched = cards.filter((card) => {
|
|
689
688
|
const allowed = Array.isArray(card.applicability?.categories) ? card.applicability.categories.map((item) => String(item || "").trim().toLowerCase()).filter(Boolean) : [];
|
|
690
|
-
|
|
689
|
+
const markets = Array.isArray(card.applicability?.markets) ? card.applicability.markets.map((item) => String(item || "").trim().toLowerCase()).filter(Boolean) : [];
|
|
690
|
+
const categoryFit = !categories.size || !allowed.length || allowed.includes("all") || allowed.includes("global") || allowed.some((item) => categories.has(item));
|
|
691
|
+
const targetMarket = String(market || "").trim().toLowerCase();
|
|
692
|
+
const marketFit = !markets.length || markets.includes("all") || markets.includes("global") || markets.includes(targetMarket);
|
|
693
|
+
return categoryFit && marketFit;
|
|
691
694
|
});
|
|
692
695
|
return matched.length ? matched : [];
|
|
693
696
|
}
|
|
@@ -710,6 +713,7 @@ function selectedCandidatePlan(value) {
|
|
|
710
713
|
throw new Error("中心缺少选中的创意候选");
|
|
711
714
|
return {
|
|
712
715
|
visualPremise: value.visualPremise,
|
|
716
|
+
learnedTemplateId: value.learnedTemplateId || null,
|
|
713
717
|
mutationAxes: value.mutationAxes,
|
|
714
718
|
culturalAnchors: value.culturalAnchors,
|
|
715
719
|
spectacleEscalation: value.spectacleEscalation,
|