From 17826fd2d1a598390a6c144c4feff791933bb8e6 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Wed, 10 Jul 2024 15:26:44 +0800 Subject: [PATCH] feat: add ollama provider --- .../src/assets/images/providers/ollama.png | Bin 0 -> 7170 bytes .../src/pages/settings/ProviderSettings.tsx | 4 +- .../settings/components/ModelAddPopup.tsx | 124 ++++++++++++++++++ .../settings/components/ModelListPopup.tsx} | 16 ++- ...{ProviderModals.tsx => ProviderModels.tsx} | 29 ++-- src/renderer/src/services/provider.ts | 85 +++++------- src/renderer/src/store/index.ts | 2 +- src/renderer/src/store/llm.ts | 8 ++ src/renderer/src/store/migrate.ts | 20 +++ src/renderer/src/utils/index.ts | 15 +++ 10 files changed, 230 insertions(+), 73 deletions(-) create mode 100644 src/renderer/src/assets/images/providers/ollama.png create mode 100644 src/renderer/src/pages/settings/components/ModelAddPopup.tsx rename src/renderer/src/{components/Popups/ModalListPopup.tsx => pages/settings/components/ModelListPopup.tsx} (85%) rename src/renderer/src/pages/settings/components/{ProviderModals.tsx => ProviderModels.tsx} (74%) diff --git a/src/renderer/src/assets/images/providers/ollama.png b/src/renderer/src/assets/images/providers/ollama.png new file mode 100644 index 0000000000000000000000000000000000000000..4ca6ec00570bbcff95a2b2bd8a1ff4a934310ffa GIT binary patch literal 7170 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 literal 0 HcmV?d00001 diff --git a/src/renderer/src/pages/settings/ProviderSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings.tsx index be787be5..5a84a48e 100644 --- a/src/renderer/src/pages/settings/ProviderSettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings.tsx @@ -2,9 +2,9 @@ import { useSystemProviders } from '@renderer/hooks/useProvider' import { Provider } from '@renderer/types' import { FC, useState } from 'react' import styled from 'styled-components' -import ProviderModals from './components/ProviderModals' import { Avatar } from 'antd' import { getProviderLogo } from '@renderer/services/provider' +import ProviderModels from './components/ProviderModels' const ProviderSettings: FC = () => { const providers = useSystemProviders() @@ -23,7 +23,7 @@ const ProviderSettings: FC = () => { ))} - + ) } diff --git a/src/renderer/src/pages/settings/components/ModelAddPopup.tsx b/src/renderer/src/pages/settings/components/ModelAddPopup.tsx new file mode 100644 index 00000000..a29f57c3 --- /dev/null +++ b/src/renderer/src/pages/settings/components/ModelAddPopup.tsx @@ -0,0 +1,124 @@ +import { TopView } from '@renderer/components/TopView' +import { useProvider } from '@renderer/hooks/useProvider' +import { Model, Provider } from '@renderer/types' +import { getDefaultGroupName } from '@renderer/utils' +import { Button, Form, FormProps, Input, Modal } from 'antd' +import { find } from 'lodash' +import { useState } from 'react' + +interface ShowParams { + title: string + provider: Provider +} + +interface Props extends ShowParams { + resolve: (data: any) => void +} + +type FieldType = { + provider: string + id: string + name?: string + group?: string +} + +const PopupContainer: React.FC = ({ title, provider, resolve }) => { + const [open, setOpen] = useState(true) + const [form] = Form.useForm() + const { addModel, models } = useProvider(provider.id) + + const onOk = () => { + setOpen(false) + } + + const onCancel = () => { + setOpen(false) + } + + const onClose = () => { + resolve({}) + } + + const onFinish: FormProps['onFinish'] = (values) => { + if (find(models, { id: values.id })) { + Modal.error({ title: 'Error', content: 'Model ID already exists' }) + return + } + + const model: Model = { + id: values.id, + provider: provider.id, + name: values.name ? values.name : values.id.toUpperCase(), + group: getDefaultGroupName(values.group || values.id), + temperature: 0.7 + } + + addModel(model) + + resolve(model) + } + + return ( + +
+ + + + + { + form.setFieldValue('name', e.target.value.toUpperCase()) + form.setFieldValue('group', getDefaultGroupName(e.target.value)) + }} + /> + + + + + + + + + + +
+
+ ) +} + +export default class ModalAddPopup { + static topviewId = 0 + static hide() { + TopView.hide(this.topviewId) + } + static show(props: ShowParams) { + return new Promise((resolve) => { + this.topviewId = TopView.show( + { + resolve(v) + this.hide() + }} + /> + ) + }) + } +} diff --git a/src/renderer/src/components/Popups/ModalListPopup.tsx b/src/renderer/src/pages/settings/components/ModelListPopup.tsx similarity index 85% rename from src/renderer/src/components/Popups/ModalListPopup.tsx rename to src/renderer/src/pages/settings/components/ModelListPopup.tsx index 14e6f76b..1cf80cfb 100644 --- a/src/renderer/src/components/Popups/ModalListPopup.tsx +++ b/src/renderer/src/pages/settings/components/ModelListPopup.tsx @@ -1,8 +1,8 @@ -import { Avatar, Button, Modal } from 'antd' +import { Avatar, Button, Empty, Modal } from 'antd' import { useState } from 'react' -import { TopView } from '../TopView' +import { TopView } from '../../../components/TopView' import { Model, Provider } from '@renderer/types' -import { groupBy } from 'lodash' +import { groupBy, isEmpty, uniqBy } from 'lodash' import styled from 'styled-components' import { MinusOutlined, PlusOutlined } from '@ant-design/icons' import { useProvider } from '@renderer/hooks/useProvider' @@ -19,10 +19,11 @@ interface Props extends ShowParams { const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { const [open, setOpen] = useState(true) - const { provider, addModel, removeModel } = useProvider(_provider.id) + const { provider, models, addModel, removeModel } = useProvider(_provider.id) - const systemModels = SYSTEM_MODELS[_provider.id] - const systemModelGroups = groupBy(systemModels, 'group') + const systemModels = SYSTEM_MODELS[_provider.id] || [] + const allModels = uniqBy([...systemModels, ...models], 'id') + const systemModelGroups = groupBy(allModels, 'group') const onOk = () => { setOpen(false) @@ -79,6 +80,7 @@ const PopupContainer: React.FC = ({ provider: _provider, resolve }) => { })} ))} + {isEmpty(allModels) && } ) @@ -124,7 +126,7 @@ const ListItemName = styled.div` margin-left: 6px; ` -export default class ModalListPopup { +export default class ModelListPopup { static topviewId = 0 static hide() { TopView.hide(this.topviewId) diff --git a/src/renderer/src/pages/settings/components/ProviderModals.tsx b/src/renderer/src/pages/settings/components/ProviderModels.tsx similarity index 74% rename from src/renderer/src/pages/settings/components/ProviderModals.tsx rename to src/renderer/src/pages/settings/components/ProviderModels.tsx index 1fbb86e1..fef0541b 100644 --- a/src/renderer/src/pages/settings/components/ProviderModals.tsx +++ b/src/renderer/src/pages/settings/components/ProviderModels.tsx @@ -1,18 +1,20 @@ import { Provider } from '@renderer/types' import { FC, useEffect, useState } from 'react' import styled from 'styled-components' -import { Avatar, Button, Card, Divider, Input } from 'antd' +import { Avatar, Button, Card, Divider, Flex, Input } from 'antd' import { useProvider } from '@renderer/hooks/useProvider' -import ModalListPopup from '@renderer/components/Popups/ModalListPopup' import { groupBy } from 'lodash' import { SettingContainer, SettingSubtitle, SettingTitle } from './SettingComponent' import { getModelLogo } from '@renderer/services/provider' +import { EditOutlined, PlusOutlined } from '@ant-design/icons' +import ModalAddPopup from './ModelAddPopup' +import ModelListPopup from './ModelListPopup' interface Props { provider: Provider } -const ProviderModals: FC = ({ provider }) => { +const ProviderModels: FC = ({ provider }) => { const [apiKey, setApiKey] = useState(provider.apiKey) const [apiHost, setApiHost] = useState(provider.apiHost) const { updateProvider, models } = useProvider(provider.id) @@ -32,8 +34,12 @@ const ProviderModals: FC = ({ provider }) => { updateProvider({ ...provider, apiHost }) } - const onAddModal = () => { - ModalListPopup.show({ provider }) + const onManageModel = () => { + ModelListPopup.show({ provider }) + } + + const onAddModel = () => { + ModalAddPopup.show({ title: 'Add Model', provider }) } return ( @@ -66,9 +72,14 @@ const ProviderModals: FC = ({ provider }) => { ))} ))} - + + + + ) } @@ -81,4 +92,4 @@ const ModelListItem = styled.div` padding: 5px 0; ` -export default ProviderModals +export default ProviderModels diff --git a/src/renderer/src/services/provider.ts b/src/renderer/src/services/provider.ts index 8efdfc8d..c1d36e52 100644 --- a/src/renderer/src/services/provider.ts +++ b/src/renderer/src/services/provider.ts @@ -4,6 +4,7 @@ import DeepSeekProviderLogo from '@renderer/assets/images/providers/deepseek.png import YiProviderLogo from '@renderer/assets/images/providers/yi.svg' import GroqProviderLogo from '@renderer/assets/images/providers/groq.png' import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png' +import OllamaProviderLogo from '@renderer/assets/images/providers/ollama.png' import ChatGPTModelLogo from '@renderer/assets/images/models/chatgpt.jpeg' import ChatGLMModelLogo from '@renderer/assets/images/models/chatglm.jpeg' import DeepSeekModelLogo from '@renderer/assets/images/models/deepseek.png' @@ -14,66 +15,42 @@ import LlamaModelLogo from '@renderer/assets/images/models/llama.jpeg' import MixtralModelLogo from '@renderer/assets/images/models/mixtral.jpeg' export function getProviderLogo(providerId: string) { - if (providerId === 'openai') { - return OpenAiProviderLogo + switch (providerId) { + case 'openai': + return OpenAiProviderLogo + case 'silicon': + return SiliconFlowProviderLogo + case 'deepseek': + return DeepSeekProviderLogo + case 'yi': + return YiProviderLogo + case 'groq': + return GroqProviderLogo + case 'zhipu': + return ZhipuProviderLogo + case 'ollama': + return OllamaProviderLogo + default: + return '' } - - if (providerId === 'silicon') { - return SiliconFlowProviderLogo - } - - if (providerId === 'deepseek') { - return DeepSeekProviderLogo - } - - if (providerId === 'yi') { - return YiProviderLogo - } - - if (providerId === 'groq') { - return GroqProviderLogo - } - - if (providerId === 'zhipu') { - return ZhipuProviderLogo - } - - return '' } export function getModelLogo(modelId: string) { - const _modelId = modelId.toLowerCase() - - if (_modelId.includes('gpt')) { - return ChatGPTModelLogo + const logoMap = { + gpt: ChatGPTModelLogo, + glm: ChatGLMModelLogo, + deepseek: DeepSeekModelLogo, + qwen: QwenModelLogo, + gemma: GemmaModelLogo, + 'yi-': YiModelLogo, + llama: LlamaModelLogo, + mixtral: MixtralModelLogo } - if (_modelId.includes('glm')) { - return ChatGLMModelLogo - } - - if (_modelId.includes('deepseek')) { - return DeepSeekModelLogo - } - - if (_modelId.includes('qwen')) { - return QwenModelLogo - } - - if (_modelId.includes('gemma')) { - return GemmaModelLogo - } - - if (_modelId.includes('yi-')) { - return YiModelLogo - } - - if (_modelId.includes('llama')) { - return LlamaModelLogo - } - - if (_modelId.includes('mixtral')) { - return MixtralModelLogo + for (const key in logoMap) { + if (modelId.toLowerCase().includes(key)) { + return logoMap[key] + } } return '' diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index e43047a3..6a29d767 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -19,7 +19,7 @@ const persistedReducer = persistReducer( { key: 'cherry-studio', storage, - version: 3, + version: 4, blacklist: ['runtime'], migrate }, diff --git a/src/renderer/src/store/llm.ts b/src/renderer/src/store/llm.ts index 98fbb2d8..23562de6 100644 --- a/src/renderer/src/store/llm.ts +++ b/src/renderer/src/store/llm.ts @@ -60,6 +60,14 @@ const initialState: LlmState = { apiHost: 'https://api.groq.com/openai', isSystem: true, models: SYSTEM_MODELS.groq.filter((m) => m.defaultEnabled) + }, + { + id: 'ollama', + name: 'Ollama', + apiKey: '', + apiHost: 'http://localhost:11434/v1/', + isSystem: true, + models: [] } ] } diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index e8b98b10..be98a915 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -42,6 +42,26 @@ const migrate = createMigrate({ ] } } + }, + // @ts-ignore store type is unknown + '4': (state: RootState) => { + return { + ...state, + llm: { + ...state.llm, + providers: [ + ...state.llm.providers, + { + id: 'ollama', + name: 'Ollama', + apiKey: '', + apiHost: 'http://localhost:11434/v1/', + isSystem: true, + models: [] + } + ] + } + } } }) diff --git a/src/renderer/src/utils/index.ts b/src/renderer/src/utils/index.ts index 0aebc309..c51e40ef 100644 --- a/src/renderer/src/utils/index.ts +++ b/src/renderer/src/utils/index.ts @@ -66,3 +66,18 @@ export const compressImage = async (file: File) => { useWebWorker: false }) } + +// Converts 'gpt-3.5-turbo-16k-0613' to 'GPT-3.5-Turbo' +// Converts 'qwen2:1.5b' to 'QWEN2' +export const getDefaultGroupName = (id: string) => { + if (id.includes(':')) { + return id.split(':')[0].toUpperCase() + } + + if (id.includes('-')) { + const parts = id.split('-') + return parts[0].toUpperCase() + '-' + parts[1].toUpperCase() + } + + return id.toUpperCase() +}