fix: use customer token for assets and video

This commit is contained in:
xuejianwu
2026-07-30 09:35:06 +08:00
parent a107e19b8f
commit e111833df6
5 changed files with 63 additions and 75 deletions

View File

@@ -79,7 +79,10 @@ async function init() {
restoreCharacterLibrary();
restoreDramaProject();
renderConnection();
if (!state.config.mockMode && !currentVideoKey()) showVideoKeyGate(false);
if (!state.config.mockMode && !currentVideoKey()) {
showVideoKeyGate(false);
return;
}
await loadAssets();
loadActiveShotIntoForm();
renderScene();
@@ -98,7 +101,7 @@ function setupVideoKeyGate() {
event.preventDefault();
const key = el.videoKeyInput.value.trim();
if (!/^sk-[A-Za-z0-9_-]{8,}$/.test(key)) {
el.videoKeyError.textContent = "请输入正确格式的生视频 Keysk-...";
el.videoKeyError.textContent = "请输入正确格式的客户令牌sk-...";
el.videoKeyError.classList.remove("hidden");
return;
}
@@ -341,7 +344,7 @@ async function loadAssets() {
}
} catch (error) {
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) {
@@ -973,7 +976,7 @@ function updateVideoAvailability() {
setStep(3);
} else {
el.videoForm.classList.add("disabled-section");
const label = !state.config.videoReady ? "缺少视频 Key" : !sceneReady() ? "等待角色" : quota.message;
const label = !state.config.videoReady ? "请输入客户令牌" : !sceneReady() ? "等待角色" : quota.message;
setVideoStatus(!state.config.videoReady || !quota.valid ? "error" : "neutral", label);
setStep(sceneReady() ? 2 : 1);
}
@@ -1453,7 +1456,7 @@ async function resumePendingVideo() {
function renderConnection() {
const videoKey = currentVideoKey();
el.changeVideoKeyButton.classList.toggle("hidden", state.config.mockMode || !videoKey);
if (videoKey) el.changeVideoKeyButton.textContent = `更换视频 Key · ${videoKey.slice(-4)}`;
if (videoKey) el.changeVideoKeyButton.textContent = `更换客户令牌 · ${videoKey.slice(-4)}`;
if (state.config.mockMode) {
el.connectionState.textContent = "Mock 模式 · 可评审完整流程";
el.connectionState.className = "connection mock";
@@ -1465,7 +1468,7 @@ function renderConnection() {
el.reviewButton.textContent = "提交真实素材审核";
el.mockHint.classList.add("hidden");
} else if (state.config.assetReady) {
el.connectionState.textContent = "请输入视频 Key";
el.connectionState.textContent = "请输入客户令牌";
el.connectionState.className = "connection live";
el.reviewButton.textContent = "提交真实素材审核";
el.mockHint.classList.add("hidden");
@@ -1504,7 +1507,7 @@ function renderCompletedVideo(videoUrl, mock) {
video.src = videoUrl;
el.videoStage.replaceChildren(video);
} else {
el.videoStage.innerHTML = `<div class="mock-frame"><div class="mock-person"></div><strong>${mock ? "Mock 流程已完成" : "任务已完成"}</strong><span>${mock ? "接入视频 Key 后此处展示真实视频" : "接口响应中未识别到视频 URL查看原始返回"}</span></div>`;
el.videoStage.innerHTML = `<div class="mock-frame"><div class="mock-person"></div><strong>${mock ? "Mock 流程已完成" : "任务已完成"}</strong><span>${mock ? "输入客户令牌后此处展示真实视频" : "接口响应中未识别到视频 URL稍后重试"}</span></div>`;
}
updateProgress(100, "视频生成完成");
}
@@ -1527,7 +1530,7 @@ function contextualizeGenerationError(message) {
if (!assetMatch || !/not found|不存在/i.test(text)) return text;
const position = referencePosition(assetMatch[0]);
const label = position ? `角色 ${ROLE_LETTERS[position.roleIndex]} · ${VIEW_LABELS[position.viewIndex]}` : "某张参考图";
return `${label}在视频接口中不存在。通常表示素材库 Token 与视频 Key 不属于同一资源空间,或素材尚未同步到视频侧。素材 ID${assetMatch[0]}`;
return `${label}在视频接口中不存在。通常表示素材已失效、尚未同步,或不属于当前客户令牌。素材 ID${assetMatch[0]}`;
}
function resetVideoStage() { el.videoStage.innerHTML = '<div class="mock-frame"><div class="mock-person"></div><span>Seedance 2 正在生成镜头…</span></div>'; }
@@ -1715,7 +1718,7 @@ async function api(path, options = {}) {
const method = options.method || "GET";
const shouldLog = shouldLogClientApi(path);
const videoKey = currentVideoKey();
const videoHeaders = videoKey && (/^\/api\/videos(?:\/|$)/.test(path) || path === "/api/config")
const videoHeaders = videoKey && (/^\/api\/(?:videos|assets)(?:[/?]|$)/.test(path) || path === "/api/config")
? { "X-Video-Key": videoKey }
: {};
if (shouldLog) logClientOperation("api.request", { method, path });
@@ -1738,7 +1741,7 @@ async function api(path, options = {}) {
const error = new Error(extractApiError(result) || `请求失败(${response.status}`);
error.payload = result;
error.status = response.status;
if (response.status === 401 && /^\/api\/videos(?:\/|$)/.test(path)) {
if (response.status === 401 && /^\/api\/(?:videos|assets)(?:[/?]|$)/.test(path)) {
localStorage.removeItem(VIDEO_KEY_STORAGE);
showVideoKeyGate(false);
}

View File

@@ -13,7 +13,7 @@
<h1>AI 短剧数字人生产</h1>
</div>
<div class="topbar-actions">
<button class="key-change-button hidden" type="button" id="changeVideoKeyButton">更换视频 Key</button>
<button class="key-change-button hidden" type="button" id="changeVideoKeyButton">更换客户令牌</button>
<div class="connection" id="connectionState">正在检查接口配置…</div>
</div>
</header>
@@ -268,10 +268,10 @@
<div class="key-gate hidden" id="videoKeyGate">
<form class="key-gate-card" id="videoKeyForm">
<span class="section-kicker">首次使用</span>
<h2>输入生视频 Key</h2>
<p>该 Key 用于本次客户的视频生成与费用统计,仅保存在当前浏览器。</p>
<h2>输入客户令牌</h2>
<p>素材库与视频生成共用该令牌,相关费用归属此令牌,仅保存在当前浏览器。</p>
<label class="field">
<span>生视频 Key</span>
<span>客户令牌</span>
<input id="videoKeyInput" type="password" autocomplete="off" spellcheck="false" placeholder="sk-..." required />
</label>
<p class="key-gate-error hidden" id="videoKeyError"></p>
@@ -283,6 +283,6 @@
</div>
<div class="toast hidden" id="toast"></div>
<script src="./app.js?v=26"></script>
<script src="./app.js?v=27"></script>
</body>
</html>