feat: add Bailian image generation support
This commit is contained in:
@@ -8,6 +8,7 @@ import { accountManagementConfigured } from "@/lib/server/organization-client";
|
||||
import { rootDir } from "@/lib/server/runtime";
|
||||
import { shouldMockVisualApi } from "@/lib/volcengine/visual-client";
|
||||
import type { EnabledImageCapability } from "@/lib/types";
|
||||
import { getBailianConfig, shouldMockBailian } from "@/lib/bailian/client";
|
||||
|
||||
export type SettingsField = {
|
||||
key: string;
|
||||
@@ -133,6 +134,18 @@ const settingDefinitions: Array<{
|
||||
{ key: "SEEDANCE_API_KEY", label: "方舟 API Key", secret: true, type: "password" }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "bailian",
|
||||
title: "阿里云百炼 API",
|
||||
description: "用于万相 2.7 文生图、参考图生图和图生视频。Base URL 会自动派生原生异步接口。",
|
||||
fields: [
|
||||
{ key: "BAILIAN_API_KEY", label: "百炼 API Key", secret: true, type: "password" },
|
||||
{ key: "BAILIAN_BASE_URL", label: "Base URL", defaultValue: "https://llm-126wneubbdo6dbr5.cn-beijing.maas.aliyuncs.com/compatible-mode/v1" },
|
||||
{ key: "BAILIAN_IMAGE_MODEL", label: "图片模型", defaultValue: "wan2.7-image-pro" },
|
||||
{ key: "BAILIAN_VIDEO_MODEL", label: "视频模型", defaultValue: "wan2.7-i2v-2026-04-25" },
|
||||
{ key: "BAILIAN_MOCK", label: "Mock 策略", type: "select", defaultValue: "auto", options: [{ label: "自动", value: "auto" }, { label: "总是 Mock", value: "true" }, { label: "总是真实接口", value: "false" }] }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "oss",
|
||||
title: "OSS 资产存储",
|
||||
@@ -155,7 +168,18 @@ const engineFieldDefinitions: FieldDefinition[] = [
|
||||
defaultValue: "jimeng",
|
||||
options: [
|
||||
{ label: "即梦 / 火山视觉", value: "jimeng" },
|
||||
{ label: "EvoLink GPT Image 2", value: "evolink" }
|
||||
{ label: "EvoLink GPT Image 2", value: "evolink" },
|
||||
{ label: "阿里云百炼 Wan 2.7", value: "bailian" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "VIDEO_GENERATE_ENGINE",
|
||||
label: "视频生成",
|
||||
type: "select",
|
||||
defaultValue: "seedance",
|
||||
options: [
|
||||
{ label: "Seedance", value: "seedance" },
|
||||
{ label: "阿里云百炼 Wan 2.7", value: "bailian" }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -197,6 +221,7 @@ export async function getApiSettings() {
|
||||
visual: shouldMockVisualApi() ? "mock" : "real",
|
||||
evolink: shouldMockEvolinkApi() ? "mock" : "real",
|
||||
seedance: shouldMockSeedance() ? "mock" : "real",
|
||||
bailian: shouldMockBailian() ? "mock" : getBailianConfig().apiKey ? "real" : "missing",
|
||||
auth: authConfigSummary(auth),
|
||||
organization: accountManagementConfigured() ? "configured" : "missing",
|
||||
data: process.env.SUPABASE_SERVICE_ROLE_KEY ? "supabase" : "local"
|
||||
@@ -215,10 +240,10 @@ export async function getApiSettings() {
|
||||
}),
|
||||
{
|
||||
id: "video.generate",
|
||||
label: "Seedance 视频生成",
|
||||
reqKey: seedance.model,
|
||||
engine: "seedance",
|
||||
engineLabel: "Seedance",
|
||||
label: "视频生成",
|
||||
reqKey: engineAssignments.find((item) => item.id === "video.generate")?.reqKey || seedance.model,
|
||||
engine: engineAssignments.find((item) => item.id === "video.generate")?.engine || "seedance",
|
||||
engineLabel: engineAssignments.find((item) => item.id === "video.generate")?.engineLabel || "Seedance",
|
||||
enabled: true
|
||||
}
|
||||
],
|
||||
@@ -252,15 +277,16 @@ function buildEngineAssignments(fileEnv: Map<string, string>): EngineAssignment[
|
||||
const capabilities = getJimengCapabilities();
|
||||
const evolink = getEvolinkImageSettings();
|
||||
const seedance = getSeedanceConfig();
|
||||
const bailian = getBailianConfig();
|
||||
const generateEngine = currentEngineValue(engineFieldDefinitions[0], fileEnv);
|
||||
const inpaintEngine = currentEngineValue(engineFieldDefinitions[1], fileEnv);
|
||||
const inpaintEngine = currentEngineValue(engineFieldDefinitions[2], fileEnv);
|
||||
|
||||
return [
|
||||
imageEngineAssignment({
|
||||
capability: "image.generate",
|
||||
label: "图片生成",
|
||||
engine: generateEngine,
|
||||
reqKey: generateEngine === "evolink" ? evolink.model : capabilities["image.generate"].reqKey,
|
||||
reqKey: generateEngine === "bailian" ? bailian.imageModel : generateEngine === "evolink" ? evolink.model : capabilities["image.generate"].reqKey,
|
||||
field: engineFieldDefinitions[0],
|
||||
fileEnv
|
||||
}),
|
||||
@@ -269,7 +295,7 @@ function buildEngineAssignments(fileEnv: Map<string, string>): EngineAssignment[
|
||||
label: "局部重绘",
|
||||
engine: inpaintEngine,
|
||||
reqKey: inpaintEngine === "evolink" ? evolink.model : capabilities["image.inpaint"].reqKey,
|
||||
field: engineFieldDefinitions[1],
|
||||
field: engineFieldDefinitions[2],
|
||||
fileEnv
|
||||
}),
|
||||
{
|
||||
@@ -285,12 +311,13 @@ function buildEngineAssignments(fileEnv: Map<string, string>): EngineAssignment[
|
||||
{
|
||||
id: "video.generate",
|
||||
label: "视频生成",
|
||||
engine: "seedance",
|
||||
engineLabel: "Seedance",
|
||||
mode: shouldMockSeedance() ? "mock" : "seedance",
|
||||
modeLabel: shouldMockSeedance() ? "Mock" : "Seedance 真实接口",
|
||||
reqKey: seedance.model,
|
||||
configurable: false
|
||||
engine: currentVideoEngineValue(engineFieldDefinitions[1], fileEnv),
|
||||
engineLabel: currentVideoEngineValue(engineFieldDefinitions[1], fileEnv) === "bailian" ? "阿里云百炼" : "Seedance",
|
||||
mode: currentVideoEngineValue(engineFieldDefinitions[1], fileEnv) === "bailian" ? (shouldMockBailian() ? "mock" : "bailian") : (shouldMockSeedance() ? "mock" : "seedance"),
|
||||
modeLabel: currentVideoEngineValue(engineFieldDefinitions[1], fileEnv) === "bailian" ? (shouldMockBailian() ? "Mock" : "百炼真实接口") : (shouldMockSeedance() ? "Mock" : "Seedance 真实接口"),
|
||||
reqKey: currentVideoEngineValue(engineFieldDefinitions[1], fileEnv) === "bailian" ? bailian.videoModel : seedance.model,
|
||||
configurable: true,
|
||||
field: fieldWithValue(engineFieldDefinitions[1], currentVideoEngineValue(engineFieldDefinitions[1], fileEnv), isConfigured(engineFieldDefinitions[1].key, fileEnv))
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -336,21 +363,27 @@ function isConfigured(key: string, fileEnv: Map<string, string>) {
|
||||
}
|
||||
|
||||
function normalizeImageEngine(value: string | undefined): ImageCreationEngine {
|
||||
return value?.trim().toLowerCase() === "evolink" ? "evolink" : "jimeng";
|
||||
const normalized = value?.trim().toLowerCase();
|
||||
return normalized === "evolink" || normalized === "bailian" ? normalized : "jimeng";
|
||||
}
|
||||
|
||||
function imageEngineLabel(engine: ImageCreationEngine) {
|
||||
return engine === "evolink" ? "EvoLink" : "即梦";
|
||||
return engine === "evolink" ? "EvoLink" : engine === "bailian" ? "阿里云百炼" : "即梦";
|
||||
}
|
||||
|
||||
function modeForImageEngine(engine: ImageCreationEngine) {
|
||||
if (engine === "evolink") return shouldMockEvolinkApi() ? "mock" : "evolink";
|
||||
if (engine === "bailian") return shouldMockBailian() ? "mock" : "bailian";
|
||||
return shouldMockVisualApi() ? "mock" : "volcengine";
|
||||
}
|
||||
|
||||
function modeLabel(mode: string, engine: ImageCreationEngine) {
|
||||
if (mode === "mock") return "Mock";
|
||||
return engine === "evolink" ? "EvoLink 真实接口" : "即梦真实接口";
|
||||
return engine === "evolink" ? "EvoLink 真实接口" : engine === "bailian" ? "百炼真实接口" : "即梦真实接口";
|
||||
}
|
||||
|
||||
function currentVideoEngineValue(field: FieldDefinition, fileEnv: Map<string, string>) {
|
||||
return (process.env[field.key] ?? fileEnv.get(field.key) ?? field.defaultValue) === "bailian" ? "bailian" : "seedance";
|
||||
}
|
||||
|
||||
function envFilePath() {
|
||||
|
||||
@@ -679,7 +679,7 @@ function imageTemplateFromRow(row: Record<string, unknown>): ImageTemplate {
|
||||
prompt: String(row.prompt || ""),
|
||||
previewImageUrl: optionalString(row.preview_image_url),
|
||||
settings: isRecord(row.settings) ? {
|
||||
engine: row.settings.engine === "jimeng" || row.settings.engine === "evolink" ? row.settings.engine : undefined,
|
||||
engine: row.settings.engine === "jimeng" || row.settings.engine === "evolink" || row.settings.engine === "bailian" ? row.settings.engine : undefined,
|
||||
width: optionalNumber(row.settings.width),
|
||||
height: optionalNumber(row.settings.height),
|
||||
forceSingle: typeof row.settings.forceSingle === "boolean" ? row.settings.forceSingle : undefined,
|
||||
|
||||
@@ -28,6 +28,16 @@ import { importRemoteImageAsAsset, saveGeneratedAsset } from "@/lib/server/stora
|
||||
import { DEFAULT_OWNER_ID, toAbsoluteUrl } from "@/lib/server/runtime";
|
||||
import type { EnabledImageCapability, GenerationJob, VisualTaskQueryResponse } from "@/lib/types";
|
||||
import { queryVisualTask, shouldMockVisualApi, submitVisualTask } from "@/lib/volcengine/visual-client";
|
||||
import {
|
||||
bailianResultUrls,
|
||||
bailianStatus,
|
||||
bailianTaskId,
|
||||
buildBailianImagePayload,
|
||||
getBailianConfig,
|
||||
queryBailianTask,
|
||||
shouldMockBailian,
|
||||
submitBailianTask
|
||||
} from "@/lib/bailian/client";
|
||||
|
||||
export type SubmitImageJobInput = {
|
||||
ownerId?: string;
|
||||
@@ -59,18 +69,22 @@ export async function submitImageJob(input: SubmitImageJobInput, origin: string)
|
||||
const capability = getEnabledImageCapability(input.capability);
|
||||
const normalizedUrls = (input.imageUrls || []).map((url) => toAbsoluteUrl(url, origin));
|
||||
const engine = getEffectiveImageEngine(input.capability, input.engine);
|
||||
const providerPayload = engine === "evolink"
|
||||
? buildEvolinkImagePayload(input.capability, { ...input, imageUrls: normalizedUrls })
|
||||
: buildJimengPayload(input.capability, capability.reqKey, { ...input, imageUrls: normalizedUrls });
|
||||
const mock = engine === "evolink" ? shouldMockEvolinkApi() : shouldMockVisualApi();
|
||||
const reqKey = engine === "evolink" ? getEvolinkImageSettings().model : capability.reqKey;
|
||||
const providerPayload = engine === "bailian"
|
||||
? buildBailianImagePayload(input.capability, { ...input, imageUrls: normalizedUrls })
|
||||
: engine === "evolink"
|
||||
? buildEvolinkImagePayload(input.capability, { ...input, imageUrls: normalizedUrls })
|
||||
: buildJimengPayload(input.capability, capability.reqKey, { ...input, imageUrls: normalizedUrls });
|
||||
const bailianConfig = getBailianConfig();
|
||||
const mock = engine === "bailian" ? shouldMockBailian() : engine === "evolink" ? shouldMockEvolinkApi() : shouldMockVisualApi();
|
||||
const missingBailianKey = engine === "bailian" && !mock && !bailianConfig.apiKey;
|
||||
const reqKey = engine === "bailian" ? bailianConfig.imageModel : engine === "evolink" ? getEvolinkImageSettings().model : capability.reqKey;
|
||||
let job = await createGenerationJob({
|
||||
ownerId,
|
||||
externalClientId: input.externalClientId,
|
||||
capability: input.capability,
|
||||
provider: mock ? "mock" : engine === "evolink" ? "evolink" : "volcengine-visual",
|
||||
provider: mock ? "mock" : engine === "bailian" ? "bailian" : engine === "evolink" ? "evolink" : "volcengine-visual",
|
||||
reqKey,
|
||||
status: "queued",
|
||||
status: missingBailianKey ? "failed" : "queued",
|
||||
prompt: input.prompt,
|
||||
inputAssetIds: input.inputAssetIds || [],
|
||||
inputUrls: normalizedUrls,
|
||||
@@ -80,6 +94,7 @@ export async function submitImageJob(input: SubmitImageJobInput, origin: string)
|
||||
input,
|
||||
providerPayload
|
||||
},
|
||||
error: missingBailianKey ? { message: "缺少 BAILIAN_API_KEY,请先在设置页配置阿里云百炼 API Key。", retryable: true } : undefined,
|
||||
retryOf: input.retryOf,
|
||||
idempotencyKey: input.idempotencyKey,
|
||||
idempotencyFingerprint: input.idempotencyFingerprint,
|
||||
@@ -103,6 +118,12 @@ export async function advanceImageJob(jobId: string, origin: string): Promise<Ge
|
||||
async function dispatchImageJob(job: GenerationJob): Promise<GenerationJob> {
|
||||
const providerPayload = asRecord(job.requestPayload.providerPayload);
|
||||
try {
|
||||
if (job.provider === "bailian") {
|
||||
const response = await submitBailianTask("image", providerPayload);
|
||||
const taskId = bailianTaskId(response);
|
||||
if (!taskId) throw new Error(response.message || "百炼图片任务响应缺少 task_id。");
|
||||
return updateGenerationJob(job.id, { status: "queued", providerTaskId: taskId, responsePayload: response });
|
||||
}
|
||||
if (job.provider === "evolink") {
|
||||
const response = await submitEvolinkImageTask(providerPayload);
|
||||
const taskId = getEvolinkTaskId(response);
|
||||
@@ -164,6 +185,7 @@ export async function syncImageJob(jobId: string, origin: string): Promise<Gener
|
||||
if (job.provider === "evolink") {
|
||||
return syncEvolinkImageJob(job, origin);
|
||||
}
|
||||
if (job.provider === "bailian") return syncBailianImageJob(job, origin);
|
||||
|
||||
const queryPayload = buildJimengQueryPayload(job.reqKey, job.providerTaskId);
|
||||
let response: VisualTaskQueryResponse;
|
||||
@@ -238,6 +260,26 @@ export async function syncImageJob(jobId: string, origin: string): Promise<Gener
|
||||
});
|
||||
}
|
||||
|
||||
async function syncBailianImageJob(job: GenerationJob, origin: string): Promise<GenerationJob> {
|
||||
const response = await queryBailianTask(job.providerTaskId!);
|
||||
const status = bailianStatus(response);
|
||||
if (status !== "succeeded") {
|
||||
return updateGenerationJob(job.id, {
|
||||
status,
|
||||
responsePayload: response,
|
||||
error: status === "failed" ? { message: response.message || "百炼图片任务失败。", retryable: false } : undefined
|
||||
});
|
||||
}
|
||||
const urls = bailianResultUrls(response, "image");
|
||||
if (!urls.length) return updateGenerationJob(job.id, { status: "failed", responsePayload: response, error: { message: "百炼图片任务未返回结果地址。", retryable: true } });
|
||||
const assets = [];
|
||||
for (let index = 0; index < urls.length; index += 1) {
|
||||
assets.push(await importRemoteImageAsAsset({ ownerId: job.ownerId, url: urls[index], origin, source: sourceForCapability(job.capability), capability: job.capability, jobId: job.id, index, tags: assetTagsForJob(job) }));
|
||||
}
|
||||
await recordUsageEvent({ ownerId: job.ownerId, jobId: job.id, capability: job.capability, quantity: assets.length, estimatedUnit: "image" });
|
||||
return updateGenerationJob(job.id, { status: "succeeded", outputAssetIds: assets.map((asset) => asset.id), responsePayload: response });
|
||||
}
|
||||
|
||||
async function syncEvolinkImageJob(job: GenerationJob, origin: string): Promise<GenerationJob> {
|
||||
if (!job.providerTaskId) return job;
|
||||
let response: EvolinkTaskResponse;
|
||||
|
||||
@@ -89,7 +89,7 @@ function optionalScale(value: unknown): number | undefined {
|
||||
|
||||
function normalizeImageEngine(value: unknown): ImageTemplate["settings"]["engine"] | undefined {
|
||||
const text = optionalText(value, 20);
|
||||
if (text === "jimeng" || text === "evolink") return text;
|
||||
if (text === "jimeng" || text === "evolink" || text === "bailian") return text;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import { submitImageJob, type SubmitImageJobInput } from "@/lib/server/generatio
|
||||
import { submitVideoJob, type SubmitVideoJobInput } from "@/lib/server/video-generation-service";
|
||||
import { publicApiOwnerId, type PublicApiClient } from "@/lib/server/public-api-auth";
|
||||
import type { EnabledImageCapability, GenerationCapability, GenerationJob } from "@/lib/types";
|
||||
import type { ImageCreationEngine } from "@/lib/evolink/image-client";
|
||||
import type { VideoCreationEngine } from "@/lib/server/video-generation-service";
|
||||
|
||||
export class PublicApiConflictError extends Error {
|
||||
status = 409;
|
||||
@@ -36,6 +38,7 @@ export type PublicJobCreateBody = {
|
||||
resolution?: "4k" | "8k";
|
||||
quality?: string;
|
||||
seed?: number;
|
||||
engine?: ImageCreationEngine | VideoCreationEngine;
|
||||
};
|
||||
|
||||
export async function createPublicGenerationJob(input: {
|
||||
@@ -92,6 +95,7 @@ export async function createPublicGenerationJob(input: {
|
||||
const job = await submitImageJob({
|
||||
...common,
|
||||
capability: imageCapability,
|
||||
engine: input.body.engine === "bailian" || input.body.engine === "evolink" || input.body.engine === "jimeng" ? input.body.engine : undefined,
|
||||
prompt: input.body.prompt || assembled?.prompt,
|
||||
imageUrls: input.body.imageUrls || input.body.inputUrls || materialImages,
|
||||
inputAssetIds: input.body.inputAssetIds || (input.body.materials || []).map((material) => material.id).filter(Boolean) as string[],
|
||||
|
||||
@@ -11,12 +11,16 @@ import { importRemoteAssetAsAsset } from "@/lib/server/storage";
|
||||
import { createSeedanceTask, getSeedanceConfig, querySeedanceTask, shouldMockSeedance, type SeedanceSettings } from "@/lib/seedance/client";
|
||||
import type { GenerationJob } from "@/lib/types";
|
||||
import { normalizeVideoDuration, normalizeVideoRatio, normalizeVideoResolution } from "@/lib/video-settings";
|
||||
import { bailianResultUrls, bailianStatus, bailianTaskId, buildBailianVideoPayload, getBailianConfig, queryBailianTask, shouldMockBailian, submitBailianTask } from "@/lib/bailian/client";
|
||||
|
||||
export type VideoCreationEngine = "seedance" | "bailian";
|
||||
|
||||
export type SubmitVideoJobInput = PromptAssemblyInput & {
|
||||
ownerId?: string;
|
||||
externalClientId?: string;
|
||||
prompt?: string;
|
||||
settings?: SeedanceSettings;
|
||||
engine?: VideoCreationEngine;
|
||||
materials?: PromptMaterial[];
|
||||
retryOf?: string;
|
||||
idempotencyKey?: string;
|
||||
@@ -28,6 +32,7 @@ export type SubmitVideoJobInput = PromptAssemblyInput & {
|
||||
|
||||
export async function submitVideoJob(input: SubmitVideoJobInput, origin: string): Promise<GenerationJob> {
|
||||
const ownerId = input.ownerId || DEFAULT_OWNER_ID;
|
||||
const engine: VideoCreationEngine = input.engine === "bailian" ? "bailian" : "seedance";
|
||||
const config = getSeedanceConfig();
|
||||
const assembled = assemblePrompt({
|
||||
...input,
|
||||
@@ -36,17 +41,24 @@ export async function submitVideoJob(input: SubmitVideoJobInput, origin: string)
|
||||
});
|
||||
const finalPrompt = input.prompt?.trim() || assembled.prompt;
|
||||
const settings: SeedanceSettings = { ...(input.settings || {}) };
|
||||
settings.ratio = normalizeVideoRatio(settings.ratio, config.ratio);
|
||||
settings.duration = normalizeVideoDuration(settings.duration) ?? config.duration;
|
||||
settings.resolution = normalizeVideoResolution(settings.resolution, config.model, config.resolution);
|
||||
const mock = shouldMockSeedance();
|
||||
if (engine === "bailian") {
|
||||
settings.duration = normalizeVideoDuration(settings.duration) ?? 10;
|
||||
settings.resolution = String(settings.resolution || "720P").toUpperCase();
|
||||
buildBailianVideoPayload({ prompt: finalPrompt, materials: assembled.materials, origin, settings });
|
||||
} else {
|
||||
settings.ratio = normalizeVideoRatio(settings.ratio, config.ratio);
|
||||
settings.duration = normalizeVideoDuration(settings.duration) ?? config.duration;
|
||||
settings.resolution = normalizeVideoResolution(settings.resolution, config.model, config.resolution);
|
||||
}
|
||||
const mock = engine === "bailian" ? shouldMockBailian() : shouldMockSeedance();
|
||||
const missingBailianKey = engine === "bailian" && !mock && !getBailianConfig().apiKey;
|
||||
let job = await createGenerationJob({
|
||||
ownerId,
|
||||
externalClientId: input.externalClientId,
|
||||
capability: "video.generate",
|
||||
provider: mock ? "mock" : "seedance",
|
||||
reqKey: config.model,
|
||||
status: "queued",
|
||||
provider: mock ? "mock" : engine,
|
||||
reqKey: engine === "bailian" ? getBailianConfig().videoModel : config.model,
|
||||
status: missingBailianKey ? "failed" : "queued",
|
||||
prompt: finalPrompt,
|
||||
inputAssetIds: input.materials?.map((material) => material.id).filter(Boolean) as string[] || [],
|
||||
inputUrls: assembled.materials.map((material) => material.url),
|
||||
@@ -54,8 +66,10 @@ export async function submitVideoJob(input: SubmitVideoJobInput, origin: string)
|
||||
requestPayload: {
|
||||
input,
|
||||
assembled,
|
||||
settings
|
||||
settings,
|
||||
engine
|
||||
},
|
||||
error: missingBailianKey ? { message: "缺少 BAILIAN_API_KEY,请先在设置页配置阿里云百炼 API Key。", retryable: true } : undefined,
|
||||
retryOf: input.retryOf,
|
||||
idempotencyKey: input.idempotencyKey,
|
||||
idempotencyFingerprint: input.idempotencyFingerprint,
|
||||
@@ -84,17 +98,14 @@ async function dispatchVideoJob(job: GenerationJob, origin: string): Promise<Gen
|
||||
const materials = Array.isArray(assembled.materials)
|
||||
? assembled.materials as PromptMaterial[]
|
||||
: input.materials || [];
|
||||
const response = await createSeedanceTask({
|
||||
prompt: job.prompt || "",
|
||||
settings,
|
||||
materials,
|
||||
origin
|
||||
});
|
||||
return updateGenerationJob(job.id, {
|
||||
status: "running",
|
||||
providerTaskId: response.providerTaskId,
|
||||
responsePayload: response.raw
|
||||
});
|
||||
if (job.provider === "bailian") {
|
||||
const response = await submitBailianTask("video", buildBailianVideoPayload({ prompt: job.prompt || "", settings: asRecord(settings), materials, origin }));
|
||||
const providerTaskId = bailianTaskId(response);
|
||||
if (!providerTaskId) throw new Error("视频任务响应缺少 task_id。");
|
||||
return updateGenerationJob(job.id, { status: "running", providerTaskId, responsePayload: response });
|
||||
}
|
||||
const response = await createSeedanceTask({ prompt: job.prompt || "", settings, materials, origin });
|
||||
return updateGenerationJob(job.id, { status: "running", providerTaskId: response.providerTaskId, responsePayload: response.raw });
|
||||
} catch (error) {
|
||||
job = await updateGenerationJob(job.id, {
|
||||
status: "failed",
|
||||
@@ -115,6 +126,15 @@ export async function syncVideoJob(jobId: string, origin: string): Promise<Gener
|
||||
if (!job.providerTaskId) return job;
|
||||
|
||||
try {
|
||||
if (job.provider === "bailian") {
|
||||
const response = await queryBailianTask(job.providerTaskId);
|
||||
const status = bailianStatus(response);
|
||||
const resultUrl = bailianResultUrls(response, "video")[0];
|
||||
if (status !== "succeeded" || !resultUrl) {
|
||||
return updateGenerationJob(job.id, { status, responsePayload: response, error: status === "failed" ? { message: response.message || "百炼视频任务失败。", retryable: false } : undefined });
|
||||
}
|
||||
return completeRemoteVideo(job, origin, resultUrl, response);
|
||||
}
|
||||
const result = await querySeedanceTask(job.providerTaskId);
|
||||
if (result.status !== "succeeded" || !result.resultUrl) {
|
||||
return updateGenerationJob(job.id, {
|
||||
@@ -123,9 +143,22 @@ export async function syncVideoJob(jobId: string, origin: string): Promise<Gener
|
||||
error: result.errorMessage ? { message: result.errorMessage, retryable: result.status === "failed" } : undefined
|
||||
});
|
||||
}
|
||||
return completeRemoteVideo(job, origin, result.resultUrl, result.raw);
|
||||
} catch (error) {
|
||||
return updateGenerationJob(job.id, {
|
||||
status: "failed",
|
||||
error: {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
retryable: false
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function completeRemoteVideo(job: GenerationJob, origin: string, resultUrl: string, responsePayload: Record<string, unknown>) {
|
||||
const asset = await importRemoteAssetAsAsset({
|
||||
ownerId: job.ownerId,
|
||||
url: result.resultUrl,
|
||||
url: resultUrl,
|
||||
origin,
|
||||
source: "generated",
|
||||
capability: "video.generate",
|
||||
@@ -144,17 +177,8 @@ export async function syncVideoJob(jobId: string, origin: string): Promise<Gener
|
||||
return updateGenerationJob(job.id, {
|
||||
status: "succeeded",
|
||||
outputAssetIds: [asset.id],
|
||||
responsePayload: result.raw
|
||||
responsePayload
|
||||
});
|
||||
} catch (error) {
|
||||
return updateGenerationJob(job.id, {
|
||||
status: "failed",
|
||||
error: {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
retryable: false
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function retryVideoJob(jobId: string, origin: string, ownerId?: string): Promise<GenerationJob> {
|
||||
|
||||
Reference in New Issue
Block a user