From b22c4c5c2f72b56f041b235861a1c9bfbfcfd2d7 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 21 Mar 2026 00:18:26 +0800 Subject: [PATCH] fix: include runway models in canvas video mode --- app/(chat)/api/models/route.ts | 40 +++++++++---- components/canvas/CanvasChatPanel.tsx | 81 +++++++++++++++++++++++---- 2 files changed, 98 insertions(+), 23 deletions(-) diff --git a/app/(chat)/api/models/route.ts b/app/(chat)/api/models/route.ts index 8a775a198..5fd6a2177 100644 --- a/app/(chat)/api/models/route.ts +++ b/app/(chat)/api/models/route.ts @@ -6,17 +6,13 @@ import { getUserById, } from "@/lib/db/queries"; import { buildVideoToolCapability } from "@/lib/ai/video-tool-capability"; +import { + isImageGenerationModelDescriptor, + isVideoModelDescriptor, +} from "@/lib/ai/model-classification"; import { ChatSDKError } from "@/lib/errors"; import { isAiModelAgentAuto, isAiModelMultimodal } from "@/lib/db/ai-model-flags"; -const VIDEO_PROVIDER_TYPES = new Set([ - "volcano-video", - "doubao-video", - "qianwen-video", - "fal-video", - "ruxa-video", -]); - function isNanoBananaProModel(model: { displayName?: string | null; modelId?: string | null }) { const displayName = String(model.displayName || "").toLowerCase(); const modelId = String(model.modelId || "").toLowerCase(); @@ -103,14 +99,34 @@ export async function GET() { })); const preferredNanoBananaModel = models.find( (model) => - model.supportsImageGen && - !VIDEO_PROVIDER_TYPES.has(model.providerType || "") && + isImageGenerationModelDescriptor({ + providerType: model.providerType, + modelId: model.modelId, + capabilities: model.capabilities, + supportsImageGen: model.supportsImageGen, + }) && + !isVideoModelDescriptor({ + providerType: model.providerType, + modelId: model.modelId, + capabilities: model.capabilities, + supportsImageGen: model.supportsImageGen, + }) && isNanoBananaProModel(model) ); const preferredImageModel = models.find( (model) => - model.supportsImageGen && - !VIDEO_PROVIDER_TYPES.has(model.providerType || "") + isImageGenerationModelDescriptor({ + providerType: model.providerType, + modelId: model.modelId, + capabilities: model.capabilities, + supportsImageGen: model.supportsImageGen, + }) && + !isVideoModelDescriptor({ + providerType: model.providerType, + modelId: model.modelId, + capabilities: model.capabilities, + supportsImageGen: model.supportsImageGen, + }) ); const resolvedDefaultModelId = diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index d97d80deb..c6ad94840 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -47,6 +47,10 @@ import { inferOriginalImageUrl, } from "@/lib/image-url"; import { buildImageProxyUrl } from "@/lib/image-delivery"; +import { + isImageGenerationModelDescriptor, + isVideoModelDescriptor, +} from "@/lib/ai/model-classification"; import type { Attachment, ChatMessage } from "@/lib/types"; import type { AgentPreset, @@ -2844,16 +2848,25 @@ export function CanvasChatPanel({ setSelectedSkillTagId(null); }, [isWorkspacePresentation, selectedSkillTagId]); - const VIDEO_PROVIDER_TYPES = new Set(["volcano-video", "doubao-video", "qianwen-video", "fal-video", "ruxa-video"]); const visualModels = useMemo(() => { const filtered = modelsData?.models?.filter( (model) => model.modelId !== "volcano-video-master" && ( - model.supportsImageGen || + isImageGenerationModelDescriptor({ + providerType: model.providerType, + modelId: model.modelId, + capabilities: model.capabilities, + supportsImageGen: model.supportsImageGen, + }) || model.isMultimodal || - VIDEO_PROVIDER_TYPES.has(model.providerType) + isVideoModelDescriptor({ + providerType: model.providerType, + modelId: model.modelId, + capabilities: model.capabilities, + supportsImageGen: model.supportsImageGen, + }) ) ) || []; @@ -2918,9 +2931,19 @@ export function CanvasChatPanel({ () => visualModels.filter( (model) => - model.supportsImageGen && + isImageGenerationModelDescriptor({ + providerType: model.providerType, + modelId: model.modelId, + capabilities: model.capabilities, + supportsImageGen: model.supportsImageGen, + }) && !model.isMultimodal && - !VIDEO_PROVIDER_TYPES.has(model.providerType) + !isVideoModelDescriptor({ + providerType: model.providerType, + modelId: model.modelId, + capabilities: model.capabilities, + supportsImageGen: model.supportsImageGen, + }) ), [visualModels] ); @@ -2931,23 +2954,59 @@ export function CanvasChatPanel({ model.id !== "auto" && model.id !== modelsData?.agentModel?.id && model.isMultimodal && - !model.supportsImageGen && - !VIDEO_PROVIDER_TYPES.has(model.providerType) + !isImageGenerationModelDescriptor({ + providerType: model.providerType, + modelId: model.modelId, + capabilities: model.capabilities, + supportsImageGen: model.supportsImageGen, + }) && + !isVideoModelDescriptor({ + providerType: model.providerType, + modelId: model.modelId, + capabilities: model.capabilities, + supportsImageGen: model.supportsImageGen, + }) ), [allModels, modelsData?.agentModel?.id] ); const videoOnlyModels = useMemo( - () => visualModels.filter((model) => VIDEO_PROVIDER_TYPES.has(model.providerType)), + () => + visualModels.filter((model) => + isVideoModelDescriptor({ + providerType: model.providerType, + modelId: model.modelId, + capabilities: model.capabilities, + supportsImageGen: model.supportsImageGen, + }) + ), [visualModels] ); const currentModel = allModels.find((model) => model.id === selectedModelId); - const isVideoModel = Boolean(currentModel && VIDEO_PROVIDER_TYPES.has(currentModel.providerType)); + const isVideoModel = Boolean( + currentModel && + isVideoModelDescriptor({ + providerType: currentModel.providerType, + modelId: currentModel.modelId, + capabilities: currentModel.capabilities, + supportsImageGen: currentModel.supportsImageGen, + }) + ); const isConversationModel = Boolean( currentModel && currentModel.id !== "auto" && currentModel.isMultimodal && - !currentModel.supportsImageGen && - !VIDEO_PROVIDER_TYPES.has(currentModel.providerType) + !isImageGenerationModelDescriptor({ + providerType: currentModel.providerType, + modelId: currentModel.modelId, + capabilities: currentModel.capabilities, + supportsImageGen: currentModel.supportsImageGen, + }) && + !isVideoModelDescriptor({ + providerType: currentModel.providerType, + modelId: currentModel.modelId, + capabilities: currentModel.capabilities, + supportsImageGen: currentModel.supportsImageGen, + }) ); const isAgentModel = !isDetailGenerationMode &&