From 9e542f813c143def3c452e968072cafb8741f55b Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Sat, 20 Jul 2024 00:30:04 +0800 Subject: [PATCH] feat: add custom llm provider --- src/renderer/src/components/TopView/index.tsx | 6 +- src/renderer/src/components/app/Navbar.tsx | 8 +- src/renderer/src/hooks/useProvider.ts | 15 +- src/renderer/src/i18n/index.ts | 14 +- .../src/pages/home/components/Navigation.tsx | 2 +- .../src/pages/settings/GeneralSettings.tsx | 4 +- .../src/pages/settings/ModelSettings.tsx | 2 +- .../src/pages/settings/ProviderSettings.tsx | 164 ++++++++++++++---- .../settings/components/AddModelPopup.tsx | 11 +- .../settings/components/AddProviderPopup.tsx | 72 ++++++++ .../settings/components/EditModelsPopup.tsx | 2 +- .../settings/components/ProviderSetting.tsx | 4 +- src/renderer/src/store/llm.ts | 4 +- src/renderer/src/utils/index.ts | 32 ++++ 14 files changed, 283 insertions(+), 57 deletions(-) create mode 100644 src/renderer/src/pages/settings/components/AddProviderPopup.tsx diff --git a/src/renderer/src/components/TopView/index.tsx b/src/renderer/src/components/TopView/index.tsx index 9b8449e1..ee938dc9 100644 --- a/src/renderer/src/components/TopView/index.tsx +++ b/src/renderer/src/components/TopView/index.tsx @@ -61,7 +61,11 @@ const TopViewContainer: React.FC = ({ children }) => {
{elements.map(({ element: Element, key }) => - typeof Element === 'function' ? : Element + typeof Element === 'function' ? ( + + ) : ( +
{Element}
+ ) )}
)} diff --git a/src/renderer/src/components/app/Navbar.tsx b/src/renderer/src/components/app/Navbar.tsx index d30e2b79..a93e1090 100644 --- a/src/renderer/src/components/app/Navbar.tsx +++ b/src/renderer/src/components/app/Navbar.tsx @@ -39,18 +39,20 @@ const NavbarLeftContainer = styled.div` display: flex; flex-direction: row; align-items: center; + font-size: 14px; + font-weight: bold; + color: var(--color-text-1); ` const NavbarCenterContainer = styled.div` flex: 1; display: flex; align-items: center; + border-right: 1px solid var(--color-border); + padding: 0 20px; font-size: 14px; font-weight: bold; color: var(--color-text-1); - text-align: center; - border-right: 1px solid var(--color-border); - padding: 0 20px; ` const NavbarRightContainer = styled.div` diff --git a/src/renderer/src/hooks/useProvider.ts b/src/renderer/src/hooks/useProvider.ts index d0a38354..d4292148 100644 --- a/src/renderer/src/hooks/useProvider.ts +++ b/src/renderer/src/hooks/useProvider.ts @@ -3,7 +3,9 @@ import { addModel as _addModel, removeModel as _removeModel, updateProvider as _updateProvider, - updateProviders as _updateProviders + updateProviders as _updateProviders, + addProvider, + removeProvider } from '@renderer/store/llm' import { Assistant, Model, Provider } from '@renderer/types' import { useDefaultModel } from './useAssistant' @@ -14,6 +16,9 @@ export function useProviders() { return { providers, + addProvider: (provider: Provider) => dispatch(addProvider(provider)), + removeProvider: (provider: Provider) => dispatch(removeProvider(provider)), + updateProvider: (provider: Provider) => dispatch(_updateProvider(provider)), updateProviders: (providers: Provider[]) => dispatch(_updateProviders(providers)) } } @@ -22,6 +27,14 @@ export function useSystemProviders() { return useAppSelector((state) => state.llm.providers.filter((p) => p.isSystem)) } +export function useUserProviders() { + return useAppSelector((state) => state.llm.providers.filter((p) => !p.isSystem)) +} + +export function useAllProviders() { + return useAppSelector((state) => state.llm.providers) +} + export function useProvider(id: string) { const provider = useAppSelector((state) => state.llm.providers.find((p) => p.id === id) as Provider) const dispatch = useAppDispatch() diff --git a/src/renderer/src/i18n/index.ts b/src/renderer/src/i18n/index.ts index ba54df38..2853de26 100644 --- a/src/renderer/src/i18n/index.ts +++ b/src/renderer/src/i18n/index.ts @@ -101,7 +101,6 @@ const resources = { 'models.default_assistant_model': 'Default Assistant Model', 'models.topic_naming_model': 'Topic Naming Model', 'models.add.add_model': 'Add Model', - 'models.add.provider_name.placeholder': 'Provider Name', 'models.add.model_id.placeholder': 'Required e.g. gpt-3.5-turbo', 'models.add.model_id': 'Model ID', 'models.add.model_id.tooltip': 'Example: gpt-3.5-turbo', @@ -117,7 +116,11 @@ const resources = { 'about.checkingUpdate': 'Checking for updates...', 'about.updateError': 'Update error', 'about.checkUpdate': 'Check Update', - 'about.downloading': 'Downloading...' + 'about.downloading': 'Downloading...', + 'provider.delete.title': 'Delete Provider', + 'provider.delete.content': 'Are you sure you want to delete this provider?', + 'provider.edit.name': 'Provider Name', + 'provider.edit.name.placeholder': 'Example: OpenAI' } } }, @@ -219,7 +222,6 @@ const resources = { 'models.default_assistant_model': '默认助手模型', 'models.topic_naming_model': '话题命名模型', 'models.add.add_model': '添加模型', - 'models.add.provider_name.placeholder': '必填 例如 OpenAI', 'models.add.model_id.placeholder': '必填 例如 gpt-3.5-turbo', 'models.add.model_id': '模型 ID', 'models.add.model_id.tooltip': '例如 gpt-3.5-turbo', @@ -235,7 +237,11 @@ const resources = { 'about.checkingUpdate': '正在检查更新...', 'about.updateError': '更新出错', 'about.checkUpdate': '检查更新', - 'about.downloading': '正在下载更新...' + 'about.downloading': '正在下载更新...', + 'provider.delete.title': '删除提供商', + 'provider.delete.content': '确定要删除此模型提供商吗?', + 'provider.edit.name': '模型提供商名称', + 'provider.edit.name.placeholder': '例如 OpenAI' } } } diff --git a/src/renderer/src/pages/home/components/Navigation.tsx b/src/renderer/src/pages/home/components/Navigation.tsx index 7c5dd086..885d0ace 100644 --- a/src/renderer/src/pages/home/components/Navigation.tsx +++ b/src/renderer/src/pages/home/components/Navigation.tsx @@ -22,7 +22,7 @@ const Navigation: FC = ({ activeAssistant }) => { .filter((p) => p.models.length > 0) .map((p) => ({ key: p.id, - label: t(`provider.${p.id}`), + label: p.isSystem ? t(`provider.${p.id}`) : p.name, type: 'group', children: p.models.map((m) => ({ key: m.id, diff --git a/src/renderer/src/pages/settings/GeneralSettings.tsx b/src/renderer/src/pages/settings/GeneralSettings.tsx index 2d346f51..26d988db 100644 --- a/src/renderer/src/pages/settings/GeneralSettings.tsx +++ b/src/renderer/src/pages/settings/GeneralSettings.tsx @@ -10,7 +10,7 @@ import { setAvatar } from '@renderer/store/runtime' import { useSettings } from '@renderer/hooks/useSettings' import { setLanguage } from '@renderer/store/settings' import { useTranslation } from 'react-i18next' -import i18next from 'i18next' +import i18n from '@renderer/i18n' const GeneralSettings: FC = () => { const avatar = useAvatar() @@ -20,7 +20,7 @@ const GeneralSettings: FC = () => { const onSelectLanguage = (value: string) => { dispatch(setLanguage(value)) - i18next.changeLanguage(value) + i18n.changeLanguage(value) localStorage.setItem('language', value) } diff --git a/src/renderer/src/pages/settings/ModelSettings.tsx b/src/renderer/src/pages/settings/ModelSettings.tsx index 3314ccaf..0937a34a 100644 --- a/src/renderer/src/pages/settings/ModelSettings.tsx +++ b/src/renderer/src/pages/settings/ModelSettings.tsx @@ -16,7 +16,7 @@ const ModelSettings: FC = () => { const selectOptions = providers .filter((p) => p.models.length > 0) .map((p) => ({ - label: t(`provider.${p.id}`), + label: p.isSystem ? t(`provider.${p.id}`) : p.name, title: p.name, options: p.models.map((m) => ({ label: m.name, diff --git a/src/renderer/src/pages/settings/ProviderSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings.tsx index 62fc56cf..7f3138d0 100644 --- a/src/renderer/src/pages/settings/ProviderSettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings.tsx @@ -1,21 +1,26 @@ import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd' -import { useProviders, useSystemProviders } from '@renderer/hooks/useProvider' +import { useAllProviders, useProviders } from '@renderer/hooks/useProvider' import { getProviderLogo } from '@renderer/config/provider' import { Provider } from '@renderer/types' -import { droppableReorder } from '@renderer/utils' -import { Avatar, Tag } from 'antd' +import { droppableReorder, generateColorFromChar, getFirstCharacter, uuid } from '@renderer/utils' +import { Avatar, Button, Dropdown, MenuProps, Tag } from 'antd' import { FC, useState } from 'react' import styled from 'styled-components' import ProviderSetting from './components/ProviderSetting' import { useTranslation } from 'react-i18next' +import { PlusOutlined } from '@ant-design/icons' +import { DeleteOutlined, EditOutlined } from '@ant-design/icons' +import AddProviderPopup from './components/AddProviderPopup' const ProviderSettings: FC = () => { - const providers = useSystemProviders() - const { updateProviders } = useProviders() + const providers = useAllProviders() + const { updateProviders, addProvider, removeProvider, updateProvider } = useProviders() const [selectedProvider, setSelectedProvider] = useState(providers[0]) const { t } = useTranslation() + const [dragging, setDragging] = useState(false) const onDragEnd = (result: DropResult) => { + setDragging(false) if (result.destination) { const sourceIndex = result.source.index const destIndex = result.destination.index @@ -24,37 +29,109 @@ const ProviderSettings: FC = () => { } } + const onAddProvider = async () => { + const prividerName = await AddProviderPopup.show() + + if (!prividerName) { + return + } + + const provider = { + id: uuid(), + name: prividerName, + apiKey: '', + apiHost: '', + models: [], + enabled: false, + isSystem: false + } as Provider + addProvider(provider) + setSelectedProvider(provider) + } + + const getDropdownMenus = (provider: Provider): MenuProps['items'] => { + return [ + { + label: t('common.edit'), + key: 'edit', + icon: , + async onClick() { + const name = await AddProviderPopup.show(provider) + name && updateProvider({ ...provider, name }) + } + }, + { + label: t('common.delete'), + key: 'delete', + icon: , + danger: true, + async onClick() { + window.modal.confirm({ + title: t('settings.provider.delete.title'), + content: t('settings.provider.delete.content'), + okButtonProps: { danger: true }, + okText: t('common.delete'), + onOk: () => { + setSelectedProvider(providers.filter((p) => p.isSystem)[0]) + removeProvider(provider) + } + }) + } + } + ] + } + return ( - - - {(provided) => ( -
- {providers.map((provider, index) => ( - - {(provided) => ( -
- setSelectedProvider(provider)}> - - {t(`provider.${provider.id}`)} - {provider.enabled && ( - - ON - - )} - -
- )} -
- ))} -
- )} -
-
+ + setDragging(true)} onDragEnd={onDragEnd}> + + {(provided) => ( +
+ {providers.map((provider, index) => ( + + {(provided) => ( +
+ + setSelectedProvider(provider)}> + {provider.isSystem && } + {!provider.isSystem && ( + + {getFirstCharacter(provider.name)} + + )} + + {provider.isSystem ? t(`provider.${provider.id}`) : provider.name} + + {provider.enabled && ( + + ON + + )} + + +
+ )} +
+ ))} +
+ )} +
+
+
+ {!dragging && ( + +