feat(frontend): auto-detect vision models via backend, separate search filter from model classification

This commit is contained in:
Roland Tannous 2026-02-16 18:18:28 +00:00
commit 09f3b6bce5
7 changed files with 68 additions and 7 deletions

View file

@ -109,7 +109,7 @@ function SliderRow({
export function ParamsSection(): ReactElement {
const store = useTrainingConfigStore();
const isLora = store.trainingMethod !== "full";
const isVision = store.modelType === "vision";
const isVision = store.isVisionModel;
const [loraOpen, setLoraOpen] = useState(false);
const [hyperOpen, setHyperOpen] = useState(false);
@ -693,7 +693,7 @@ export function ParamsSection(): ReactElement {
</SelectContent>
</Select>
</Row>
{store.modelType !== "vision" && (
{!store.isVisionModel && (
<div className="flex items-center gap-2">
<Checkbox
id="packing"

View file

@ -70,7 +70,7 @@ export function StudioPage(): ReactElement {
datasetSplit={config.datasetSplit}
mode={dialogMode}
initialData={dialogInitial}
isVlm={config.modelType === "vision"}
isVlm={config.isVisionModel}
/>
{canGoBack && (

View file

@ -72,7 +72,7 @@ function buildCustomFormatMapping(
const { input, output } = config.datasetManualMapping;
if (!input || !output) return undefined;
if (config.modelType === "vision") {
if (config.isVisionModel) {
return { [input]: "image", [output]: "text" };
}

View file

@ -0,0 +1,21 @@
import { authFetch } from "@/features/auth";
interface VisionCheckResponse {
model_name: string;
is_vision: boolean;
}
/**
* Check whether a model is a vision model by asking the backend.
* Calls GET /api/models/check-vision/{model_name}.
*/
export async function checkVisionModel(modelName: string): Promise<boolean> {
const encoded = encodeURIComponent(modelName);
const response = await authFetch(`/api/models/check-vision/${encoded}`);
if (!response.ok) {
// If the check fails (e.g. network error), default to non-vision
return false;
}
const data = (await response.json()) as VisionCheckResponse;
return data.is_vision;
}

View file

@ -29,7 +29,7 @@ export function useTrainingActions() {
try {
const datasetName = getDatasetName(config);
const isVlm = config.modelType === "vision";
const isVlm = config.isVisionModel;
if (datasetName) {
const check = await checkDatasetFormat({

View file

@ -3,6 +3,7 @@ import type { StepNumber } from "@/types/training";
import { create } from "zustand";
import { persist } from "zustand/middleware";
import type { TrainingConfigState, TrainingConfigStore } from "../types/config";
import { checkVisionModel } from "../api/models-api";
const MIN_STEP: StepNumber = 1;
const MAX_STEP: StepNumber = STEPS.length as StepNumber;
@ -24,9 +25,15 @@ const initialState: TrainingConfigState = {
datasetSplit: null,
datasetManualMapping: emptyManualMapping(),
uploadedFile: null,
isCheckingVision: false,
isVisionModel: false,
...DEFAULT_HYPERPARAMS,
};
// AbortController for in-flight vision checks so rapid model changes
// cancel stale requests.
let _visionCheckController: AbortController | null = null;
function clampStep(step: number): StepNumber {
return Math.min(MAX_STEP, Math.max(MIN_STEP, step)) as StepNumber;
}
@ -57,7 +64,38 @@ export const useTrainingConfigStore = create<TrainingConfigStore>()(
nextStep: () => set({ currentStep: clampStep(get().currentStep + 1) }),
prevStep: () => set({ currentStep: clampStep(get().currentStep - 1) }),
setModelType: (modelType) => set({ modelType, selectedModel: null }),
setSelectedModel: (selectedModel) => set({ selectedModel }),
setSelectedModel: (selectedModel) => {
set({ selectedModel });
// Cancel any in-flight vision check
_visionCheckController?.abort();
_visionCheckController = null;
if (!selectedModel) {
set({ isCheckingVision: false });
return;
}
// Fire async backend check to determine if model is vision
const controller = new AbortController();
_visionCheckController = controller;
set({ isCheckingVision: true });
checkVisionModel(selectedModel)
.then((isVision) => {
// Only apply if this is still the active check
if (controller.signal.aborted) return;
set({
isVisionModel: isVision,
isCheckingVision: false,
});
})
.catch(() => {
if (controller.signal.aborted) return;
// On error, default to text and stop loading
set({ isCheckingVision: false });
});
},
setTrainingMethod: (trainingMethod) => set({ trainingMethod }),
setHfToken: (hfToken) => set({ hfToken }),
setDatasetSource: (datasetSource) => set({ datasetSource }),
@ -130,7 +168,7 @@ export const useTrainingConfigStore = create<TrainingConfigStore>()(
return s as unknown as TrainingConfigStore;
},
partialize: (state) => {
const { modelType, ...rest } = state;
const { modelType, isCheckingVision, isVisionModel, ...rest } = state;
return rest;
},
},

View file

@ -50,6 +50,8 @@ export interface TrainingConfigState {
enableTensorboard: boolean;
tensorboardDir: string;
logFrequency: number;
isCheckingVision: boolean;
isVisionModel: boolean;
finetuneVisionLayers: boolean;
finetuneLanguageLayers: boolean;
finetuneAttentionModules: boolean;