Initial commit: OmniAI Web Frontend
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import {
|
||||
analyzeProductImages,
|
||||
buildProductSummary,
|
||||
extractSellingPoints,
|
||||
generateCreativeOptions,
|
||||
generateStoryboard,
|
||||
generateVideoPrompts,
|
||||
checkCompliance,
|
||||
type AdVideoUserConfig,
|
||||
} from "../../api/adVideoPlanClient";
|
||||
import { aiGenerationClient } from "../../api/aiGenerationClient";
|
||||
import { uploadAssetWithProgress } from "../../api/uploadWithProgress";
|
||||
import { waitForTask } from "../../api/taskSubscription";
|
||||
import { resolveVideoRequestModel } from "../../utils/resolveVideoModel";
|
||||
import type {
|
||||
EcommerceVideoPlanResult,
|
||||
EcommerceVideoSceneTask,
|
||||
PlanStep,
|
||||
} from "./ecommerceVideoTypes";
|
||||
|
||||
export interface PlanCallbacks {
|
||||
onStepStart: (step: PlanStep) => void;
|
||||
onStepDone: (step: PlanStep) => void;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export async function runVideoPlan(
|
||||
imageDataUrls: string[],
|
||||
manualText: string,
|
||||
config: AdVideoUserConfig,
|
||||
callbacks: PlanCallbacks,
|
||||
): Promise<EcommerceVideoPlanResult> {
|
||||
const { onStepStart, onStepDone, signal } = callbacks;
|
||||
|
||||
onStepStart("upload");
|
||||
const imageUrls: string[] = [];
|
||||
for (const dataUrl of imageDataUrls) {
|
||||
const result = await uploadAssetWithProgress(
|
||||
{ dataUrl, scope: "ecommerce-product", mimeType: "image/png" },
|
||||
{ signal },
|
||||
);
|
||||
imageUrls.push(result.url);
|
||||
}
|
||||
onStepDone("upload");
|
||||
|
||||
onStepStart("analyze");
|
||||
const imageDesc = await analyzeProductImages(imageUrls, signal);
|
||||
onStepDone("analyze");
|
||||
|
||||
onStepStart("summary");
|
||||
const summary = await buildProductSummary(imageDesc, manualText, signal);
|
||||
onStepDone("summary");
|
||||
|
||||
onStepStart("selling");
|
||||
const selling = await extractSellingPoints(summary, signal);
|
||||
onStepDone("selling");
|
||||
|
||||
onStepStart("creative");
|
||||
const creatives = await generateCreativeOptions(selling, config, signal);
|
||||
if (!creatives.length) throw new Error("未能生成有效的广告创意");
|
||||
onStepDone("creative");
|
||||
|
||||
onStepStart("storyboard");
|
||||
const storyboard = await generateStoryboard(creatives[0], summary, config, signal);
|
||||
onStepDone("storyboard");
|
||||
|
||||
onStepStart("prompts");
|
||||
const videoPrompts = await generateVideoPrompts(storyboard, summary, signal);
|
||||
onStepDone("prompts");
|
||||
|
||||
onStepStart("compliance");
|
||||
const compliance = await checkCompliance(summary, selling, storyboard, signal);
|
||||
onStepDone("compliance");
|
||||
|
||||
return { summary, selling, creatives, storyboard, videoPrompts, compliance };
|
||||
}
|
||||
|
||||
export interface RenderSceneInput {
|
||||
sceneId: number;
|
||||
prompt: string;
|
||||
durationSeconds: number;
|
||||
imageUrl: string;
|
||||
aspectRatio: string;
|
||||
resolution: string;
|
||||
model?: string;
|
||||
}
|
||||
|
||||
export interface RenderCallbacks {
|
||||
onSceneSubmitted: (sceneId: number, taskId: string) => void;
|
||||
onSceneProgress: (sceneId: number, progress: number) => void;
|
||||
onSceneCompleted: (sceneId: number, resultUrl: string) => void;
|
||||
onSceneFailed: (sceneId: number, error: string) => void;
|
||||
}
|
||||
|
||||
export async function renderScene(
|
||||
input: RenderSceneInput,
|
||||
callbacks: RenderCallbacks,
|
||||
abortRef: { current: boolean },
|
||||
): Promise<void> {
|
||||
const model = resolveVideoRequestModel({
|
||||
model: input.model || "happyhorse-1.0",
|
||||
referenceUrls: [input.imageUrl],
|
||||
});
|
||||
|
||||
const { taskId } = await aiGenerationClient.createVideoTask({
|
||||
model,
|
||||
prompt: input.prompt,
|
||||
ratio: input.aspectRatio,
|
||||
duration: input.durationSeconds,
|
||||
quality: input.resolution,
|
||||
resolution: input.resolution,
|
||||
imageUrl: input.imageUrl,
|
||||
referenceUrls: [input.imageUrl],
|
||||
hasReferenceVideo: false,
|
||||
});
|
||||
|
||||
callbacks.onSceneSubmitted(input.sceneId, taskId);
|
||||
|
||||
const resultUrl = await waitForTask(taskId, {
|
||||
abortRef,
|
||||
onProgress: (e) => callbacks.onSceneProgress(input.sceneId, e.progress),
|
||||
});
|
||||
|
||||
if (resultUrl) {
|
||||
callbacks.onSceneCompleted(input.sceneId, resultUrl);
|
||||
} else {
|
||||
callbacks.onSceneFailed(input.sceneId, "任务未返回结果");
|
||||
}
|
||||
}
|
||||
|
||||
export function buildSceneTasks(
|
||||
plan: EcommerceVideoPlanResult,
|
||||
): EcommerceVideoSceneTask[] {
|
||||
return plan.storyboard.scenes.map((scene) => {
|
||||
const prompt = plan.videoPrompts.find((p) => p.scene_id === scene.scene_id);
|
||||
return {
|
||||
sceneId: scene.scene_id,
|
||||
prompt: prompt?.positive_prompt || scene.visual_description,
|
||||
durationSeconds: Number.parseInt(scene.duration, 10) || 5,
|
||||
status: "idle",
|
||||
progress: 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user