diff --git a/web/src/hooks/llmHooks.ts b/web/src/hooks/llmHooks.ts index 7800074db2..eed420c8ea 100644 --- a/web/src/hooks/llmHooks.ts +++ b/web/src/hooks/llmHooks.ts @@ -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 = 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({ + type: 'settingModel/set_api_key', + payload: savingParams, + }); + }, + [dispatch], + ); + + return saveApiKey; +}; diff --git a/web/src/interfaces/database/llm.ts b/web/src/interfaces/database/llm.ts index 6e8c7a919d..ff99613ab7 100644 --- a/web/src/interfaces/database/llm.ts +++ b/web/src/interfaces/database/llm.ts @@ -14,3 +14,25 @@ export interface IThirdOAIModel { } export type IThirdOAIModelCollection = Record; + +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; +} diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx index b06eda3ae6..e57e7eac21 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx @@ -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 = () => { - + + + + { ); } - return null; }} diff --git a/web/src/pages/setting/model.ts b/web/src/pages/setting/model.ts index 381380d000..79f1e92bc1 100644 --- a/web/src/pages/setting/model.ts +++ b/web/src/pages/setting/model.ts @@ -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; llmInfo: IThirdAiModelCollection; - myLlm: any[]; - factoriesList: any[]; + myLlmList: Record; + factoryList: IFactory[]; userInfo: IUserInfo; } @@ -24,13 +27,12 @@ const model: DvaModel = { 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 = { }, *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 = { } }, *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 = { 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; }, }, }; diff --git a/web/src/pages/user-setting/setting-model/api-key-modal/index.tsx b/web/src/pages/user-setting/setting-model/api-key-modal/index.tsx new file mode 100644 index 0000000000..62b403ab93 --- /dev/null +++ b/web/src/pages/user-setting/setting-model/api-key-modal/index.tsx @@ -0,0 +1,78 @@ +import { IModalManagerChildrenProps } from '@/components/modal-manager'; +import { Form, Input, Modal } from 'antd'; +import { useEffect } from 'react'; + +interface IProps extends Omit { + 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 ( + +
+ + label="Api key" + name="api_key" + rules={[{ required: true, message: 'Please input api key!' }]} + > + + + +
+ ); +}; + +export default ApiKeyModal; diff --git a/web/src/pages/user-setting/setting-model/hooks.ts b/web/src/pages/user-setting/setting-model/hooks.ts new file mode 100644 index 0000000000..725f6328dd --- /dev/null +++ b/web/src/pages/user-setting/setting-model/hooks.ts @@ -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; + +export const useSubmitApiKey = () => { + const [savingParams, setSavingParams] = useState( + {} 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, + }; +}; diff --git a/web/src/pages/user-setting/setting-model/index.less b/web/src/pages/user-setting/setting-model/index.less new file mode 100644 index 0000000000..d91b0c01d9 --- /dev/null +++ b/web/src/pages/user-setting/setting-model/index.less @@ -0,0 +1,6 @@ +.modelWrapper { + width: 100%; + .factoryOperationWrapper { + text-align: right; + } +} diff --git a/web/src/pages/user-setting/setting-model/index.tsx b/web/src/pages/user-setting/setting-model/index.tsx index fea0ea4a62..451d233410 100644 --- a/web/src/pages/user-setting/setting-model/index.tsx +++ b/web/src/pages/user-setting/setting-model/index.tsx @@ -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
UserSettingModel
; + const factoryList = useFetchLlmFactoryListOnMount(); + const llmList = useFetchMyLlmListOnMount(); + const { + saveApiKeyLoading, + initialApiKey, + onApiKeySavingOk, + apiKeyVisible, + hideApiKeyModal, + showApiKeyModal, + } = useSubmitApiKey(); + + const handleApiKeyClick = (llmFactory: string) => () => { + showApiKeyModal({ llm_factory: llmFactory }); + }; + + return ( + <> +
+ + + ( + + + + + + + + {item.name} +
+ {item.tags.split(',').map((x) => ( + {x} + ))} +
+
+
+ + + + + + + +
+ {item.name}} + /> +
+
+ )} + /> +

Models to be added

+ ( + + + + + + {item.name} + + {item.tags.split(',').map((x) => ( + {x} + ))} + + + + + + )} + /> +
+ + + ); }; export default UserSettingModel;