feat: remember ai video provider per project
This commit is contained in:
@@ -16,6 +16,12 @@
|
|||||||
- `Seedance 配置` 提示也会随引擎切换即时更新,表单第一眼就能看出这次走的是默认视频链,还是 `Seedance 2.0 -> 火山视频配置`。
|
- `Seedance 配置` 提示也会随引擎切换即时更新,表单第一眼就能看出这次走的是默认视频链,还是 `Seedance 2.0 -> 火山视频配置`。
|
||||||
- 这套联动同样保留“手动改过就不再自动覆盖”的原则,避免把用户已经输入的模型名冲掉。
|
- 这套联动同样保留“手动改过就不再自动覆盖”的原则,避免把用户已经输入的模型名冲掉。
|
||||||
|
|
||||||
|
### AI 视频开始按项目记忆最近一次视频引擎
|
||||||
|
|
||||||
|
- `创建 AI 视频任务` 现在会按项目记住你最近一次使用的 `视频引擎 / 引擎模型`。
|
||||||
|
- 如果某个项目最近一次就是用 `Seedance 2.0 + seedance-2.0-pro`,下次再打开这张表单时会优先带出这套组合,不用每次重新选。
|
||||||
|
- 这套记忆只在当前项目内生效,不会把一个项目的视频引擎偏好串到别的项目上。
|
||||||
|
|
||||||
### 修复额度页套餐建议引起的全局渲染报错
|
### 修复额度页套餐建议引起的全局渲染报错
|
||||||
|
|
||||||
- `额度` 页面现在会先初始化 `packageRecommendation` 再渲染套餐建议,不再因为变量未定义把整个工作台渲染链打断。
|
- `额度` 页面现在会先初始化 `packageRecommendation` 再渲染套餐建议,不再因为变量未定义把整个工作台渲染链打断。
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const STORAGE_KEY = "storyforge-web-v4-session";
|
|||||||
const SESSION_STORE = StoryForgeSessionStore.create(STORAGE_KEY);
|
const SESSION_STORE = StoryForgeSessionStore.create(STORAGE_KEY);
|
||||||
const DEFAULT_BACKEND_URL = StoryForgeApiClient.detectDefaultBackendUrl();
|
const DEFAULT_BACKEND_URL = StoryForgeApiClient.detectDefaultBackendUrl();
|
||||||
const RECOVERY_HISTORY_KEY = STORAGE_KEY + ":recovery-history";
|
const RECOVERY_HISTORY_KEY = STORAGE_KEY + ":recovery-history";
|
||||||
|
const AI_VIDEO_DEFAULTS_KEY = STORAGE_KEY + ":ai-video-defaults";
|
||||||
|
|
||||||
const navButtons = document.querySelectorAll("[data-screen-target]");
|
const navButtons = document.querySelectorAll("[data-screen-target]");
|
||||||
const screens = Array.from(document.querySelectorAll("[data-screen]"));
|
const screens = Array.from(document.querySelectorAll("[data-screen]"));
|
||||||
@@ -578,6 +579,34 @@ function getCompletedJobById(jobId = "") {
|
|||||||
|| null;
|
|| null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAiVideoPreferences(projectId = "") {
|
||||||
|
const normalizedProjectId = String(projectId || "").trim();
|
||||||
|
if (!normalizedProjectId) return {};
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(localStorage.getItem(AI_VIDEO_DEFAULTS_KEY) || "{}");
|
||||||
|
const saved = payload && typeof payload === "object" ? payload[normalizedProjectId] : null;
|
||||||
|
return saved && typeof saved === "object" ? saved : {};
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAiVideoPreferences(projectId = "", values = {}) {
|
||||||
|
const normalizedProjectId = String(projectId || "").trim();
|
||||||
|
if (!normalizedProjectId) return;
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(localStorage.getItem(AI_VIDEO_DEFAULTS_KEY) || "{}");
|
||||||
|
const nextPayload = payload && typeof payload === "object" ? payload : {};
|
||||||
|
nextPayload[normalizedProjectId] = {
|
||||||
|
videoProvider: String(values.videoProvider || "doubao").trim() || "doubao",
|
||||||
|
videoModel: String(values.videoModel || "").trim()
|
||||||
|
};
|
||||||
|
localStorage.setItem(AI_VIDEO_DEFAULTS_KEY, JSON.stringify(nextPayload));
|
||||||
|
} catch {
|
||||||
|
// ignore local preference persistence failures
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function bindCreativeSourceJobRecommendations(fields, options = {}) {
|
function bindCreativeSourceJobRecommendations(fields, options = {}) {
|
||||||
const sourceJobSelect = fields.querySelector('[data-action-field="sourceJobId"]');
|
const sourceJobSelect = fields.querySelector('[data-action-field="sourceJobId"]');
|
||||||
if (!(sourceJobSelect instanceof HTMLSelectElement)) return;
|
if (!(sourceJobSelect instanceof HTMLSelectElement)) return;
|
||||||
@@ -12173,11 +12202,12 @@ function openCreateAiVideoAction(defaults = {}) {
|
|||||||
const defaultAssistantId = assistant?.id || "";
|
const defaultAssistantId = assistant?.id || "";
|
||||||
const sourceJob = defaults.sourceJob || null;
|
const sourceJob = defaults.sourceJob || null;
|
||||||
const defaultPlatform = normalizePlatformValue(defaults.platform || sourceJob?.platform || "douyin");
|
const defaultPlatform = normalizePlatformValue(defaults.platform || sourceJob?.platform || "douyin");
|
||||||
|
const persistedDefaults = getAiVideoPreferences(project.id);
|
||||||
const defaultVideoProvider = String(
|
const defaultVideoProvider = String(
|
||||||
defaults.videoProvider || defaults.video_provider || sourceJob?.artifacts?.video_provider || "doubao"
|
defaults.videoProvider || defaults.video_provider || sourceJob?.artifacts?.video_provider || persistedDefaults.videoProvider || "doubao"
|
||||||
).trim() || "doubao";
|
).trim() || "doubao";
|
||||||
const defaultVideoModel = String(
|
const defaultVideoModel = String(
|
||||||
defaults.videoModel || defaults.video_model || sourceJob?.artifacts?.video_model || ""
|
defaults.videoModel || defaults.video_model || sourceJob?.artifacts?.video_model || persistedDefaults.videoModel || ""
|
||||||
).trim() || (defaultVideoProvider === "seedance2" ? "seedance-2.0-pro" : "");
|
).trim() || (defaultVideoProvider === "seedance2" ? "seedance-2.0-pro" : "");
|
||||||
openActionModal({
|
openActionModal({
|
||||||
title: "创建 AI 视频任务",
|
title: "创建 AI 视频任务",
|
||||||
@@ -12282,6 +12312,10 @@ function openCreateAiVideoAction(defaults = {}) {
|
|||||||
video_model: values.videoModel || "",
|
video_model: values.videoModel || "",
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
saveAiVideoPreferences(project.id, {
|
||||||
|
videoProvider: values.videoProvider || "doubao",
|
||||||
|
videoModel: values.videoModel || ""
|
||||||
|
});
|
||||||
rememberAction(
|
rememberAction(
|
||||||
"AI 视频任务已创建",
|
"AI 视频任务已创建",
|
||||||
`已创建任务 ${job.title || job.id},引擎 ${normalizedProvider === "seedance2" ? "Seedance 2.0" : "当前默认引擎"}。`,
|
`已创建任务 ${job.title || job.id},引擎 ${normalizedProvider === "seedance2" ? "Seedance 2.0" : "当前默认引擎"}。`,
|
||||||
|
|||||||
@@ -1126,8 +1126,13 @@ test("ai video form explains where Seedance 火山配置 lives", () => {
|
|||||||
assert.match(APP, /id="integration-\$\{escapeHtml\(item\.key\)\}-anchor"/);
|
assert.match(APP, /id="integration-\$\{escapeHtml\(item\.key\)\}-anchor"/);
|
||||||
assert.match(APP, /function focusAutomationHealthWorkspace\(anchorId = "integration-huobao-anchor"\)/);
|
assert.match(APP, /function focusAutomationHealthWorkspace\(anchorId = "integration-huobao-anchor"\)/);
|
||||||
assert.match(APP, /function bindAiVideoProviderRecommendations\(fields, options = \{\}\)/);
|
assert.match(APP, /function bindAiVideoProviderRecommendations\(fields, options = \{\}\)/);
|
||||||
|
assert.match(APP, /const AI_VIDEO_DEFAULTS_KEY = STORAGE_KEY \+ ":ai-video-defaults"/);
|
||||||
|
assert.match(APP, /function getAiVideoPreferences\(projectId = ""\)/);
|
||||||
|
assert.match(APP, /function saveAiVideoPreferences\(projectId = "", values = \{\}\)/);
|
||||||
|
assert.match(APP, /const persistedDefaults = getAiVideoPreferences\(project\.id\)/);
|
||||||
assert.match(APP, /modelInput\.placeholder = provider === "seedance2" \? "例如:seedance-2\.0-pro" : "留空则沿用当前默认视频模型"/);
|
assert.match(APP, /modelInput\.placeholder = provider === "seedance2" \? "例如:seedance-2\.0-pro" : "留空则沿用当前默认视频模型"/);
|
||||||
assert.match(APP, /bindAiVideoProviderRecommendations\(fields, \{ seedanceModel: defaultVideoModel \|\| "seedance-2\.0-pro" \}\)/);
|
assert.match(APP, /bindAiVideoProviderRecommendations\(fields, \{ seedanceModel: defaultVideoModel \|\| "seedance-2\.0-pro" \}\)/);
|
||||||
|
assert.match(APP, /saveAiVideoPreferences\(project\.id, \{/);
|
||||||
assert.match(clickActions, /name === "focus-huobao-video-config"[\s\S]*focusAutomationHealthWorkspace\("integration-huobao-anchor"\)/);
|
assert.match(clickActions, /name === "focus-huobao-video-config"[\s\S]*focusAutomationHealthWorkspace\("integration-huobao-anchor"\)/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user