feat: remember ai video provider per project
This commit is contained in:
@@ -16,6 +16,12 @@
|
||||
- `Seedance 配置` 提示也会随引擎切换即时更新,表单第一眼就能看出这次走的是默认视频链,还是 `Seedance 2.0 -> 火山视频配置`。
|
||||
- 这套联动同样保留“手动改过就不再自动覆盖”的原则,避免把用户已经输入的模型名冲掉。
|
||||
|
||||
### AI 视频开始按项目记忆最近一次视频引擎
|
||||
|
||||
- `创建 AI 视频任务` 现在会按项目记住你最近一次使用的 `视频引擎 / 引擎模型`。
|
||||
- 如果某个项目最近一次就是用 `Seedance 2.0 + seedance-2.0-pro`,下次再打开这张表单时会优先带出这套组合,不用每次重新选。
|
||||
- 这套记忆只在当前项目内生效,不会把一个项目的视频引擎偏好串到别的项目上。
|
||||
|
||||
### 修复额度页套餐建议引起的全局渲染报错
|
||||
|
||||
- `额度` 页面现在会先初始化 `packageRecommendation` 再渲染套餐建议,不再因为变量未定义把整个工作台渲染链打断。
|
||||
|
||||
@@ -2,6 +2,7 @@ const STORAGE_KEY = "storyforge-web-v4-session";
|
||||
const SESSION_STORE = StoryForgeSessionStore.create(STORAGE_KEY);
|
||||
const DEFAULT_BACKEND_URL = StoryForgeApiClient.detectDefaultBackendUrl();
|
||||
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 screens = Array.from(document.querySelectorAll("[data-screen]"));
|
||||
@@ -578,6 +579,34 @@ function getCompletedJobById(jobId = "") {
|
||||
|| 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 = {}) {
|
||||
const sourceJobSelect = fields.querySelector('[data-action-field="sourceJobId"]');
|
||||
if (!(sourceJobSelect instanceof HTMLSelectElement)) return;
|
||||
@@ -12173,11 +12202,12 @@ function openCreateAiVideoAction(defaults = {}) {
|
||||
const defaultAssistantId = assistant?.id || "";
|
||||
const sourceJob = defaults.sourceJob || null;
|
||||
const defaultPlatform = normalizePlatformValue(defaults.platform || sourceJob?.platform || "douyin");
|
||||
const persistedDefaults = getAiVideoPreferences(project.id);
|
||||
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";
|
||||
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" : "");
|
||||
openActionModal({
|
||||
title: "创建 AI 视频任务",
|
||||
@@ -12282,6 +12312,10 @@ function openCreateAiVideoAction(defaults = {}) {
|
||||
video_model: values.videoModel || "",
|
||||
}
|
||||
});
|
||||
saveAiVideoPreferences(project.id, {
|
||||
videoProvider: values.videoProvider || "doubao",
|
||||
videoModel: values.videoModel || ""
|
||||
});
|
||||
rememberAction(
|
||||
"AI 视频任务已创建",
|
||||
`已创建任务 ${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, /function focusAutomationHealthWorkspace\(anchorId = "integration-huobao-anchor"\)/);
|
||||
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, /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"\)/);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user