增加即梦的生图和生视频功能

This commit is contained in:
andy committed 2026-08-19 17:58:22 +08:00
1 parent a9a4cdf125
commit 542246e4ce
20 files changed
+1008 -53

No files matched your search

+3 -3
View File
@@ -139,12 +139,12 @@ export function materialContentForProvider(materials: PromptMaterial[], origin:
return normalizeMaterials(materials).map((material) => {
const url = toAbsoluteUrl(material.url, origin);
if (material.type === "video") {
return { type: "video_url", video_url: { url }, role: "reference_video", label: material.label };
return { type: "video_url", video_url: { url }, role: "reference_video" };
}
if (material.type === "audio") {
return { type: "audio_url", audio_url: { url }, role: "reference_audio", label: material.label };
return { type: "audio_url", audio_url: { url }, role: "reference_audio" };
}
return { type: "image_url", image_url: { url }, role: "reference_image", label: material.label };
return { type: "image_url", image_url: { url }, role: "reference_image" };
});
}
+8 -3
View File
@@ -23,7 +23,7 @@ export type SeedanceCreateInput = {
};
export type SeedanceQueryResult = {
status: "queued" | "running" | "succeeded" | "failed" | "cancelled";
status: "queued" | "running" | "succeeded" | "failed" | "cancelled" | "expired";
resultUrl?: string;
errorMessage?: string;
usage?: SeedanceUsage;
@@ -49,11 +49,15 @@ export function getSeedanceConfig() {
export async function createSeedanceTask(input: SeedanceCreateInput) {
const config = getSeedanceConfig();
if (!config.apiKey) throw new Error("缺少 SEEDANCE_API_KEY。请在 .env.local 配置火山方舟 API Key。");
const materialContent = materialContentForProvider(input.materials, input.origin);
if (materialContent.length > 4) {
throw new Error("Seedance 2.0 最多支持 4 个素材(加上文本后 content 最多 5 项)。");
}
const payload = {
model: config.model,
content: [
{ type: "text", text: input.prompt },
...materialContentForProvider(input.materials, input.origin)
...materialContent
],
generate_audio: true,
ratio: normalizeVideoRatio(input.settings.ratio, config.ratio),
@@ -123,7 +127,8 @@ export function extractSeedanceUsage(value: unknown): SeedanceUsage | undefined
function normalizeSeedanceStatus(status: unknown): SeedanceQueryResult["status"] {
const value = String(status || "").toLowerCase();
if (["succeeded", "success", "completed"].includes(value)) return "succeeded";
if (["failed", "error", "expired", "timeout"].includes(value)) return "failed";
if (["expired", "timeout"].includes(value)) return "expired";
if (["failed", "error"].includes(value)) return "failed";
if (["cancelled", "canceled"].includes(value)) return "cancelled";
if (["running", "processing", "generating"].includes(value)) return "running";
return "queued";
+1 -1
View File
@@ -11,7 +11,7 @@ export const VIDEO_RATIO_DEFAULT = "9:16";
export const VIDEO_RATIOS = ["9:16", "16:9", "1:1", "4:3", "3:4", "21:9", "adaptive"] as const;
export const VIDEO_RESOLUTION_DEFAULT = "720p";
export const VIDEO_RESOLUTIONS = ["720p", "1080p", "480p", "4k"] as const;
export const VIDEO_RESOLUTIONS = ["720p", "1080p", "480p"] as const;
export const VIDEO_FAST_RESOLUTIONS = ["720p", "480p"] as const;
type VideoDurationOptions = {
+32 -4
View File
@@ -27,7 +27,8 @@ export async function submitVisualTask(
config = getVisualClientConfig()
): Promise<VisualTaskSubmitResponse> {
if (!config) throw new Error("Volcengine Visual credentials are not configured.");
return callVisualApi<VisualTaskSubmitResponse>("CVSync2AsyncSubmitTask", payload, config);
const protocol = visualProtocolForPayload(payload);
return callVisualApi<VisualTaskSubmitResponse>(protocol.submitAction, protocol.version, payload, config);
}
export async function queryVisualTask(
@@ -35,11 +36,38 @@ export async function queryVisualTask(
config = getVisualClientConfig()
): Promise<VisualTaskQueryResponse> {
if (!config) throw new Error("Volcengine Visual credentials are not configured.");
return callVisualApi<VisualTaskQueryResponse>("CVSync2AsyncGetResult", payload, config);
const protocol = visualProtocolForPayload(payload);
return callVisualApi<VisualTaskQueryResponse>(protocol.queryAction, protocol.version, payload, config);
}
type VisualAction =
| "CVSync2AsyncSubmitTask"
| "CVSync2AsyncGetResult"
| "JimengSeedream46CVToBSubmitTask"
| "JimengSeedream46CVToBGetResult";
function visualProtocolForPayload(payload: Record<string, unknown>): {
submitAction: VisualAction;
queryAction: VisualAction;
version: string;
} {
if (String(payload.req_key || "").trim() === "jimeng_seedream46_cvtob") {
return {
submitAction: "JimengSeedream46CVToBSubmitTask",
queryAction: "JimengSeedream46CVToBGetResult",
version: "2024-06-06"
};
}
return {
submitAction: "CVSync2AsyncSubmitTask",
queryAction: "CVSync2AsyncGetResult",
version: "2022-08-31"
};
}
async function callVisualApi<T>(
action: "CVSync2AsyncSubmitTask" | "CVSync2AsyncGetResult",
action: VisualAction,
version: string,
payload: Record<string, unknown>,
config: VisualClientConfig
): Promise<T> {
@@ -49,7 +77,7 @@ async function callVisualApi<T>(
endpoint: config.endpoint,
query: {
Action: action,
Version: "2022-08-31"
Version: version
},
body,
accessKeyId: config.accessKeyId,