fix: use dedicated legacy asset token API

This commit is contained in:
xuejianwu
2026-07-30 09:19:20 +08:00
parent 225e3a9663
commit a107e19b8f
3 changed files with 38 additions and 23 deletions

View File

@@ -99,7 +99,7 @@ npm start
- 素材审核与视频生成统一请求 `https://ai-api.kkidc.com`,但分别使用服务端素材库 Token 与客户视频 Key。
- 新版素材库只在列表中返回 `ready` 素材;`pending` / `failed` 通过单素材接口查询。
- 视频生成应长期保存 `asset://mat_*`,不要依赖临时签名视频地址
- 视频生成应长期保存素材库返回的 `asset://...` 引用,不要依赖原始图片 URL
- 参考图/参考视频/首帧/首尾帧属于不同互斥调用方式;本 Demo 采用“多模态参考图”方式。
- 默认视频 720p时长 415 秒;真人/数字人必须使用审核通过的 `mat_*` 素材 ID。

View File

@@ -312,7 +312,7 @@ async function loadAssets() {
el.assetHistoryCount.textContent = String(items.length);
el.assetList.innerHTML = "";
if (!items.length) {
el.assetList.innerHTML = '<div class="empty-small">新版素材库暂无历史素材</div>';
el.assetList.innerHTML = '<div class="empty-small">素材库暂无历史素材</div>';
return;
}
for (const item of items) {
@@ -340,7 +340,15 @@ async function loadAssets() {
el.assetList.appendChild(button);
}
} catch (error) {
el.assetList.innerHTML = `<div class="empty-small">${escapeHtml(error.message)}</div>`;
const message = /unauthorized|invalid token/i.test(String(error.message || ""))
? "素材库 Token 无效或已过期,请联系管理员更新"
: error.message;
el.assetList.innerHTML = `<div class="empty-small">${escapeHtml(message)}</div>`;
if (!state.config?.mockMode) {
el.connectionState.textContent = "素材库连接失败";
el.connectionState.className = "connection blocked";
setAssetStatus("error", "素材库不可用");
}
}
}
@@ -1119,7 +1127,7 @@ function restoreCharacterLibrary() {
state.characters = Array.isArray(saved) ? saved.map((character) => ({
id: character.id || `character-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
name: String(character.name || "未命名人物"),
references: Array.isArray(character.references) ? character.references.map(String).filter((value) => state.config.mockMode || /^asset:\/\/mat_/i.test(value)).slice(0, 9) : [],
references: Array.isArray(character.references) ? character.references.map(String).filter((value) => state.config.mockMode || /^asset:\/\/(?:mat_|asset-)/i.test(value)).slice(0, 9) : [],
source: character.source || "manual",
})).filter((character) => character.references.length) : [];
}
@@ -1220,7 +1228,7 @@ function restoreSceneRoles() {
id: role.id || createRole("").id,
name: role.name || `角色 ${ROLE_LETTERS[index]}`,
references: Array.isArray(role.references)
? role.references.slice(0, 3).map(String).filter((value) => state.config.mockMode || /^asset:\/\/mat_/i.test(value))
? role.references.slice(0, 3).map(String).filter((value) => state.config.mockMode || /^asset:\/\/(?:mat_|asset-)/i.test(value))
: [],
}));
state.activeRoleIndex = Math.min(Number(saved.activeRoleIndex) || 0, state.roles.length - 1);

View File

@@ -182,24 +182,24 @@ async function handleApi(request, response, url) {
const labels = { front: "正面", side: "侧面", back: "背面" };
const uploaded = await Promise.all(imageInputs.map(async (input, index) => {
const viewLabel = labels[input.view] || `参考图${index + 1}`;
const upstream = await callJson("https://ai-api.kkidc.com/v1/assets/upload", {
const upstream = await callJson("https://ai.kkidc.com/api/assets/upload", {
method: "POST",
headers: bearerHeaders(assetToken),
headers: assetHeaders(),
body: JSON.stringify({
url: input.url,
type: "image",
purpose: `${body.name || "数字人形象"} · ${viewLabel}`,
asset_type: "Image",
name: `${body.name || "数字人形象"} · ${viewLabel}`,
}),
});
if (upstream.status >= 400 || upstream.body?.success === false || !upstream.body?.data?.asset_id) {
if (upstream.status >= 400 || Number(upstream.body?.code) !== 0 || !upstream.body?.data?.Id) {
throw new ClientError(extractUpstreamMessage(upstream.body) || `${viewLabel}图提交失败`, upstream.status || 400);
}
return {
sourceUrl: input.url,
view: input.view || "reference",
assetId: upstream.body.data.asset_id,
groupId: upstream.body.data.group_id || "",
status: String(upstream.body.data.status || "pending").toLowerCase(),
assetId: upstream.body.data.Id,
groupId: "",
status: "processing",
errorMessage: "",
};
}));
@@ -232,11 +232,11 @@ async function handleApi(request, response, url) {
return;
}
requireToken(assetToken, "KK_ASSET_TOKEN");
const upstreamUrl = new URL("https://ai-api.kkidc.com/v1/assets");
for (const key of ["page", "page_size", "purpose", "sort_order", "group_id"]) {
const upstreamUrl = new URL("https://ai.kkidc.com/api/assets");
for (const key of ["page", "page_size", "name", "statuses", "sort_by", "sort_order"]) {
for (const value of url.searchParams.getAll(key)) upstreamUrl.searchParams.append(key, value);
}
const upstream = await callJson(upstreamUrl, { headers: bearerHeaders(assetToken, false) });
const upstream = await callJson(upstreamUrl, { headers: assetHeaders(false) });
sendJson(response, upstream.status, upstream.body, upstream.trackId);
return;
}
@@ -555,7 +555,7 @@ function buildVideoPayload(body) {
const videoReferences = Array.isArray(body.videoReferences) ? body.videoReferences : [];
const imageReferences = [...assetReferences, ...extraImageReferences];
if (assetReferences.length < 1 || assetReferences.some((item) => !/^asset:\/\/(?:mat_|asset-)/i.test(item))) {
throw new ClientError("请先选择审核通过的人物素材asset://mat_...");
throw new ClientError("请先选择审核通过的人物素材asset://...");
}
if (imageReferences.length > 9) {
throw new ClientError(`人物图片与其他参考图片合计最多 9 张,当前为 ${imageReferences.length}`);
@@ -619,6 +619,13 @@ function bearerHeaders(token, includeContentType = true) {
};
}
function assetHeaders(includeContentType = true) {
return {
Authorization: assetToken,
...(includeContentType ? { "Content-Type": "application/json" } : {}),
};
}
function extractUpstreamMessage(body) {
if (!body) return "";
if (typeof body === "string") return body;
@@ -628,16 +635,16 @@ function extractUpstreamMessage(body) {
async function refreshAssetReviewTask(task) {
await Promise.all(task.items.map(async (item) => {
if (["ready", "failed"].includes(item.status)) return;
const upstreamUrl = new URL(`https://ai-api.kkidc.com/v1/assets/${encodeURIComponent(item.assetId)}`);
if (item.groupId) upstreamUrl.searchParams.set("group_id", item.groupId);
const upstream = await callJson(upstreamUrl, { headers: bearerHeaders(assetToken, false) });
if (upstream.status >= 400 || upstream.body?.success === false) {
const upstreamUrl = new URL(`https://ai.kkidc.com/api/assets/${encodeURIComponent(item.assetId)}`);
const upstream = await callJson(upstreamUrl, { headers: assetHeaders(false) });
if (upstream.status >= 400 || Number(upstream.body?.code) !== 0) {
item.status = "failed";
item.errorMessage = extractUpstreamMessage(upstream.body) || "素材状态查询失败";
return;
}
item.status = String(upstream.body?.data?.status || item.status || "pending").toLowerCase();
item.errorMessage = upstream.body?.data?.error_message || upstream.body?.message || "";
const remoteStatus = String(upstream.body?.data?.Status || "Processing").toLowerCase();
item.status = remoteStatus === "active" ? "ready" : remoteStatus === "failed" ? "failed" : "processing";
item.errorMessage = item.status === "failed" ? upstream.body?.message || "素材审核失败" : "";
}));
}