diff --git a/components/multimodal-input.tsx b/components/multimodal-input.tsx index b7c90c2f0..2992989f1 100644 --- a/components/multimodal-input.tsx +++ b/components/multimodal-input.tsx @@ -52,6 +52,14 @@ const VIDEO_PROVIDER_TYPES = new Set([ "fal-video", ]); +function isVideoProviderType(providerType?: string | null) { + return VIDEO_PROVIDER_TYPES.has(String(providerType || "")); +} + +function isImageGenerationModel(model: DynamicModel) { + return model.supportsImageGen === true && !isVideoProviderType(model.providerType); +} + function isNanoBananaProModel(model: { displayName?: string | null; modelId?: string | null }) { const displayName = String(model.displayName || "").toLowerCase(); const modelId = String(model.modelId || "").toLowerCase(); @@ -237,21 +245,20 @@ function PureMultimodalInput({ const models = modelCatalog?.models || []; const nanoBananaImage = models.find( (model) => - model.supportsImageGen && - !VIDEO_PROVIDER_TYPES.has(model.providerType || "") && + isImageGenerationModel(model) && isNanoBananaProModel(model) ); const imageMain = models.find( - (model) => model.supportsImageGen && !VIDEO_PROVIDER_TYPES.has(model.providerType || "") && !model.isSubModel + (model) => isImageGenerationModel(model) && !model.isSubModel ); const imageFallback = models.find( - (model) => model.supportsImageGen && !VIDEO_PROVIDER_TYPES.has(model.providerType || "") + (model) => isImageGenerationModel(model) ); const videoMain = models.find( - (model) => VIDEO_PROVIDER_TYPES.has(model.providerType || "") && !model.isSubModel + (model) => isVideoProviderType(model.providerType) && !model.isSubModel ); const videoFallback = models.find((model) => - VIDEO_PROVIDER_TYPES.has(model.providerType || "") + isVideoProviderType(model.providerType) ); return { image: nanoBananaImage || imageMain || imageFallback || null, @@ -260,14 +267,14 @@ function PureMultimodalInput({ }, [modelCatalog?.models]); const activeQuickMode = useMemo<"image" | "video" | null>(() => { - if (isVideoModel || VIDEO_PROVIDER_TYPES.has(currentSelectedModel?.providerType || "")) { + if (isVideoModel || isVideoProviderType(currentSelectedModel?.providerType)) { return "video"; } - if (isImageModel || currentSelectedModel?.supportsImageGen) { + if (isImageModel || (currentSelectedModel && isImageGenerationModel(currentSelectedModel))) { return "image"; } return null; - }, [currentSelectedModel?.providerType, currentSelectedModel?.supportsImageGen, isImageModel, isVideoModel]); + }, [currentSelectedModel, isImageModel, isVideoModel]); const switchComposerMode = useCallback( (targetMode: "image" | "video") => { @@ -1255,6 +1262,7 @@ function PureMultimodalInput({ selectedModelId={selectedModelId} attachments={attachments} avatarOnly={true} + modeFilter="video" /> ) : ( @@ -1269,6 +1277,7 @@ function PureMultimodalInput({ onModelChange={onModelChange} selectedModelId={selectedModelId} attachments={attachments} + modeFilter="image" /> ) : null} {isImageModel && onImageParamsChange && ( @@ -1417,12 +1426,14 @@ function PureModelSelectorCompact({ attachments, avatarOnly = false, readOnly = false, + modeFilter = "all", }: { selectedModelId: string; onModelChange?: (modelId: string) => void; attachments: Attachment[]; avatarOnly?: boolean; readOnly?: boolean; + modeFilter?: "all" | "image" | "video"; }) { const [optimisticModelId, setOptimisticModelId] = useState(selectedModelId); @@ -1496,9 +1507,20 @@ function PureModelSelectorCompact({ providerName: "System", }; + const modeFilteredModels = useMemo(() => { + if (modeFilter === "video") { + return dynamicModels.filter((model) => isVideoProviderType(model.providerType)); + } + if (modeFilter === "image") { + return dynamicModels.filter((model) => isImageGenerationModel(model)); + } + return dynamicModels; + }, [dynamicModels, modeFilter]); + // 合并模型列表(根据设置决定是否包含自动模型) const showAutoModel = siteSettings?.showAutoModel ?? true; - const displayModels = showAutoModel ? [autoModel, ...dynamicModels] : dynamicModels; + const canShowAutoModel = showAutoModel && modeFilter === "all"; + const displayModels = canShowAutoModel ? [autoModel, ...modeFilteredModels] : modeFilteredModels; useEffect(() => { setOptimisticModelId(selectedModelId); @@ -1510,10 +1532,12 @@ function PureModelSelectorCompact({ const currentExists = displayModels.some((m) => m.id === selectedModelId); if (currentExists) return; + const fallbackImageModel = + modeFilteredModels.find((model) => isNanoBananaProModel(model)) || + modeFilteredModels.find((model) => model.isDefault) || + modeFilteredModels[0]; const fallbackModelId = - dynamicModels.find((m) => m.isDefault)?.id ?? - dynamicModels[0]?.id ?? - (showAutoModel ? "auto" : ""); + fallbackImageModel?.id ?? (canShowAutoModel ? "auto" : ""); if (!fallbackModelId) return; @@ -1522,7 +1546,7 @@ function PureModelSelectorCompact({ startTransition(() => { saveChatModelAsCookie(fallbackModelId); }); - }, [displayModels, dynamicModels, onModelChange, selectedModelId, showAutoModel]); + }, [canShowAutoModel, displayModels, modeFilteredModels, onModelChange, selectedModelId]); // 自动选择模型的逻辑 - 必须在所有条件 return 之前定义 const selectBestModel = useCallback(() => { @@ -1760,6 +1784,9 @@ const ModelSelectorCompact = memo( if (prevProps.readOnly !== nextProps.readOnly) { return false; } + if (prevProps.modeFilter !== nextProps.modeFilter) { + return false; + } if (!equal(prevProps.attachments, nextProps.attachments)) { return false; }