feat(assets): 素材库 - 方舟素材 ID 引用与人像认证标记
- 素材编辑弹窗新增「方舟素材 ID」(asset- 前缀校验)与「人像认证」开关, 卡片封面显示人像认证徽章,详情抽屉展示方舟素材 ID - 画布图片节点绑定的素材录入方舟素材 ID 后,方舟视频渠道(volcengine-ark-video 或 Ark Plan BaseURL)生成时参考图直接提交 asset:// 引用,跳过可信素材上传同步; 浏览器直连路径同样优先 asset://;非方舟渠道与本地回退路径不受影响 - 素材字段随 payload_json 透传,AssetBase 与 parseAssetRecord 白名单同步扩展
This commit is contained in:
@@ -7,6 +7,7 @@ import { isLocalDreaminaBackgroundTask, localDreaminaTaskId, projectLocalDreamin
|
||||
import { modelCapabilityConfigFor } from "@/lib/model-capabilities";
|
||||
import { grokImagePromptLimitError } from "@/lib/grok-image-prompt-limit";
|
||||
import { resolveGenerationWorkflowExecution, type GenerationWorkflowExecution } from "@/lib/generation-workflow-execution";
|
||||
import { isArkPlanBaseUrl } from "@/lib/seedance-video";
|
||||
import { resolveVideoOperation } from "@/lib/model-selection";
|
||||
import { logicalModelIDForConfig, modelOptionName, resolveModelChannel, resolveModelRequestConfig, type AiConfig } from "@/stores/use-config-store";
|
||||
import { useLocalDreaminaModelStore } from "@/stores/use-local-dreamina-model-store";
|
||||
@@ -115,7 +116,7 @@ export async function runBackendGenerationTask(
|
||||
);
|
||||
}
|
||||
assertBackendRuntimeConfigured(config, mode);
|
||||
const prepared = await prepareGenerationReferences({ referenceImages, referenceVideos, referenceAudios, mask });
|
||||
const prepared = await prepareGenerationReferences({ config, referenceImages, referenceVideos, referenceAudios, mask });
|
||||
throwIfAborted(signal);
|
||||
return createAndWaitGenerationTask({ projectId, mode, prompt, config, referenceImages, referenceVideos, referenceAudios, textHistory, signal, metadata, onTaskUpdate, onTextDelta, streamText, enableThinking, clientOperationId, retryOf, attemptGroupId }, prepared, dependencies);
|
||||
}
|
||||
@@ -432,18 +433,26 @@ function assertClientPromptLimit(mode: BackendGenerationMode, prompt: string, co
|
||||
}
|
||||
|
||||
async function prepareGenerationReferences({
|
||||
config,
|
||||
referenceImages = [],
|
||||
referenceVideos = [],
|
||||
referenceAudios = [],
|
||||
mask,
|
||||
}: Pick<BackendGenerationTaskOptions, "referenceImages" | "referenceVideos" | "referenceAudios" | "mask">): Promise<PreparedGenerationReferences> {
|
||||
const preparedImages = await Promise.all(referenceImages.map(prepareBackendImageReference));
|
||||
}: Pick<BackendGenerationTaskOptions, "config" | "referenceImages" | "referenceVideos" | "referenceAudios" | "mask">): Promise<PreparedGenerationReferences> {
|
||||
const preferArkAssetUrl = usesArkVideoAssetReference(config);
|
||||
const preparedImages = await Promise.all(referenceImages.map((image) => prepareBackendImageReference(image, preferArkAssetUrl)));
|
||||
const preparedVideos = await Promise.all(referenceVideos.map(prepareBackendMediaReference));
|
||||
const preparedAudios = await Promise.all(referenceAudios.map(prepareBackendMediaReference));
|
||||
const preparedMask = mask ? await prepareBackendImageReference(mask) : undefined;
|
||||
const preparedMask = mask ? await prepareBackendImageReference(mask, false) : undefined;
|
||||
return { referenceImages: preparedImages, referenceVideos: preparedVideos, referenceAudios: preparedAudios, mask: preparedMask };
|
||||
}
|
||||
|
||||
// 与后端 isArkPrivateAssetVideoConfig 对齐:方舟视频渠道允许参考图直接 asset:// 引用,
|
||||
// 跳过可信素材上传同步(适合已录入方舟素材 ID 的素材,例如被授权的真人像素材)。
|
||||
function usesArkVideoAssetReference(config: AiConfig) {
|
||||
return resolveModelRequestConfig(config, config.model).interfaceType === "volcengine-ark-video" || isArkPlanBaseUrl(config.baseUrl || "");
|
||||
}
|
||||
|
||||
async function createAndWaitGenerationTask(options: BackendGenerationTaskOptions, prepared: PreparedGenerationReferences, dependencies: GenerationTaskDependencies) {
|
||||
const task = await createBackendGenerationTask(options, prepared, dependencies);
|
||||
const { signal, onTaskUpdate, onTextDelta } = options;
|
||||
@@ -535,7 +544,8 @@ async function prepareBackendMediaReference(media: ReferenceVideo | ReferenceAud
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareBackendImageReference(image: ReferenceImage) {
|
||||
async function prepareBackendImageReference(image: ReferenceImage, preferArkAssetUrl = false) {
|
||||
if (preferArkAssetUrl && image.arkAssetId) return backendImageReference(image, { url: `asset://${image.arkAssetId}` });
|
||||
if (resourceIdFromStorageKey(image.storageKey)) return backendImageReference(image, { storageKey: image.storageKey });
|
||||
const sourceUrl = image.url || image.dataUrl;
|
||||
if (/^https?:\/\//i.test(sourceUrl)) return backendImageReference(image, { url: sourceUrl });
|
||||
|
||||
@@ -106,7 +106,7 @@ async function buildVolcengineArkContent(prompt: string, references: ReferenceIm
|
||||
const imagePlan = resolveVideoImageReferences(references.slice(0, SEEDANCE_REFERENCE_LIMITS.images), options, { videoCount: videoReferences.length, audioCount: audioReferences.length });
|
||||
if (prompt.trim()) content.push({ type: "text", text: prompt.trim() });
|
||||
for (const { image, role } of imagePlan) {
|
||||
content.push({ type: "image_url", image_url: { url: await resolveVolcengineArkReferenceUrl(image.url || image.dataUrl, image.storageKey) }, role });
|
||||
content.push({ type: "image_url", image_url: { url: await resolveVolcengineArkReferenceUrl(image.arkAssetId ? `asset://${image.arkAssetId}` : image.url || image.dataUrl, image.storageKey) }, role });
|
||||
}
|
||||
for (const video of videoReferences.slice(0, SEEDANCE_REFERENCE_LIMITS.videos)) {
|
||||
content.push({ type: "video_url", video_url: { url: await resolveVolcengineArkReferenceUrl(video.url, video.storageKey) }, role: "reference_video" });
|
||||
@@ -118,8 +118,10 @@ async function buildVolcengineArkContent(prompt: string, references: ReferenceIm
|
||||
}
|
||||
|
||||
async function resolveVolcengineArkReferenceUrl(value: string | undefined, storageKey?: string) {
|
||||
// 已录入的方舟素材 ID 优先:已过审或被授权的素材直接引用,无需再换对象存储地址。
|
||||
if (String(value || "").startsWith("asset://")) return String(value);
|
||||
if (storageKey?.startsWith("resource:")) return getResourceOSSUrl(storageKey);
|
||||
if (isPublicMediaUrl(value || "") || String(value || "").startsWith("asset://")) return String(value);
|
||||
if (isPublicMediaUrl(value || "")) return String(value);
|
||||
throw new Error("火山方舟视频参考素材需要公网 URL 或 asset:// 素材 ID;请先将本地素材保存到对象存储");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user