feat: add ollama provider
This commit is contained in:
parent
8009e05c80
commit
17826fd2d1
BIN
src/renderer/src/assets/images/providers/ollama.png
Normal file
BIN
src/renderer/src/assets/images/providers/ollama.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
@ -2,9 +2,9 @@ import { useSystemProviders } from '@renderer/hooks/useProvider'
|
|||||||
import { Provider } from '@renderer/types'
|
import { Provider } from '@renderer/types'
|
||||||
import { FC, useState } from 'react'
|
import { FC, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import ProviderModals from './components/ProviderModals'
|
|
||||||
import { Avatar } from 'antd'
|
import { Avatar } from 'antd'
|
||||||
import { getProviderLogo } from '@renderer/services/provider'
|
import { getProviderLogo } from '@renderer/services/provider'
|
||||||
|
import ProviderModels from './components/ProviderModels'
|
||||||
|
|
||||||
const ProviderSettings: FC = () => {
|
const ProviderSettings: FC = () => {
|
||||||
const providers = useSystemProviders()
|
const providers = useSystemProviders()
|
||||||
@ -23,7 +23,7 @@ const ProviderSettings: FC = () => {
|
|||||||
</ProviderListItem>
|
</ProviderListItem>
|
||||||
))}
|
))}
|
||||||
</ProviderListContainer>
|
</ProviderListContainer>
|
||||||
<ProviderModals provider={selectedProvider} />
|
<ProviderModels provider={selectedProvider} />
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
124
src/renderer/src/pages/settings/components/ModelAddPopup.tsx
Normal file
124
src/renderer/src/pages/settings/components/ModelAddPopup.tsx
Normal file
@ -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<Props> = ({ 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<FieldType>['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 (
|
||||||
|
<Modal
|
||||||
|
title={title}
|
||||||
|
open={open}
|
||||||
|
onOk={onOk}
|
||||||
|
onCancel={onCancel}
|
||||||
|
maskClosable={false}
|
||||||
|
afterClose={onClose}
|
||||||
|
footer={null}>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
labelCol={{ flex: '110px' }}
|
||||||
|
labelAlign="left"
|
||||||
|
colon={false}
|
||||||
|
style={{ marginTop: 25 }}
|
||||||
|
onFinish={onFinish}>
|
||||||
|
<Form.Item label="Provider" name="provider" initialValue={provider.id} rules={[{ required: true }]}>
|
||||||
|
<Input placeholder="Provider Name" disabled />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Model ID" name="id" tooltip="Example: gpt-3.5-turbo" rules={[{ required: true }]}>
|
||||||
|
<Input
|
||||||
|
placeholder="Required e.g. gpt-3.5-turbo"
|
||||||
|
spellCheck={false}
|
||||||
|
onChange={(e) => {
|
||||||
|
form.setFieldValue('name', e.target.value.toUpperCase())
|
||||||
|
form.setFieldValue('group', getDefaultGroupName(e.target.value))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Model Name" tooltip="Example: GPT-3.5" name="name">
|
||||||
|
<Input placeholder="Optional e.g. GPT-4" spellCheck={false} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="Group Name" tooltip="Example: ChatGPT" name="group">
|
||||||
|
<Input placeholder="Optional e.g. OpenAI" spellCheck={false} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label=" ">
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Add Model
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ModalAddPopup {
|
||||||
|
static topviewId = 0
|
||||||
|
static hide() {
|
||||||
|
TopView.hide(this.topviewId)
|
||||||
|
}
|
||||||
|
static show(props: ShowParams) {
|
||||||
|
return new Promise<any>((resolve) => {
|
||||||
|
this.topviewId = TopView.show(
|
||||||
|
<PopupContainer
|
||||||
|
{...props}
|
||||||
|
resolve={(v) => {
|
||||||
|
resolve(v)
|
||||||
|
this.hide()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { Avatar, Button, Modal } from 'antd'
|
import { Avatar, Button, Empty, Modal } from 'antd'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { TopView } from '../TopView'
|
import { TopView } from '../../../components/TopView'
|
||||||
import { Model, Provider } from '@renderer/types'
|
import { Model, Provider } from '@renderer/types'
|
||||||
import { groupBy } from 'lodash'
|
import { groupBy, isEmpty, uniqBy } from 'lodash'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { MinusOutlined, PlusOutlined } from '@ant-design/icons'
|
import { MinusOutlined, PlusOutlined } from '@ant-design/icons'
|
||||||
import { useProvider } from '@renderer/hooks/useProvider'
|
import { useProvider } from '@renderer/hooks/useProvider'
|
||||||
@ -19,10 +19,11 @@ interface Props extends ShowParams {
|
|||||||
|
|
||||||
const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
||||||
const [open, setOpen] = useState(true)
|
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 systemModels = SYSTEM_MODELS[_provider.id] || []
|
||||||
const systemModelGroups = groupBy(systemModels, 'group')
|
const allModels = uniqBy([...systemModels, ...models], 'id')
|
||||||
|
const systemModelGroups = groupBy(allModels, 'group')
|
||||||
|
|
||||||
const onOk = () => {
|
const onOk = () => {
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
@ -79,6 +80,7 @@ const PopupContainer: React.FC<Props> = ({ provider: _provider, resolve }) => {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
{isEmpty(allModels) && <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="No models" />}
|
||||||
</ListContainer>
|
</ListContainer>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
@ -124,7 +126,7 @@ const ListItemName = styled.div`
|
|||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default class ModalListPopup {
|
export default class ModelListPopup {
|
||||||
static topviewId = 0
|
static topviewId = 0
|
||||||
static hide() {
|
static hide() {
|
||||||
TopView.hide(this.topviewId)
|
TopView.hide(this.topviewId)
|
||||||
@ -1,18 +1,20 @@
|
|||||||
import { Provider } from '@renderer/types'
|
import { Provider } from '@renderer/types'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
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 { useProvider } from '@renderer/hooks/useProvider'
|
||||||
import ModalListPopup from '@renderer/components/Popups/ModalListPopup'
|
|
||||||
import { groupBy } from 'lodash'
|
import { groupBy } from 'lodash'
|
||||||
import { SettingContainer, SettingSubtitle, SettingTitle } from './SettingComponent'
|
import { SettingContainer, SettingSubtitle, SettingTitle } from './SettingComponent'
|
||||||
import { getModelLogo } from '@renderer/services/provider'
|
import { getModelLogo } from '@renderer/services/provider'
|
||||||
|
import { EditOutlined, PlusOutlined } from '@ant-design/icons'
|
||||||
|
import ModalAddPopup from './ModelAddPopup'
|
||||||
|
import ModelListPopup from './ModelListPopup'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
provider: Provider
|
provider: Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProviderModals: FC<Props> = ({ provider }) => {
|
const ProviderModels: FC<Props> = ({ provider }) => {
|
||||||
const [apiKey, setApiKey] = useState(provider.apiKey)
|
const [apiKey, setApiKey] = useState(provider.apiKey)
|
||||||
const [apiHost, setApiHost] = useState(provider.apiHost)
|
const [apiHost, setApiHost] = useState(provider.apiHost)
|
||||||
const { updateProvider, models } = useProvider(provider.id)
|
const { updateProvider, models } = useProvider(provider.id)
|
||||||
@ -32,8 +34,12 @@ const ProviderModals: FC<Props> = ({ provider }) => {
|
|||||||
updateProvider({ ...provider, apiHost })
|
updateProvider({ ...provider, apiHost })
|
||||||
}
|
}
|
||||||
|
|
||||||
const onAddModal = () => {
|
const onManageModel = () => {
|
||||||
ModalListPopup.show({ provider })
|
ModelListPopup.show({ provider })
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAddModel = () => {
|
||||||
|
ModalAddPopup.show({ title: 'Add Model', provider })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -66,9 +72,14 @@ const ProviderModals: FC<Props> = ({ provider }) => {
|
|||||||
))}
|
))}
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
<Button type="primary" style={{ width: '100px', marginTop: '10px' }} onClick={onAddModal}>
|
<Flex gap={10} style={{ marginTop: '10px' }}>
|
||||||
Edit Models
|
<Button type="primary" onClick={onManageModel} icon={<EditOutlined />}>
|
||||||
</Button>
|
Manage
|
||||||
|
</Button>
|
||||||
|
<Button type="default" onClick={onAddModel} icon={<PlusOutlined />}>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -81,4 +92,4 @@ const ModelListItem = styled.div`
|
|||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default ProviderModals
|
export default ProviderModels
|
||||||
@ -4,6 +4,7 @@ import DeepSeekProviderLogo from '@renderer/assets/images/providers/deepseek.png
|
|||||||
import YiProviderLogo from '@renderer/assets/images/providers/yi.svg'
|
import YiProviderLogo from '@renderer/assets/images/providers/yi.svg'
|
||||||
import GroqProviderLogo from '@renderer/assets/images/providers/groq.png'
|
import GroqProviderLogo from '@renderer/assets/images/providers/groq.png'
|
||||||
import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.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 ChatGPTModelLogo from '@renderer/assets/images/models/chatgpt.jpeg'
|
||||||
import ChatGLMModelLogo from '@renderer/assets/images/models/chatglm.jpeg'
|
import ChatGLMModelLogo from '@renderer/assets/images/models/chatglm.jpeg'
|
||||||
import DeepSeekModelLogo from '@renderer/assets/images/models/deepseek.png'
|
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'
|
import MixtralModelLogo from '@renderer/assets/images/models/mixtral.jpeg'
|
||||||
|
|
||||||
export function getProviderLogo(providerId: string) {
|
export function getProviderLogo(providerId: string) {
|
||||||
if (providerId === 'openai') {
|
switch (providerId) {
|
||||||
return OpenAiProviderLogo
|
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) {
|
export function getModelLogo(modelId: string) {
|
||||||
const _modelId = modelId.toLowerCase()
|
const logoMap = {
|
||||||
|
gpt: ChatGPTModelLogo,
|
||||||
if (_modelId.includes('gpt')) {
|
glm: ChatGLMModelLogo,
|
||||||
return ChatGPTModelLogo
|
deepseek: DeepSeekModelLogo,
|
||||||
|
qwen: QwenModelLogo,
|
||||||
|
gemma: GemmaModelLogo,
|
||||||
|
'yi-': YiModelLogo,
|
||||||
|
llama: LlamaModelLogo,
|
||||||
|
mixtral: MixtralModelLogo
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_modelId.includes('glm')) {
|
for (const key in logoMap) {
|
||||||
return ChatGLMModelLogo
|
if (modelId.toLowerCase().includes(key)) {
|
||||||
}
|
return logoMap[key]
|
||||||
|
}
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
|
|||||||
@ -19,7 +19,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 3,
|
version: 4,
|
||||||
blacklist: ['runtime'],
|
blacklist: ['runtime'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@ -60,6 +60,14 @@ const initialState: LlmState = {
|
|||||||
apiHost: 'https://api.groq.com/openai',
|
apiHost: 'https://api.groq.com/openai',
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
models: SYSTEM_MODELS.groq.filter((m) => m.defaultEnabled)
|
models: SYSTEM_MODELS.groq.filter((m) => m.defaultEnabled)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ollama',
|
||||||
|
name: 'Ollama',
|
||||||
|
apiKey: '',
|
||||||
|
apiHost: 'http://localhost:11434/v1/',
|
||||||
|
isSystem: true,
|
||||||
|
models: []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -66,3 +66,18 @@ export const compressImage = async (file: File) => {
|
|||||||
useWebWorker: false
|
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()
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user