diff --git a/.memory/worklog.json b/.memory/worklog.json
index a6362b8..723c2bf 100644
--- a/.memory/worklog.json
+++ b/.memory/worklog.json
@@ -1,11 +1,5 @@
{
"entries": [
- {
- "files_changed": 1,
- "message": "Codex 会话活跃 · 最近命令:codex · 分支 main · 1 项未提交变更 · 最近提交:fix: center scaled workbench vertically",
- "ts": "2026-05-20T12:15:32Z",
- "type": "session-heartbeat"
- },
{
"files_changed": 2,
"hash": "bd64b94",
@@ -3199,6 +3193,13 @@
"message": "chore: harden production deploy scripts",
"hash": "b6a7e7b",
"files_changed": 2
+ },
+ {
+ "ts": "2026-05-27T23:02:52+08:00",
+ "type": "commit",
+ "message": "auto-save 2026-05-27 23:01 (~5)",
+ "hash": "d7f72f6",
+ "files_changed": 5
}
]
}
diff --git a/web/canvas-app/src/components/nodes/VideoConfigNode.vue b/web/canvas-app/src/components/nodes/VideoConfigNode.vue
index 83a04b4..07dcc59 100644
--- a/web/canvas-app/src/components/nodes/VideoConfigNode.vue
+++ b/web/canvas-app/src/components/nodes/VideoConfigNode.vue
@@ -74,6 +74,19 @@
+
+
+ 清晰度
+
+
+
+
+
@@ -154,7 +167,7 @@ import { useVideoGeneration } from '../../hooks'
import { updateNode, removeNode, duplicateNode, addNode, addEdge, nodes, edges } from '../../stores/canvas'
import NodeHandleMenu from './NodeHandleMenu.vue'
import { useModelStore } from '../../stores/pinia'
-import { getModelRatioOptions, getModelDurationOptions, getModelConfig, DEFAULT_VIDEO_MODEL } from '../../stores/models'
+import { getModelRatioOptions, getModelDurationOptions, getModelResolutionOptions, getModelConfig, DEFAULT_VIDEO_MODEL } from '../../stores/models'
// 使用 Pinia store 获取模型选项(根据渠道过滤)
const modelStore = useModelStore()
@@ -170,12 +183,25 @@ const { updateNodeInternals } = useVueFlow()
// Video generation hook | 视频生成 hook
const { loading, error, status, video: generatedVideo, progress, createVideoTaskOnly } = useVideoGeneration()
+const currentModelDefaultResolution = (modelKey) => {
+ const config = getModelConfig(modelKey)
+ return config?.defaultParams?.resolution || config?.defaultResolution || config?.resolutions?.[0] || '720p'
+}
+
+const normalizeResolutionForModel = (modelKey, resolution) => {
+ const options = getModelResolutionOptions(modelKey)
+ const allowed = options.map(option => option.key)
+ if (resolution && allowed.includes(resolution)) return resolution
+ return currentModelDefaultResolution(modelKey)
+}
+
// Local state | 本地状态
const showHandleMenu = ref(false)
const isGenerating = ref(false) // 任务创建中状态
const localModel = ref(props.data?.model || DEFAULT_VIDEO_MODEL)
const localRatio = ref(props.data?.ratio || '16:9')
const localDuration = ref(props.data?.dur || 5)
+const localResolution = ref(props.data?.resolution || currentModelDefaultResolution(props.data?.model || DEFAULT_VIDEO_MODEL))
// Label editing state | Label 编辑状态
const isEditingLabel = ref(false)
@@ -244,6 +270,11 @@ const durationOptions = computed(() => {
return getModelDurationOptions(localModel.value)
})
+// Resolution options based on model | 基于模型的清晰度选项
+const resolutionOptions = computed(() => {
+ return getModelResolutionOptions(localModel.value)
+})
+
// Handle model selection | 处理模型选择
const handleModelSelect = (key) => {
localModel.value = key
@@ -258,6 +289,9 @@ const handleModelSelect = (key) => {
localDuration.value = config.defaultParams.duration
updates.dur = config.defaultParams.duration
}
+ const nextResolution = currentModelDefaultResolution(key)
+ localResolution.value = nextResolution
+ updates.resolution = nextResolution
updateNode(props.id, updates)
}
@@ -284,6 +318,12 @@ const handleDurationSelect = (key) => {
updateNode(props.id, { dur: key })
}
+// Handle resolution selection | 处理清晰度选择
+const handleResolutionSelect = (key) => {
+ localResolution.value = key
+ updateNode(props.id, { resolution: key })
+}
+
// Get connected inputs by role | 根据角色获取连接的输入
const getConnectedInputs = () => {
const connectedEdges = edges.value.filter(e => e.target === props.id)
@@ -411,6 +451,11 @@ const handleGenerate = async () => {
params.dur = localDuration.value
}
+ // Add resolution | 添加清晰度
+ if (localResolution.value) {
+ params.resolution = localResolution.value
+ }
+
// 只创建任务,获取 taskId,不在这里轮询
const { taskId: newTaskId, url } = await createVideoTaskOnly(params)
@@ -492,7 +537,14 @@ onMounted(() => {
// 使用 store 中的默认模型或第一个可用模型
const selected = availableModels.find(m => m.key === modelStore.selectedVideoModel)?.key
localModel.value = selected || availableModels[0]?.key || DEFAULT_VIDEO_MODEL
- updateNode(props.id, { model: localModel.value })
+ localResolution.value = normalizeResolutionForModel(localModel.value, localResolution.value)
+ updateNode(props.id, { model: localModel.value, resolution: localResolution.value })
+ } else {
+ const nextResolution = normalizeResolutionForModel(localModel.value, localResolution.value)
+ if (nextResolution !== localResolution.value || !props.data?.resolution) {
+ localResolution.value = nextResolution
+ updateNode(props.id, { resolution: nextResolution })
+ }
}
})
@@ -500,6 +552,13 @@ onMounted(() => {
watch(() => props.data?.model, (newModel) => {
if (newModel && newModel !== localModel.value) {
localModel.value = newModel
+ localResolution.value = normalizeResolutionForModel(newModel, props.data?.resolution || localResolution.value)
+ }
+})
+
+watch(() => props.data?.resolution, (newResolution) => {
+ if (newResolution && newResolution !== localResolution.value) {
+ localResolution.value = normalizeResolutionForModel(localModel.value, newResolution)
}
})
diff --git a/web/canvas-app/src/hooks/useApi.js b/web/canvas-app/src/hooks/useApi.js
index 913c115..5b2985c 100644
--- a/web/canvas-app/src/hooks/useApi.js
+++ b/web/canvas-app/src/hooks/useApi.js
@@ -282,7 +282,8 @@ export const useVideoGeneration = () => {
first_image: firstFile ? { kind: 'keyframe', frame_idx: 0 } : null,
last_image: lastFrameIdx !== null ? { kind: 'keyframe', frame_idx: lastFrameIdx } : null,
model: params.model || 'seedance',
- size: normalizeVideoSize(params.ratio || params.size)
+ size: normalizeVideoSize(params.ratio || params.size),
+ resolution: params.resolution || '720p'
})
})
const created = newestGeneratedVideo(updated)
diff --git a/web/canvas-app/src/stores/models.js b/web/canvas-app/src/stores/models.js
index 888ed61..c42e264 100644
--- a/web/canvas-app/src/stores/models.js
+++ b/web/canvas-app/src/stores/models.js
@@ -150,7 +150,7 @@ export const getModelDurationOptions = (modelKey) => {
*/
export const getModelResolutionOptions = (modelKey) => {
const model = getModelConfig(modelKey) || VIDEO_MODELS.find(m => m.key === modelKey)
- if (model?.resolutionOptions) {
+ if (model?.resolutionOptions?.length) {
return model.resolutionOptions
}
if (!model?.resolutions) return SEEDANCE_RESOLUTION_OPTIONS
diff --git a/web/canvas-app/src/stores/pinia/models.js b/web/canvas-app/src/stores/pinia/models.js
index 34fb7c6..d24aa2e 100644
--- a/web/canvas-app/src/stores/pinia/models.js
+++ b/web/canvas-app/src/stores/pinia/models.js
@@ -173,7 +173,7 @@ const normalizeRuntimeVideoModel = (item) => {
const sizeOptions = normalizeRuntimeSizeOptions(item.size_options)
const durationOptions = normalizeRuntimeDurationOptions(item.duration_options)
const resolutionOptions = normalizeRuntimeResolutionOptions(item.resolution_options)
- const resolutions = resolutionOptions.map(option => option.key)
+ const resolutions = resolutionOptions.length ? resolutionOptions.map(option => option.key) : ['720p']
const defaultResolution = item.default_resolution || resolutions[0] || '720p'
return {
label: item.label || item.model || key,