mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-10 21:55:42 +08:00
Feat: Add edit model type function (#16029)
### What problem does this PR solve? Feat: Add edit model type function ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
IAddProviderInstanceRequestBody,
|
||||
IAddProviderRequestBody,
|
||||
IDeleteProviderInstanceRequestBody,
|
||||
IEditInstanceModelRequestBody,
|
||||
IListAllModelsRequestParams,
|
||||
IListProviderModelsRequestBody,
|
||||
IListProvidersRequestParams,
|
||||
@@ -37,6 +38,7 @@ export const enum LLMApiAction {
|
||||
VerifyProviderConnection = 'verifyProviderConnection',
|
||||
ListProviderModels = 'listProviderModels',
|
||||
AddInstanceModel = 'addInstanceModel',
|
||||
EditInstanceModel = 'editInstanceModel',
|
||||
DeleteProviderInstance = 'deleteProviderInstance',
|
||||
ListDefaultModels = 'listDefaultModels',
|
||||
SetDefaultModel = 'setDefaultModel',
|
||||
@@ -354,6 +356,44 @@ export const useAddInstanceModel = () => {
|
||||
return { data, loading, addInstanceModel: mutateAsync };
|
||||
};
|
||||
|
||||
export const useEditInstanceModel = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.EditInstanceModel],
|
||||
mutationFn: async (
|
||||
params: {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
} & IEditInstanceModelRequestBody,
|
||||
) => {
|
||||
const { data } = await llmService.editInstanceModel(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.modified'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.instanceModels(
|
||||
params.provider_name,
|
||||
params.instance_name,
|
||||
),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.allModels(),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.defaultModels(),
|
||||
});
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, editInstanceModel: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteProviderInstance = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -57,6 +57,11 @@ export interface IAddInstanceModelRequestBody {
|
||||
extra?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface IEditInstanceModelRequestBody {
|
||||
model_name: string[];
|
||||
model_type: string[];
|
||||
}
|
||||
|
||||
export interface IListAllModelsRequestParams {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
@@ -1824,6 +1824,7 @@ Example: Virtual Hosted Style`,
|
||||
'Please select at least one model before verification.',
|
||||
addCustomModel: 'Add custom model',
|
||||
addCustomModelTitle: 'Add custom model',
|
||||
editCustomModelTitle: 'Edit model',
|
||||
modelMaxTokens: 'Max tokens',
|
||||
modelFeatures: 'Model features',
|
||||
modelFeatureToolCall: 'Tool call',
|
||||
|
||||
@@ -1510,6 +1510,7 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系
|
||||
selectModelBeforeVerify: '请至少选择一个模型后再验证。',
|
||||
addCustomModel: '添加自定义模型',
|
||||
addCustomModelTitle: '添加自定义模型',
|
||||
editCustomModelTitle: '编辑模型',
|
||||
modelMaxTokens: '最大 Token 数',
|
||||
modelTypes: {
|
||||
chat: 'Chat',
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Switch } from '@/components/ui/switch';
|
||||
import { ModelStatus } from '@/constants/llm';
|
||||
import {
|
||||
useDeleteProviderInstance,
|
||||
useEditInstanceModel,
|
||||
useFetchAddedProviders,
|
||||
useFetchInstanceModels,
|
||||
useFetchProviderInstance,
|
||||
@@ -21,9 +22,19 @@ import {
|
||||
IInstanceModel,
|
||||
IProviderInstance,
|
||||
} from '@/interfaces/database/llm';
|
||||
import { ChevronsDown, ChevronsUp, Settings, Trash2 } from 'lucide-react';
|
||||
import { IProviderModelItem } from '@/interfaces/request/llm';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
ChevronsDown,
|
||||
ChevronsUp,
|
||||
Pencil,
|
||||
Settings,
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { AddCustomModelDialog } from '../modal/provider-modal/components/add-custom-model-dialog';
|
||||
import { useCustomModelFields } from '../modal/provider-modal/components/use-custom-model-fields';
|
||||
import { mapModelKey } from './un-add-model';
|
||||
|
||||
export function UsedModel({
|
||||
@@ -287,7 +298,11 @@ function ModelListItem({
|
||||
providerName: string;
|
||||
instanceName: string;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { updateModelStatus } = useUpdateModelStatus();
|
||||
const { editInstanceModel, loading: editLoading } = useEditInstanceModel();
|
||||
const customModelFields = useCustomModelFields();
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
|
||||
const handleStatusChange = (checked: boolean) => {
|
||||
updateModelStatus({
|
||||
@@ -298,11 +313,45 @@ function ModelListItem({
|
||||
});
|
||||
};
|
||||
|
||||
// Only show name / model_types / max_tokens; only model_types is editable.
|
||||
const editFields = useMemo(
|
||||
() =>
|
||||
customModelFields
|
||||
.filter((f) => ['name', 'model_types', 'max_tokens'].includes(f.name))
|
||||
.map((f) => ({
|
||||
...f,
|
||||
disabled: f.name !== 'model_types',
|
||||
})),
|
||||
[customModelFields],
|
||||
);
|
||||
|
||||
const editDefaultValues = useMemo(
|
||||
() => ({
|
||||
name: model.name,
|
||||
model_types: model.model_type ?? [],
|
||||
max_tokens: model.max_tokens ?? 0,
|
||||
}),
|
||||
[model],
|
||||
);
|
||||
|
||||
const handleEditSubmit = useCallback(
|
||||
async (item: IProviderModelItem) => {
|
||||
await editInstanceModel({
|
||||
provider_name: providerName,
|
||||
instance_name: instanceName,
|
||||
model_name: [model.name],
|
||||
model_type: item.model_types ?? [],
|
||||
});
|
||||
setEditOpen(false);
|
||||
},
|
||||
[editInstanceModel, providerName, instanceName, model.name],
|
||||
);
|
||||
|
||||
return (
|
||||
<li className="flex items-center border-b-[0.5px] border-border-button justify-between p-3 hover:bg-bg-card transition-colors last:border-b-0">
|
||||
<li className="flex items-center border-b-[0.5px] border-border-button justify-between p-3 hover:bg-bg-card transition-colors last:border-b-0 group">
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="font-medium text-text-primary">{model.name}</span>
|
||||
{model.model_type.map((modelType) => (
|
||||
{model.model_type.slice(0, 3).map((modelType) => (
|
||||
<span
|
||||
className="px-2 py-1 text-xs bg-bg-card text-text-secondary rounded-md"
|
||||
key={modelType}
|
||||
@@ -310,11 +359,41 @@ function ModelListItem({
|
||||
{modelType}
|
||||
</span>
|
||||
))}
|
||||
{model.model_type.length > 3 && (
|
||||
<span
|
||||
className="px-2 py-1 text-xs bg-bg-card text-text-secondary rounded-md"
|
||||
key="ellipsis"
|
||||
>
|
||||
...
|
||||
</span>
|
||||
)}
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className={cn('h-6 w-6 hidden', 'group-hover:flex')}
|
||||
onClick={(e: any) => {
|
||||
e.stopPropagation();
|
||||
setEditOpen(true);
|
||||
}}
|
||||
aria-label={t('setting.editCustomModelTitle')}
|
||||
>
|
||||
<Pencil size={12} />
|
||||
</Button>
|
||||
</div>
|
||||
<Switch
|
||||
checked={model.status === ModelStatus.Active}
|
||||
onCheckedChange={handleStatusChange}
|
||||
/>
|
||||
<AddCustomModelDialog
|
||||
open={editOpen}
|
||||
onOpenChange={setEditOpen}
|
||||
title={t('setting.editCustomModelTitle')}
|
||||
fields={editFields}
|
||||
defaultValues={editDefaultValues}
|
||||
onSubmit={handleEditSubmit}
|
||||
loading={editLoading}
|
||||
existingNames={[]}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ export interface AddCustomModelDialogFields {
|
||||
defaultValue?: unknown;
|
||||
/** Minimum value for number type */
|
||||
min?: number;
|
||||
/** Whether this field is disabled (non-editable) */
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface AddCustomModelDialogProps {
|
||||
@@ -56,6 +58,8 @@ interface AddCustomModelDialogProps {
|
||||
loading?: boolean;
|
||||
/** Existing model names for uniqueness validation */
|
||||
existingNames: string[];
|
||||
/** Initial form values (overrides field-level defaults). Useful for edit mode. */
|
||||
defaultValues?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
type FormValues = Record<string, unknown>;
|
||||
@@ -74,8 +78,10 @@ export const AddCustomModelDialog = ({
|
||||
cancelText,
|
||||
loading = false,
|
||||
existingNames,
|
||||
defaultValues,
|
||||
}: AddCustomModelDialogProps) => {
|
||||
const { t } = useTranslate('setting');
|
||||
const { t: commonT } = useTranslate('common');
|
||||
const formRef = useRef<DynamicFormRef>(null);
|
||||
|
||||
// Translate AddCustomModelDialogFields -> FormFieldConfig for DynamicForm.
|
||||
@@ -96,6 +102,7 @@ export const AddCustomModelDialog = ({
|
||||
type: FormFieldType.Custom,
|
||||
required: field.required,
|
||||
defaultValue,
|
||||
disabled: field.disabled,
|
||||
schema: field.required
|
||||
? z.array(z.string()).min(1, t('modelTypeRequired'))
|
||||
: z.array(z.string()).optional(),
|
||||
@@ -120,7 +127,9 @@ export const AddCustomModelDialog = ({
|
||||
<Switch
|
||||
id={switchId}
|
||||
checked={isChecked}
|
||||
disabled={field.disabled}
|
||||
onCheckedChange={(checked) => {
|
||||
if (field.disabled) return;
|
||||
const next = checked
|
||||
? [...currentValues, opt.value]
|
||||
: currentValues.filter((v) => v !== opt.value);
|
||||
@@ -148,6 +157,7 @@ export const AddCustomModelDialog = ({
|
||||
type: typeMap[field.type as 'text' | 'number' | 'multi-select'],
|
||||
required: field.required,
|
||||
defaultValue,
|
||||
disabled: field.disabled,
|
||||
options: field.options,
|
||||
placeholder: field.label,
|
||||
...(field.min !== undefined
|
||||
@@ -193,12 +203,14 @@ export const AddCustomModelDialog = ({
|
||||
[onSubmit],
|
||||
);
|
||||
|
||||
// Reset form whenever the dialog closes, so the next open starts fresh.
|
||||
// Reset form whenever the dialog opens/closes, applying defaultValues for edit mode.
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
formRef.current?.reset();
|
||||
} else if (defaultValues) {
|
||||
formRef.current?.reset(defaultValues);
|
||||
}
|
||||
}, [open]);
|
||||
}, [open, defaultValues]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
@@ -211,8 +223,9 @@ export const AddCustomModelDialog = ({
|
||||
ref={formRef}
|
||||
fields={dynamicFields}
|
||||
onSubmit={handleSubmit}
|
||||
defaultValues={defaultValues}
|
||||
>
|
||||
<DialogFooter>
|
||||
<DialogFooter className="mb-0 pb-0">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
@@ -223,7 +236,7 @@ export const AddCustomModelDialog = ({
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{submitText ?? t('confirm')}
|
||||
{submitText ?? commonT('confirm')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DynamicForm.Root>
|
||||
|
||||
@@ -13,6 +13,7 @@ const {
|
||||
listInstanceModels,
|
||||
showProviderInstance,
|
||||
addInstanceModel,
|
||||
editInstanceModel,
|
||||
deleteProviderInstance,
|
||||
updateModelStatus,
|
||||
} = api;
|
||||
@@ -66,6 +67,10 @@ const methods = {
|
||||
url: addInstanceModel,
|
||||
method: 'post',
|
||||
},
|
||||
editInstanceModel: {
|
||||
url: editInstanceModel,
|
||||
method: 'put',
|
||||
},
|
||||
deleteProviderInstance: {
|
||||
url: deleteProviderInstance,
|
||||
method: 'delete',
|
||||
|
||||
@@ -59,6 +59,14 @@ export default {
|
||||
instance_name: string;
|
||||
}) =>
|
||||
`${restAPIv1}/providers/${provider_name}/instances/${instance_name}/models`,
|
||||
editInstanceModel: ({
|
||||
provider_name,
|
||||
instance_name,
|
||||
}: {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
}) =>
|
||||
`${restAPIv1}/providers/${provider_name}/instances/${instance_name}/models`,
|
||||
deleteProviderInstance: ({ provider_name }: { provider_name: string }) =>
|
||||
`${restAPIv1}/providers/${provider_name}/instances`,
|
||||
updateModelStatus: ({
|
||||
|
||||
Reference in New Issue
Block a user