balibabu commited on
Commit
21cf732
·
1 Parent(s): 5769711

feat: add support for ollama #221 (#260)

Browse files

### 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 ADDED
web/src/assets/svg/llm/google.svg ADDED
web/src/{icons → assets/svg/llm}/moonshot.svg RENAMED
File without changes
web/src/assets/svg/llm/ollama.svg ADDED
web/src/{icons → assets/svg/llm}/openai.svg RENAMED
File without changes
web/src/{icons → assets/svg/llm}/tongyi.svg RENAMED
File without changes
web/src/{icons → assets/svg/llm}/wenxin.svg RENAMED
File without changes
web/src/{icons → assets/svg/llm}/zhipu.svg RENAMED
File without changes
web/src/components/svg-icon.tsx CHANGED
@@ -21,13 +21,16 @@ try {
21
  interface IProps extends IconComponentProps {
22
  name: string;
23
  width: string | number;
 
24
  }
25
 
26
- const SvgIcon = ({ name, width, ...restProps }: IProps) => {
27
  const ListItem = routeList.find((item) => item.name === name);
28
  return (
29
  <Icon
30
- component={() => <img src={ListItem?.value} alt="" width={width} />}
 
 
31
  {...(restProps as any)}
32
  />
33
  );
 
21
  interface IProps extends IconComponentProps {
22
  name: string;
23
  width: string | number;
24
+ height?: string | number;
25
  }
26
 
27
+ const SvgIcon = ({ name, width, height, ...restProps }: IProps) => {
28
  const ListItem = routeList.find((item) => item.name === name);
29
  return (
30
  <Icon
31
+ component={() => (
32
+ <img src={ListItem?.value} alt="" width={width} height={height} />
33
+ )}
34
  {...(restProps as any)}
35
  />
36
  );
web/src/hooks/llmHooks.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
  IMyLlmValue,
5
  IThirdOAIModelCollection,
6
  } from '@/interfaces/database/llm';
 
7
  import { useCallback, useEffect, useMemo } from 'react';
8
  import { useDispatch, useSelector } from 'umi';
9
 
@@ -206,3 +207,19 @@ export const useSaveTenantInfo = () => {
206
 
207
  return saveTenantInfo;
208
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  IMyLlmValue,
5
  IThirdOAIModelCollection,
6
  } from '@/interfaces/database/llm';
7
+ import { IAddLlmRequestBody } from '@/interfaces/request/llm';
8
  import { useCallback, useEffect, useMemo } from 'react';
9
  import { useDispatch, useSelector } from 'umi';
10
 
 
207
 
208
  return saveTenantInfo;
209
  };
210
+
211
+ export const useAddLlm = () => {
212
+ const dispatch = useDispatch();
213
+
214
+ const saveTenantInfo = useCallback(
215
+ (requestBody: IAddLlmRequestBody) => {
216
+ return dispatch<any>({
217
+ type: 'settingModel/add_llm',
218
+ payload: requestBody,
219
+ });
220
+ },
221
+ [dispatch],
222
+ );
223
+
224
+ return saveTenantInfo;
225
+ };
web/src/interfaces/common.ts CHANGED
@@ -7,3 +7,11 @@ export interface BaseState {
7
  pagination: Pagination;
8
  searchString: string;
9
  }
 
 
 
 
 
 
 
 
 
7
  pagination: Pagination;
8
  searchString: string;
9
  }
10
+
11
+ export interface IModalProps<T> {
12
+ showModal?(): void;
13
+ hideModal(): void;
14
+ visible: boolean;
15
+ loading?: boolean;
16
+ onOk?(payload?: T): Promise<void> | void;
17
+ }
web/src/interfaces/request/llm.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export interface IAddLlmRequestBody {
2
+ llm_factory: string; // Ollama
3
+ llm_name: string;
4
+ model_type: string;
5
+ api_base?: string; // chat|embedding|speech2text|image2text
6
+ }
web/src/locales/en.ts CHANGED
@@ -390,6 +390,14 @@ export default {
390
  'The default ASR model all the newly created knowledgebase will use. Use this model to translate voices to corresponding text.',
391
  workspace: 'Workspace',
392
  upgrade: 'Upgrade',
 
 
 
 
 
 
 
 
393
  },
394
  message: {
395
  registered: 'Registered!',
 
390
  'The default ASR model all the newly created knowledgebase will use. Use this model to translate voices to corresponding text.',
391
  workspace: 'Workspace',
392
  upgrade: 'Upgrade',
393
+ addLlmTitle: 'Add LLM',
394
+ modelName: 'Model name',
395
+ modelNameMessage: 'Please input your model name!',
396
+ modelType: 'Model type',
397
+ modelTypeMessage: 'Please input your model type!',
398
+ addLlmBaseUrl: 'Base url',
399
+ baseUrlNameMessage: 'Please input your base url!',
400
+ vision: 'Does it support Vision?',
401
  },
402
  message: {
403
  registered: 'Registered!',
web/src/locales/zh.ts CHANGED
@@ -375,6 +375,14 @@ export default {
375
  '所有新创建的知识库都将使用默认的 ASR 模型。 使用此模型将语音翻译为相应的文本。',
376
  workspace: '工作空间',
377
  upgrade: '升级',
 
 
 
 
 
 
 
 
378
  },
379
  message: {
380
  registered: '注册成功',
 
375
  '所有新创建的知识库都将使用默认的 ASR 模型。 使用此模型将语音翻译为相应的文本。',
376
  workspace: '工作空间',
377
  upgrade: '升级',
378
+ addLlmTitle: '添加 LLM',
379
+ modelName: '模型名称',
380
+ modelType: '模型类型',
381
+ addLlmBaseUrl: '基础 Url',
382
+ vision: '是否支持 Vision',
383
+ modelNameMessage: '请输入模型名称!',
384
+ modelTypeMessage: '请输入模型类型!',
385
+ baseUrlNameMessage: '请输入基础 Url!',
386
  },
387
  message: {
388
  registered: '注册成功',
web/src/pages/user-setting/model.ts CHANGED
@@ -151,6 +151,17 @@ const model: DvaModel<SettingModelState> = {
151
  }
152
  return retcode;
153
  },
 
 
 
 
 
 
 
 
 
 
 
154
  },
155
  };
156
  export default model;
 
151
  }
152
  return retcode;
153
  },
154
+ *add_llm({ payload = {} }, { call, put }) {
155
+ const { data } = yield call(userService.add_llm, payload);
156
+ const { retcode } = data;
157
+ if (retcode === 0) {
158
+ message.success(i18n.t('message.modified'));
159
+
160
+ yield put({ type: 'my_llm' });
161
+ yield put({ type: 'factories_list' });
162
+ }
163
+ return retcode;
164
+ },
165
  },
166
  };
167
  export default model;
web/src/pages/user-setting/setting-model/api-key-modal/index.tsx CHANGED
@@ -46,8 +46,10 @@ const ApiKeyModal = ({
46
  };
47
 
48
  useEffect(() => {
49
- form.setFieldValue('api_key', initialValue);
50
- }, [initialValue, form]);
 
 
51
 
52
  return (
53
  <Modal
 
46
  };
47
 
48
  useEffect(() => {
49
+ if (visible) {
50
+ form.setFieldValue('api_key', initialValue);
51
+ }
52
+ }, [initialValue, form, visible]);
53
 
54
  return (
55
  <Modal
web/src/pages/user-setting/setting-model/hooks.ts CHANGED
@@ -2,6 +2,7 @@ import { useSetModalState } from '@/hooks/commonHooks';
2
  import {
3
  IApiKeySavingParams,
4
  ISystemModelSettingSavingParams,
 
5
  useFetchLlmList,
6
  useSaveApiKey,
7
  useSaveTenantInfo,
@@ -12,6 +13,7 @@ import {
12
  useFetchTenantInfo,
13
  useSelectTenantInfo,
14
  } from '@/hooks/userSettingHook';
 
15
  import { useCallback, useEffect, useState } from 'react';
16
 
17
  type SavingParamsState = Omit<IApiKeySavingParams, 'api_key'>;
@@ -127,3 +129,31 @@ export const useSelectModelProvidersLoading = () => {
127
 
128
  return loading;
129
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import {
3
  IApiKeySavingParams,
4
  ISystemModelSettingSavingParams,
5
+ useAddLlm,
6
  useFetchLlmList,
7
  useSaveApiKey,
8
  useSaveTenantInfo,
 
13
  useFetchTenantInfo,
14
  useSelectTenantInfo,
15
  } from '@/hooks/userSettingHook';
16
+ import { IAddLlmRequestBody } from '@/interfaces/request/llm';
17
  import { useCallback, useEffect, useState } from 'react';
18
 
19
  type SavingParamsState = Omit<IApiKeySavingParams, 'api_key'>;
 
129
 
130
  return loading;
131
  };
132
+
133
+ export const useSubmitOllama = () => {
134
+ const loading = useOneNamespaceEffectsLoading('settingModel', ['add_llm']);
135
+ const addLlm = useAddLlm();
136
+ const {
137
+ visible: llmAddingVisible,
138
+ hideModal: hideLlmAddingModal,
139
+ showModal: showLlmAddingModal,
140
+ } = useSetModalState();
141
+
142
+ const onLlmAddingOk = useCallback(
143
+ async (payload: IAddLlmRequestBody) => {
144
+ const ret = await addLlm(payload);
145
+ if (ret === 0) {
146
+ hideLlmAddingModal();
147
+ }
148
+ },
149
+ [hideLlmAddingModal, addLlm],
150
+ );
151
+
152
+ return {
153
+ llmAddingLoading: loading,
154
+ onLlmAddingOk,
155
+ llmAddingVisible,
156
+ hideLlmAddingModal,
157
+ showLlmAddingModal,
158
+ };
159
+ };
web/src/pages/user-setting/setting-model/index.tsx CHANGED
@@ -1,15 +1,11 @@
1
  import { ReactComponent as MoreModelIcon } from '@/assets/svg/more-model.svg';
 
2
  import { useSetModalState, useTranslate } from '@/hooks/commonHooks';
3
  import {
4
  LlmItem,
5
  useFetchLlmFactoryListOnMount,
6
  useFetchMyLlmListOnMount,
7
  } from '@/hooks/llmHooks';
8
- import { ReactComponent as MoonshotIcon } from '@/icons/moonshot.svg';
9
- import { ReactComponent as OpenAiIcon } from '@/icons/openai.svg';
10
- import { ReactComponent as TongYiIcon } from '@/icons/tongyi.svg';
11
- import { ReactComponent as WenXinIcon } from '@/icons/wenxin.svg';
12
- import { ReactComponent as ZhiPuIcon } from '@/icons/zhipu.svg';
13
  import { SettingOutlined, UserOutlined } from '@ant-design/icons';
14
  import {
15
  Avatar,
@@ -33,24 +29,27 @@ import ApiKeyModal from './api-key-modal';
33
  import {
34
  useSelectModelProvidersLoading,
35
  useSubmitApiKey,
 
36
  useSubmitSystemModelSetting,
37
  } from './hooks';
38
- import SystemModelSettingModal from './system-model-setting-modal';
39
-
40
  import styles from './index.less';
 
 
41
 
42
  const IconMap = {
43
- 'Tongyi-Qianwen': TongYiIcon,
44
- Moonshot: MoonshotIcon,
45
- OpenAI: OpenAiIcon,
46
- 'ZHIPU-AI': ZhiPuIcon,
47
- 文心一言: WenXinIcon,
 
48
  };
49
 
50
  const LlmIcon = ({ name }: { name: string }) => {
51
- const Icon = IconMap[name as keyof typeof IconMap];
52
- return Icon ? (
53
- <Icon width={48} height={48}></Icon>
 
54
  ) : (
55
  <Avatar shape="square" size="large" icon={<UserOutlined />} />
56
  );
@@ -90,7 +89,7 @@ const ModelCard = ({ item, clickApiKey }: IModelCardProps) => {
90
  <Col span={12} className={styles.factoryOperationWrapper}>
91
  <Space size={'middle'}>
92
  <Button onClick={handleApiKeyClick}>
93
- API-Key
94
  <SettingOutlined />
95
  </Button>
96
  <Button onClick={handleShowMoreClick}>
@@ -142,16 +141,31 @@ const UserSettingModel = () => {
142
  showSystemSettingModal,
143
  } = useSubmitSystemModelSetting();
144
  const { t } = useTranslate('setting');
 
 
 
 
 
 
 
145
 
146
  const handleApiKeyClick = useCallback(
147
  (llmFactory: string) => {
148
- showApiKeyModal({ llm_factory: llmFactory });
 
 
 
 
149
  },
150
- [showApiKeyModal],
151
  );
152
 
153
  const handleAddModel = (llmFactory: string) => () => {
154
- handleApiKeyClick(llmFactory);
 
 
 
 
155
  };
156
 
157
  const items: CollapseProps['items'] = [
@@ -216,7 +230,7 @@ const UserSettingModel = () => {
216
  clickButton={showSystemSettingModal}
217
  ></SettingTitle>
218
  <Divider></Divider>
219
- <Collapse defaultActiveKey={['1']} ghost items={items} />
220
  </section>
221
  </Spin>
222
  <ApiKeyModal
@@ -233,6 +247,12 @@ const UserSettingModel = () => {
233
  hideModal={hideSystemSettingModal}
234
  loading={saveSystemModelSettingLoading}
235
  ></SystemModelSettingModal>
 
 
 
 
 
 
236
  </>
237
  );
238
  };
 
1
  import { ReactComponent as MoreModelIcon } from '@/assets/svg/more-model.svg';
2
+ import SvgIcon from '@/components/svg-icon';
3
  import { useSetModalState, useTranslate } from '@/hooks/commonHooks';
4
  import {
5
  LlmItem,
6
  useFetchLlmFactoryListOnMount,
7
  useFetchMyLlmListOnMount,
8
  } from '@/hooks/llmHooks';
 
 
 
 
 
9
  import { SettingOutlined, UserOutlined } from '@ant-design/icons';
10
  import {
11
  Avatar,
 
29
  import {
30
  useSelectModelProvidersLoading,
31
  useSubmitApiKey,
32
+ useSubmitOllama,
33
  useSubmitSystemModelSetting,
34
  } from './hooks';
 
 
35
  import styles from './index.less';
36
+ import OllamaModal from './ollama-modal';
37
+ import SystemModelSettingModal from './system-model-setting-modal';
38
 
39
  const IconMap = {
40
+ 'Tongyi-Qianwen': 'tongyi',
41
+ Moonshot: 'moonshot',
42
+ OpenAI: 'openai',
43
+ 'ZHIPU-AI': 'zhipu',
44
+ 文心一言: 'wenxin',
45
+ Ollama: 'ollama',
46
  };
47
 
48
  const LlmIcon = ({ name }: { name: string }) => {
49
+ const icon = IconMap[name as keyof typeof IconMap];
50
+
51
+ return icon ? (
52
+ <SvgIcon name={`llm/${icon}`} width={48} height={48}></SvgIcon>
53
  ) : (
54
  <Avatar shape="square" size="large" icon={<UserOutlined />} />
55
  );
 
89
  <Col span={12} className={styles.factoryOperationWrapper}>
90
  <Space size={'middle'}>
91
  <Button onClick={handleApiKeyClick}>
92
+ {item.name === 'Ollama' ? t('addTheModel') : 'API-Key'}
93
  <SettingOutlined />
94
  </Button>
95
  <Button onClick={handleShowMoreClick}>
 
141
  showSystemSettingModal,
142
  } = useSubmitSystemModelSetting();
143
  const { t } = useTranslate('setting');
144
+ const {
145
+ llmAddingVisible,
146
+ hideLlmAddingModal,
147
+ showLlmAddingModal,
148
+ onLlmAddingOk,
149
+ llmAddingLoading,
150
+ } = useSubmitOllama();
151
 
152
  const handleApiKeyClick = useCallback(
153
  (llmFactory: string) => {
154
+ if (llmFactory === 'Ollama') {
155
+ showLlmAddingModal();
156
+ } else {
157
+ showApiKeyModal({ llm_factory: llmFactory });
158
+ }
159
  },
160
+ [showApiKeyModal, showLlmAddingModal],
161
  );
162
 
163
  const handleAddModel = (llmFactory: string) => () => {
164
+ if (llmFactory === 'Ollama') {
165
+ showLlmAddingModal();
166
+ } else {
167
+ handleApiKeyClick(llmFactory);
168
+ }
169
  };
170
 
171
  const items: CollapseProps['items'] = [
 
230
  clickButton={showSystemSettingModal}
231
  ></SettingTitle>
232
  <Divider></Divider>
233
+ <Collapse defaultActiveKey={['1', '2']} ghost items={items} />
234
  </section>
235
  </Spin>
236
  <ApiKeyModal
 
247
  hideModal={hideSystemSettingModal}
248
  loading={saveSystemModelSettingLoading}
249
  ></SystemModelSettingModal>
250
+ <OllamaModal
251
+ visible={llmAddingVisible}
252
+ hideModal={hideLlmAddingModal}
253
+ onOk={onLlmAddingOk}
254
+ loading={llmAddingLoading}
255
+ ></OllamaModal>
256
  </>
257
  );
258
  };
web/src/pages/user-setting/setting-model/ollama-modal/index.tsx ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useTranslate } from '@/hooks/commonHooks';
2
+ import { IModalProps } from '@/interfaces/common';
3
+ import { IAddLlmRequestBody } from '@/interfaces/request/llm';
4
+ import { Form, Input, Modal, Select, Switch } from 'antd';
5
+ import omit from 'lodash/omit';
6
+
7
+ type FieldType = IAddLlmRequestBody & { vision: boolean };
8
+
9
+ const { Option } = Select;
10
+
11
+ const OllamaModal = ({
12
+ visible,
13
+ hideModal,
14
+ onOk,
15
+ loading,
16
+ }: IModalProps<IAddLlmRequestBody>) => {
17
+ const [form] = Form.useForm<FieldType>();
18
+
19
+ const { t } = useTranslate('setting');
20
+
21
+ const handleOk = async () => {
22
+ const values = await form.validateFields();
23
+ const modelType =
24
+ values.model_type === 'chat' && values.vision
25
+ ? 'image2text'
26
+ : values.model_type;
27
+
28
+ const data = {
29
+ ...omit(values, ['vision']),
30
+ model_type: modelType,
31
+ llm_factory: 'Ollama',
32
+ };
33
+ console.info(data);
34
+
35
+ onOk?.(data);
36
+ };
37
+
38
+ return (
39
+ <Modal
40
+ title={t('addLlmTitle')}
41
+ open={visible}
42
+ onOk={handleOk}
43
+ onCancel={hideModal}
44
+ okButtonProps={{ loading }}
45
+ >
46
+ <Form
47
+ name="basic"
48
+ style={{ maxWidth: 600 }}
49
+ autoComplete="off"
50
+ layout={'vertical'}
51
+ form={form}
52
+ >
53
+ <Form.Item<FieldType>
54
+ label={t('modelType')}
55
+ name="model_type"
56
+ initialValue={'chat'}
57
+ rules={[{ required: true, message: t('modelTypeMessage') }]}
58
+ >
59
+ <Select placeholder={t('modelTypeMessage')}>
60
+ <Option value="chat">chat</Option>
61
+ <Option value="embedding">embedding</Option>
62
+ </Select>
63
+ </Form.Item>
64
+ <Form.Item<FieldType>
65
+ label={t('modelName')}
66
+ name="llm_name"
67
+ rules={[{ required: true, message: t('modelNameMessage') }]}
68
+ >
69
+ <Input placeholder={t('modelNameMessage')} />
70
+ </Form.Item>
71
+ <Form.Item<FieldType>
72
+ label={t('addLlmBaseUrl')}
73
+ name="api_base"
74
+ rules={[{ required: true, message: t('baseUrlNameMessage') }]}
75
+ >
76
+ <Input placeholder={t('baseUrlNameMessage')} />
77
+ </Form.Item>
78
+ <Form.Item noStyle dependencies={['model_type']}>
79
+ {({ getFieldValue }) =>
80
+ getFieldValue('model_type') === 'chat' && (
81
+ <Form.Item
82
+ label={t('vision')}
83
+ valuePropName="checked"
84
+ name={'vision'}
85
+ >
86
+ <Switch />
87
+ </Form.Item>
88
+ )
89
+ }
90
+ </Form.Item>
91
+ </Form>
92
+ </Modal>
93
+ );
94
+ };
95
+
96
+ export default OllamaModal;
web/src/pages/user-setting/setting-model/system-model-setting-modal/index.tsx CHANGED
@@ -30,8 +30,10 @@ const SystemModelSettingModal = ({
30
  };
31
 
32
  useEffect(() => {
33
- form.setFieldsValue(initialValues);
34
- }, [form, initialValues]);
 
 
35
 
36
  const onFormLayoutChange = () => {};
37
 
 
30
  };
31
 
32
  useEffect(() => {
33
+ if (visible) {
34
+ form.setFieldsValue(initialValues);
35
+ }
36
+ }, [form, initialValues, visible]);
37
 
38
  const onFormLayoutChange = () => {};
39
 
web/src/services/userService.ts CHANGED
@@ -14,6 +14,7 @@ const {
14
  my_llm,
15
  set_api_key,
16
  set_tenant_info,
 
17
  } = api;
18
 
19
  const methods = {
@@ -61,6 +62,10 @@ const methods = {
61
  url: set_api_key,
62
  method: 'post',
63
  },
 
 
 
 
64
  } as const;
65
 
66
  const userService = registerServer<keyof typeof methods>(methods, request);
 
14
  my_llm,
15
  set_api_key,
16
  set_tenant_info,
17
+ add_llm,
18
  } = api;
19
 
20
  const methods = {
 
62
  url: set_api_key,
63
  method: 'post',
64
  },
65
+ add_llm: {
66
+ url: add_llm,
67
+ method: 'post',
68
+ },
69
  } as const;
70
 
71
  const userService = registerServer<keyof typeof methods>(methods, request);
web/src/utils/api.ts CHANGED
@@ -17,6 +17,7 @@ export default {
17
  llm_list: `${api_host}/llm/list`,
18
  my_llm: `${api_host}/llm/my_llms`,
19
  set_api_key: `${api_host}/llm/set_api_key`,
 
20
 
21
  //知识库管理
22
  kb_list: `${api_host}/kb/list`,
 
17
  llm_list: `${api_host}/llm/list`,
18
  my_llm: `${api_host}/llm/my_llms`,
19
  set_api_key: `${api_host}/llm/set_api_key`,
20
+ add_llm: `${api_host}/llm/add_llm`,
21
 
22
  //知识库管理
23
  kb_list: `${api_host}/kb/list`,