From 6fb878d3b681e2e0331011ce718107a3bb55e57f Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Tue, 2 Sep 2025 02:48:43 +0800 Subject: [PATCH] Revert "fix: complete PoeLogo rendering support across provider UI components (#9756)" This reverts commit df7fd26907b65e78dc7143ea3fb2faa4a8f9dade. --- .../components/ProviderLogoPicker/index.tsx | 27 +- src/renderer/src/config/providers.ts | 4 + src/renderer/src/hooks/useProviderLogo.tsx | 112 ------- src/renderer/src/hooks/useTimer.ts | 10 +- .../src/pages/paintings/AihubmixPage.tsx | 18 +- .../src/pages/paintings/DmxapiPage.tsx | 18 +- .../src/pages/paintings/NewApiPage.tsx | 18 +- .../src/pages/paintings/TokenFluxPage.tsx | 13 +- .../src/pages/paintings/ZhipuPage.tsx | 18 +- .../ProviderSettings/AddProviderPopup.tsx | 104 +++++-- .../ProviderSettings/ProviderList.tsx | 283 +++++++++++------- src/renderer/src/services/ImageStorage.ts | 4 +- .../src/services/__tests__/ApiService.test.ts | 3 +- src/renderer/src/store/index.ts | 2 +- src/renderer/src/store/llm.ts | 20 +- src/renderer/src/store/migrate.ts | 9 - 16 files changed, 338 insertions(+), 325 deletions(-) delete mode 100644 src/renderer/src/hooks/useProviderLogo.tsx diff --git a/src/renderer/src/components/ProviderLogoPicker/index.tsx b/src/renderer/src/components/ProviderLogoPicker/index.tsx index 619c41ebec..b18b2b47d7 100644 --- a/src/renderer/src/components/ProviderLogoPicker/index.tsx +++ b/src/renderer/src/components/ProviderLogoPicker/index.tsx @@ -1,9 +1,6 @@ import { SearchOutlined } from '@ant-design/icons' import { PROVIDER_LOGO_MAP } from '@renderer/config/providers' -import { useProviderAvatar } from '@renderer/hooks/useProviderLogo' import { getProviderLabel } from '@renderer/i18n/label' -import { useAppSelector } from '@renderer/store' -import { isSystemProvider } from '@renderer/types' import { Input, Tooltip } from 'antd' import { FC, useMemo, useState } from 'react' import styled from 'styled-components' @@ -15,21 +12,19 @@ interface Props { // 用于选择内置头像的提供商Logo选择器组件 const ProviderLogoPicker: FC = ({ onProviderClick }) => { const [searchText, setSearchText] = useState('') - const providers = useAppSelector((state) => state.llm.providers) - const { ProviderAvatar } = useProviderAvatar() const filteredProviders = useMemo(() => { - const _providers = providers.filter(isSystemProvider).map((p) => ({ - id: p.id, - name: p.name, - label: getProviderLabel(p.id), - logo: PROVIDER_LOGO_MAP[p.id] + const providers = Object.entries(PROVIDER_LOGO_MAP).map(([id, logo]) => ({ + id, + logo, + name: getProviderLabel(id) })) - if (!searchText) return _providers + + if (!searchText) return providers const searchLower = searchText.toLowerCase() - return _providers.filter((p) => `${p.name} ${p.id} ${p.label}`.toLowerCase().includes(searchLower)) - }, [providers, searchText]) + return providers.filter((p) => p.name.toLowerCase().includes(searchLower)) + }, [searchText]) const handleProviderClick = (event: React.MouseEvent, providerId: string) => { event.stopPropagation() @@ -53,10 +48,10 @@ const ProviderLogoPicker: FC = ({ onProviderClick }) => { /> - {filteredProviders.map(({ id, label }) => ( - + {filteredProviders.map(({ id, logo, name }) => ( + handleProviderClick(e, id)}> - + {name} ))} diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index af678ed765..194d4c20b7 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -664,6 +664,10 @@ export const PROVIDER_LOGO_MAP: AtLeast = { poe: 'svg' // use svg icon component } as const +export function getProviderLogo(providerId: string) { + return PROVIDER_LOGO_MAP[providerId as keyof typeof PROVIDER_LOGO_MAP] +} + // export const SUPPORTED_REANK_PROVIDERS = ['silicon', 'jina', 'voyageai', 'dashscope', 'aihubmix'] export const NOT_SUPPORTED_RERANK_PROVIDERS = ['ollama', 'lmstudio'] as const satisfies SystemProviderId[] export const ONLY_SUPPORTED_DIMENSION_PROVIDERS = ['ollama', 'infini'] as const satisfies SystemProviderId[] diff --git a/src/renderer/src/hooks/useProviderLogo.tsx b/src/renderer/src/hooks/useProviderLogo.tsx deleted file mode 100644 index 49d48e9ebd..0000000000 --- a/src/renderer/src/hooks/useProviderLogo.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { loggerService } from '@logger' -import { PoeLogo } from '@renderer/components/Icons' -import { PROVIDER_LOGO_MAP } from '@renderer/config/providers' -import ImageStorage from '@renderer/services/ImageStorage' -import { getProviderById } from '@renderer/services/ProviderService' -import { useAppSelector } from '@renderer/store' -import { removeLogo, setLogo, setLogos } from '@renderer/store/llm' -import { isSystemProviderId, Provider } from '@renderer/types' -import { generateColorFromChar, getFirstCharacter, getForegroundColor } from '@renderer/utils' -import { Avatar, AvatarProps } from 'antd' -import { AvatarSize } from 'antd/es/avatar/AvatarContext' -import { isEmpty } from 'lodash' -import { useCallback, useEffect } from 'react' -import { useDispatch } from 'react-redux' -import styled, { CSSProperties } from 'styled-components' - -const logger = loggerService.withContext('useProviderLogo') - -export const useProviderAvatar = () => { - const providers = useAppSelector((state) => state.llm.providers) - const logos = useAppSelector((state) => state.llm.logos) - const dispatch = useDispatch() - - const saveLogo = useCallback( - async (logo: string, providerId: string) => { - ImageStorage.set(`provider-${providerId}`, logo) - dispatch(setLogo({ id: providerId, logo })) - }, - [dispatch] - ) - - const deleteLogo = useCallback( - async (id: string) => { - ImageStorage.remove(id).catch((e) => logger.error('Falied to remove image.', e as Error)) - dispatch(removeLogo(id)) - }, - [dispatch] - ) - - useEffect(() => { - const getLogos = async () => { - const _logos = {} - for (const p of providers) { - _logos[p.id] = await ImageStorage.get(`provider-${p.id}`) - } - dispatch(setLogos(_logos)) - } - getLogos() - }, [dispatch, providers]) - - const ProviderAvatar = useCallback( - ({ - pid, - name, - size, - src, - style, - ...rest - }: { - pid?: string - name?: string - size?: number - src?: string - } & AvatarProps) => { - if (src) { - return - } - let provider: Provider | undefined - if (pid) { - // 特殊处理一下svg格式 - if (isSystemProviderId(pid)) { - const logoSrc = PROVIDER_LOGO_MAP[pid] - switch (pid) { - case 'poe': - return - default: - return - } - } - - const customLogo = logos[pid] - if (customLogo) { - return - } - if (!name) { - // generate a avatar for custom provider - provider = getProviderById(pid) - if (!provider) { - return null - } - } - } - return - }, - [logos] - ) - - function GeneratedAvatar({ name, size, style }: { name: string; size?: AvatarSize; style?: CSSProperties }) { - const backgroundColor = generateColorFromChar(name) - const color = name ? getForegroundColor(backgroundColor) : 'white' - return ( - - {getFirstCharacter(!isEmpty(name) ? name : 'P')} - - ) - } - return { ProviderAvatar, GeneratedAvatar, saveLogo, deleteLogo, logos } -} - -const ProviderLogo = styled(Avatar)` - border: 0.5px solid var(--color-border); -` diff --git a/src/renderer/src/hooks/useTimer.ts b/src/renderer/src/hooks/useTimer.ts index 556ff64567..139c3e7143 100644 --- a/src/renderer/src/hooks/useTimer.ts +++ b/src/renderer/src/hooks/useTimer.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef } from 'react' +import { useEffect, useRef } from 'react' /** * 定时器管理 Hook,用于管理 setTimeout 和 setInterval 定时器,支持通过 key 来标识不同的定时器 @@ -65,12 +65,12 @@ export const useTimer = () => { * cleanup(); * ``` */ - const setTimeoutTimer = useCallback((key: string, ...args: Parameters) => { + const setTimeoutTimer = (key: string, ...args: Parameters) => { clearTimeout(timeoutMapRef.current.get(key)) const timer = setTimeout(...args) timeoutMapRef.current.set(key, timer) return () => clearTimeoutTimer(key) - }, []) + } /** * 设置一个 setInterval 定时器 @@ -89,12 +89,12 @@ export const useTimer = () => { * cleanup(); * ``` */ - const setIntervalTimer = useCallback((key: string, ...args: Parameters) => { + const setIntervalTimer = (key: string, ...args: Parameters) => { clearInterval(intervalMapRef.current.get(key)) const timer = setInterval(...args) intervalMapRef.current.set(key, timer) return () => clearIntervalTimer(key) - }, []) + } /** * 清除指定 key 的 setTimeout 定时器 diff --git a/src/renderer/src/pages/paintings/AihubmixPage.tsx b/src/renderer/src/pages/paintings/AihubmixPage.tsx index 3b2ea14d5c..6c9236e7dd 100644 --- a/src/renderer/src/pages/paintings/AihubmixPage.tsx +++ b/src/renderer/src/pages/paintings/AihubmixPage.tsx @@ -7,11 +7,11 @@ import { HStack } from '@renderer/components/Layout' import Scrollbar from '@renderer/components/Scrollbar' import TranslateButton from '@renderer/components/TranslateButton' import { isMac } from '@renderer/config/constant' +import { getProviderLogo } from '@renderer/config/providers' import { LanguagesEnum } from '@renderer/config/translate' import { useTheme } from '@renderer/context/ThemeProvider' import { usePaintings } from '@renderer/hooks/usePaintings' import { useAllProviders } from '@renderer/hooks/useProvider' -import { useProviderAvatar } from '@renderer/hooks/useProviderLogo' import { useRuntime } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' import { getProviderLabel } from '@renderer/i18n/label' @@ -22,7 +22,7 @@ import { setGenerating } from '@renderer/store/runtime' import type { FileMetadata } from '@renderer/types' import type { PaintingAction, PaintingsState } from '@renderer/types' import { getErrorMessage, uuid } from '@renderer/utils' -import { Button, Input, InputNumber, Radio, Segmented, Select, Slider, Switch, Tooltip, Upload } from 'antd' +import { Avatar, Button, Input, InputNumber, Radio, Segmented, Select, Slider, Switch, Tooltip, Upload } from 'antd' import TextArea from 'antd/es/input/TextArea' import { Info } from 'lucide-react' import type { FC } from 'react' @@ -54,7 +54,6 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { aihubmix_image_edit, aihubmix_image_upscale } = usePaintings() - const { ProviderAvatar } = useProviderAvatar() const paintings = useMemo(() => { return { @@ -848,7 +847,12 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { {t('common.provider')} {t('paintings.learn_more')} - + @@ -856,7 +860,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { {providerOptions.map((provider) => ( - + {provider.label} @@ -1025,6 +1029,10 @@ const StyledInputNumber = styled(InputNumber)` width: 70px; ` +const ProviderLogo = styled(Avatar)` + border: 0.5px solid var(--color-border); +` + // 添加新的样式组件 const ModeSegmentedContainer = styled.div` display: flex; diff --git a/src/renderer/src/pages/paintings/DmxapiPage.tsx b/src/renderer/src/pages/paintings/DmxapiPage.tsx index 9212bb262c..21a784397f 100644 --- a/src/renderer/src/pages/paintings/DmxapiPage.tsx +++ b/src/renderer/src/pages/paintings/DmxapiPage.tsx @@ -4,9 +4,9 @@ import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navb import { HStack } from '@renderer/components/Layout' import Scrollbar from '@renderer/components/Scrollbar' import { isMac } from '@renderer/config/constant' +import { getProviderLogo } from '@renderer/config/providers' import { usePaintings } from '@renderer/hooks/usePaintings' import { useAllProviders } from '@renderer/hooks/useProvider' -import { useProviderAvatar } from '@renderer/hooks/useProviderLogo' import { useRuntime } from '@renderer/hooks/useRuntime' import { getProviderLabel } from '@renderer/i18n/label' import FileManager from '@renderer/services/FileManager' @@ -15,7 +15,7 @@ import { setGenerating } from '@renderer/store/runtime' import type { FileMetadata } from '@renderer/types' import { convertToBase64, uuid } from '@renderer/utils' import { DmxapiPainting } from '@types' -import { Button, Input, InputNumber, Segmented, Select, Switch, Tooltip } from 'antd' +import { Avatar, Button, Input, InputNumber, Segmented, Select, Switch, Tooltip } from 'antd' import TextArea from 'antd/es/input/TextArea' import { Info } from 'lucide-react' import React, { FC, useEffect, useRef, useState } from 'react' @@ -46,7 +46,6 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { const [painting, setPainting] = useState(dmxapi_paintings?.[0] || DEFAULT_PAINTING) const { t } = useTranslation() const providers = useAllProviders() - const { ProviderAvatar } = useProviderAvatar() const providerOptions = Options.map((option) => { const provider = providers.find((p) => p.id === option) if (provider) { @@ -815,15 +814,19 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { {t('paintings.top_up')} - + {providerOptions.map((provider) => ( - + {provider.label} @@ -577,4 +581,8 @@ const SelectOptionContainer = styled.div` gap: 8px; ` +const ProviderLogo = styled(Avatar)` + border-radius: 4px; +` + export default ZhipuPage diff --git a/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx index 821c0ee7bc..586271e902 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx @@ -1,18 +1,18 @@ +import { loggerService } from '@logger' import { Center, VStack } from '@renderer/components/Layout' import ProviderLogoPicker from '@renderer/components/ProviderLogoPicker' import { TopView } from '@renderer/components/TopView' import { PROVIDER_LOGO_MAP } from '@renderer/config/providers' -import { useProviderAvatar } from '@renderer/hooks/useProviderLogo' import ImageStorage from '@renderer/services/ImageStorage' import { Provider, ProviderType } from '@renderer/types' -import { compressImage } from '@renderer/utils' +import { compressImage, generateColorFromChar, getForegroundColor } from '@renderer/utils' import { Divider, Dropdown, Form, Input, Modal, Popover, Select, Upload } from 'antd' import { ItemType } from 'antd/es/menu/interface' import React, { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -// const logger = loggerService.withContext('AddProviderPopup') +const logger = loggerService.withContext('AddProviderPopup') interface Props { provider?: Provider @@ -23,20 +23,27 @@ const PopupContainer: React.FC = ({ provider, resolve }) => { const [open, setOpen] = useState(true) const [name, setName] = useState(provider?.name || '') const [type, setType] = useState(provider?.type || 'openai') + const [logo, setLogo] = useState(null) const [logoPickerOpen, setLogoPickerOpen] = useState(false) const [dropdownOpen, setDropdownOpen] = useState(false) const { t } = useTranslation() const uploadRef = useRef(null) - const [logo, setLogo] = useState() - - const { ProviderAvatar, logos, saveLogo } = useProviderAvatar() useEffect(() => { - if (provider) { - const logo = logos[provider.id] - setLogo(logo) + if (provider?.id) { + const loadLogo = async () => { + try { + const logoData = await ImageStorage.get(`provider-${provider.id}`) + if (logoData) { + setLogo(logoData) + } + } catch (error) { + logger.error('Failed to load logo', error as Error) + } + } + loadLogo() } - }, [provider, logos, setLogo]) + }, [provider]) const onOk = async () => { setOpen(false) @@ -67,9 +74,12 @@ const PopupContainer: React.FC = ({ provider, resolve }) => { const logoUrl = PROVIDER_LOGO_MAP[providerId] if (provider?.id) { - saveLogo(logoUrl, provider.id) + await ImageStorage.set(`provider-${provider.id}`, logoUrl) + const savedLogo = await ImageStorage.get(`provider-${provider.id}`) + setLogo(savedLogo) + } else { + setLogo(logoUrl) } - setLogo(logoUrl) setLogoPickerOpen(false) } catch (error: any) { @@ -79,9 +89,10 @@ const PopupContainer: React.FC = ({ provider, resolve }) => { const handleReset = async () => { try { + setLogo(null) + if (provider?.id) { - saveLogo('', provider.id) - ImageStorage.set(`provider-${provider.id}`, '') + await ImageStorage.set(`provider-${provider.id}`, '') } setDropdownOpen(false) @@ -90,6 +101,10 @@ const PopupContainer: React.FC = ({ provider, resolve }) => { } } + const getInitials = () => { + return name.charAt(0) || 'P' + } + const items = [ { key: 'upload', @@ -118,7 +133,7 @@ const PopupContainer: React.FC = ({ provider, resolve }) => { await ImageStorage.set(`provider-${provider.id}`, logoData) } const savedLogo = await ImageStorage.get(`provider-${provider.id}`) - saveLogo(savedLogo, provider.id) + setLogo(savedLogo) } else { // 临时保存在内存中,等创建 provider 后会在调用方保存 const tempUrl = await new Promise((resolve) => { @@ -156,6 +171,10 @@ const PopupContainer: React.FC = ({ provider, resolve }) => { } ] satisfies ItemType[] + // for logo + const backgroundColor = generateColorFromChar(name) + const color = name ? getForegroundColor(backgroundColor) : 'white' + return ( = ({ provider, resolve }) => { } }} placement="bottom"> - - - + {logo ? ( + + ) : ( + + {getInitials()} + + )} @@ -235,6 +258,39 @@ const PopupContainer: React.FC = ({ provider, resolve }) => { ) } +const ProviderLogo = styled.img` + cursor: pointer; + width: 60px; + height: 60px; + border-radius: 12px; + object-fit: contain; + transition: opacity 0.3s ease; + background-color: var(--color-background-soft); + padding: 5px; + border: 0.5px solid var(--color-border); + &:hover { + opacity: 0.8; + } +` + +const ProviderInitialsLogo = styled.div` + cursor: pointer; + width: 60px; + height: 60px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 30px; + font-weight: 500; + transition: opacity 0.3s ease; + background-color: var(--color-background-soft); + border: 0.5px solid var(--color-border); + &:hover { + opacity: 0.8; + } +` + const MenuItem = styled.div` width: 100%; text-align: center; @@ -265,15 +321,3 @@ export default class AddProviderPopup { }) } } - -const ProviderLogo = styled.div` - cursor: pointer; - object-fit: contain; - border-radius: 12px; - transition: opacity 0.3s ease; - background-color: var(--color-background-soft); - border: 0.5px solid var(--color-border); - &:hover { - opacity: 0.8; - } -` diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx index dd6b4a5489..a5f2b0139e 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx @@ -5,13 +5,22 @@ import { type DraggableVirtualListRef, useDraggableReorder } from '@renderer/components/DraggableList' -import { DeleteIcon, EditIcon } from '@renderer/components/Icons' +import { DeleteIcon, EditIcon, PoeLogo } from '@renderer/components/Icons' +import { getProviderLogo } from '@renderer/config/providers' import { useAllProviders, useProviders } from '@renderer/hooks/useProvider' -import { useProviderAvatar } from '@renderer/hooks/useProviderLogo' import { useTimer } from '@renderer/hooks/useTimer' +import ImageStorage from '@renderer/services/ImageStorage' import { isSystemProvider, Provider, ProviderType } from '@renderer/types' -import { getFancyProviderName, matchKeywordsInModel, matchKeywordsInProvider, uuid } from '@renderer/utils' -import { Button, Dropdown, Input, MenuProps, Tag } from 'antd' +import { + generateColorFromChar, + getFancyProviderName, + getFirstCharacter, + getForegroundColor, + matchKeywordsInModel, + matchKeywordsInProvider, + uuid +} from '@renderer/utils' +import { Avatar, Button, Dropdown, Input, MenuProps, Tag } from 'antd' import { GripVertical, PlusIcon, Search, UserPen } from 'lucide-react' import { FC, startTransition, useCallback, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -36,13 +45,34 @@ const ProviderList: FC = () => { const { t } = useTranslation() const [searchText, setSearchText] = useState('') const [dragging, setDragging] = useState(false) + const [providerLogos, setProviderLogos] = useState>({}) const listRef = useRef(null) - const { ProviderAvatar, saveLogo, deleteLogo } = useProviderAvatar() const setSelectedProvider = useCallback((provider: Provider) => { startTransition(() => _setSelectedProvider(provider)) }, []) + useEffect(() => { + const loadAllLogos = async () => { + const logos: Record = {} + for (const provider of providers) { + if (provider.id) { + try { + const logoData = await ImageStorage.get(`provider-${provider.id}`) + if (logoData) { + logos[provider.id] = logoData + } + } catch (error) { + logger.error(`Failed to load logo for provider ${provider.id}`, error as Error) + } + } + } + setProviderLogos(logos) + } + + loadAllLogos() + }, [providers]) + useEffect(() => { if (searchParams.get('id')) { const providerId = searchParams.get('id') @@ -132,10 +162,17 @@ const ProviderList: FC = () => { models: [], enabled: true, isSystem: false - } satisfies Provider + } as Provider + + let updatedLogos = { ...providerLogos } if (logo) { try { - saveLogo(logo, provider.id) + await ImageStorage.set(`provider-${provider.id}`, logo) + updatedLogos = { + ...updatedLogos, + [provider.id]: logo + } + setProviderLogos(updatedLogos) } catch (error) { logger.error('Failed to save logo', error as Error) window.message.error('保存Provider Logo失败') @@ -146,91 +183,132 @@ const ProviderList: FC = () => { setSelectedProvider(provider) } - const getDropdownMenus = useCallback( - (provider: Provider): MenuProps['items'] => { - const noteMenu = { - label: t('settings.provider.notes.title'), - key: 'notes', - icon: , - onClick: () => ModelNotesPopup.show({ provider }) - } + const getDropdownMenus = (provider: Provider): MenuProps['items'] => { + const noteMenu = { + label: t('settings.provider.notes.title'), + key: 'notes', + icon: , + onClick: () => ModelNotesPopup.show({ provider }) + } - const editMenu = { - label: t('common.edit'), - key: 'edit', - icon: , - async onClick() { - const { name, type, logoFile, logo } = await AddProviderPopup.show(provider) + const editMenu = { + label: t('common.edit'), + key: 'edit', + icon: , + async onClick() { + const { name, type, logoFile, logo } = await AddProviderPopup.show(provider) - if (name) { - updateProvider({ ...provider, name, type }) - if (provider.id) { - if (logo) { - try { - saveLogo(logo, provider.id) - } catch (error) { - logger.error('Failed to save logo', error as Error) - window.message.error('更新Provider Logo失败') - } - } else if (logo === undefined && logoFile === undefined) { - try { - deleteLogo(provider.id) - } catch (error) { - logger.error('Failed to reset logo', error as Error) - } + if (name) { + updateProvider({ ...provider, name, type }) + if (provider.id) { + if (logo) { + try { + await ImageStorage.set(`provider-${provider.id}`, logo) + setProviderLogos((prev) => ({ + ...prev, + [provider.id]: logo + })) + } catch (error) { + logger.error('Failed to save logo', error as Error) + window.message.error('更新Provider Logo失败') + } + } else if (logo === undefined && logoFile === undefined) { + try { + await ImageStorage.set(`provider-${provider.id}`, '') + setProviderLogos((prev) => { + const newLogos = { ...prev } + delete newLogos[provider.id] + return newLogos + }) + } catch (error) { + logger.error('Failed to reset logo', error as Error) } } } } } + } - const deleteMenu = { - 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'), - centered: true, - onOk: async () => { - // 删除provider前先清理其logo - if (provider.id) { - try { - deleteLogo(provider.id) - } catch (error) { - logger.error('Failed to delete logo', error as Error) - } + const deleteMenu = { + 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'), + centered: true, + onOk: async () => { + // 删除provider前先清理其logo + if (provider.id) { + try { + await ImageStorage.remove(`provider-${provider.id}`) + setProviderLogos((prev) => { + const newLogos = { ...prev } + delete newLogos[provider.id] + return newLogos + }) + } catch (error) { + logger.error('Failed to delete logo', error as Error) } - - setSelectedProvider(providers.filter((p) => isSystemProvider(p))[0]) - removeProvider(provider) } - }) - } - } - const menus = [editMenu, noteMenu, deleteMenu] - - if (providers.filter((p) => p.id === provider.id).length > 1) { - return menus + setSelectedProvider(providers.filter((p) => isSystemProvider(p))[0]) + removeProvider(provider) + } + }) } + } - if (isSystemProvider(provider)) { - return [noteMenu] - } else if (provider.isSystem) { - // 这里是处理数据中存在新版本删掉的系统提供商的情况 - // 未来期望能重构一下,不要依赖isSystem字段 - return [noteMenu, deleteMenu] - } else { - return menus + const menus = [editMenu, noteMenu, deleteMenu] + + if (providers.filter((p) => p.id === provider.id).length > 1) { + return menus + } + + if (isSystemProvider(provider)) { + return [noteMenu] + } else if (provider.isSystem) { + // 这里是处理数据中存在新版本删掉的系统提供商的情况 + // 未来期望能重构一下,不要依赖isSystem字段 + return [noteMenu, deleteMenu] + } else { + return menus + } + } + + const getProviderAvatar = (provider: Provider, size: number = 25) => { + // 特殊处理一下svg格式 + if (isSystemProvider(provider)) { + switch (provider.id) { + case 'poe': + return } - }, - [providers, deleteLogo, removeProvider, saveLogo, setSelectedProvider, t, updateProvider] - ) + } + + const logoSrc = getProviderLogo(provider.id) + if (logoSrc) { + return + } + + const customLogo = providerLogos[provider.id] + if (customLogo) { + return + } + + // generate color for custom provider + const backgroundColor = generateColorFromChar(provider.name) + const color = provider.name ? getForegroundColor(backgroundColor) : 'white' + + return ( + + {getFirstCharacter(provider.name)} + + ) + } const filteredProviders = providers.filter((provider) => { const keywords = searchText.toLowerCase().split(/\s+/).filter(Boolean) @@ -258,31 +336,6 @@ const ProviderList: FC = () => { [handleReorder] ) - const providerRender = useCallback( - (provider: Provider) => { - return ( - - setSelectedProvider(provider)}> - - - - - {getFancyProviderName(provider)} - {provider.enabled && ( - - ON - - )} - - - ) - }, - [ProviderAvatar, getDropdownMenus, selectedProvider?.id, setSelectedProvider] - ) - return ( @@ -320,7 +373,25 @@ const ProviderList: FC = () => { paddingRight: 5 }} itemContainerStyle={{ paddingBottom: 5 }}> - {providerRender} + {(provider) => ( + + setSelectedProvider(provider)}> + + + + {getProviderAvatar(provider)} + {getFancyProviderName(provider)} + {provider.enabled && ( + + ON + + )} + + + )}