From 84d9de6b30c21d796cf4c84a8c37f4c5220c5eaa Mon Sep 17 00:00:00 2001 From: kang Date: Mon, 25 May 2026 18:55:33 +0800 Subject: [PATCH] fix: align canvas model options with backend --- RULES.md | 2 +- api/main.py | 16 +- docs/source-analysis.html | 19 +- web/canvas-app/src/components/ApiSettings.vue | 38 --- .../src/components/nodes/ImageConfigNode.vue | 12 +- .../src/components/nodes/ImageNode.vue | 12 +- .../src/components/nodes/LLMConfigNode.vue | 6 +- .../src/components/nodes/TextNode.vue | 6 +- .../src/components/nodes/VideoConfigNode.vue | 3 +- web/canvas-app/src/config/models.js | 226 +++++------------- web/canvas-app/src/config/workflows.js | 64 ++--- web/canvas-app/src/hooks/useModelConfig.js | 84 ++----- web/canvas-app/src/stores/canvas.js | 2 +- web/canvas-app/src/stores/pinia/models.js | 84 ++----- web/canvas-app/src/stores/projects.js | 4 +- 15 files changed, 181 insertions(+), 397 deletions(-) diff --git a/RULES.md b/RULES.md index ba1f34e..eacd3f0 100644 --- a/RULES.md +++ b/RULES.md @@ -12,7 +12,7 @@ - 详见 `CLAUDE.md` 立项决策段 + `.memory/plan.md` 七步管线拆解 - 风格:`04-Dark-Gallery-Ambient`(路径:`~/Projects/research/20260305-网页风格库/04-Dark-Gallery-Ambient.md`) - 第一冲刺:步骤 1-4(下载 / 拆轨 / 关键帧 / ASR+翻译) -- 当前产品方向(2026-05-25 上游画布能力恢复版):默认入口是多人通用的 SKG 营销内容生产平台,`https://marketing.skg.com` 登录后直接进入个人生成画布,`/canvas/` 只作为旧链接兼容跳转到根域名。终端可见品牌位只放 SKG logo,不在主界面展示“生图生视频”“SKG 生成画布”或长系统名。画布本体尽量恢复 `chatfire-AI/huobao-canvas` 的成熟交互,不再削成三模式单输入框:保留首页推荐词、画布底部推荐词、AI 润色、自动执行、工作流模板、首帧/尾帧/参考图节点、图片/视频/LLM 配置节点、模型配置和批量下载等上游能力;多角度分镜、故事板、图转视频、绘本等工作流按上游结构创建节点。API 接入是例外:生成调用继续走本项目后端 `/api` 和当前登录 Cookie,不要求员工在浏览器配置个人 API Key;API 设置弹窗只保留模型/端点配置外观和本地模型管理,不能出现上游注册链接或外部品牌。用户登录后仍只看到自己的任务、结果和详情页,继续沿用后端 owner 隔离;每个浏览器的画布项目先保存在本地 localStorage,图片/视频资产按登录用户写入后端 job。旧 TK 复刻工作台、Agent Cut 一键出片和营销图文方案保留为高级/详情页能力,不再作为默认首页入口或默认理解框架。 +- 当前产品方向(2026-05-25 上游画布能力恢复版):默认入口是多人通用的 SKG 营销内容生产平台,`https://marketing.skg.com` 登录后直接进入个人生成画布,`/canvas/` 只作为旧链接兼容跳转到根域名。终端可见品牌位只放 SKG logo,不在主界面展示“生图生视频”“SKG 生成画布”或长系统名。画布本体尽量恢复 `chatfire-AI/huobao-canvas` 的成熟交互,不再削成三模式单输入框:保留首页推荐词、画布底部推荐词、AI 润色、自动执行、工作流模板、首帧/尾帧/参考图节点、图片/视频/LLM 配置节点、模型配置和批量下载等上游能力;多角度分镜、故事板、图转视频、绘本等工作流按上游结构创建节点。API 接入是例外:生成调用继续走本项目后端 `/api` 和当前登录 Cookie,不要求员工在浏览器配置个人 API Key;图片/视频模型选择只显示后端已经接通的媒体模型,不能让浏览器本地自定义或旧缓存模型进入生成下拉。API 设置弹窗只保留模型/端点配置外观,不能出现上游注册链接或外部品牌。用户登录后仍只看到自己的任务、结果和详情页,继续沿用后端 owner 隔离;每个浏览器的画布项目先保存在本地 localStorage,图片/视频资产按登录用户写入后端 job。旧 TK 复刻工作台、Agent Cut 一键出片和营销图文方案保留为高级/详情页能力,不再作为默认首页入口或默认理解框架。 ## 部署事实 - 平台:VPS `76.13.31.179`(Ubuntu 24.04 / Docker Compose / Coolify Traefik) diff --git a/api/main.py b/api/main.py index a9e143d..6ae5501 100644 --- a/api/main.py +++ b/api/main.py @@ -4404,25 +4404,27 @@ def _normalize_video_size(raw: str | None) -> str: def video_model_options() -> list[dict]: label_map = { - "seedance": "Seedance", + "seedance": "Seedance 2.0 Fast", "kling": "Kling", "veo3": "Veo 3", "veo": "Veo", "voe": "Veo", } - seen: set[str] = set() + concrete_label_map = { + "doubao-seedance-2-0-fast-260128": "Seedance 2.0 Fast", + } + seen_models: set[str] = set() options: list[dict] = [] for key in ["seedance", "kling", "veo3", "veo"]: if key not in VIDEO_MODEL_ALIASES: continue model = VIDEO_MODEL_ALIASES[key] - unique_key = f"{key}:{model}" - if unique_key in seen: + if model in seen_models: continue - seen.add(unique_key) + seen_models.add(model) options.append({ "id": key, - "label": label_map.get(key, key), + "label": concrete_label_map.get(model, label_map.get(key, key)), "model": model, "description": f"当前视频网关可选模型;单次时长最高 {max(video_duration_options())} 秒", "duration_options": video_duration_options(), @@ -7814,7 +7816,7 @@ def resolve_video_model(raw: str | None) -> str: requested = (raw or VIDEO_MODEL or "seedance").strip() lowered = requested.lower() if lowered in {"sora", "sora-2", "sora_2"}: - raise HTTPException(400, "Sora 已停用,请选择 Seedance / Kling / Veo 3") + raise HTTPException(400, "Sora 已停用,请选择当前已接入的 Seedance") return VIDEO_MODEL_ALIASES.get(lowered, requested) diff --git a/docs/source-analysis.html b/docs/source-analysis.html index 5f55012..b1464f9 100644 --- a/docs/source-analysis.html +++ b/docs/source-analysis.html @@ -580,8 +580,9 @@

2026-05-25 三模式版:默认首页再收敛为一个中央对话框,首页和画布底部输入框只让用户选文生图、文生视频、图生视频,然后手写提示词生成。图生视频只显示“上传图片”,不再把首帧 / 首尾帧这类模型实现概念作为主入口;营销图文不再作为首页默认入口。后端 /health 返回可选图片 / 视频模型、图片尺寸、视频画幅和真实可用视频时长,首页按返回值显示模型和规格选择;当前 Doubao / Seedance 生产链路单条最长 15 秒,不向用户暴露 30 秒按钮。

2026-05-25 根域名画布版:https://marketing.skg.com 登录后直接进入个人生成画布,不再先进入 React 单对话框首页再点画布;/canvas/ 只保留为旧链接兼容跳转。后续优先少改成熟画布结构,只在必要时改模式文案、生成接入和结果/队列显示。

2026-05-25 上游能力恢复版:用户明确要求“API 没关系,其他恢复,别削弱”。因此根域名画布恢复 chatfire-AI/huobao-canvas 的成熟节点和工作流结构:推荐词、AI 润色、自动执行、工作流模板、首帧/尾帧/参考图节点、图片/视频/LLM 配置、多角度分镜、故事板、绘本和批量下载都保留;只继续替换品牌、路由和 API 接入。生成请求仍走 SKG 后端 /api 与登录 Cookie,员工不需要个人 API Key。

+

2026-05-25 媒体模型接入收口:图片和视频模型选择只暴露当前后端真实可用项:图片为 autogpt-image-2gemini-3-pro-image-preview;视频当前只接通 Seedance 2.0 Fast(真实模型 doubao-seedance-2-0-fast-260128)。旧上游的 Nano Banana、Seedream、Kling、Veo 或浏览器本地自定义媒体模型不能进入生成下拉,避免同事选到实际不可用的模型。

-

当前默认业务管线是“个人隔离任务 → 根域名进入个人画布 → 用提示词、推荐词、AI 润色或工作流模板创建节点 → 画布自动执行或手动连接图片/视频/文本节点 → 生成结果沉淀在当前个人画布 → 需要时进入详情页继续编辑”。画布不再被削成三模式入口;首帧、尾帧、参考图、图生视频、多角度分镜、故事板和绘本等上游概念按节点能力保留。底层生成仍由 web/canvas-app/src/hooks/useApi.js 适配到本项目 /creative/jobs/image/jobs/{id}/frames/{idx}/generate/jobs/{id}/frames/{idx}/storyboard/video,并按当前登录用户写入个人 job。多人互不影响依赖后端 owner_id 和飞书 / 备用登录会话隔离。旧 React 单对话框首页、信息流复刻链路仍保留在源码里作为回滚/高级能力,但不作为生产默认入口。

+

当前默认业务管线是“个人隔离任务 → 根域名进入个人画布 → 用提示词、推荐词、AI 润色或工作流模板创建节点 → 画布自动执行或手动连接图片/视频/文本节点 → 生成结果沉淀在当前个人画布 → 需要时进入详情页继续编辑”。画布不再被削成三模式入口;首帧、尾帧、参考图、图生视频、多角度分镜、故事板和绘本等上游概念按节点能力保留。底层生成仍由 web/canvas-app/src/hooks/useApi.js 适配到本项目 /creative/jobs/image/jobs/{id}/frames/{idx}/generate/jobs/{id}/frames/{idx}/storyboard/video,并按当前登录用户写入个人 job。图片尺寸只显示 auto1024x15361024x10241536x1024;视频画幅只显示 720x12801280x7201024x1024960x1280;视频时长只显示 5/8/10/12/15 秒。多人互不影响依赖后端 owner_id 和飞书 / 备用登录会话隔离。旧 React 单对话框首页、信息流复刻链路仍保留在源码里作为回滚/高级能力,但不作为生产默认入口。

01

个人任务

GET /jobs 按当前登录用户过滤;旧无 owner 任务只对备用账号可见。

02

进入画布

用户直接在根域名个人画布里操作,上游项目列表、推荐词、节点菜单、工作流模板和批量下载能力保留。

@@ -607,6 +608,7 @@ web/app/page.tsx旧 React 单对话框生成台源码仍保留,便于以后回滚或抽能力;当前生产根域名已经由 web/canvas-app/ 画布产物覆盖,不再把这个 React 首页作为默认首屏。该页面里的模式也已收敛为文生图、文生视频、图生视频;图生视频只显示“上传图片”,不把“首帧/首尾帧”作为用户入口。旧 TK 复刻工作台组件仍保留在 web/components/ad-recreation-board.tsx,但不再作为默认首页渲染。 web/canvas-app/SKG 内部画布应用:从 chatfire-AI/huobao-canvas 交互逻辑改造而来。当前策略是“保留成熟画布能力,替换品牌/路由/API”:Vue Flow 节点画布、项目列表、推荐词、AI 润色、自动执行、工作流模板、首帧/尾帧/参考图节点、图片/视频/LLM 配置节点、模型配置和批量下载都保留;可见品牌收敛为 SKG logo,不展示上游注册链接或外部品牌。生产路径固定为根域名 /,内部路由用 /p/:id?;来源说明保存在 THIRD_PARTY_NOTICES.md,不展示给终端用户。 web/canvas-app/src/views/Canvas.vue画布主交互:恢复上游底部 prompt composer、AI 润色自动执行、推荐词、节点菜单、工作流面板、API/模型设置入口和批量下载入口。自动执行会调用 useWorkflowOrchestrator 分析提示词,创建文生图、图转视频、故事板、多角度分镜或绘本节点组;手动模式只创建文本节点,用户自行连接节点。 + web/canvas-app/src/config/models.js画布媒体模型和规格的前端白名单:图片只内置 autogpt-image-2gemini-3-pro-image-preview,尺寸只内置 auto1024x15361024x10241536x1024;视频只内置 seedance / Seedance 2.0 Fast,画幅和时长对齐后端 /health 能力边界。useModelConfig.js 和 Pinia 模型 store 会忽略浏览器本地自定义图片/视频模型,防止旧缓存把不可用模型带回生成下拉。 web/canvas-app/src/hooks/useApi.js画布到本项目后端的适配层:不再读取浏览器 API Key,而是使用当前登录会话 Cookie 调用 /api。文生图 / 图生图先创建轻量 creative job,再调用 /frames/0/generate;文生视频 / 图生视频调用 /storyboard/video 并轮询 /jobs/{id},完成后把图片或 mp4 URL 写回画布节点。 web/scripts/sync-canvas-root.mjs构建桥接脚本:在 next build 静态导出完成后,把 Vite 画布产物 web/canvas-app/dist 覆盖到 web/out 根目录,使 https://marketing.skg.com 登录后直接进入画布;旧 web/scripts/sync-canvas-dist.mjs 保留但不再由生产构建调用。 web/app/detail/page.tsx任务详情页:静态导出路由 /detail/?job=<id>,通过 query 读取 job id,调用 getJob 恢复同一任务。页面展示参考图、全部生成图、视频候选、营销图文方案和历史提示词,可继续调用 generateImagegenerateStoryboardVideogenerateCreativeCopy,并支持删除图片/视频。该页继续依赖后端 owner 过滤,用户不能通过切换 URL 读取别人的任务。 @@ -639,6 +641,7 @@ + @@ -1206,6 +1209,20 @@ ProductRefStateItem {

变更记录

这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。

+
+
+

2026-05-25 · 媒体模型选择对齐真实后端能力

+ API + UI + Docs +
+
+

问题:恢复上游画布后,前端仍可能显示 Nano Banana、Seedream、Kling、Veo 等上游或旧缓存模型,但这些并不是当前 SKG 后端已经验证可用的生成通道,用户一旦选择就会失败或误解模型能力。

+

改动:web/canvas-app/src/config/models.js 将图片模型收口为 autogpt-image-2gemini-3-pro-image-preview,视频模型收口为 seedance / Seedance 2.0 Fast;图片尺寸、视频画幅和视频时长对齐后端真实能力。useModelConfig.js 和 Pinia 模型 store 会忽略浏览器本地自定义图片/视频模型,并把旧 localStorage 选中值回退到默认可用模型。

+

后端:api/main.pyvideo_model_options() 按真实模型去重:当前 klingveo 等别名如果都指向 doubao-seedance-2-0-fast-260128/health 只返回一个 Seedance 选项,不再假装有多个不同视频模型。

+

影响:员工在画布里只能选到当前可直接生成的图片/视频模型;后续新增 Kling、Veo 或其他图片模型时,必须先在后端完成真实模型配置和探针验证,再更新前端白名单。

+
+

2026-05-25 · 恢复上游画布能力,API 保持 SKG 接入

diff --git a/web/canvas-app/src/components/ApiSettings.vue b/web/canvas-app/src/components/ApiSettings.vue index 5851903..ec01258 100644 --- a/web/canvas-app/src/components/ApiSettings.vue +++ b/web/canvas-app/src/components/ApiSettings.vue @@ -102,17 +102,6 @@ 图片模型 {{ allImageModels.length }} 个
-
- - - 添加 - -
视频模型 {{ allVideoModels.length }} 个
-
- - - 添加 - -
{ @@ -273,20 +249,6 @@ const handleAddChatModel = () => { } } -const handleAddImageModel = () => { - if (newImageModel.value.trim()) { - modelStore.addCustomImageModel(newImageModel.value.trim()) - newImageModel.value = '' - } -} - -const handleAddVideoModel = () => { - if (newVideoModel.value.trim()) { - modelStore.addCustomVideoModel(newVideoModel.value.trim()) - newVideoModel.value = '' - } -} - // Handle remove models | 处理删除模型 const handleRemoveChatModel = (modelKey) => { modelStore.removeCustomChatModel(modelKey) diff --git a/web/canvas-app/src/components/nodes/ImageConfigNode.vue b/web/canvas-app/src/components/nodes/ImageConfigNode.vue index 19653ac..654df97 100644 --- a/web/canvas-app/src/components/nodes/ImageConfigNode.vue +++ b/web/canvas-app/src/components/nodes/ImageConfigNode.vue @@ -166,7 +166,7 @@ import { useImageGeneration } from '../../hooks' import { updateNode, addNode, addEdge, nodes, edges, duplicateNode, removeNode } from '../../stores/canvas' import NodeHandleMenu from './NodeHandleMenu.vue' import { useModelStore } from '../../stores/pinia' -import { getModelSizeOptions, getModelQualityOptions, getModelConfig, DEFAULT_IMAGE_MODEL } from '../../stores/models' +import { getModelSizeOptions, getModelQualityOptions, getModelConfig, DEFAULT_IMAGE_MODEL, DEFAULT_IMAGE_SIZE } from '../../stores/models' import { parseMentions } from '../../hooks/useNodeRef' // 使用 Pinia store 获取模型选项(根据渠道过滤) @@ -189,7 +189,7 @@ const { loading, error, images: generatedImages, generate } = useImageGeneration // Local state | 本地状态 const showHandleMenu = ref(false) const localModel = ref(props.data?.model || DEFAULT_IMAGE_MODEL) -const localSize = ref(props.data?.size || '2048x2048') +const localSize = ref(props.data?.size || DEFAULT_IMAGE_SIZE) const localQuality = ref(props.data?.quality || 'standard') // Label editing state | Label 编辑状态 @@ -288,7 +288,8 @@ onMounted(() => { if (!localModel.value || !isModelAvailable) { // 使用 store 中的默认模型或第一个可用模型 - localModel.value = modelStore.selectedImageModel || availableModels[0]?.key || DEFAULT_IMAGE_MODEL + const selected = availableModels.find(m => m.key === modelStore.selectedImageModel)?.key + localModel.value = selected || availableModels[0]?.key || DEFAULT_IMAGE_MODEL updateNode(props.id, { model: localModel.value }) } }) @@ -486,8 +487,7 @@ const handleModelSelect = (key) => { let defaultSize = config?.defaultParams?.size if (!defaultSize && newSizeOptions.length > 0) { - // 备用逻辑:查找 2048 或最接近的尺寸 - defaultSize = newSizeOptions.find(o => o.key === '2048x2048')?.key + defaultSize = newSizeOptions.find(o => o.key === DEFAULT_IMAGE_SIZE)?.key || newSizeOptions.find(o => o.key.includes('1024'))?.key || newSizeOptions[0].key } @@ -508,7 +508,7 @@ const handleQualitySelect = (quality) => { // Update size to first option of new quality | 更新尺寸为新画质的第一个选项 const newSizeOptions = getModelSizeOptions(localModel.value, quality) if (newSizeOptions.length > 0) { - const defaultSize = quality === '4k' ? newSizeOptions.find(o => o.key.includes('4096'))?.key || newSizeOptions[4]?.key : newSizeOptions[4]?.key + const defaultSize = newSizeOptions.find(o => o.key === DEFAULT_IMAGE_SIZE)?.key localSize.value = defaultSize || newSizeOptions[0].key updateNode(props.id, { quality, size: localSize.value }) } else { diff --git a/web/canvas-app/src/components/nodes/ImageNode.vue b/web/canvas-app/src/components/nodes/ImageNode.vue index caa48dc..c1ca598 100644 --- a/web/canvas-app/src/components/nodes/ImageNode.vue +++ b/web/canvas-app/src/components/nodes/ImageNode.vue @@ -417,8 +417,8 @@ const handleSelect = (item) => { // Create imageConfig node const configNodeId = addNode('imageConfig', { x: nodeX + 900, y: nodeY }, { - model: 'doubao-seedream-4-5-251128', - size: '2048x2048', + model: 'auto', + size: '1024x1536', label: '生图配置' }) @@ -626,8 +626,8 @@ const createInpaintWorkflow = () => { // Create imageConfig node for inpainting | 创建图生图配置节点 const configNodeId = addNode('imageConfig', { x: nodeX + 600, y: nodeY }, { - model: 'doubao-seedream-4-5-251128', - size: '2048x2048', + model: 'auto', + size: '1024x1536', label: '局部重绘', inpaintMode: true }) @@ -848,8 +848,8 @@ const handleImageGen = () => { // Create imageConfig node for generation | 创建生图配置节点 const configNodeId = addNode('imageConfig', { x: nodeX + 900, y: nodeY }, { - model: 'doubao-seedream-4-5-251128', - size: '2048x2048', + model: 'auto', + size: '1024x1536', label: '生图配置' }) diff --git a/web/canvas-app/src/components/nodes/LLMConfigNode.vue b/web/canvas-app/src/components/nodes/LLMConfigNode.vue index 05a955b..d31aa23 100644 --- a/web/canvas-app/src/components/nodes/LLMConfigNode.vue +++ b/web/canvas-app/src/components/nodes/LLMConfigNode.vue @@ -417,7 +417,7 @@ const handleSelect = (item) => { const nodeY = currentNode?.position?.y || 0 const defaultData = { - imageConfig: { model: 'doubao-seedream-4-5-251128', size: '2048x2048', label: '文生图' }, + imageConfig: { model: 'auto', size: '1024x1536', label: '文生图' }, videoConfig: { label: '视频生成' }, text: { content: '', label: '文本输入' } } @@ -1003,8 +1003,8 @@ const handleSplitToTextWithImage = () => { position: { x: baseX + colSpacing, y: segY }, data: { label: `图片 ${i + 1}`, - model: 'doubao-seedream-4-5-251128', - size: '2048x2048' + model: 'auto', + size: '1024x1536' } } nodeSpecs.push(imageConfigSpec) diff --git a/web/canvas-app/src/components/nodes/TextNode.vue b/web/canvas-app/src/components/nodes/TextNode.vue index a928ede..da4d97c 100644 --- a/web/canvas-app/src/components/nodes/TextNode.vue +++ b/web/canvas-app/src/components/nodes/TextNode.vue @@ -440,7 +440,7 @@ const handleSelect = (item) => { const nodeY = currentNode?.position?.y || 0 const defaultData = { - imageConfig: { model: 'doubao-seedream-4-5-251128', size: '2048x2048', label: '文生图' }, + imageConfig: { model: 'auto', size: '1024x1536', label: '文生图' }, videoConfig: { label: '视频生成' }, llmConfig: { label: 'LLM文本生成' } } @@ -695,8 +695,8 @@ const handleImageGen = () => { // Create imageConfig node | 创建text生图配置节点 const configNodeId = addNode('imageConfig', { x: nodeX + 400, y: nodeY }, { - model: 'doubao-seedream-4-5-251128', - size: '2048x2048', + model: 'auto', + size: '1024x1536', label: '文生图' }) diff --git a/web/canvas-app/src/components/nodes/VideoConfigNode.vue b/web/canvas-app/src/components/nodes/VideoConfigNode.vue index 499b985..b8aa0e5 100644 --- a/web/canvas-app/src/components/nodes/VideoConfigNode.vue +++ b/web/canvas-app/src/components/nodes/VideoConfigNode.vue @@ -489,7 +489,8 @@ onMounted(() => { if (!localModel.value || !isModelAvailable) { // 使用 store 中的默认模型或第一个可用模型 - localModel.value = modelStore.selectedVideoModel || availableModels[0]?.key || DEFAULT_VIDEO_MODEL + 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 }) } }) diff --git a/web/canvas-app/src/config/models.js b/web/canvas-app/src/config/models.js index 2dbaf72..6a85923 100644 --- a/web/canvas-app/src/config/models.js +++ b/web/canvas-app/src/config/models.js @@ -3,36 +3,20 @@ * Centralized model configuration | 集中模型配置 */ -// Seedream image size options | 豆包图片尺寸选项 +// SKG backend image size options | SKG 后端图片尺寸选项 export const SEEDREAM_SIZE_OPTIONS = [ - { label: '21:9', key: '3024x1296' }, - { label: '16:9', key: '2560x1440' }, - { label: '4:3', key: '2304x1728' }, - { label: '3:2', key: '2496x1664' }, - { label: '1:1', key: '2048x2048' }, - { label: '2:3', key: '1664x2496' }, - { label: '3:4', key: '1728x2304' }, - { label: '9:16', key: '1440x2560' }, - { label: '9:21', key: '1296x3024' } + { label: '自动', key: 'auto' }, + { label: '竖图 2:3', key: '1024x1536' }, + { label: '方图 1:1', key: '1024x1024' }, + { label: '横图 3:2', key: '1536x1024' } ] -// Seedream 4K image size options | 豆包4K图片尺寸选项 -export const SEEDREAM_4K_SIZE_OPTIONS = [ - { label: '21:9', key: '6198x2656' }, - { label: '16:9', key: '5404x3040' }, - { label: '4:3', key: '4694x3520' }, - { label: '3:2', key: '4992x3328' }, - { label: '1:1', key: '4096x4096' }, - { label: '2:3', key: '3328x4992' }, - { label: '3:4', key: '3520x4694' }, - { label: '9:16', key: '3040x5404' }, - { label: '9:21', key: '2656x6198' } -] +// Kept for compatibility with upstream model helpers. +export const SEEDREAM_4K_SIZE_OPTIONS = SEEDREAM_SIZE_OPTIONS -// Seedream quality options | 豆包画质选项 +// SKG backend currently exposes model choice and size; quality is retained as a no-op UI field. export const SEEDREAM_QUALITY_OPTIONS = [ - { label: '标准画质', key: 'standard' }, - { label: '4K 高清', key: '4k' } + { label: '标准', key: 'standard' } ] export const BANANA_SIZE_OPTIONS = [ @@ -48,51 +32,37 @@ export const BANANA_SIZE_OPTIONS = [ // Image generation models | 图片生成模型 export const IMAGE_MODELS = [ { - label: 'Nano Banana 2', - key: 'nano-banana-2', - provider: ['chatfire'], // 火宝渠道 - sizes: BANANA_SIZE_OPTIONS.map(s => s.key), - // qualities: SEEDREAM_QUALITY_OPTIONS, - // getSizesByQuality: (quality) => quality === '4k' ? SEEDREAM_4K_SIZE_OPTIONS : SEEDREAM_SIZE_OPTIONS, - defaultParams: { - size: '1x1', - quality: 'standard', - style: 'vivid' - } - }, - { - label: 'Nano Banana Pro', - key: 'nano-banana-pro', - provider: ['chatfire'], // 火宝渠道 - sizes: BANANA_SIZE_OPTIONS.map(s => s.key), - // qualities: SEEDREAM_QUALITY_OPTIONS, - // getSizesByQuality: (quality) => quality === '4k' ? SEEDREAM_4K_SIZE_OPTIONS : SEEDREAM_SIZE_OPTIONS, - defaultParams: { - size: '1x1', - quality: 'standard', - style: 'vivid' - } - }, - { - label: '豆包 Seedream 4.5', - key: 'doubao-seedream-4-5-251128', - provider: ['chatfire'], // 火宝渠道 + label: '自动', + key: 'auto', + provider: ['chatfire'], sizes: SEEDREAM_SIZE_OPTIONS.map(s => s.key), qualities: SEEDREAM_QUALITY_OPTIONS, - getSizesByQuality: (quality) => quality === '4k' ? SEEDREAM_4K_SIZE_OPTIONS : SEEDREAM_SIZE_OPTIONS, defaultParams: { - size: '2048x2048', + size: '1024x1536', quality: 'standard', style: 'vivid' } }, { - label: 'Nano Banana', - key: 'nano-banana', - provider: ['chatfire'], // 火宝渠道 - tips: '尺寸写在提示词中: 尺寸 9:16', - sizes: [], + label: 'GPT Image 2', + key: 'gpt-image-2', + provider: ['chatfire'], + sizes: SEEDREAM_SIZE_OPTIONS.map(s => s.key), + qualities: SEEDREAM_QUALITY_OPTIONS, defaultParams: { + size: '1024x1536', + quality: 'standard', + style: 'vivid' + } + }, + { + label: 'Gemini 图片', + key: 'gemini-3-pro-image-preview', + provider: ['chatfire'], + sizes: SEEDREAM_SIZE_OPTIONS.map(s => s.key), + qualities: SEEDREAM_QUALITY_OPTIONS, + defaultParams: { + size: '1024x1536', quality: 'standard', style: 'vivid' } @@ -102,11 +72,10 @@ export const IMAGE_MODELS = [ // Video ratio options | 视频比例选项 export const VIDEO_RATIO_LIST = [ - { label: '16:9 (横版)', key: '16x9' }, - { label: '4:3', key: '4x3' }, - { label: '1:1 (方形)', key: '1x1' }, - { label: '3:4', key: '3x4' }, - { label: '9:16 (竖版)', key: '9x16' } + { label: '竖屏 9:16', key: '720x1280' }, + { label: '横屏 16:9', key: '1280x720' }, + { label: '方形 1:1', key: '1024x1024' }, + { label: '竖屏 3:4', key: '960x1280' } ] // Video resolution options for Seedance | Seedance 分辨率选项 @@ -118,102 +87,23 @@ export const SEEDANCE_RESOLUTION_OPTIONS = [ // Video generation models | 视频生成模型 export const VIDEO_MODELS = [ - // Seedance 模型 - 1.5 Pro { - label: 'Seedance 1.5 Pro (图文视频)', - key: 'doubao-seedance-1-5-pro-251215', + label: 'Seedance 2.0 Fast', + key: 'seedance', provider: ['chatfire'], type: 't2v+i2v', - ratios: ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'], - durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - resolutions: ['480p', '720p', '1080p'], - defaultResolution: '1080p', - defaultParams: { ratio: '16:9', duration: 10, resolution: '1080p' } - }, - // Seedance 模型 - 文生视频 - { - label: 'Seedance 1.0 Lite (文生视频)', - key: 'doubao-seedance-1-0-lite-t2v-250428', - provider: ['chatfire'], - type: 't2v', // 文生视频 - ratios: ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'], - durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - resolutions: ['480p', '720p', '1080p'], + ratios: ['720x1280', '1280x720', '1024x1024', '960x1280'], + durs: [ + { label: '5 秒', key: 5 }, + { label: '8 秒', key: 8 }, + { label: '10 秒', key: 10 }, + { label: '12 秒', key: 12 }, + { label: '15 秒', key: 15 } + ], + resolutions: ['720p'], defaultResolution: '720p', - defaultParams: { ratio: '16:9', duration: 5, resolution: '720p' } + defaultParams: { ratio: '720x1280', duration: 10, resolution: '720p' } }, - // Seedance 模型 - 图生视频 - { - label: 'Seedance 1.0 Lite (图生视频)', - key: 'doubao-seedance-1-0-lite-i2v-250428', - provider: ['chatfire'], - type: 'i2v', // 图生视频 - ratios: ['16:9'], - durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - resolutions: ['480p', '720p', '1080p'], - defaultResolution: '720p', - defaultParams: { ratio: '16:9', duration: 5, resolution: '720p' } - }, - // Seedance 模型 - 图文视频 Pro - { - label: 'Seedance 1.0 Pro (图文视频)', - key: 'doubao-seedance-1-0-pro-250528', - provider: ['chatfire'], - type: 't2v+i2v', // 图文视频 - ratios: ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9', '16:9'], - durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - resolutions: ['480p', '720p', '1080p'], - defaultResolution: '1080p', - defaultParams: { ratio: '16:9', duration: 5, resolution: '1080p' } - }, - - // Seedance 模型 - 1.0 Pro Fast - { - label: 'Seedance 1.0 Pro Fast (图文视频)', - key: 'doubao-seedance-1-0-pro-fast-251015', - provider: ['chatfire'], - type: 't2v+i2v', - ratios: ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'], - durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - resolutions: ['480p', '720p', '1080p'], - defaultResolution: '1080p', - defaultParams: { ratio: '16:9', duration: 5, resolution: '1080p' } - }, - // 可灵 Kling - // { - // label: '可灵 Kling v2.5-turbo', - // key: 'kling-v2-1', - // provider: ['chatfire'], // 仅火宝渠道 - // ratios: VIDEO_RATIO_LIST.map(s => s.key), - // durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - // defaultParams: { ratio: '9:16', duration: 10 } - // }, - // { - // label: 'runway/gen4-turbo', - // key: 'runway/gen4-turbo', - // ratios: VIDEO_RATIO_LIST.map(s => s.key), - // durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - // defaultParams: { ratio: '16:9', duration: 5 } - // }, - // { - // label: '可灵视频 O1', - // key: 'kling-video-o1', - // ratios: VIDEO_RATIO_LIST.map(s => s.key), - // durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - // defaultParams: { ratio: '16:9', duration: 5 } - // }, - // { - // label: 'viduq2-pro_720p', key: 'viduq2-pro_720p', - // ratios: VIDEO_RATIO_LIST.map(s => s.key), - // durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - // defaultParams: { ratio: '16:9', duration: 5 } - // }, - // { - // label: 'Sora 2', key: 'sora-2', - // ratios: VIDEO_RATIO_LIST.map(s => s.key), - // durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - // defaultParams: { ratio: '16:9', duration: 5 } - // } ] // Chat/LLM models | 对话模型 @@ -228,9 +118,10 @@ export const CHAT_MODELS = [ // Image size options | 图片尺寸选项 export const IMAGE_SIZE_OPTIONS = [ - { label: '2048x2048', key: '2048x2048' }, - { label: '1792x1024 (横版)', key: '1792x1024' }, - { label: '1024x1792 (竖版)', key: '1024x1792' } + { label: '自动', key: 'auto' }, + { label: '竖图 2:3', key: '1024x1536' }, + { label: '方图 1:1', key: '1024x1024' }, + { label: '横图 3:2', key: '1536x1024' } ] // Image quality options | 图片质量选项 @@ -251,16 +142,19 @@ export const VIDEO_RATIO_OPTIONS = VIDEO_RATIO_LIST // Video duration options | 视频时长选项 export const VIDEO_DURATION_OPTIONS = [ { label: '5 秒', key: 5 }, - { label: '10 秒', key: 10 } + { label: '8 秒', key: 8 }, + { label: '10 秒', key: 10 }, + { label: '12 秒', key: 12 }, + { label: '15 秒', key: 15 } ] // Default values | 默认值 -export const DEFAULT_IMAGE_MODEL = 'nano-banana-pro' -export const DEFAULT_VIDEO_MODEL = 'doubao-seedance-1-5-pro-251215' +export const DEFAULT_IMAGE_MODEL = 'auto' +export const DEFAULT_VIDEO_MODEL = 'seedance' export const DEFAULT_CHAT_MODEL = 'gpt-4o-mini' -export const DEFAULT_IMAGE_SIZE = '2048x2048' -export const DEFAULT_VIDEO_RATIO = '16:9' -export const DEFAULT_VIDEO_DURATION = 5 +export const DEFAULT_IMAGE_SIZE = '1024x1536' +export const DEFAULT_VIDEO_RATIO = '720x1280' +export const DEFAULT_VIDEO_DURATION = 10 // Get model by key | 根据 key 获取模型 export const getModelByName = (key) => { diff --git a/web/canvas-app/src/config/workflows.js b/web/canvas-app/src/config/workflows.js index d3a92bd..83d0868 100644 --- a/web/canvas-app/src/config/workflows.js +++ b/web/canvas-app/src/config/workflows.js @@ -81,8 +81,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + nodeSpacing, y: startPosition.y + rowSpacing * 1.5 }, data: { label: '主角色图', - model: 'doubao-seedream-4-5-251128', - size: '2048x2048' + model: 'auto', + size: '1024x1536' } }) @@ -145,8 +145,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: currentX, y: angleY }, data: { label: `${angleConfig.label} (${angleConfig.english})`, - model: 'doubao-seedream-4-5-251128', - size: '2048x2048' + model: 'auto', + size: '1024x1536' } }) @@ -281,8 +281,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 2, y: startPosition.y }, data: { label: '生成模特图', - model: 'doubao-seedream-4-5-251128', - size: '2048x2048' + model: 'auto', + size: '1024x1536' } }) @@ -294,8 +294,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 2, y: startPosition.y + rowSpacing }, data: { label: '侧面展示图', - model: 'doubao-seedream-4-5-251128', - size: '2048x2048' + model: 'auto', + size: '1024x1536' } }) @@ -307,8 +307,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 2, y: startPosition.y + rowSpacing * 2 }, data: { label: '俯瞰展示图', - model: 'doubao-seedream-4-5-251128', - size: '2048x2048' + model: 'auto', + size: '1024x1536' } }) @@ -320,8 +320,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 2, y: startPosition.y + rowSpacing * 3 }, data: { label: '拆解图', - model: 'doubao-seedream-4-5-251128', - size: '2048x2048' + model: 'auto', + size: '1024x1536' } }) @@ -507,8 +507,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 2, y: startPosition.y }, data: { label: '生成正面全身图', - model: 'doubao-seedream-4-5-251128', - size: '1440x2560' + model: 'auto', + size: '1024x1536' } }) @@ -569,8 +569,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 4 + 100, y: startPosition.y + rowSpacing }, data: { label: '侧面半身图', - model: 'doubao-seedream-4-5-251128', - size: '2048x2048' + model: 'auto', + size: '1024x1536' } }) @@ -582,8 +582,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 4 + 100, y: startPosition.y + rowSpacing * 2 }, data: { label: '表情特写图', - model: 'doubao-seedream-4-5-251128', - size: '2048x2048' + model: 'auto', + size: '1024x1536' } }) @@ -595,8 +595,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 4 + 100, y: startPosition.y + rowSpacing * 3 }, data: { label: '背面全身图', - model: 'doubao-seedream-4-5-251128', - size: '1440x2560' + model: 'auto', + size: '1024x1536' } }) @@ -753,8 +753,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 2, y: startPosition.y }, data: { label: '生成基础场景', - model: 'doubao-seedream-4-5-251128', - size: '2560x1440' + model: 'auto', + size: '1536x1024' } }) @@ -815,8 +815,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 4 + 100, y: startPosition.y + rowSpacing }, data: { label: '傍晚场景', - model: 'doubao-seedream-4-5-251128', - size: '2560x1440' + model: 'auto', + size: '1536x1024' } }) @@ -828,8 +828,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 4 + 100, y: startPosition.y + rowSpacing * 2 }, data: { label: '夜晚场景', - model: 'doubao-seedream-4-5-251128', - size: '2560x1440' + model: 'auto', + size: '1536x1024' } }) @@ -841,8 +841,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 4 + 100, y: startPosition.y + rowSpacing * 3 }, data: { label: '雨天场景', - model: 'doubao-seedream-4-5-251128', - size: '2560x1440' + model: 'auto', + size: '1536x1024' } }) @@ -996,8 +996,8 @@ export const WORKFLOW_TEMPLATES = [ // position: { x: startPosition.x + colSpacing * 2, y: startPosition.y + rowSpacing }, // data: { // label: '分镜画面', - // model: 'doubao-seedream-4-5-251128', - // size: '2560x1440' + // model: 'auto', + // size: '1536x1024' // } // }) @@ -1123,8 +1123,8 @@ export const WORKFLOW_TEMPLATES = [ position: { x: startPosition.x + colSpacing * 2, y: startPosition.y - rowSpacing }, data: { label: '角色参考图', - model: 'doubao-seedream-4-5-251128', - size: '2048x2048' + model: 'auto', + size: '1024x1536' } }) diff --git a/web/canvas-app/src/hooks/useModelConfig.js b/web/canvas-app/src/hooks/useModelConfig.js index 989b40f..ad7f6d5 100644 --- a/web/canvas-app/src/hooks/useModelConfig.js +++ b/web/canvas-app/src/hooks/useModelConfig.js @@ -79,6 +79,11 @@ const setStored = (key, value) => { } } +const getValidStoredModel = (key, defaultValue, builtInModels) => { + const stored = getStored(key, defaultValue) + return builtInModels.some(model => model.key === stored) ? stored : defaultValue +} + // Shared reactive state (singleton pattern) const customChatModels = ref(getStoredJson(STORAGE_KEYS.CUSTOM_CHAT_MODELS, [])) const customImageModels = ref(getStoredJson(STORAGE_KEYS.CUSTOM_IMAGE_MODELS, [])) @@ -90,8 +95,8 @@ const customImageModelsByProvider = ref(getStoredJson(STORAGE_KEYS.CUSTOM_IMAGE_ const customVideoModelsByProvider = ref(getStoredJson(STORAGE_KEYS.CUSTOM_VIDEO_MODELS_BY_PROVIDER || 'custom-video-models-by-provider', {})) const selectedChatModel = ref(getStored(STORAGE_KEYS.SELECTED_CHAT_MODEL, DEFAULT_CHAT_MODEL)) -const selectedImageModel = ref(getStored(STORAGE_KEYS.SELECTED_IMAGE_MODEL, DEFAULT_IMAGE_MODEL)) -const selectedVideoModel = ref(getStored(STORAGE_KEYS.SELECTED_VIDEO_MODEL, DEFAULT_VIDEO_MODEL)) +const selectedImageModel = ref(getValidStoredModel(STORAGE_KEYS.SELECTED_IMAGE_MODEL, DEFAULT_IMAGE_MODEL, IMAGE_MODELS)) +const selectedVideoModel = ref(getValidStoredModel(STORAGE_KEYS.SELECTED_VIDEO_MODEL, DEFAULT_VIDEO_MODEL, VIDEO_MODELS)) /** * Model Configuration Hook @@ -117,47 +122,13 @@ export const useModelConfig = () => { })) ]) - const allImageModels = computed(() => [ - ...IMAGE_MODELS.map(m => ({ ...m, isCustom: false })), - ...customImageModels.value.map(m => ({ - label: m.label || m.key, - key: m.key, - isCustom: true, - sizes: [], - defaultParams: { quality: 'standard', style: 'vivid' } - })), - // 添加当前渠道的自定义模型 - ...(customImageModelsByProvider.value[currentProvider.value] || []).map(m => ({ - label: m.label || m.key, - key: m.key, - isCustom: true, - sizes: [], - defaultParams: { quality: 'standard', style: 'vivid' }, - provider: [currentProvider.value] - })) - ]) + const allImageModels = computed(() => + IMAGE_MODELS.map(m => ({ ...m, isCustom: false })) + ) - const allVideoModels = computed(() => [ - ...VIDEO_MODELS.map(m => ({ ...m, isCustom: false })), - ...customVideoModels.value.map(m => ({ - label: m.label || m.key, - key: m.key, - isCustom: true, - ratios: ['16x9', '9:16', '1:1'], - durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - defaultParams: { ratio: '16:9', duration: 5 } - })), - // 添加当前渠道的自定义模型 - ...(customVideoModelsByProvider.value[currentProvider.value] || []).map(m => ({ - label: m.label || m.key, - key: m.key, - isCustom: true, - ratios: ['16x9', '9:16', '1:1'], - durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - defaultParams: { ratio: '16:9', duration: 5 }, - provider: [currentProvider.value] - })) - ]) + const allVideoModels = computed(() => + VIDEO_MODELS.map(m => ({ ...m, isCustom: false })) + ) // Available models filtered by provider | 根据渠道过滤的可用模型 const availableChatModels = computed(() => @@ -188,29 +159,12 @@ export const useModelConfig = () => { provider: [provider] })) ] - const image = [ - ...IMAGE_MODELS.filter(m => isModelSupported(m, provider)).map(m => ({ ...m, isCustom: false })), - ...(customImageModelsByProvider.value[provider] || []).map(m => ({ - label: m.label || m.key, - key: m.key, - isCustom: true, - sizes: [], - defaultParams: { quality: 'standard', style: 'vivid' }, - provider: [provider] - })) - ] - const video = [ - ...VIDEO_MODELS.filter(m => isModelSupported(m, provider)).map(m => ({ ...m, isCustom: false })), - ...(customVideoModelsByProvider.value[provider] || []).map(m => ({ - label: m.label || m.key, - key: m.key, - isCustom: true, - ratios: ['16x9', '9:16', '1:1'], - durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - defaultParams: { ratio: '16:9', duration: 5 }, - provider: [provider] - })) - ] + const image = IMAGE_MODELS + .filter(m => isModelSupported(m, provider)) + .map(m => ({ ...m, isCustom: false })) + const video = VIDEO_MODELS + .filter(m => isModelSupported(m, provider)) + .map(m => ({ ...m, isCustom: false })) return { chat, image, video } } diff --git a/web/canvas-app/src/stores/canvas.js b/web/canvas-app/src/stores/canvas.js index d37a2fe..d2c73f3 100644 --- a/web/canvas-app/src/stores/canvas.js +++ b/web/canvas-app/src/stores/canvas.js @@ -401,7 +401,7 @@ export const initSampleData = () => { // Add image config node | 添加文生图配置节点 addNode('imageConfig', { x: 450, y: 150 }, { prompt: '', - model: 'doubao-seedream-4-5-251128', + model: 'auto', ratio: '16:9 | 4张 | 高清', label: '文生图' }) diff --git a/web/canvas-app/src/stores/pinia/models.js b/web/canvas-app/src/stores/pinia/models.js index cd9a09c..ef1a2bc 100644 --- a/web/canvas-app/src/stores/pinia/models.js +++ b/web/canvas-app/src/stores/pinia/models.js @@ -88,6 +88,11 @@ const setStoredJson = (key, value) => { } } +const getValidStoredModel = (key, defaultValue, builtInModels) => { + const stored = getStored(key, defaultValue) + return builtInModels.some(model => model.key === stored) ? stored : defaultValue +} + /** * 检查模型是否支持指定渠道 */ @@ -160,8 +165,8 @@ export const useModelStore = defineStore('model', () => { // 选中的模型 const selectedChatModel = ref(getStored(STORAGE_KEYS.SELECTED_CHAT_MODEL, DEFAULT_CHAT_MODEL)) - const selectedImageModel = ref(getStored(STORAGE_KEYS.SELECTED_IMAGE_MODEL, DEFAULT_IMAGE_MODEL)) - const selectedVideoModel = ref(getStored(STORAGE_KEYS.SELECTED_VIDEO_MODEL, DEFAULT_VIDEO_MODEL)) + const selectedImageModel = ref(getValidStoredModel(STORAGE_KEYS.SELECTED_IMAGE_MODEL, DEFAULT_IMAGE_MODEL, IMAGE_MODELS)) + const selectedVideoModel = ref(getValidStoredModel(STORAGE_KEYS.SELECTED_VIDEO_MODEL, DEFAULT_VIDEO_MODEL, VIDEO_MODELS)) // 按渠道存储的 API 配置 const apiKeysByProvider = ref(getStoredJson(STORAGE_KEYS.API_KEYS_BY_PROVIDER, {})) @@ -205,47 +210,13 @@ export const useModelStore = defineStore('model', () => { })) ]) - const allImageModels = computed(() => [ - ...IMAGE_MODELS.map(m => ({ ...m, isCustom: false })), - ...customImageModels.value.map(m => ({ - label: m.label || m.key, - key: m.key, - isCustom: true, - sizes: [], - defaultParams: { quality: 'standard', style: 'vivid' } - })), - // 添加当前渠道的自定义模型 - ...(customImageModelsByProvider.value[currentProvider.value] || []).map(m => ({ - label: m.label || m.key, - key: m.key, - isCustom: true, - sizes: [], - defaultParams: { quality: 'standard', style: 'vivid' }, - provider: [currentProvider.value] - })) - ]) + const allImageModels = computed(() => + IMAGE_MODELS.map(m => ({ ...m, isCustom: false })) + ) - const allVideoModels = computed(() => [ - ...VIDEO_MODELS.map(m => ({ ...m, isCustom: false })), - ...customVideoModels.value.map(m => ({ - label: m.label || m.key, - key: m.key, - isCustom: true, - ratios: ['16x9', '9:16', '1:1'], - durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - defaultParams: { ratio: '16:9', duration: 5 } - })), - // 添加当前渠道的自定义模型 - ...(customVideoModelsByProvider.value[currentProvider.value] || []).map(m => ({ - label: m.label || m.key, - key: m.key, - isCustom: true, - ratios: ['16x9', '9:16', '1:1'], - durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - defaultParams: { ratio: '16:9', duration: 5 }, - provider: [currentProvider.value] - })) - ]) + const allVideoModels = computed(() => + VIDEO_MODELS.map(m => ({ ...m, isCustom: false })) + ) // ============ Computed: Available Models (filtered by provider) ============ @@ -412,29 +383,12 @@ export const useModelStore = defineStore('model', () => { provider: [provider] })) ] - const image = [ - ...IMAGE_MODELS.filter(m => isModelSupported(m, provider)).map(m => ({ ...m, isCustom: false })), - ...(customImageModelsByProvider.value[provider] || []).map(m => ({ - label: m.label || m.key, - key: m.key, - isCustom: true, - sizes: [], - defaultParams: { quality: 'standard', style: 'vivid' }, - provider: [provider] - })) - ] - const video = [ - ...VIDEO_MODELS.filter(m => isModelSupported(m, provider)).map(m => ({ ...m, isCustom: false })), - ...(customVideoModelsByProvider.value[provider] || []).map(m => ({ - label: m.label || m.key, - key: m.key, - isCustom: true, - ratios: ['16x9', '9:16', '1:1'], - durs: [{ label: '5 秒', key: 5 }, { label: '10 秒', key: 10 }], - defaultParams: { ratio: '16:9', duration: 5 }, - provider: [provider] - })) - ] + const image = IMAGE_MODELS + .filter(m => isModelSupported(m, provider)) + .map(m => ({ ...m, isCustom: false })) + const video = VIDEO_MODELS + .filter(m => isModelSupported(m, provider)) + .map(m => ({ ...m, isCustom: false })) return { chat, image, video } } diff --git a/web/canvas-app/src/stores/projects.js b/web/canvas-app/src/stores/projects.js index b285370..5072d13 100644 --- a/web/canvas-app/src/stores/projects.js +++ b/web/canvas-app/src/stores/projects.js @@ -345,8 +345,8 @@ export const initProjectsStore = () => { position: { x: 500, y: 150 }, data: { prompt: '', - model: 'doubao-seedream-4-5-251128', - size: '512x512', + model: 'auto', + size: '1024x1024', label: '文生图' } }
api/main.pyFastAPI 单文件后端:登录会话、状态模型、任务恢复、下载、抽帧、Vision、清洗、元素、分镜、原音频转写/翻译、声音与背景音分析、后续口播改写/TTS、文件返回;同时承载全局 prompt_libraryasset_library 的磁盘索引、CRUD、删除保护和复制到 job API。轻量创作入口 POST /creative/jobs/image 把上传图片或空白底图写成一个只有 0 号关键帧的 Job,让首页直接复用生图/生视频接口;该接口兼容无 body / JSON 空对象 / 正常 multipart 上传,避免无首帧文生图或文生视频时空 multipart 被 FastAPI 在业务前置解析阶段拒绝;/health 返回 image_optionsimage_size_optionsvideo_optionsvideo_size_optionsvideo_duration_optionsvideo_max_duration_seconds/frames/{idx}/generatemodel 字段用于图片模型偏好,size 字段用于图片输出尺寸;/storyboard/video 继续使用 model 字段选择视频别名,并先校验画幅与时长能力边界,然后把 GeneratedVideo 写成 queued 占位并进入进程内视频队列。队列默认 VIDEO_QUEUE_MAX_CONCURRENT=2VIDEO_QUEUE_MAX_CONCURRENT_PER_USER=1,同一用户连续提交不会占满全局并发;排队任务会回写 queue_positionqueue_sizequeue_message。旧 AgentRun 一键出片状态机、TK 复刻接口和 POST /creative/copy 继续保留。
video_model_options()视频模型能力出口:如果 seedanceklingveo3veo 等业务别名实际都映射到同一个真实模型,会按真实模型去重,只给前端返回一个可用选项;当前生产真实模型为 doubao-seedance-2-0-fast-260128,前端显示为 Seedance 2.0 Fast。后续只有在服务器真的配置了不同可用视频模型时,才应把新的模型重新暴露给画布。
api/product_library/skg-products内置 SKG 白底产品图库:manifest.json 记录从桌面产品图筛出的 gallery 白底图和桌面 4 张产品角度图,images/ 存 45 张参考图。
api/character_library/skg-characters内置相似主体形象库:从桌面 5 套策划形象导入,manifest.json 记录运动阳光男、都市型男、优雅白领女、运动辣妹、绅士大叔,每套含 7 张透明骨架参考图和一段 prompt_brief。相似主体生成时优先使用文字 brief 作为创意方向,避免把内置图作为强参考图复制。
asset_library/全局素材库目录,和 jobs/ 平级,不写入任何 job state。四类目录为 subjectsproductsscenesvideos;每个素材自带 manifest.json 和图片/视频文件,index.json 只是启动扫描重建出来的缓存。库素材选用到 job 时必须复制文件到 jobs/<jobId>/assetsstoryboard-videos,禁止直接保存 library 引用。