From 370263cd83dc9df02770edab2c04a6c5a8de620d Mon Sep 17 00:00:00 2001 From: HouYunFei <1844025705@qq.com> Date: Sat, 23 May 2026 17:25:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(canvas):=20=E6=B7=BB=E5=8A=A0=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=A8=A1=E5=9E=8B=E9=85=8D=E7=BD=AE=E5=92=8C=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在系统配置中新增 defaultVideoModel 字段用于设置默认视频模型 - 更新应用配置模态框以支持视频模型的选择和验证 - 修改画布节点逻辑以支持视频类型的生成配置 - 实现视频节点的生成和上传功能 - 更新模型选择器组件以支持视频模型选择 - 在管理后台设置页面添加视频模型配置选项 - 优化模型选择器的层级和交互体验 - 添加对 DeepSeek 和 GLM 模型图标的支持 --- docs/backend-database.md | 1 + docs/pending-test.md | 1 + model/setting.go | 1 + web/public/icons/deepseek.svg | 1 + web/public/icons/glm.svg | 1 + web/src/app/(admin)/admin/settings/page.tsx | 12 +++++++++--- .../(user)/canvas/[id]/canvas-client-page.tsx | 7 ++++++- .../(user)/canvas/components/canvas-node.tsx | 2 +- web/src/components/layout/app-config-modal.tsx | 8 ++++++-- web/src/components/model-picker.tsx | 18 ++++++++++++++++-- web/src/components/ui/select.tsx | 2 +- web/src/services/api/admin.ts | 1 + web/src/services/api/video.ts | 7 ++++--- web/src/stores/use-config-store.ts | 2 +- 14 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 web/public/icons/deepseek.svg create mode 100644 web/public/icons/glm.svg diff --git a/docs/backend-database.md b/docs/backend-database.md index 16e0c08..f91a3eb 100644 --- a/docs/backend-database.md +++ b/docs/backend-database.md @@ -120,6 +120,7 @@ | `availableModels` | string[] | 系统可用模型列表 | | `defaultModel` | string | 默认模型 | | `defaultImageModel` | string | 默认图片模型 | +| `defaultVideoModel` | string | 默认视频模型 | | `defaultTextModel` | string | 默认文本模型 | | `systemPrompt` | string | 系统提示词 | | `allowCustomChannel` | bool | 是否允许用户自定义渠道,默认允许,关闭后前端只提供走后端渠道的模式 | diff --git a/docs/pending-test.md b/docs/pending-test.md index 465b2cf..2449f3f 100644 --- a/docs/pending-test.md +++ b/docs/pending-test.md @@ -1,5 +1,6 @@ # 待测试 +- 配置弹窗和后台公开配置新增默认视频模型;视频节点生成会使用该默认视频模型,需要确认本地直连和云端渠道下默认视频模型选择、保存和生成请求都正常。 - 画布新增视频节点:工具栏可新建视频节点,上传/拖拽本地视频后可在节点内播放、下载、保存到我的素材;节点下方对话框和生成配置节点支持视频生成,前端会调用 OpenAI 兼容 `videos` 接口并把生成结果保存为本地视频 Blob;后端远程渠道新增 `/api/v1/videos`、`/api/v1/videos/:id`、`/api/v1/videos/:id/content` 代理,需要确认本地渠道和远程渠道都能生成、轮询和下载视频。 - 模型选择器改为共享的 shadcn 风格下拉,按模型名称显示 OpenAI、Claude、Gemini 图标,并把画布节点下方、右侧助手和配置弹窗统一到同一套组件;右侧助手的模型和模式按钮会随工具栏宽度自动在文字态和图标态之间切换,需要确认生图页、画布助手、节点配置和配置弹窗中的选中态和下拉列表显示正常。 - 画布图像设置里的尺寸输入去掉了原生数字步进箭头,需要确认宽高仍可直接输入、禁用态正常、设置值回写正常。 diff --git a/model/setting.go b/model/setting.go index c4cdf1a..701d4a1 100644 --- a/model/setting.go +++ b/model/setting.go @@ -26,6 +26,7 @@ type PublicModelChannelSetting struct { AvailableModels []string `json:"availableModels"` DefaultModel string `json:"defaultModel"` DefaultImageModel string `json:"defaultImageModel"` + DefaultVideoModel string `json:"defaultVideoModel"` DefaultTextModel string `json:"defaultTextModel"` SystemPrompt string `json:"systemPrompt"` AllowCustomChannel *bool `json:"allowCustomChannel"` diff --git a/web/public/icons/deepseek.svg b/web/public/icons/deepseek.svg new file mode 100644 index 0000000..3fc2302 --- /dev/null +++ b/web/public/icons/deepseek.svg @@ -0,0 +1 @@ +DeepSeek \ No newline at end of file diff --git a/web/public/icons/glm.svg b/web/public/icons/glm.svg new file mode 100644 index 0000000..0c6e61c --- /dev/null +++ b/web/public/icons/glm.svg @@ -0,0 +1 @@ +Zhipu \ No newline at end of file diff --git a/web/src/app/(admin)/admin/settings/page.tsx b/web/src/app/(admin)/admin/settings/page.tsx index 3177735..456763a 100644 --- a/web/src/app/(admin)/admin/settings/page.tsx +++ b/web/src/app/(admin)/admin/settings/page.tsx @@ -30,6 +30,7 @@ const emptySettings: AdminSettings = { availableModels: [], defaultModel: "", defaultImageModel: "", + defaultVideoModel: "", defaultTextModel: "", systemPrompt: "", allowCustomChannel: true, @@ -315,17 +316,22 @@ export default function AdminSettingsPage() { ({ label: item, value: item }))} /> - + ({ label: item, value: item }))} /> + + + event.stopPropagation()} onMouseDown={(event) => event.stopPropagation()} > @@ -93,5 +105,7 @@ function resolveModelIcon(model: string) { if (name.includes("gemini") || name.includes("google")) return "/icons/gemini.svg"; if (name.includes("gpt") || name.includes("openai")) return "/icons/openai.svg"; if (name.includes("grok") || name.includes("grok")) return "/icons/grok.svg"; + if (name.includes("deepseek") || name.includes("deepseek")) return "/icons/deepseek.svg"; + if (name.includes("glm") || name.includes("glm")) return "/icons/glm.svg"; return ""; } diff --git a/web/src/components/ui/select.tsx b/web/src/components/ui/select.tsx index 706d3d9..414714f 100644 --- a/web/src/components/ui/select.tsx +++ b/web/src/components/ui/select.tsx @@ -73,7 +73,7 @@ function SelectContent({ (aiApiUrl(config, "/videos"), { model: config.model, prompt, size: config.size || undefined }, { headers: { ...(aiHeaders(config) || {}), "Content-Type": "application/json" } }); + const model = config.model || config.videoModel; + const created = await axios.post(aiApiUrl(config, "/videos"), { model, prompt, size: config.size || undefined }, { headers: { ...(aiHeaders(config) || {}), "Content-Type": "application/json" } }); for (;;) { - const video = await axios.get(aiApiUrl(config, `/videos/${created.data.id}`), { headers: aiHeaders(config), params: config.channelMode === "remote" ? { model: config.model } : undefined }); + const video = await axios.get(aiApiUrl(config, `/videos/${created.data.id}`), { headers: aiHeaders(config), params: config.channelMode === "remote" ? { model } : undefined }); if (video.data.status === "completed") break; if (video.data.status === "failed" || video.data.status === "cancelled") throw new Error(video.data.error?.message || "视频生成失败"); await new Promise((resolve) => setTimeout(resolve, 2500)); } - const content = await axios.get(aiApiUrl(config, `/videos/${created.data.id}/content`), { headers: aiHeaders(config), params: config.channelMode === "remote" ? { model: config.model } : undefined, responseType: "blob" }); + const content = await axios.get(aiApiUrl(config, `/videos/${created.data.id}/content`), { headers: aiHeaders(config), params: config.channelMode === "remote" ? { model } : undefined, responseType: "blob" }); return content.data; } diff --git a/web/src/stores/use-config-store.ts b/web/src/stores/use-config-store.ts index db9332f..698eaca 100644 --- a/web/src/stores/use-config-store.ts +++ b/web/src/stores/use-config-store.ts @@ -63,7 +63,7 @@ function resolveEffectiveConfig(config: AiConfig, modelChannel: AdminPublicSetti models, model: models.includes(config.model) ? config.model : modelChannel.defaultModel, imageModel: models.includes(config.imageModel) ? config.imageModel : modelChannel.defaultImageModel || modelChannel.defaultModel, - videoModel: models.includes(config.videoModel) ? config.videoModel : "sora-2", + videoModel: models.includes(config.videoModel) ? config.videoModel : modelChannel.defaultVideoModel || modelChannel.defaultModel || "sora-2", textModel: models.includes(config.textModel) ? config.textModel : modelChannel.defaultTextModel || modelChannel.defaultModel, systemPrompt: modelChannel.systemPrompt, };