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) => {