feat: submit api key and add language to Configuration and fetch llm factory list on UserSettingModel mount (#121)

* feat: fetch llm factory list on UserSettingModel mount

* feat: add language to Configuration

* feat: submit api key
This commit is contained in:
balibabu
2024-03-12 18:58:09 +08:00
committed by GitHub
parent 0feb085c88
commit 2ca0dc0fc5
8 changed files with 422 additions and 35 deletions

View File

@ -1,5 +1,9 @@
import { LlmModelType } from '@/constants/knowledge';
import { IThirdOAIModelCollection } from '@/interfaces/database/llm';
import {
IFactory,
IMyLlmValue,
IThirdOAIModelCollection,
} from '@/interfaces/database/llm';
import { useCallback, useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'umi';
@ -38,3 +42,96 @@ export const useSelectLlmOptions = () => {
return embeddingModelOptions;
};
export const useSelectLlmFactoryList = () => {
const factoryList: IFactory[] = useSelector(
(state: any) => state.settingModel.factoryList,
);
return factoryList;
};
export const useSelectMyLlmList = () => {
const myLlmList: Record<string, IMyLlmValue> = useSelector(
(state: any) => state.settingModel.myLlmList,
);
return myLlmList;
};
export const useFetchLlmFactoryListOnMount = () => {
const dispatch = useDispatch();
const factoryList = useSelectLlmFactoryList();
const myLlmList = useSelectMyLlmList();
const list = useMemo(
() =>
factoryList.filter((x) =>
Object.keys(myLlmList).every((y) => y !== x.name),
),
[factoryList, myLlmList],
);
const fetchLlmFactoryList = useCallback(() => {
dispatch({
type: 'settingModel/factories_list',
});
}, [dispatch]);
useEffect(() => {
fetchLlmFactoryList();
}, [fetchLlmFactoryList]);
return list;
};
export const useFetchMyLlmListOnMount = () => {
const dispatch = useDispatch();
const llmList = useSelectMyLlmList();
const factoryList = useSelectLlmFactoryList();
const list: Array<{ name: string; logo: string } & IMyLlmValue> =
useMemo(() => {
return Object.entries(llmList).map(([key, value]) => ({
name: key,
logo: factoryList.find((x) => x.name === key)?.logo ?? '',
...value,
}));
}, [llmList, factoryList]);
const fetchMyLlmList = useCallback(() => {
dispatch({
type: 'settingModel/my_llm',
});
}, [dispatch]);
useEffect(() => {
fetchMyLlmList();
}, [fetchMyLlmList]);
return list;
};
export interface IApiKeySavingParams {
llm_factory: string;
api_key: string;
llm_name?: string;
model_type?: string;
api_base?: string;
}
export const useSaveApiKey = () => {
const dispatch = useDispatch();
const saveApiKey = useCallback(
(savingParams: IApiKeySavingParams) => {
return dispatch<any>({
type: 'settingModel/set_api_key',
payload: savingParams,
});
},
[dispatch],
);
return saveApiKey;
};

View File

@ -14,3 +14,25 @@ export interface IThirdOAIModel {
}
export type IThirdOAIModelCollection = Record<string, IThirdOAIModel[]>;
export interface IFactory {
create_date: string;
create_time: number;
logo: string;
name: string;
status: string;
tags: string;
update_date: string;
update_time: number;
}
export interface IMyLlmValue {
llm: Llm[];
tags: string;
}
export interface Llm {
name: string;
type: string;
used_token: number;
}

View File

@ -2,11 +2,19 @@ import {
useFetchKnowledgeBaseConfiguration,
useKnowledgeBaseId,
} from '@/hooks/knowledgeHook';
import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import {
useFetchTenantInfo,
useSelectParserList,
} from '@/hooks/userSettingHook';
import { IKnowledge } from '@/interfaces/database/knowledge';
import {
getBase64FromUploadFileList,
getUploadFileListFromBase64,
normFile,
} from '@/utils/fileUtil';
import { PlusOutlined } from '@ant-design/icons';
import {
Button,
Divider,
@ -25,17 +33,8 @@ import {
import pick from 'lodash/pick';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'umi';
import { useFetchLlmList, useSelectLlmOptions } from '@/hooks/llmHooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import { IKnowledge } from '@/interfaces/database/knowledge';
import {
getBase64FromUploadFileList,
getUploadFileListFromBase64,
normFile,
} from '@/utils/fileUtil';
import { PlusOutlined } from '@ant-design/icons';
import { LlmModelType } from '../../constant';
import styles from './index.less';
const { Title } = Typography;
@ -83,6 +82,7 @@ const Configuration = () => {
'permission',
'embd_id',
'parser_id',
'language',
'parser_config.chunk_token_num',
]),
avatar: fileList,
@ -131,9 +131,20 @@ const Configuration = () => {
</button>
</Upload>
</Form.Item>
<Form.Item name="description" label="Knowledge base bio">
<Form.Item name="description" label="Description">
<Input />
</Form.Item>
<Form.Item
label="Language"
name="language"
initialValue={'Chinese'}
rules={[{ required: true, message: 'Please input your language!' }]}
>
<Select placeholder="select your language">
<Option value="English">English</Option>
<Option value="Chinese">Chinese</Option>
</Select>
</Form.Item>
<Form.Item
name="permission"
label="Permissions"
@ -207,7 +218,6 @@ const Configuration = () => {
</Form.Item>
);
}
return null;
}}
</Form.Item>

View File

@ -1,5 +1,9 @@
import { ITenantInfo } from '@/interfaces/database/knowledge';
import { IThirdOAIModelCollection as IThirdAiModelCollection } from '@/interfaces/database/llm';
import {
IFactory,
IMyLlmValue,
IThirdOAIModelCollection as IThirdAiModelCollection,
} from '@/interfaces/database/llm';
import { IUserInfo } from '@/interfaces/database/userSetting';
import userService from '@/services/userService';
import { message } from 'antd';
@ -9,13 +13,12 @@ import { DvaModel } from 'umi';
export interface SettingModelState {
isShowPSwModal: boolean;
isShowTntModal: boolean;
isShowSAKModal: boolean;
isShowSSModal: boolean;
llm_factory: string;
tenantIfo: Nullable<ITenantInfo>;
llmInfo: IThirdAiModelCollection;
myLlm: any[];
factoriesList: any[];
myLlmList: Record<string, IMyLlmValue>;
factoryList: IFactory[];
userInfo: IUserInfo;
}
@ -24,13 +27,12 @@ const model: DvaModel<SettingModelState> = {
state: {
isShowPSwModal: false,
isShowTntModal: false,
isShowSAKModal: false,
isShowSSModal: false,
llm_factory: '',
tenantIfo: null,
llmInfo: {},
myLlm: [],
factoriesList: [],
myLlmList: {},
factoryList: [],
userInfo: {} as IUserInfo,
},
reducers: {
@ -116,16 +118,13 @@ const model: DvaModel<SettingModelState> = {
},
*factories_list({ payload = {} }, { call, put }) {
const { data, response } = yield call(
userService.factories_list,
payload,
);
const { retcode, data: res, retmsg } = data;
const { data } = yield call(userService.factories_list);
const { retcode, data: res } = data;
if (retcode === 0) {
yield put({
type: 'updateState',
payload: {
factoriesList: res,
factoryList: res,
},
});
}
@ -143,13 +142,13 @@ const model: DvaModel<SettingModelState> = {
}
},
*my_llm({ payload = {} }, { call, put }) {
const { data, response } = yield call(userService.my_llm, payload);
const { retcode, data: res, retmsg } = data;
const { data } = yield call(userService.my_llm, payload);
const { retcode, data: res } = data;
if (retcode === 0) {
yield put({
type: 'updateState',
payload: {
myLlm: res,
myLlmList: res,
},
});
}
@ -158,14 +157,12 @@ const model: DvaModel<SettingModelState> = {
const { data } = yield call(userService.set_api_key, payload);
const { retcode } = data;
if (retcode === 0) {
message.success('设置API KEY成功');
message.success('Modified!');
yield put({
type: 'updateState',
payload: {
isShowSAKModal: false,
},
});
}
return retcode;
},
},
};

View File

@ -0,0 +1,78 @@
import { IModalManagerChildrenProps } from '@/components/modal-manager';
import { Form, Input, Modal } from 'antd';
import { useEffect } from 'react';
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
loading: boolean;
initialValue: string;
onOk: (name: string) => void;
showModal?(): void;
}
type FieldType = {
api_key?: string;
};
const ApiKeyModal = ({
visible,
hideModal,
loading,
initialValue,
onOk,
}: IProps) => {
const [form] = Form.useForm();
const handleOk = async () => {
const ret = await form.validateFields();
return onOk(ret.api_key);
};
const handleCancel = () => {
hideModal();
};
const onFinish = (values: any) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
useEffect(() => {
form.setFieldValue('api_key', initialValue);
}, [initialValue, form]);
return (
<Modal
title="Modify"
open={visible}
onOk={handleOk}
onCancel={handleCancel}
okButtonProps={{ loading }}
confirmLoading={loading}
>
<Form
name="basic"
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
style={{ maxWidth: 600 }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
form={form}
>
<Form.Item<FieldType>
label="Api key"
name="api_key"
rules={[{ required: true, message: 'Please input api key!' }]}
>
<Input />
</Form.Item>
</Form>
</Modal>
);
};
export default ApiKeyModal;

View File

@ -0,0 +1,50 @@
import { useSetModalState } from '@/hooks/commonHooks';
import { IApiKeySavingParams, useSaveApiKey } from '@/hooks/llmHooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import { useCallback, useState } from 'react';
type SavingParamsState = Omit<IApiKeySavingParams, 'api_key'>;
export const useSubmitApiKey = () => {
const [savingParams, setSavingParams] = useState<SavingParamsState>(
{} as SavingParamsState,
);
const saveApiKey = useSaveApiKey();
const {
visible: apiKeyVisible,
hideModal: hideApiKeyModal,
showModal: showApiKeyModal,
} = useSetModalState();
const onApiKeySavingOk = useCallback(
async (apiKey: string) => {
const ret = await saveApiKey({ ...savingParams, api_key: apiKey });
if (ret.retcode === 0) {
hideApiKeyModal();
}
},
[hideApiKeyModal, saveApiKey, savingParams],
);
const onShowApiKeyModal = useCallback(
(savingParams: SavingParamsState) => {
setSavingParams(savingParams);
showApiKeyModal();
},
[showApiKeyModal, setSavingParams],
);
const loading = useOneNamespaceEffectsLoading('settingModel', [
'set_api_key',
]);
return {
saveApiKeyLoading: loading,
initialApiKey: '',
onApiKeySavingOk,
apiKeyVisible,
hideApiKeyModal,
showApiKeyModal: onShowApiKeyModal,
};
};

View File

@ -0,0 +1,6 @@
.modelWrapper {
width: 100%;
.factoryOperationWrapper {
text-align: right;
}
}

View File

@ -1,5 +1,132 @@
import {
useFetchLlmFactoryListOnMount,
useFetchMyLlmListOnMount,
} from '@/hooks/llmHooks';
import { SettingOutlined } from '@ant-design/icons';
import {
Avatar,
Button,
Card,
Col,
Divider,
Flex,
List,
Row,
Space,
Tag,
} from 'antd';
import SettingTitle from '../components/setting-title';
import ApiKeyModal from './api-key-modal';
import { useSubmitApiKey } from './hooks';
import styles from './index.less';
const UserSettingModel = () => {
return <div>UserSettingModel</div>;
const factoryList = useFetchLlmFactoryListOnMount();
const llmList = useFetchMyLlmListOnMount();
const {
saveApiKeyLoading,
initialApiKey,
onApiKeySavingOk,
apiKeyVisible,
hideApiKeyModal,
showApiKeyModal,
} = useSubmitApiKey();
const handleApiKeyClick = (llmFactory: string) => () => {
showApiKeyModal({ llm_factory: llmFactory });
};
return (
<>
<section className={styles.modelWrapper}>
<SettingTitle
title="Model Setting"
description="Manage your account settings and preferences here."
></SettingTitle>
<Divider></Divider>
<List
grid={{ gutter: 16, column: 1 }}
dataSource={llmList}
renderItem={(item) => (
<List.Item>
<Card>
<Row align={'middle'}>
<Col span={12}>
<Flex gap={'middle'} align="center">
<Avatar shape="square" size="large" src={item.logo} />
<Flex vertical gap={'small'}>
<b>{item.name}</b>
<div>
{item.tags.split(',').map((x) => (
<Tag key={x}>{x}</Tag>
))}
</div>
</Flex>
</Flex>
</Col>
<Col span={12} className={styles.factoryOperationWrapper}>
<Space size={'middle'}>
<Button onClick={handleApiKeyClick(item.name)}>
API-Key
<SettingOutlined />
</Button>
<Button>
Show more models
<SettingOutlined />
</Button>
</Space>
</Col>
</Row>
<List
size="small"
dataSource={item.llm}
renderItem={(item) => <List.Item>{item.name}</List.Item>}
/>
</Card>
</List.Item>
)}
/>
<p>Models to be added</p>
<List
grid={{
gutter: 16,
xs: 1,
sm: 2,
md: 3,
lg: 4,
xl: 4,
xxl: 8,
}}
dataSource={factoryList}
renderItem={(item) => (
<List.Item>
<Card>
<Flex vertical gap={'large'}>
<Avatar shape="square" size="large" src={item.logo} />
<Flex vertical gap={'middle'}>
<b>{item.name}</b>
<Space wrap>
{item.tags.split(',').map((x) => (
<Tag key={x}>{x}</Tag>
))}
</Space>
</Flex>
</Flex>
</Card>
</List.Item>
)}
/>
</section>
<ApiKeyModal
visible={apiKeyVisible}
hideModal={hideApiKeyModal}
loading={saveApiKeyLoading}
initialValue={initialApiKey}
onOk={onApiKeySavingOk}
></ApiKeyModal>
</>
);
};
export default UserSettingModel;