fix(chat): split image and video model lists by mode
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-02 22:10:41 +08:00
parent abadcdaa18
commit eec91f3c6f

View File

@ -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;
}