mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-18 03:26:42 +08:00
Feat: Add model editing functionality with improved UI labels (#8855)
### What problem does this PR solve? Add edit button for local LLM models <img width="1531" height="1428" alt="image" src="https://github.com/user-attachments/assets/19d62255-59a6-4a7e-9772-8b8743101f78" /> <img width="1531" height="1428" alt="image" src="https://github.com/user-attachments/assets/c3a0f77e-cc6b-4190-95a6-13835463428b" /> ### Type of change - [ ] Bug Fix (non-breaking change which fixes an issue) - [x] New Feature (non-breaking change which adds functionality) - [ ] Documentation Update - [ ] Refactoring - [ ] Performance Improvement - [ ] Other (please describe): --------- Co-authored-by: Liu An <asiro@qq.com>
This commit is contained in:
@ -9,6 +9,7 @@ interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
|
||||
loading: boolean;
|
||||
initialValue: string;
|
||||
llmFactory: string;
|
||||
editMode?: boolean;
|
||||
onOk: (postBody: ApiKeyPostBody) => void;
|
||||
showModal?(): void;
|
||||
}
|
||||
@ -27,6 +28,7 @@ const ApiKeyModal = ({
|
||||
llmFactory,
|
||||
loading,
|
||||
initialValue,
|
||||
editMode = false,
|
||||
onOk,
|
||||
}: IProps) => {
|
||||
const [form] = Form.useForm();
|
||||
@ -52,7 +54,7 @@ const ApiKeyModal = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('modify')}
|
||||
title={editMode ? t('editModel') : t('modify')}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
} from '@/hooks/llm-hooks';
|
||||
import { useFetchTenantInfo } from '@/hooks/user-setting-hooks';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { getRealModelName } from '@/utils/llm-util';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { ApiKeyPostBody } from '../interface';
|
||||
|
||||
@ -20,6 +21,7 @@ export const useSubmitApiKey = () => {
|
||||
const [savingParams, setSavingParams] = useState<SavingParamsState>(
|
||||
{} as SavingParamsState,
|
||||
);
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const { saveApiKey, loading } = useSaveApiKey();
|
||||
const {
|
||||
visible: apiKeyVisible,
|
||||
@ -36,14 +38,16 @@ export const useSubmitApiKey = () => {
|
||||
|
||||
if (ret === 0) {
|
||||
hideApiKeyModal();
|
||||
setEditMode(false);
|
||||
}
|
||||
},
|
||||
[hideApiKeyModal, saveApiKey, savingParams],
|
||||
);
|
||||
|
||||
const onShowApiKeyModal = useCallback(
|
||||
(savingParams: SavingParamsState) => {
|
||||
(savingParams: SavingParamsState, isEdit = false) => {
|
||||
setSavingParams(savingParams);
|
||||
setEditMode(isEdit);
|
||||
showApiKeyModal();
|
||||
},
|
||||
[showApiKeyModal, setSavingParams],
|
||||
@ -53,6 +57,7 @@ export const useSubmitApiKey = () => {
|
||||
saveApiKeyLoading: loading,
|
||||
initialApiKey: '',
|
||||
llmFactory: savingParams.llm_factory,
|
||||
editMode,
|
||||
onApiKeySavingOk,
|
||||
apiKeyVisible,
|
||||
hideApiKeyModal,
|
||||
@ -105,6 +110,9 @@ export const useFetchSystemModelSettingOnMount = () => {
|
||||
|
||||
export const useSubmitOllama = () => {
|
||||
const [selectedLlmFactory, setSelectedLlmFactory] = useState<string>('');
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [initialValues, setInitialValues] = useState<Partial<IAddLlmRequestBody> | undefined>();
|
||||
const [originalModelName, setOriginalModelName] = useState<string>('');
|
||||
const { addLlm, loading } = useAddLlm();
|
||||
const {
|
||||
visible: llmAddingVisible,
|
||||
@ -114,21 +122,44 @@ export const useSubmitOllama = () => {
|
||||
|
||||
const onLlmAddingOk = useCallback(
|
||||
async (payload: IAddLlmRequestBody) => {
|
||||
const ret = await addLlm(payload);
|
||||
const cleanedPayload = { ...payload };
|
||||
if (!cleanedPayload.api_key || cleanedPayload.api_key.trim() === '') {
|
||||
delete cleanedPayload.api_key;
|
||||
}
|
||||
|
||||
const ret = await addLlm(cleanedPayload);
|
||||
if (ret === 0) {
|
||||
hideLlmAddingModal();
|
||||
setEditMode(false);
|
||||
setInitialValues(undefined);
|
||||
}
|
||||
},
|
||||
[hideLlmAddingModal, addLlm],
|
||||
);
|
||||
|
||||
const handleShowLlmAddingModal = (llmFactory: string) => {
|
||||
const handleShowLlmAddingModal = (llmFactory: string, isEdit = false, modelData?: any, detailedData?: any) => {
|
||||
setSelectedLlmFactory(llmFactory);
|
||||
setEditMode(isEdit);
|
||||
|
||||
if (isEdit && detailedData) {
|
||||
const initialVals = {
|
||||
llm_name: getRealModelName(detailedData.name),
|
||||
model_type: detailedData.type,
|
||||
api_base: detailedData.api_base || '',
|
||||
max_tokens: detailedData.max_tokens || 8192,
|
||||
api_key: '',
|
||||
};
|
||||
setInitialValues(initialVals);
|
||||
} else {
|
||||
setInitialValues(undefined);
|
||||
}
|
||||
showLlmAddingModal();
|
||||
};
|
||||
|
||||
return {
|
||||
llmAddingLoading: loading,
|
||||
editMode,
|
||||
initialValues,
|
||||
onLlmAddingOk,
|
||||
llmAddingVisible,
|
||||
hideLlmAddingModal,
|
||||
|
||||
@ -3,9 +3,9 @@ import { LlmIcon } from '@/components/svg-icon';
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { LLMFactory } from '@/constants/llm';
|
||||
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
|
||||
import { LlmItem, useSelectLlmList } from '@/hooks/llm-hooks';
|
||||
import { LlmItem, useSelectLlmList, useFetchMyLlmListDetailed } from '@/hooks/llm-hooks';
|
||||
import { getRealModelName } from '@/utils/llm-util';
|
||||
import { CloseCircleOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
import { CloseCircleOutlined, EditOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
@ -60,9 +60,10 @@ const { Text } = Typography;
|
||||
interface IModelCardProps {
|
||||
item: LlmItem;
|
||||
clickApiKey: (llmFactory: string) => void;
|
||||
handleEditModel: (model: any, factory: LlmItem) => void;
|
||||
}
|
||||
|
||||
const ModelCard = ({ item, clickApiKey }: IModelCardProps) => {
|
||||
const ModelCard = ({ item, clickApiKey, handleEditModel }: IModelCardProps) => {
|
||||
const { visible, switchVisible } = useSetModalState();
|
||||
const { t } = useTranslate('setting');
|
||||
const { theme } = useTheme();
|
||||
@ -112,7 +113,7 @@ const ModelCard = ({ item, clickApiKey }: IModelCardProps) => {
|
||||
</Button>
|
||||
<Button onClick={handleShowMoreClick}>
|
||||
<Flex align="center" gap={4}>
|
||||
{t('showMoreModels')}
|
||||
{visible ? t('hideModels') : t('showMoreModels')}
|
||||
<MoreModelIcon />
|
||||
</Flex>
|
||||
</Button>
|
||||
@ -129,13 +130,20 @@ const ModelCard = ({ item, clickApiKey }: IModelCardProps) => {
|
||||
size="small"
|
||||
dataSource={item.llm}
|
||||
className={styles.llmList}
|
||||
renderItem={(item) => (
|
||||
renderItem={(model) => (
|
||||
<List.Item>
|
||||
<Space>
|
||||
{getRealModelName(item.name)}
|
||||
<Tag color="#b8b8b8">{item.type}</Tag>
|
||||
{getRealModelName(model.name)}
|
||||
<Tag color="#b8b8b8">{model.type}</Tag>
|
||||
{isLocalLlmFactory(item.name) && (
|
||||
<Tooltip title={t('edit', { keyPrefix: 'common' })}>
|
||||
<Button type={'text'} onClick={() => handleEditModel(model, item)}>
|
||||
<EditOutlined style={{ color: '#1890ff' }} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title={t('delete', { keyPrefix: 'common' })}>
|
||||
<Button type={'text'} onClick={handleDeleteLlm(item.name)}>
|
||||
<Button type={'text'} onClick={handleDeleteLlm(model.name)}>
|
||||
<CloseCircleOutlined style={{ color: '#D92D20' }} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
@ -151,11 +159,13 @@ const ModelCard = ({ item, clickApiKey }: IModelCardProps) => {
|
||||
|
||||
const UserSettingModel = () => {
|
||||
const { factoryList, myLlmList: llmList, loading } = useSelectLlmList();
|
||||
const { data: detailedLlmList } = useFetchMyLlmListDetailed();
|
||||
const { theme } = useTheme();
|
||||
const {
|
||||
saveApiKeyLoading,
|
||||
initialApiKey,
|
||||
llmFactory,
|
||||
editMode,
|
||||
onApiKeySavingOk,
|
||||
apiKeyVisible,
|
||||
hideApiKeyModal,
|
||||
@ -175,6 +185,8 @@ const UserSettingModel = () => {
|
||||
showLlmAddingModal,
|
||||
onLlmAddingOk,
|
||||
llmAddingLoading,
|
||||
editMode: llmEditMode,
|
||||
initialValues: llmInitialValues,
|
||||
selectedLlmFactory,
|
||||
} = useSubmitOllama();
|
||||
|
||||
@ -288,6 +300,30 @@ const UserSettingModel = () => {
|
||||
[showApiKeyModal, showLlmAddingModal, ModalMap],
|
||||
);
|
||||
|
||||
const handleEditModel = useCallback(
|
||||
(model: any, factory: LlmItem) => {
|
||||
if (factory) {
|
||||
const detailedFactory = detailedLlmList[factory.name];
|
||||
const detailedModel = detailedFactory?.llm?.find((m: any) => m.name === model.name);
|
||||
|
||||
const editData = {
|
||||
llm_factory: factory.name,
|
||||
llm_name: model.name,
|
||||
model_type: model.type
|
||||
};
|
||||
|
||||
if (isLocalLlmFactory(factory.name)) {
|
||||
showLlmAddingModal(factory.name, true, editData, detailedModel);
|
||||
} else if (factory.name in ModalMap) {
|
||||
ModalMap[factory.name as keyof typeof ModalMap]();
|
||||
} else {
|
||||
showApiKeyModal(editData, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
[showApiKeyModal, showLlmAddingModal, ModalMap, detailedLlmList],
|
||||
);
|
||||
|
||||
const items: CollapseProps['items'] = [
|
||||
{
|
||||
key: '1',
|
||||
@ -297,7 +333,7 @@ const UserSettingModel = () => {
|
||||
grid={{ gutter: 16, column: 1 }}
|
||||
dataSource={llmList}
|
||||
renderItem={(item) => (
|
||||
<ModelCard item={item} clickApiKey={handleAddModel}></ModelCard>
|
||||
<ModelCard item={item} clickApiKey={handleAddModel} handleEditModel={handleEditModel}></ModelCard>
|
||||
)}
|
||||
/>
|
||||
),
|
||||
@ -384,6 +420,7 @@ const UserSettingModel = () => {
|
||||
hideModal={hideApiKeyModal}
|
||||
loading={saveApiKeyLoading}
|
||||
initialValue={initialApiKey}
|
||||
editMode={editMode}
|
||||
onOk={onApiKeySavingOk}
|
||||
llmFactory={llmFactory}
|
||||
></ApiKeyModal>
|
||||
@ -400,6 +437,8 @@ const UserSettingModel = () => {
|
||||
hideModal={hideLlmAddingModal}
|
||||
onOk={onLlmAddingOk}
|
||||
loading={llmAddingLoading}
|
||||
editMode={llmEditMode}
|
||||
initialValues={llmInitialValues}
|
||||
llmFactory={selectedLlmFactory}
|
||||
></OllamaModal>
|
||||
<VolcEngineModal
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
Switch,
|
||||
} from 'antd';
|
||||
import omit from 'lodash/omit';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & { vision: boolean };
|
||||
|
||||
@ -45,7 +46,13 @@ const OllamaModal = ({
|
||||
onOk,
|
||||
loading,
|
||||
llmFactory,
|
||||
}: IModalProps<IAddLlmRequestBody> & { llmFactory: string }) => {
|
||||
editMode = false,
|
||||
initialValues,
|
||||
}: IModalProps<IAddLlmRequestBody> & {
|
||||
llmFactory: string;
|
||||
editMode?: boolean;
|
||||
initialValues?: Partial<IAddLlmRequestBody>;
|
||||
}) => {
|
||||
const [form] = Form.useForm<FieldType>();
|
||||
|
||||
const { t } = useTranslate('setting');
|
||||
@ -73,6 +80,22 @@ const OllamaModal = ({
|
||||
await handleOk();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (visible && editMode && initialValues) {
|
||||
const formValues = {
|
||||
llm_name: initialValues.llm_name,
|
||||
model_type: initialValues.model_type,
|
||||
api_base: initialValues.api_base,
|
||||
max_tokens: initialValues.max_tokens || 8192,
|
||||
api_key: '',
|
||||
...initialValues,
|
||||
};
|
||||
form.setFieldsValue(formValues);
|
||||
} else if (visible && !editMode) {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [visible, editMode, initialValues, form]);
|
||||
|
||||
const url =
|
||||
llmFactoryToUrlMap[llmFactory as LlmFactory] ||
|
||||
@ -111,7 +134,7 @@ const OllamaModal = ({
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
title={t('addLlmTitle', { name: llmFactory })}
|
||||
title={editMode ? t('editLlmTitle', { name: llmFactory }) : t('addLlmTitle', { name: llmFactory })}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
@ -173,7 +196,10 @@ const OllamaModal = ({
|
||||
name="api_key"
|
||||
rules={[{ required: false, message: t('apiKeyMessage') }]}
|
||||
>
|
||||
<Input placeholder={t('apiKeyMessage')} onKeyDown={handleKeyDown} />
|
||||
<Input
|
||||
placeholder={t('apiKeyMessage')}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType>
|
||||
label={t('maxTokens')}
|
||||
|
||||
Reference in New Issue
Block a user