fix(llmodels): don't evaluate on cluster seed before a model is selected
The basic form seeds a default cluster on open, which fired the full cluster-change handler and triggered an evaluate request before any model was picked. Split off a seed callback that refreshes scoped options without evaluating, and guard the evaluate handler with a model-selected check as a backstop.
This commit is contained in:
@@ -85,6 +85,7 @@ interface BasicFormProps {
|
|||||||
}
|
}
|
||||||
>[];
|
>[];
|
||||||
handleClusterChange: (value: number) => void;
|
handleClusterChange: (value: number) => void;
|
||||||
|
onClusterSeed: (value: number) => void;
|
||||||
onSourceChange?: (value: string) => void;
|
onSourceChange?: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +95,7 @@ const BasicForm: React.FC<BasicFormProps> = (props) => {
|
|||||||
clusterList,
|
clusterList,
|
||||||
sourceDisable,
|
sourceDisable,
|
||||||
handleClusterChange,
|
handleClusterChange,
|
||||||
|
onClusterSeed,
|
||||||
onSourceChange
|
onSourceChange
|
||||||
} = props;
|
} = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -148,6 +150,8 @@ const BasicForm: React.FC<BasicFormProps> = (props) => {
|
|||||||
// selection and fall back to the scope's default cluster (then a Ready one,
|
// selection and fall back to the scope's default cluster (then a Ready one,
|
||||||
// then the first) so GPU/backend options refetch for it. A selection that's
|
// then the first) so GPU/backend options refetch for it. A selection that's
|
||||||
// still valid is left untouched, so a user's (or edit's) choice is kept.
|
// still valid is left untouched, so a user's (or edit's) choice is kept.
|
||||||
|
// Use the seed callback (not handleClusterChange) so this auto-pick refreshes
|
||||||
|
// options without firing an evaluate request before a model is selected.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!clusterOptions?.length) {
|
if (!clusterOptions?.length) {
|
||||||
return;
|
return;
|
||||||
@@ -167,8 +171,8 @@ const BasicForm: React.FC<BasicFormProps> = (props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
form.setFieldValue('cluster_id', next);
|
form.setFieldValue('cluster_id', next);
|
||||||
handleClusterChange?.(next);
|
onClusterSeed?.(next);
|
||||||
}, [clusterOptions, form, handleClusterChange]);
|
}, [clusterOptions, form, onClusterSeed]);
|
||||||
|
|
||||||
const clusterOptionRender = (option: any) => {
|
const clusterOptionRender = (option: any) => {
|
||||||
const { data } = option;
|
const { data } = option;
|
||||||
|
|||||||
@@ -236,20 +236,35 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
onOk(allValues);
|
onOk(allValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClusterChange = async (value: number) => {
|
// Shared work when the target cluster changes: refetch the GPU/backend
|
||||||
await onClusterChange?.(value);
|
// options for the new cluster and reset schedule/gpu selection.
|
||||||
|
const applyClusterScopedOptions = (value: number) => {
|
||||||
getGPUOptionList({ clusterId: value });
|
getGPUOptionList({ clusterId: value });
|
||||||
getBackendOptions({ cluster_id: value });
|
getBackendOptions({ cluster_id: value });
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
scheduleType: ScheduleValueMap.Auto,
|
scheduleType: ScheduleValueMap.Auto,
|
||||||
gpu_selector: null
|
gpu_selector: null
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// User explicitly picked a cluster: refresh scoped options and re-evaluate.
|
||||||
|
const handleClusterChange = async (value: number) => {
|
||||||
|
await onClusterChange?.(value);
|
||||||
|
applyClusterScopedOptions(value);
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 150);
|
setTimeout(resolve, 150);
|
||||||
});
|
});
|
||||||
onValuesChange?.({}, form.getFieldsValue());
|
onValuesChange?.({}, form.getFieldsValue());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// The basic form seeds a default cluster on open, before a model is picked.
|
||||||
|
// Refresh scoped options for it but don't fire the evaluate request — there
|
||||||
|
// is no model to evaluate yet.
|
||||||
|
const handleClusterSeed = async (value: number) => {
|
||||||
|
await onClusterChange?.(value);
|
||||||
|
applyClusterScopedOptions(value);
|
||||||
|
};
|
||||||
|
|
||||||
const getFieldPaths = (obj: Record<string, any>, prefix = ''): string => {
|
const getFieldPaths = (obj: Record<string, any>, prefix = ''): string => {
|
||||||
const result = Object.entries(obj).flatMap(([key, value]) => {
|
const result = Object.entries(obj).flatMap(([key, value]) => {
|
||||||
const path = prefix ? `${prefix}.${key}` : key;
|
const path = prefix ? `${prefix}.${key}` : key;
|
||||||
@@ -467,6 +482,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
|||||||
clusterList={clusterList}
|
clusterList={clusterList}
|
||||||
sourceDisable={sourceDisable}
|
sourceDisable={sourceDisable}
|
||||||
handleClusterChange={handleClusterChange}
|
handleClusterChange={handleClusterChange}
|
||||||
|
onClusterSeed={handleClusterSeed}
|
||||||
onSourceChange={onSourceChange}
|
onSourceChange={onSourceChange}
|
||||||
></BasicForm>
|
></BasicForm>
|
||||||
<CollapsePanel
|
<CollapsePanel
|
||||||
|
|||||||
@@ -401,6 +401,24 @@ export const useCheckCompatibility = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Evaluation needs a model reference. The basic form seeds a default cluster
|
||||||
|
// on open, which can fire onValuesChange before the user has picked a model —
|
||||||
|
// skip evaluation until the current source's model field is filled.
|
||||||
|
const noModelSelected = (allValues: any) => {
|
||||||
|
switch (allValues.source) {
|
||||||
|
case modelSourceMap.huggingface_value:
|
||||||
|
return !allValues.huggingface_repo_id;
|
||||||
|
case modelSourceMap.modelscope_value:
|
||||||
|
return !allValues.model_scope_model_id;
|
||||||
|
case modelSourceMap.ollama_library_value:
|
||||||
|
return !allValues.ollama_library_model_name;
|
||||||
|
case modelSourceMap.local_path_value:
|
||||||
|
return !allValues.local_path;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleOnValuesChange = async (params: {
|
const handleOnValuesChange = async (params: {
|
||||||
changedValues: any;
|
changedValues: any;
|
||||||
allValues: any;
|
allValues: any;
|
||||||
@@ -410,6 +428,7 @@ export const useCheckCompatibility = () => {
|
|||||||
if (
|
if (
|
||||||
_.isEqual(cacheFormValuesRef.current, allValues) ||
|
_.isEqual(cacheFormValuesRef.current, allValues) ||
|
||||||
noLocalPathValue(allValues) ||
|
noLocalPathValue(allValues) ||
|
||||||
|
noModelSelected(allValues) ||
|
||||||
!allValues.replicas
|
!allValues.replicas
|
||||||
) {
|
) {
|
||||||
console.log('No changes detected, skipping evaluation.');
|
console.log('No changes detected, skipping evaluation.');
|
||||||
|
|||||||
Reference in New Issue
Block a user