mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-30 04:29:24 +08:00
fix(web): identify provider instances by id and skip clean cards on save (#17424)
This commit is contained in:
@@ -57,13 +57,8 @@ export const LlmKeys = {
|
||||
[LLMApiAction.AllModels, modelType] as const,
|
||||
providerInstances: (providerName: string) =>
|
||||
[LLMApiAction.AddedProviders, providerName, 'instances'] as const,
|
||||
providerInstance: (providerName: string, instanceName: string) =>
|
||||
[
|
||||
LLMApiAction.AddedProviders,
|
||||
providerName,
|
||||
instanceName,
|
||||
'instance',
|
||||
] as const,
|
||||
providerInstance: (providerName: string, id: string) =>
|
||||
[LLMApiAction.AddedProviders, providerName, id, 'instance'] as const,
|
||||
instanceModels: (providerName: string, instanceName: string) =>
|
||||
[
|
||||
LLMApiAction.AddedProviders,
|
||||
@@ -165,18 +160,15 @@ export const useFetchProviderInstances = (providerName: string) => {
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchProviderInstance = (
|
||||
providerName: string,
|
||||
instanceName: string,
|
||||
) => {
|
||||
export const useFetchProviderInstance = (providerName: string, id: string) => {
|
||||
return useQuery<IProviderInstance>({
|
||||
queryKey: LlmKeys.providerInstance(providerName, instanceName),
|
||||
queryKey: LlmKeys.providerInstance(providerName, id),
|
||||
initialData: undefined as unknown as IProviderInstance,
|
||||
gcTime: 0,
|
||||
enabled: false,
|
||||
queryFn: async () => {
|
||||
const { data } = await llmService.showProviderInstance(
|
||||
{ provider_name: providerName, instance_name: instanceName },
|
||||
{ provider_name: providerName, id },
|
||||
true,
|
||||
);
|
||||
return (data?.data ?? {}) as IProviderInstance;
|
||||
@@ -584,10 +576,7 @@ export const useUpdateProviderInstance = () => {
|
||||
queryKey: LlmKeys.providerInstances(params.provider_name),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.providerInstance(
|
||||
params.provider_name,
|
||||
params.instance_name,
|
||||
),
|
||||
queryKey: LlmKeys.providerInstance(params.provider_name, params.id),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.instanceModels(
|
||||
|
||||
@@ -54,7 +54,7 @@ export interface IDeleteProviderInstanceRequestBody {
|
||||
|
||||
export interface IShowProviderInstanceRequestParams {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface IAddInstanceModelRequestBody {
|
||||
@@ -104,7 +104,7 @@ export interface IDeleteInstanceModelsRequestBody {
|
||||
export interface IUpdateProviderInstanceRequestBody {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
id?: string;
|
||||
id: string;
|
||||
/**
|
||||
* Either a plain API-key string, or — for providers that need an
|
||||
* extra credential such as MiniMax's `group_id` — an object bundling
|
||||
|
||||
@@ -176,20 +176,18 @@ const SettingModelV2: FC = () => {
|
||||
);
|
||||
if (refs.length === 0) return;
|
||||
|
||||
// 1. Validate every card up front. Block all saves if any is invalid.
|
||||
const dirty = refs
|
||||
.map((r) => ({ ref: r, payload: r.getSavePayload() }))
|
||||
.filter((e) => e.payload !== null);
|
||||
if (dirty.length === 0) return;
|
||||
|
||||
const validations = await Promise.all(
|
||||
refs.map(async (r) => ({ ref: r, valid: await r.validate() })),
|
||||
dirty.map(async (e) => ({ ...e, valid: await e.ref.validate() })),
|
||||
);
|
||||
if (validations.some((v) => !v.valid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Collect dirty payloads (null = nothing to save for this card).
|
||||
const entries = validations
|
||||
.map((v) => ({ ref: v.ref, payload: v.ref.getSavePayload() }))
|
||||
.filter((e) => e.payload !== null);
|
||||
if (entries.length === 0) return;
|
||||
|
||||
setSaving(true);
|
||||
// Pin the auto-show guard so a draft isn't re-spawned while the
|
||||
// providerInstances refetch is in flight (during that window both
|
||||
@@ -200,7 +198,7 @@ const SettingModelV2: FC = () => {
|
||||
// 3. Dispatch one API call per dirty card. Sequential so any
|
||||
// backend error stops the batch and the user can retry the
|
||||
// remaining cards after fixing the issue.
|
||||
for (const { ref, payload } of entries) {
|
||||
for (const { ref, payload } of validations) {
|
||||
if (!payload) continue;
|
||||
if (payload.apiKind === 'add') {
|
||||
const ret = await addProviderInstance(payload.payload as any);
|
||||
|
||||
@@ -167,7 +167,7 @@ export const BedrockInstanceCard = forwardRef<
|
||||
const { data: instanceDetails, refetch: refetchInstanceDetails } =
|
||||
useFetchProviderInstance(
|
||||
isDraft ? '' : providerName,
|
||||
isDraft ? '' : instance.instance_name,
|
||||
isDraft ? '' : instance.id,
|
||||
);
|
||||
|
||||
// Lazily fetch full instance details only when the card is open.
|
||||
@@ -365,6 +365,12 @@ export const BedrockInstanceCard = forwardRef<
|
||||
apiKind: 'add' as const,
|
||||
};
|
||||
}
|
||||
// Skip cards the user hasn't actually edited. Collapsed cards
|
||||
// never fetch `instanceDetails`, so their form is still on empty
|
||||
// defaults - the signature check below would false-positive on
|
||||
// defaulting differences. `form.formState.isDirty` tracks real
|
||||
// user interaction.
|
||||
if (!form.formState.isDirty) return null;
|
||||
const values = form.getValues();
|
||||
const payload = buildPayload(values, instance.instance_name);
|
||||
const finalPayload = {
|
||||
|
||||
@@ -232,21 +232,21 @@ export function useProviderInitialValues(
|
||||
*/
|
||||
export function useLazyInstanceDetails(
|
||||
providerName: string,
|
||||
instanceName: string,
|
||||
instanceId: string,
|
||||
isDraft: boolean,
|
||||
open: boolean,
|
||||
) {
|
||||
const { data: instanceDetails, refetch: refetchInstanceDetails } =
|
||||
useFetchProviderInstance(
|
||||
isDraft ? '' : providerName,
|
||||
isDraft ? '' : instanceName,
|
||||
isDraft ? '' : instanceId,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDraft && open && providerName && instanceName) {
|
||||
if (!isDraft && open && providerName && instanceId) {
|
||||
refetchInstanceDetails();
|
||||
}
|
||||
}, [isDraft, open, providerName, instanceName, refetchInstanceDetails]);
|
||||
}, [isDraft, open, providerName, instanceId, refetchInstanceDetails]);
|
||||
|
||||
return { instanceDetails, refetchInstanceDetails };
|
||||
}
|
||||
@@ -590,9 +590,14 @@ export function useInstanceSaveState({
|
||||
// `getSavePayload()` is the imperative entry point the parent calls
|
||||
// when the user clicks the top Save button. For drafts it always
|
||||
// returns a payload (provided the name is non-empty); for saved
|
||||
// cards it returns `null` when the current signature matches the
|
||||
// baseline, so the parent skips the no-op PUT.
|
||||
// cards it returns `null` when the card hasn't been touched by the
|
||||
// user, so the parent skips both validation and the no-op PUT.
|
||||
const getSavePayload = useCallback((): InstanceSavePayload | null => {
|
||||
if (!isDraft) {
|
||||
const formDirty = formRef.current?.isDirty() ?? false;
|
||||
const renamed = editedNameRef.current !== instanceName;
|
||||
if (!formDirty && !renamed) return null;
|
||||
}
|
||||
const payload = buildPayload();
|
||||
if (!payload) return null;
|
||||
if (!isDraft) {
|
||||
@@ -610,7 +615,7 @@ export function useInstanceSaveState({
|
||||
// `IUpdateProviderInstanceRequestBody`).
|
||||
apiKind: isDraft ? 'add' : 'update',
|
||||
};
|
||||
}, [buildPayload, isDraft, instanceName]);
|
||||
}, [buildPayload, isDraft, instanceName, formRef]);
|
||||
|
||||
// After a successful save the parent calls `markSaved()` so the
|
||||
// baseline catches up to the just-persisted values. Without this,
|
||||
|
||||
@@ -592,6 +592,7 @@ export function useModelMutations({
|
||||
const { apiKey, baseUrl } = resolveCreds();
|
||||
await updateProviderInstance({
|
||||
provider_name: providerName,
|
||||
id: instance!.id,
|
||||
instance_name: instanceName,
|
||||
api_key: apiKey,
|
||||
base_url: baseUrl,
|
||||
|
||||
@@ -127,7 +127,7 @@ const GenericProviderInstanceCard = forwardRef<
|
||||
const { baseUrlOptions } = useProviderBaseUrlOptions(providerName);
|
||||
const { instanceDetails } = useLazyInstanceDetails(
|
||||
providerName,
|
||||
instance.instance_name,
|
||||
instance.id,
|
||||
isDraft,
|
||||
open,
|
||||
);
|
||||
|
||||
@@ -169,7 +169,7 @@ export const SoMarkInstanceCard = forwardRef<
|
||||
const { data: instanceDetails, refetch: refetchInstanceDetails } =
|
||||
useFetchProviderInstance(
|
||||
isDraft ? '' : providerName,
|
||||
isDraft ? '' : instance.instance_name,
|
||||
isDraft ? '' : instance.id,
|
||||
);
|
||||
|
||||
// Lazily fetch full instance details only when the card is open.
|
||||
@@ -398,6 +398,7 @@ export const SoMarkInstanceCard = forwardRef<
|
||||
};
|
||||
}
|
||||
const values = form.getValues();
|
||||
if (!form.formState.isDirty) return null;
|
||||
const payload = buildPayload(values, instance.instance_name);
|
||||
const finalPayload = {
|
||||
...payload,
|
||||
|
||||
@@ -49,11 +49,11 @@ export default {
|
||||
`${restAPIv1}/providers/${provider_name}/instances/${instance_name}/models`,
|
||||
showProviderInstance: ({
|
||||
provider_name,
|
||||
instance_name,
|
||||
id,
|
||||
}: {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
}) => `${restAPIv1}/providers/${provider_name}/instances/${instance_name}`,
|
||||
id: string;
|
||||
}) => `${restAPIv1}/providers/${provider_name}/instances/${id}`,
|
||||
addInstanceModel: ({
|
||||
provider_name,
|
||||
instance_name,
|
||||
@@ -74,11 +74,11 @@ export default {
|
||||
`${restAPIv1}/providers/${provider_name}/instances`,
|
||||
updateProviderInstance: ({
|
||||
provider_name,
|
||||
instance_name,
|
||||
id,
|
||||
}: {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
}) => `${restAPIv1}/providers/${provider_name}/instances/${instance_name}`,
|
||||
id: string;
|
||||
}) => `${restAPIv1}/providers/${provider_name}/instances/${id}`,
|
||||
updateModelStatus: ({
|
||||
provider_name,
|
||||
instance_name,
|
||||
|
||||
Reference in New Issue
Block a user