From 265a7a283ae1a29a4827d2a973d62cad14c41dc5 Mon Sep 17 00:00:00 2001 From: balibabu Date: Mon, 8 Apr 2024 19:13:45 +0800 Subject: [PATCH] feat: add support for ollama #221 (#260) ### What problem does this PR solve? add support for ollama Issue link:#221 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/assets/svg/llm/github.svg | 10 ++ web/src/assets/svg/llm/google.svg | 13 +++ .../{icons => assets/svg/llm}/moonshot.svg | 0 web/src/assets/svg/llm/ollama.svg | 9 ++ web/src/{icons => assets/svg/llm}/openai.svg | 0 web/src/{icons => assets/svg/llm}/tongyi.svg | 0 web/src/{icons => assets/svg/llm}/wenxin.svg | 0 web/src/{icons => assets/svg/llm}/zhipu.svg | 0 web/src/components/svg-icon.tsx | 7 +- web/src/hooks/llmHooks.ts | 17 ++++ web/src/interfaces/common.ts | 8 ++ web/src/interfaces/request/llm.ts | 6 ++ web/src/locales/en.ts | 8 ++ web/src/locales/zh.ts | 8 ++ web/src/pages/user-setting/model.ts | 11 +++ .../setting-model/api-key-modal/index.tsx | 6 +- .../pages/user-setting/setting-model/hooks.ts | 30 ++++++ .../user-setting/setting-model/index.tsx | 60 ++++++++---- .../setting-model/ollama-modal/index.tsx | 96 +++++++++++++++++++ .../system-model-setting-modal/index.tsx | 6 +- web/src/services/userService.ts | 5 + web/src/utils/api.ts | 1 + 22 files changed, 275 insertions(+), 26 deletions(-) create mode 100644 web/src/assets/svg/llm/github.svg create mode 100644 web/src/assets/svg/llm/google.svg rename web/src/{icons => assets/svg/llm}/moonshot.svg (100%) create mode 100644 web/src/assets/svg/llm/ollama.svg rename web/src/{icons => assets/svg/llm}/openai.svg (100%) rename web/src/{icons => assets/svg/llm}/tongyi.svg (100%) rename web/src/{icons => assets/svg/llm}/wenxin.svg (100%) rename web/src/{icons => assets/svg/llm}/zhipu.svg (100%) create mode 100644 web/src/interfaces/request/llm.ts create mode 100644 web/src/pages/user-setting/setting-model/ollama-modal/index.tsx diff --git a/web/src/assets/svg/llm/github.svg b/web/src/assets/svg/llm/github.svg new file mode 100644 index 000000000..6f80a87ed --- /dev/null +++ b/web/src/assets/svg/llm/github.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/src/assets/svg/llm/google.svg b/web/src/assets/svg/llm/google.svg new file mode 100644 index 000000000..f0d10ecfd --- /dev/null +++ b/web/src/assets/svg/llm/google.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/web/src/icons/moonshot.svg b/web/src/assets/svg/llm/moonshot.svg similarity index 100% rename from web/src/icons/moonshot.svg rename to web/src/assets/svg/llm/moonshot.svg diff --git a/web/src/assets/svg/llm/ollama.svg b/web/src/assets/svg/llm/ollama.svg new file mode 100644 index 000000000..6e9fb283c --- /dev/null +++ b/web/src/assets/svg/llm/ollama.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/web/src/icons/openai.svg b/web/src/assets/svg/llm/openai.svg similarity index 100% rename from web/src/icons/openai.svg rename to web/src/assets/svg/llm/openai.svg diff --git a/web/src/icons/tongyi.svg b/web/src/assets/svg/llm/tongyi.svg similarity index 100% rename from web/src/icons/tongyi.svg rename to web/src/assets/svg/llm/tongyi.svg diff --git a/web/src/icons/wenxin.svg b/web/src/assets/svg/llm/wenxin.svg similarity index 100% rename from web/src/icons/wenxin.svg rename to web/src/assets/svg/llm/wenxin.svg diff --git a/web/src/icons/zhipu.svg b/web/src/assets/svg/llm/zhipu.svg similarity index 100% rename from web/src/icons/zhipu.svg rename to web/src/assets/svg/llm/zhipu.svg diff --git a/web/src/components/svg-icon.tsx b/web/src/components/svg-icon.tsx index f740f2c9c..2171fc42c 100644 --- a/web/src/components/svg-icon.tsx +++ b/web/src/components/svg-icon.tsx @@ -21,13 +21,16 @@ try { interface IProps extends IconComponentProps { name: string; width: string | number; + height?: string | number; } -const SvgIcon = ({ name, width, ...restProps }: IProps) => { +const SvgIcon = ({ name, width, height, ...restProps }: IProps) => { const ListItem = routeList.find((item) => item.name === name); return ( } + component={() => ( + + )} {...(restProps as any)} /> ); diff --git a/web/src/hooks/llmHooks.ts b/web/src/hooks/llmHooks.ts index 3e2e75709..9068ebd43 100644 --- a/web/src/hooks/llmHooks.ts +++ b/web/src/hooks/llmHooks.ts @@ -4,6 +4,7 @@ import { IMyLlmValue, IThirdOAIModelCollection, } from '@/interfaces/database/llm'; +import { IAddLlmRequestBody } from '@/interfaces/request/llm'; import { useCallback, useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'umi'; @@ -206,3 +207,19 @@ export const useSaveTenantInfo = () => { return saveTenantInfo; }; + +export const useAddLlm = () => { + const dispatch = useDispatch(); + + const saveTenantInfo = useCallback( + (requestBody: IAddLlmRequestBody) => { + return dispatch({ + type: 'settingModel/add_llm', + payload: requestBody, + }); + }, + [dispatch], + ); + + return saveTenantInfo; +}; diff --git a/web/src/interfaces/common.ts b/web/src/interfaces/common.ts index 518d077c0..76a385fee 100644 --- a/web/src/interfaces/common.ts +++ b/web/src/interfaces/common.ts @@ -7,3 +7,11 @@ export interface BaseState { pagination: Pagination; searchString: string; } + +export interface IModalProps { + showModal?(): void; + hideModal(): void; + visible: boolean; + loading?: boolean; + onOk?(payload?: T): Promise | void; +} diff --git a/web/src/interfaces/request/llm.ts b/web/src/interfaces/request/llm.ts new file mode 100644 index 000000000..0505c0a47 --- /dev/null +++ b/web/src/interfaces/request/llm.ts @@ -0,0 +1,6 @@ +export interface IAddLlmRequestBody { + llm_factory: string; // Ollama + llm_name: string; + model_type: string; + api_base?: string; // chat|embedding|speech2text|image2text +} diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 661af3ca5..b6d1cec1f 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -390,6 +390,14 @@ export default { 'The default ASR model all the newly created knowledgebase will use. Use this model to translate voices to corresponding text.', workspace: 'Workspace', upgrade: 'Upgrade', + addLlmTitle: 'Add LLM', + modelName: 'Model name', + modelNameMessage: 'Please input your model name!', + modelType: 'Model type', + modelTypeMessage: 'Please input your model type!', + addLlmBaseUrl: 'Base url', + baseUrlNameMessage: 'Please input your base url!', + vision: 'Does it support Vision?', }, message: { registered: 'Registered!', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index e2165ea72..a8b18fb36 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -375,6 +375,14 @@ export default { '所有新创建的知识库都将使用默认的 ASR 模型。 使用此模型将语音翻译为相应的文本。', workspace: '工作空间', upgrade: '升级', + addLlmTitle: '添加 LLM', + modelName: '模型名称', + modelType: '模型类型', + addLlmBaseUrl: '基础 Url', + vision: '是否支持 Vision', + modelNameMessage: '请输入模型名称!', + modelTypeMessage: '请输入模型类型!', + baseUrlNameMessage: '请输入基础 Url!', }, message: { registered: '注册成功', diff --git a/web/src/pages/user-setting/model.ts b/web/src/pages/user-setting/model.ts index fa4add77b..da2ceed7d 100644 --- a/web/src/pages/user-setting/model.ts +++ b/web/src/pages/user-setting/model.ts @@ -151,6 +151,17 @@ const model: DvaModel = { } return retcode; }, + *add_llm({ payload = {} }, { call, put }) { + const { data } = yield call(userService.add_llm, payload); + const { retcode } = data; + if (retcode === 0) { + message.success(i18n.t('message.modified')); + + yield put({ type: 'my_llm' }); + yield put({ type: 'factories_list' }); + } + return retcode; + }, }, }; export default model; 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 index 7a4850797..f841cba06 100644 --- 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 @@ -46,8 +46,10 @@ const ApiKeyModal = ({ }; useEffect(() => { - form.setFieldValue('api_key', initialValue); - }, [initialValue, form]); + if (visible) { + form.setFieldValue('api_key', initialValue); + } + }, [initialValue, form, visible]); return ( ; @@ -127,3 +129,31 @@ export const useSelectModelProvidersLoading = () => { return loading; }; + +export const useSubmitOllama = () => { + const loading = useOneNamespaceEffectsLoading('settingModel', ['add_llm']); + const addLlm = useAddLlm(); + const { + visible: llmAddingVisible, + hideModal: hideLlmAddingModal, + showModal: showLlmAddingModal, + } = useSetModalState(); + + const onLlmAddingOk = useCallback( + async (payload: IAddLlmRequestBody) => { + const ret = await addLlm(payload); + if (ret === 0) { + hideLlmAddingModal(); + } + }, + [hideLlmAddingModal, addLlm], + ); + + return { + llmAddingLoading: loading, + onLlmAddingOk, + llmAddingVisible, + hideLlmAddingModal, + showLlmAddingModal, + }; +}; diff --git a/web/src/pages/user-setting/setting-model/index.tsx b/web/src/pages/user-setting/setting-model/index.tsx index 03dd050d7..272ffb5bc 100644 --- a/web/src/pages/user-setting/setting-model/index.tsx +++ b/web/src/pages/user-setting/setting-model/index.tsx @@ -1,15 +1,11 @@ import { ReactComponent as MoreModelIcon } from '@/assets/svg/more-model.svg'; +import SvgIcon from '@/components/svg-icon'; import { useSetModalState, useTranslate } from '@/hooks/commonHooks'; import { LlmItem, useFetchLlmFactoryListOnMount, useFetchMyLlmListOnMount, } from '@/hooks/llmHooks'; -import { ReactComponent as MoonshotIcon } from '@/icons/moonshot.svg'; -import { ReactComponent as OpenAiIcon } from '@/icons/openai.svg'; -import { ReactComponent as TongYiIcon } from '@/icons/tongyi.svg'; -import { ReactComponent as WenXinIcon } from '@/icons/wenxin.svg'; -import { ReactComponent as ZhiPuIcon } from '@/icons/zhipu.svg'; import { SettingOutlined, UserOutlined } from '@ant-design/icons'; import { Avatar, @@ -33,24 +29,27 @@ import ApiKeyModal from './api-key-modal'; import { useSelectModelProvidersLoading, useSubmitApiKey, + useSubmitOllama, useSubmitSystemModelSetting, } from './hooks'; +import styles from './index.less'; +import OllamaModal from './ollama-modal'; import SystemModelSettingModal from './system-model-setting-modal'; -import styles from './index.less'; - const IconMap = { - 'Tongyi-Qianwen': TongYiIcon, - Moonshot: MoonshotIcon, - OpenAI: OpenAiIcon, - 'ZHIPU-AI': ZhiPuIcon, - 文心一言: WenXinIcon, + 'Tongyi-Qianwen': 'tongyi', + Moonshot: 'moonshot', + OpenAI: 'openai', + 'ZHIPU-AI': 'zhipu', + 文心一言: 'wenxin', + Ollama: 'ollama', }; const LlmIcon = ({ name }: { name: string }) => { - const Icon = IconMap[name as keyof typeof IconMap]; - return Icon ? ( - + const icon = IconMap[name as keyof typeof IconMap]; + + return icon ? ( + ) : ( } /> ); @@ -90,7 +89,7 @@ const ModelCard = ({ item, clickApiKey }: IModelCardProps) => {