Browse Source

视频模型参数传递

feature/interaction
TianYun 1 month ago
parent
commit
e330a9ae16
  1. 82
      src/store/execution/__tests__/generateVideoExecutor.test.ts
  2. 36
      src/store/execution/generateVideoExecutor.ts

82
src/store/execution/__tests__/generateVideoExecutor.test.ts

@ -347,6 +347,88 @@ describe("executeGenerateVideo", () => {
}); });
}); });
it("normalizes stale PopiServer image-to-video subtype to all-purpose reference for multiple images", async () => {
const node = makeNode({
selectedModel: { provider: "popiserver", modelId: "44", displayName: "Seedance" },
parameters: { subType: 202 },
});
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ success: true, video: "data:video/mp4;base64,output" }),
});
const ctx = makeCtx(node, {
getConnectedInputs: vi.fn().mockReturnValue({
images: [
"https://static.popi.art/1.png",
"https://static.popi.art/2.png",
"https://static.popi.art/3.png",
],
videos: [],
audio: [],
text: "video prompt",
dynamicInputs: {},
easeCurve: null,
}),
});
await executeGenerateVideo(ctx);
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
expect(body.parameters.subType).toBe(203);
});
it("keeps PopiServer first-last-frame subtype for exactly two images", async () => {
const node = makeNode({
selectedModel: { provider: "popiserver", modelId: "44", displayName: "Seedance" },
parameters: { subType: 204 },
});
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ success: true, video: "data:video/mp4;base64,output" }),
});
const ctx = makeCtx(node, {
getConnectedInputs: vi.fn().mockReturnValue({
images: ["https://static.popi.art/first.png", "https://static.popi.art/last.png"],
videos: [],
audio: [],
text: "video prompt",
dynamicInputs: {},
easeCurve: null,
}),
});
await executeGenerateVideo(ctx);
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
expect(body.parameters.subType).toBe(204);
});
it("defaults PopiServer video subtype to image-to-video for exactly one image", async () => {
const node = makeNode({
selectedModel: { provider: "popiserver", modelId: "44", displayName: "Seedance" },
parameters: {},
});
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({ success: true, video: "data:video/mp4;base64,output" }),
});
const ctx = makeCtx(node, {
getConnectedInputs: vi.fn().mockReturnValue({
images: ["https://static.popi.art/1.png"],
videos: [],
audio: [],
text: "video prompt",
dynamicInputs: {},
easeCurve: null,
}),
});
await executeGenerateVideo(ctx);
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
expect(body.parameters.subType).toBe(202);
});
it("uses persisted config over legacy video fields when executing", async () => { it("uses persisted config over legacy video fields when executing", async () => {
const node = makeNode({ const node = makeNode({
selectedModel: { provider: "kie", modelId: "legacy-video", displayName: "Legacy Video" }, selectedModel: { provider: "kie", modelId: "legacy-video", displayName: "Legacy Video" },

36
src/store/execution/generateVideoExecutor.ts

@ -38,6 +38,10 @@ export interface GenerateVideoOptions {
useStoredFallback?: boolean; useStoredFallback?: boolean;
} }
const VIDEO_GENERATION_SUB_TYPE_IMAGE_TO_VIDEO = 202;
const VIDEO_GENERATION_SUB_TYPE_ALL_PURPOSE_REFERENCE = 203;
const VIDEO_GENERATION_SUB_TYPE_FIRST_LAST_FRAME = 204;
function getGeneratedImageUrl(value: unknown): string | undefined { function getGeneratedImageUrl(value: unknown): string | undefined {
if (typeof value === "string" && value.length > 0) return value; if (typeof value === "string" && value.length > 0) return value;
if (!value || typeof value !== "object") return undefined; if (!value || typeof value !== "object") return undefined;
@ -46,13 +50,40 @@ function getGeneratedImageUrl(value: unknown): string | undefined {
return typeof record.url === "string" && record.url.length > 0 ? record.url : undefined; return typeof record.url === "string" && record.url.length > 0 ? record.url : undefined;
} }
function normalizeVideoGenerationSubType(
parameters: Record<string, unknown>,
imageCount: number
): Record<string, unknown> {
const subType = Number(parameters.subType);
if (subType === VIDEO_GENERATION_SUB_TYPE_FIRST_LAST_FRAME) {
return imageCount === 2
? parameters
: { ...parameters, subType: VIDEO_GENERATION_SUB_TYPE_ALL_PURPOSE_REFERENCE };
}
if (subType === VIDEO_GENERATION_SUB_TYPE_IMAGE_TO_VIDEO) {
return imageCount === 1
? parameters
: { ...parameters, subType: VIDEO_GENERATION_SUB_TYPE_ALL_PURPOSE_REFERENCE };
}
if (Number.isFinite(subType)) {
return parameters;
}
return {
...parameters,
subType: imageCount === 1
? VIDEO_GENERATION_SUB_TYPE_IMAGE_TO_VIDEO
: VIDEO_GENERATION_SUB_TYPE_ALL_PURPOSE_REFERENCE,
};
}
function buildVideoParameters( function buildVideoParameters(
nodeData: GenerateVideoNodeData, nodeData: GenerateVideoNodeData,
nodeConfig: ReturnType<typeof readVideoGenerationConfig>, nodeConfig: ReturnType<typeof readVideoGenerationConfig>,
modelToUse: SelectedModel, modelToUse: SelectedModel,
imageCount: number,
parametersOverride?: Record<string, unknown> parametersOverride?: Record<string, unknown>
): Record<string, unknown> | undefined { ): Record<string, unknown> | undefined {
const parameters = removeFirstClassGenerationParameters(mergeSchemaDefaults( let parameters = removeFirstClassGenerationParameters(mergeSchemaDefaults(
buildDefaultParametersFromSchema(nodeData.parameterSchema ?? []), buildDefaultParametersFromSchema(nodeData.parameterSchema ?? []),
parametersOverride ?? nodeConfig.parameters parametersOverride ?? nodeConfig.parameters
)); ));
@ -61,6 +92,7 @@ function buildVideoParameters(
const soundParameterName = getVideoSoundParameterName(modelToUse); const soundParameterName = getVideoSoundParameterName(modelToUse);
if (modelToUse.provider === "popiserver") { if (modelToUse.provider === "popiserver") {
parameters = normalizeVideoGenerationSubType(parameters, imageCount);
const merged = mergeConfiguredParametersOverride(parameters, { const merged = mergeConfiguredParametersOverride(parameters, {
...(nodeConfig.aspectRatio ...(nodeConfig.aspectRatio
? { aspectRatio: nodeConfig.aspectRatio, aspect_ratio: nodeConfig.aspectRatio, ratio: nodeConfig.aspectRatio } ? { aspectRatio: nodeConfig.aspectRatio, aspect_ratio: nodeConfig.aspectRatio, ratio: nodeConfig.aspectRatio }
@ -283,7 +315,7 @@ export async function executeGenerateVideo(
images: preparedInputs.images, images: preparedInputs.images,
prompt: text, prompt: text,
selectedModel: modelToUse, selectedModel: modelToUse,
parameters: buildVideoParameters(nodeData, nodeConfig, modelToUse, parametersOverride), parameters: buildVideoParameters(nodeData, nodeConfig, modelToUse, preparedInputs.images.length, parametersOverride),
dynamicInputs: preparedInputs.dynamicInputs, dynamicInputs: preparedInputs.dynamicInputs,
mediaType: "video" as const, mediaType: "video" as const,
}; };

Loading…
Cancel
Save