mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-22 00:13:09 +08:00
Revert "fix: complete PoeLogo rendering support across provider UI components (#9756)"
This reverts commit df7fd26907.
This commit is contained in:
parent
80f49aecd7
commit
6fb878d3b6
@ -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<Props> = ({ 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<Props> = ({ onProviderClick }) => {
|
||||
/>
|
||||
</SearchContainer>
|
||||
<LogoGrid>
|
||||
{filteredProviders.map(({ id, label }) => (
|
||||
<Tooltip key={id} title={label} placement="top" mouseLeaveDelay={0}>
|
||||
{filteredProviders.map(({ id, logo, name }) => (
|
||||
<Tooltip key={id} title={name} placement="top" mouseLeaveDelay={0}>
|
||||
<LogoItem onClick={(e) => handleProviderClick(e, id)}>
|
||||
<ProviderAvatar pid={id} size={32} />
|
||||
<img src={logo} alt={name} draggable={false} />
|
||||
</LogoItem>
|
||||
</Tooltip>
|
||||
))}
|
||||
|
||||
@ -664,6 +664,10 @@ export const PROVIDER_LOGO_MAP: AtLeast<SystemProviderId, string> = {
|
||||
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[]
|
||||
|
||||
@ -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 <ProviderLogo draggable="false" shape="square" src={src} size={size} style={style} {...rest} />
|
||||
}
|
||||
let provider: Provider | undefined
|
||||
if (pid) {
|
||||
// 特殊处理一下svg格式
|
||||
if (isSystemProviderId(pid)) {
|
||||
const logoSrc = PROVIDER_LOGO_MAP[pid]
|
||||
switch (pid) {
|
||||
case 'poe':
|
||||
return <PoeLogo fontSize={typeof size === 'number' ? size : 18} style={style} />
|
||||
default:
|
||||
return <ProviderLogo draggable="false" shape="square" src={logoSrc} size={size} style={style} {...rest} />
|
||||
}
|
||||
}
|
||||
|
||||
const customLogo = logos[pid]
|
||||
if (customLogo) {
|
||||
return <ProviderLogo draggable="false" shape="square" src={customLogo} size={size} style={style} {...rest} />
|
||||
}
|
||||
if (!name) {
|
||||
// generate a avatar for custom provider
|
||||
provider = getProviderById(pid)
|
||||
if (!provider) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
return <GeneratedAvatar name={name ?? provider?.name ?? 'P'} size={size} style={style} />
|
||||
},
|
||||
[logos]
|
||||
)
|
||||
|
||||
function GeneratedAvatar({ name, size, style }: { name: string; size?: AvatarSize; style?: CSSProperties }) {
|
||||
const backgroundColor = generateColorFromChar(name)
|
||||
const color = name ? getForegroundColor(backgroundColor) : 'white'
|
||||
return (
|
||||
<ProviderLogo size={size} shape="square" style={{ backgroundColor, color, ...style }}>
|
||||
{getFirstCharacter(!isEmpty(name) ? name : 'P')}
|
||||
</ProviderLogo>
|
||||
)
|
||||
}
|
||||
return { ProviderAvatar, GeneratedAvatar, saveLogo, deleteLogo, logos }
|
||||
}
|
||||
|
||||
const ProviderLogo = styled(Avatar)`
|
||||
border: 0.5px solid var(--color-border);
|
||||
`
|
||||
@ -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<typeof setTimeout>) => {
|
||||
const setTimeoutTimer = (key: string, ...args: Parameters<typeof setTimeout>) => {
|
||||
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<typeof setInterval>) => {
|
||||
const setIntervalTimer = (key: string, ...args: Parameters<typeof setInterval>) => {
|
||||
clearInterval(intervalMapRef.current.get(key))
|
||||
const timer = setInterval(...args)
|
||||
intervalMapRef.current.set(key, timer)
|
||||
return () => clearIntervalTimer(key)
|
||||
}, [])
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定 key 的 setTimeout 定时器
|
||||
|
||||
@ -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 }) => {
|
||||
<SettingTitle style={{ marginBottom: 5 }}>{t('common.provider')}</SettingTitle>
|
||||
<SettingHelpLink target="_blank" href={aihubmixProvider.apiHost}>
|
||||
{t('paintings.learn_more')}
|
||||
<ProviderAvatar pid={aihubmixProvider.id} size={16} style={{ marginLeft: 5 }} />
|
||||
<ProviderLogo
|
||||
shape="square"
|
||||
src={getProviderLogo(aihubmixProvider.id)}
|
||||
size={16}
|
||||
style={{ marginLeft: 5 }}
|
||||
/>
|
||||
</SettingHelpLink>
|
||||
</ProviderTitleContainer>
|
||||
|
||||
@ -856,7 +860,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
{providerOptions.map((provider) => (
|
||||
<Select.Option value={provider.value} key={provider.value}>
|
||||
<SelectOptionContainer>
|
||||
<ProviderAvatar pid={provider.value ?? ''} size={16} />
|
||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
||||
{provider.label}
|
||||
</SelectOptionContainer>
|
||||
</Select.Option>
|
||||
@ -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;
|
||||
|
||||
@ -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<DmxapiPainting>(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 }) => {
|
||||
<SettingHelpLink target="_blank" href={TOP_UP_URL}>
|
||||
{t('paintings.top_up')}
|
||||
</SettingHelpLink>
|
||||
<ProviderAvatar pid={dmxapiProvider.id} size={16} style={{ marginLeft: 5 }} />
|
||||
<ProviderLogo
|
||||
shape="square"
|
||||
src={getProviderLogo(dmxapiProvider.id)}
|
||||
size={16}
|
||||
style={{ marginLeft: 5 }}
|
||||
/>
|
||||
</div>
|
||||
</ProviderTitleContainer>
|
||||
<Select value={providerOptions[3].value} onChange={handleProviderChange} style={{ marginBottom: 15 }}>
|
||||
{providerOptions.map((provider) => (
|
||||
<Select.Option value={provider.value} key={provider.value}>
|
||||
<SelectOptionContainer>
|
||||
<ProviderAvatar pid={provider.value || ''} size={16} />
|
||||
|
||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
||||
{provider.label}
|
||||
</SelectOptionContainer>
|
||||
</Select.Option>
|
||||
@ -1035,6 +1038,9 @@ const ProviderTitleContainer = styled.div`
|
||||
margin-bottom: 5px;
|
||||
`
|
||||
|
||||
const ProviderLogo = styled(Avatar)`
|
||||
border: 0.5px solid var(--color-border);
|
||||
`
|
||||
const SelectOptionContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -6,11 +6,11 @@ import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navb
|
||||
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 {
|
||||
@ -29,7 +29,7 @@ import { setGenerating } from '@renderer/store/runtime'
|
||||
import type { PaintingAction, PaintingsState } from '@renderer/types'
|
||||
import { FileMetadata } from '@renderer/types'
|
||||
import { getErrorMessage, uuid } from '@renderer/utils'
|
||||
import { Button, Empty, InputNumber, Segmented, Select, Upload } from 'antd'
|
||||
import { Avatar, Button, Empty, InputNumber, Segmented, Select, Upload } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import React, { FC } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
@ -47,7 +47,6 @@ const logger = loggerService.withContext('NewApiPage')
|
||||
const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
const [mode, setMode] = useState<keyof PaintingsState>('openai_image_generate')
|
||||
const { addPainting, removePainting, updatePainting, openai_image_generate, openai_image_edit } = usePaintings()
|
||||
const { ProviderAvatar } = useProviderAvatar()
|
||||
|
||||
const newApiPaintings = useMemo(() => {
|
||||
return {
|
||||
@ -510,7 +509,12 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
<SettingTitle style={{ marginBottom: 5 }}>{t('common.provider')}</SettingTitle>
|
||||
<SettingHelpLink target="_blank" href={'https://docs.newapi.pro/apps/cherry-studio/'}>
|
||||
{t('paintings.learn_more')}
|
||||
<ProviderAvatar pid={newApiProvider.id} size={16} style={{ marginLeft: 5 }} />
|
||||
<ProviderLogo
|
||||
shape="square"
|
||||
src={getProviderLogo(newApiProvider.id)}
|
||||
size={16}
|
||||
style={{ marginLeft: 5 }}
|
||||
/>
|
||||
</SettingHelpLink>
|
||||
</ProviderTitleContainer>
|
||||
|
||||
@ -521,7 +525,7 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
{providerOptions.map((provider) => (
|
||||
<Select.Option value={provider.value} key={provider.value}>
|
||||
<SelectOptionContainer>
|
||||
<ProviderAvatar pid={provider.value} size={16} />
|
||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
||||
{provider.label}
|
||||
</SelectOptionContainer>
|
||||
</Select.Option>
|
||||
@ -790,6 +794,10 @@ const ToolbarMenu = styled.div`
|
||||
gap: 6px;
|
||||
`
|
||||
|
||||
const ProviderLogo = styled(Avatar)`
|
||||
border: 0.5px solid var(--color-border);
|
||||
`
|
||||
|
||||
// 添加新的样式组件
|
||||
const ModeSegmentedContainer = styled.div`
|
||||
display: flex;
|
||||
|
||||
@ -4,10 +4,10 @@ import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navb
|
||||
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 { 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'
|
||||
@ -17,7 +17,7 @@ import { useAppDispatch } from '@renderer/store'
|
||||
import { setGenerating } from '@renderer/store/runtime'
|
||||
import type { TokenFluxPainting } from '@renderer/types'
|
||||
import { getErrorMessage, uuid } from '@renderer/utils'
|
||||
import { Button, Select, Tooltip } from 'antd'
|
||||
import { Avatar, Button, Select, Tooltip } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { Info } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
@ -50,7 +50,6 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
const { t, i18n } = useTranslation()
|
||||
const providers = useAllProviders()
|
||||
const { addPainting, removePainting, updatePainting, tokenflux_paintings } = usePaintings()
|
||||
const { ProviderAvatar } = useProviderAvatar()
|
||||
const tokenFluxPaintings = tokenflux_paintings
|
||||
const [painting, setPainting] = useState<TokenFluxPainting>(
|
||||
tokenFluxPaintings[0] || { ...DEFAULT_TOKENFLUX_PAINTING, id: uuid() }
|
||||
@ -384,7 +383,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
<SettingTitle style={{ marginBottom: 8 }}>{t('common.provider')}</SettingTitle>
|
||||
<SettingHelpLink target="_blank" href="https://tokenflux.ai">
|
||||
{t('paintings.learn_more')}
|
||||
<ProviderAvatar pid={'tokenflux'} size={16} style={{ marginLeft: 5 }} />
|
||||
<ProviderLogo shape="square" src={getProviderLogo('tokenflux')} size={16} style={{ marginLeft: 5 }} />
|
||||
</SettingHelpLink>
|
||||
</ProviderTitleContainer>
|
||||
|
||||
@ -395,7 +394,7 @@ const TokenFluxPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
{providerOptions.map((provider) => (
|
||||
<Select.Option value={provider.value} key={provider.value}>
|
||||
<SelectOptionContainer>
|
||||
<ProviderAvatar pid={provider.value} size={16} />
|
||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
||||
{provider.label}
|
||||
</SelectOptionContainer>
|
||||
</Select.Option>
|
||||
@ -766,6 +765,10 @@ const InfoIcon = styled(Info)`
|
||||
}
|
||||
`
|
||||
|
||||
const ProviderLogo = styled(Avatar)`
|
||||
border: 0.5px solid var(--color-border);
|
||||
`
|
||||
|
||||
const ProviderTitleContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@ -4,16 +4,16 @@ 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'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setGenerating } from '@renderer/store/runtime'
|
||||
import { getErrorMessage, uuid } from '@renderer/utils'
|
||||
import { Button, InputNumber, Radio, Select } from 'antd'
|
||||
import { Avatar, Button, InputNumber, Radio, Select } from 'antd'
|
||||
import TextArea from 'antd/es/input/TextArea'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -39,7 +39,6 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
const [painting, setPainting] = useState<any>(zhipu_paintings?.[0] || DEFAULT_PAINTING)
|
||||
const { t } = useTranslation()
|
||||
const providers = useAllProviders()
|
||||
const { ProviderAvatar } = useProviderAvatar()
|
||||
|
||||
// 确保painting使用智谱的cogview系列模型
|
||||
useEffect(() => {
|
||||
@ -369,14 +368,19 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
|
||||
<SettingHelpLink target="_blank" href={COURSE_URL}>
|
||||
{t('paintings.paint_course')}
|
||||
</SettingHelpLink>
|
||||
<ProviderAvatar pid={zhipuProvider.id} size={16} style={{ marginLeft: 5 }} />
|
||||
<ProviderLogo
|
||||
shape="square"
|
||||
src={getProviderLogo(zhipuProvider.id)}
|
||||
size={16}
|
||||
style={{ marginLeft: 5 }}
|
||||
/>
|
||||
</div>
|
||||
</ProviderTitleContainer>
|
||||
<Select value={providerOptions[0].value} onChange={handleProviderChange} style={{ marginBottom: 15 }}>
|
||||
{providerOptions.map((provider) => (
|
||||
<Select.Option value={provider.value} key={provider.value}>
|
||||
<SelectOptionContainer>
|
||||
<ProviderAvatar pid={provider.value} size={16} />
|
||||
<ProviderLogo shape="square" src={getProviderLogo(provider.value || '')} size={16} />
|
||||
{provider.label}
|
||||
</SelectOptionContainer>
|
||||
</Select.Option>
|
||||
@ -577,4 +581,8 @@ const SelectOptionContainer = styled.div`
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
const ProviderLogo = styled(Avatar)`
|
||||
border-radius: 4px;
|
||||
`
|
||||
|
||||
export default ZhipuPage
|
||||
|
||||
@ -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<Props> = ({ provider, resolve }) => {
|
||||
const [open, setOpen] = useState(true)
|
||||
const [name, setName] = useState(provider?.name || '')
|
||||
const [type, setType] = useState<ProviderType>(provider?.type || 'openai')
|
||||
const [logo, setLogo] = useState<string | null>(null)
|
||||
const [logoPickerOpen, setLogoPickerOpen] = useState(false)
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
const uploadRef = useRef<HTMLDivElement>(null)
|
||||
const [logo, setLogo] = useState<string>()
|
||||
|
||||
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<Props> = ({ 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<Props> = ({ 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<Props> = ({ provider, resolve }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const getInitials = () => {
|
||||
return name.charAt(0) || 'P'
|
||||
}
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: 'upload',
|
||||
@ -118,7 +133,7 @@ const PopupContainer: React.FC<Props> = ({ 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<string>((resolve) => {
|
||||
@ -156,6 +171,10 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
||||
}
|
||||
] satisfies ItemType[]
|
||||
|
||||
// for logo
|
||||
const backgroundColor = generateColorFromChar(name)
|
||||
const color = name ? getForegroundColor(backgroundColor) : 'white'
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
@ -195,9 +214,13 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
||||
}
|
||||
}}
|
||||
placement="bottom">
|
||||
<ProviderLogo>
|
||||
<ProviderAvatar pid={provider?.id} name={name} src={logo} size={60} style={{ fontSize: 32 }} />
|
||||
</ProviderLogo>
|
||||
{logo ? (
|
||||
<ProviderLogo src={logo} />
|
||||
) : (
|
||||
<ProviderInitialsLogo style={name ? { backgroundColor, color } : undefined}>
|
||||
{getInitials()}
|
||||
</ProviderInitialsLogo>
|
||||
)}
|
||||
</Popover>
|
||||
</Dropdown>
|
||||
</VStack>
|
||||
@ -235,6 +258,39 @@ const PopupContainer: React.FC<Props> = ({ 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;
|
||||
}
|
||||
`
|
||||
|
||||
@ -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<string>('')
|
||||
const [dragging, setDragging] = useState(false)
|
||||
const [providerLogos, setProviderLogos] = useState<Record<string, string>>({})
|
||||
const listRef = useRef<DraggableVirtualListRef>(null)
|
||||
const { ProviderAvatar, saveLogo, deleteLogo } = useProviderAvatar()
|
||||
|
||||
const setSelectedProvider = useCallback((provider: Provider) => {
|
||||
startTransition(() => _setSelectedProvider(provider))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const loadAllLogos = async () => {
|
||||
const logos: Record<string, string> = {}
|
||||
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: <UserPen size={14} />,
|
||||
onClick: () => ModelNotesPopup.show({ provider })
|
||||
}
|
||||
const getDropdownMenus = (provider: Provider): MenuProps['items'] => {
|
||||
const noteMenu = {
|
||||
label: t('settings.provider.notes.title'),
|
||||
key: 'notes',
|
||||
icon: <UserPen size={14} />,
|
||||
onClick: () => ModelNotesPopup.show({ provider })
|
||||
}
|
||||
|
||||
const editMenu = {
|
||||
label: t('common.edit'),
|
||||
key: 'edit',
|
||||
icon: <EditIcon size={14} />,
|
||||
async onClick() {
|
||||
const { name, type, logoFile, logo } = await AddProviderPopup.show(provider)
|
||||
const editMenu = {
|
||||
label: t('common.edit'),
|
||||
key: 'edit',
|
||||
icon: <EditIcon size={14} />,
|
||||
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: <DeleteIcon size={14} className="lucide-custom" />,
|
||||
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: <DeleteIcon size={14} className="lucide-custom" />,
|
||||
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 <PoeLogo fontSize={size} />
|
||||
}
|
||||
},
|
||||
[providers, deleteLogo, removeProvider, saveLogo, setSelectedProvider, t, updateProvider]
|
||||
)
|
||||
}
|
||||
|
||||
const logoSrc = getProviderLogo(provider.id)
|
||||
if (logoSrc) {
|
||||
return <ProviderLogo draggable="false" shape="circle" src={logoSrc} size={size} />
|
||||
}
|
||||
|
||||
const customLogo = providerLogos[provider.id]
|
||||
if (customLogo) {
|
||||
return <ProviderLogo draggable="false" shape="square" src={customLogo} size={size} />
|
||||
}
|
||||
|
||||
// generate color for custom provider
|
||||
const backgroundColor = generateColorFromChar(provider.name)
|
||||
const color = provider.name ? getForegroundColor(backgroundColor) : 'white'
|
||||
|
||||
return (
|
||||
<ProviderLogo size={size} shape="square" style={{ backgroundColor, color, minWidth: size }}>
|
||||
{getFirstCharacter(provider.name)}
|
||||
</ProviderLogo>
|
||||
)
|
||||
}
|
||||
|
||||
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 (
|
||||
<Dropdown menu={{ items: getDropdownMenus(provider) }} trigger={['contextMenu']}>
|
||||
<ProviderListItem
|
||||
key={provider.id}
|
||||
className={provider.id === selectedProvider?.id ? 'active' : ''}
|
||||
onClick={() => setSelectedProvider(provider)}>
|
||||
<DragHandle>
|
||||
<GripVertical size={12} />
|
||||
</DragHandle>
|
||||
<ProviderAvatar pid={provider.id} name={provider.name} size={25} />
|
||||
<ProviderItemName className="text-nowrap">{getFancyProviderName(provider)}</ProviderItemName>
|
||||
{provider.enabled && (
|
||||
<Tag color="green" style={{ marginLeft: 'auto', marginRight: 0, borderRadius: 16 }}>
|
||||
ON
|
||||
</Tag>
|
||||
)}
|
||||
</ProviderListItem>
|
||||
</Dropdown>
|
||||
)
|
||||
},
|
||||
[ProviderAvatar, getDropdownMenus, selectedProvider?.id, setSelectedProvider]
|
||||
)
|
||||
|
||||
return (
|
||||
<Container className="selectable">
|
||||
<ProviderListContainer>
|
||||
@ -320,7 +373,25 @@ const ProviderList: FC = () => {
|
||||
paddingRight: 5
|
||||
}}
|
||||
itemContainerStyle={{ paddingBottom: 5 }}>
|
||||
{providerRender}
|
||||
{(provider) => (
|
||||
<Dropdown menu={{ items: getDropdownMenus(provider) }} trigger={['contextMenu']}>
|
||||
<ProviderListItem
|
||||
key={provider.id}
|
||||
className={provider.id === selectedProvider?.id ? 'active' : ''}
|
||||
onClick={() => setSelectedProvider(provider)}>
|
||||
<DragHandle>
|
||||
<GripVertical size={12} />
|
||||
</DragHandle>
|
||||
{getProviderAvatar(provider)}
|
||||
<ProviderItemName className="text-nowrap">{getFancyProviderName(provider)}</ProviderItemName>
|
||||
{provider.enabled && (
|
||||
<Tag color="green" style={{ marginLeft: 'auto', marginRight: 0, borderRadius: 16 }}>
|
||||
ON
|
||||
</Tag>
|
||||
)}
|
||||
</ProviderListItem>
|
||||
</Dropdown>
|
||||
)}
|
||||
</DraggableVirtualList>
|
||||
<AddButtonWrapper>
|
||||
<Button
|
||||
@ -395,6 +466,10 @@ const DragHandle = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const ProviderLogo = styled(Avatar)`
|
||||
border: 0.5px solid var(--color-border);
|
||||
`
|
||||
|
||||
const ProviderItemName = styled.div`
|
||||
margin-left: 10px;
|
||||
font-weight: 500;
|
||||
|
||||
@ -16,7 +16,7 @@ export default class ImageStorage {
|
||||
db.settings.update(id, { value })
|
||||
return
|
||||
}
|
||||
await db.settings.put({ id, value })
|
||||
await db.settings.add({ id, value })
|
||||
} else {
|
||||
// file image
|
||||
const base64Image = await convertToBase64(value)
|
||||
@ -25,7 +25,7 @@ export default class ImageStorage {
|
||||
db.settings.update(id, { value: base64Image })
|
||||
return
|
||||
}
|
||||
await db.settings.put({ id, value: base64Image })
|
||||
await db.settings.add({ id, value: base64Image })
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@ -233,8 +233,7 @@ vi.mock('@renderer/store/llm.ts', () => {
|
||||
secretAccessKey: '',
|
||||
region: ''
|
||||
}
|
||||
},
|
||||
logos: {}
|
||||
}
|
||||
} satisfies LlmState
|
||||
|
||||
const mockReducer = (state = mockInitialState) => {
|
||||
|
||||
@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 144,
|
||||
version: 143,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -39,7 +39,6 @@ export interface LlmState {
|
||||
translateModel: Model
|
||||
quickAssistantId: string
|
||||
settings: LlmSettings
|
||||
logos: Record<string, string>
|
||||
}
|
||||
|
||||
export const initialState: LlmState = {
|
||||
@ -72,8 +71,7 @@ export const initialState: LlmState = {
|
||||
secretAccessKey: '',
|
||||
region: ''
|
||||
}
|
||||
},
|
||||
logos: {}
|
||||
}
|
||||
}
|
||||
|
||||
// 由于 isLocalAi 目前总是为false,该函数暂未被使用
|
||||
@ -221,17 +219,6 @@ const llmSlice = createSlice({
|
||||
provider.models[modelIndex] = action.payload.model
|
||||
}
|
||||
}
|
||||
},
|
||||
setLogos: (state, action: PayloadAction<Record<string, string>>) => {
|
||||
state.logos = action.payload
|
||||
},
|
||||
setLogo: (state, action: PayloadAction<{ id: string; logo: string }>) => {
|
||||
const { id, logo } = action.payload
|
||||
state.logos[id] = logo
|
||||
},
|
||||
removeLogo: (state, action: PayloadAction<string>) => {
|
||||
const id = action.payload
|
||||
delete state.logos[id]
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -257,10 +244,7 @@ export const {
|
||||
setAwsBedrockAccessKeyId,
|
||||
setAwsBedrockSecretAccessKey,
|
||||
setAwsBedrockRegion,
|
||||
updateModel,
|
||||
setLogos,
|
||||
setLogo,
|
||||
removeLogo
|
||||
updateModel
|
||||
} = llmSlice.actions
|
||||
|
||||
export default llmSlice.reducer
|
||||
|
||||
@ -2322,15 +2322,6 @@ const migrateConfig = {
|
||||
} catch (error) {
|
||||
return state
|
||||
}
|
||||
},
|
||||
// after 1.5.8
|
||||
'144': (state: RootState) => {
|
||||
try {
|
||||
state.llm.logos = {}
|
||||
return state
|
||||
} catch (error) {
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user