fix(web): 修正在线测试模型类型筛选

移除可能将 image_to_video 误判为图像模型的子串回退,在没有匹配模型时显示空状态。

新增图像生成、图像编辑和视频模式回归测试。验证通过:前端 94 项测试、类型检查、lint 和生产构建。
This commit is contained in:
chengcheng
2026-07-21 13:12:40 +08:00
parent e533ec2367
commit 69b0c107d3
2 changed files with 56 additions and 8 deletions
@@ -0,0 +1,48 @@
import type { PlatformModel } from '@easyai-ai-gateway/contracts';
import { describe, expect, it } from 'vitest';
import { filterModelsForMode } from './PlaygroundPage';
function model(id: string, modelType: string[]) {
return { id, modelType } as PlatformModel;
}
function modelIds(models: PlatformModel[]) {
return models.map((item) => item.id);
}
describe('playground model filtering', () => {
const models = [
model('image-generation', ['image_generate']),
model('legacy-image', ['image']),
model('image-edit', ['image_edit']),
model('image-to-video', ['video_generate', 'image_to_video']),
model('text-to-video', ['text_to_video']),
];
it('only shows image generation models when no reference image is present', () => {
expect(modelIds(filterModelsForMode(models, 'image', false, 'text_to_video'))).toEqual([
'image-generation',
'legacy-image',
]);
});
it('only shows image editing models when a reference image is present', () => {
expect(modelIds(filterModelsForMode(models, 'image', true, 'text_to_video'))).toEqual([
'legacy-image',
'image-edit',
]);
});
it('does not fall back to image-to-video models when no image model is available', () => {
const videoOnlyModels = [
model('kling-3-turbo', ['video_generate', 'image_to_video']),
model('kling-1-5', ['image_to_video']),
];
expect(filterModelsForMode(videoOnlyModels, 'image', false, 'text_to_video')).toEqual([]);
});
it('keeps image-to-video models available for the matching video mode', () => {
expect(modelIds(filterModelsForMode(models, 'video', false, 'first_last_frame'))).toContain('image-to-video');
});
});
+8 -8
View File
@@ -810,7 +810,7 @@ function Composer(props: {
<Select className="playgroundModelSelect" value={props.selectedModel ?? ''} disabled={!props.modelOptions.length} onChange={(event) => props.onModelChange(event.target.value)}>
{props.modelOptions.length ? props.modelOptions.map((item) => (
<option value={item.value} key={item.value}>{modelOptionLabel(item)}</option>
)) : <option value=""></option>}
)) : <option value="">{props.compact ? '模型选择' : '暂无可用模型'}</option>}
</Select>
{props.mode !== 'chat' && props.mediaSettings && props.onMediaSettingsChange && (
<MediaSettingsPopover
@@ -977,25 +977,25 @@ function mediaPromptPlaceholder(mode: PlaygroundMode) {
return placeholderByMode.chat;
}
function filterModelsForMode(models: PlatformModel[], mode: PlaygroundMode, hasReference: boolean, videoMode: VideoCreateMode) {
export function filterModelsForMode(models: PlatformModel[], mode: PlaygroundMode, hasReference: boolean, videoMode: VideoCreateMode) {
if (mode === 'chat') {
return filterWithFallback(models, ['text_generate', 'chat', 'responses', 'text']);
return filterModelsByType(models, ['text_generate', 'chat', 'responses', 'text']);
}
if (mode === 'image') {
const preferredTypes = hasReference ? ['image_edit', 'images.edits'] : ['image_generate', 'images.generations'];
return filterWithFallback(models, [...preferredTypes, 'image']);
return filterModelsByType(models, [...preferredTypes, 'image']);
}
const videoTypesByMode: Record<VideoCreateMode, string[]> = {
first_last_frame: ['video_first_last_frame', 'image_to_video', 'video_generate'],
omni_reference: ['omni_video', 'video_reference', 'video_generate'],
text_to_video: ['text_to_video', 'video_generate'],
};
return filterWithFallback(models, [...videoTypesByMode[videoMode], 'video']);
return filterModelsByType(models, [...videoTypesByMode[videoMode], 'video']);
}
function filterWithFallback(models: PlatformModel[], modelTypes: string[]) {
const exact = models.filter((model) => model.modelType.some((type) => modelTypes.includes(type)));
return exact.length ? exact : models.filter((model) => modelTypes.some((type) => model.modelType.some((modelType) => modelType.includes(type) || type.includes(modelType))));
function filterModelsByType(models: PlatformModel[], modelTypes: string[]) {
const acceptedTypes = new Set(modelTypes);
return models.filter((model) => model.modelType.some((type) => acceptedTypes.has(type)));
}
function buildModelOptions(models: PlatformModel[]): ModelOption[] {