fix: 对齐模型设置中 avatar 的样式 (#9829)

* fix: 对齐模型设置中 avatar 的样式

* update

* update

* fix: 修复上传弹出两次文件夹的问题

* update
This commit is contained in:
Konv Suu 2025-09-05 12:33:43 +08:00 committed by GitHub
parent b1a39e9b38
commit 507a653d81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 178 additions and 63 deletions

View File

@ -0,0 +1,144 @@
import { PoeLogo } from '@renderer/components/Icons'
import { getProviderLogo } from '@renderer/config/providers'
import { Provider } from '@renderer/types'
import { generateColorFromChar, getFirstCharacter, getForegroundColor } from '@renderer/utils'
import { Avatar } from 'antd'
import React from 'react'
import styled from 'styled-components'
interface ProviderAvatarPrimitiveProps {
providerId: string
providerName: string
logoSrc?: string
size?: number
className?: string
style?: React.CSSProperties
}
interface ProviderAvatarProps {
provider: Provider
customLogos?: Record<string, string>
size?: number
className?: string
style?: React.CSSProperties
}
const ProviderSvgLogo = styled.div`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border: 0.5px solid var(--color-border);
border-radius: 100%;
& > svg {
width: 80%;
height: 80%;
}
`
const ProviderLogo = styled(Avatar)`
width: 100%;
height: 100%;
border: 0.5px solid var(--color-border);
`
export const ProviderAvatarPrimitive: React.FC<ProviderAvatarPrimitiveProps> = ({
providerId,
providerName,
logoSrc,
size,
className,
style
}) => {
if (providerId === 'poe') {
return (
<ProviderSvgLogo className={className} style={style}>
<PoeLogo fontSize={size} />
</ProviderSvgLogo>
)
}
if (logoSrc) {
return (
<ProviderLogo draggable="false" shape="circle" src={logoSrc} className={className} style={style} size={size} />
)
}
const backgroundColor = generateColorFromChar(providerName)
const color = providerName ? getForegroundColor(backgroundColor) : 'white'
return (
<ProviderLogo
size={size}
shape="circle"
className={className}
style={{
backgroundColor,
color,
...style
}}>
{getFirstCharacter(providerName)}
</ProviderLogo>
)
}
export const ProviderAvatar: React.FC<ProviderAvatarProps> = ({
provider,
customLogos = {},
className,
style,
size
}) => {
const systemLogoSrc = getProviderLogo(provider.id)
if (systemLogoSrc) {
return (
<ProviderAvatarPrimitive
size={size}
providerId={provider.id}
providerName={provider.name}
logoSrc={systemLogoSrc}
className={className}
style={style}
/>
)
}
const customLogo = customLogos[provider.id]
if (customLogo) {
if (customLogo === 'poe') {
return (
<ProviderAvatarPrimitive
size={size}
providerId="poe"
providerName={provider.name}
className={className}
style={style}
/>
)
}
return (
<ProviderAvatarPrimitive
providerId={provider.id}
providerName={provider.name}
logoSrc={customLogo}
size={size}
className={className}
style={style}
/>
)
}
return (
<ProviderAvatarPrimitive
providerId={provider.id}
providerName={provider.name}
size={size}
className={className}
style={style}
/>
)
}

View File

@ -1,4 +1,5 @@
import { SearchOutlined } from '@ant-design/icons'
import { ProviderAvatarPrimitive } from '@renderer/components/ProviderAvatar'
import { PROVIDER_LOGO_MAP } from '@renderer/config/providers'
import { getProviderLabel } from '@renderer/i18n/label'
import { Input, Tooltip } from 'antd'
@ -48,10 +49,10 @@ const ProviderLogoPicker: FC<Props> = ({ onProviderClick }) => {
/>
</SearchContainer>
<LogoGrid>
{filteredProviders.map(({ id, logo, name }) => (
{filteredProviders.map(({ id, name, logo }) => (
<Tooltip key={id} title={name} placement="top" mouseLeaveDelay={0}>
<LogoItem onClick={(e) => handleProviderClick(e, id)}>
<img src={logo} alt={name} draggable={false} />
<ProviderAvatarPrimitive providerId={id} size={52} providerName={name} logoSrc={logo} />
</LogoItem>
</Tooltip>
))}
@ -86,11 +87,12 @@ const LogoGrid = styled.div`
const LogoItem = styled.div`
width: 52px;
height: 52px;
border-radius: 100%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
background: var(--color-background-soft);
border: 0.5px solid var(--color-border);
@ -102,8 +104,8 @@ const LogoItem = styled.div`
}
img {
width: 32px;
height: 32px;
width: 100%;
height: 100%;
object-fit: contain;
user-select: none;
-webkit-user-drag: none;

View File

@ -661,7 +661,7 @@ export const PROVIDER_LOGO_MAP: AtLeast<SystemProviderId, string> = {
vertexai: VertexAIProviderLogo,
'new-api': NewAPIProviderLogo,
'aws-bedrock': AwsProviderLogo,
poe: 'svg' // use svg icon component
poe: 'poe' // use svg icon component
} as const
export function getProviderLogo(providerId: string) {

View File

@ -1,5 +1,6 @@
import { loggerService } from '@logger'
import { Center, VStack } from '@renderer/components/Layout'
import { ProviderAvatarPrimitive } from '@renderer/components/ProviderAvatar'
import ProviderLogoPicker from '@renderer/components/ProviderLogoPicker'
import { TopView } from '@renderer/components/TopView'
import { PROVIDER_LOGO_MAP } from '@renderer/config/providers'
@ -143,7 +144,6 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
})
setLogo(tempUrl)
}
setDropdownOpen(false)
} catch (error: any) {
window.message.error(error.message)
@ -152,7 +152,8 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
<MenuItem ref={uploadRef}>{t('settings.general.image_upload')}</MenuItem>
</Upload>
),
onClick: () => {
onClick: (e: any) => {
e.stopPropagation()
uploadRef.current?.click()
}
},
@ -215,7 +216,9 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
}}
placement="bottom">
{logo ? (
<ProviderLogo src={logo} />
<ProviderLogo>
<ProviderAvatarPrimitive providerId={logo} providerName={name} logoSrc={logo} size={60} />
</ProviderLogo>
) : (
<ProviderInitialsLogo style={name ? { backgroundColor, color } : undefined}>
{getInitials()}
@ -258,16 +261,17 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
)
}
const ProviderLogo = styled.img`
const ProviderLogo = styled.div`
cursor: pointer;
width: 60px;
height: 60px;
border-radius: 12px;
object-fit: contain;
border-radius: 100%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.3s ease;
background-color: var(--color-background-soft);
padding: 5px;
border: 0.5px solid var(--color-border);
&:hover {
opacity: 0.8;
}
@ -277,7 +281,7 @@ const ProviderInitialsLogo = styled.div`
cursor: pointer;
width: 60px;
height: 60px;
border-radius: 12px;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;

View File

@ -5,22 +5,14 @@ import {
type DraggableVirtualListRef,
useDraggableReorder
} from '@renderer/components/DraggableList'
import { DeleteIcon, EditIcon, PoeLogo } from '@renderer/components/Icons'
import { getProviderLogo } from '@renderer/config/providers'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import { ProviderAvatar } from '@renderer/components/ProviderAvatar'
import { useAllProviders, useProviders } from '@renderer/hooks/useProvider'
import { useTimer } from '@renderer/hooks/useTimer'
import ImageStorage from '@renderer/services/ImageStorage'
import { isSystemProvider, Provider, ProviderType } from '@renderer/types'
import {
generateColorFromChar,
getFancyProviderName,
getFirstCharacter,
getForegroundColor,
matchKeywordsInModel,
matchKeywordsInProvider,
uuid
} from '@renderer/utils'
import { Avatar, Button, Dropdown, Input, MenuProps, Tag } from 'antd'
import { getFancyProviderName, matchKeywordsInModel, matchKeywordsInProvider, uuid } from '@renderer/utils'
import { 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'
@ -280,36 +272,6 @@ const ProviderList: FC = () => {
}
}
const getProviderAvatar = (provider: Provider, size: number = 25) => {
// 特殊处理一下svg格式
if (isSystemProvider(provider)) {
switch (provider.id) {
case 'poe':
return <PoeLogo fontSize={size} />
}
}
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)
const isProviderMatch = matchKeywordsInProvider(keywords, provider)
@ -382,7 +344,14 @@ const ProviderList: FC = () => {
<DragHandle>
<GripVertical size={12} />
</DragHandle>
{getProviderAvatar(provider)}
<ProviderAvatar
style={{
width: 24,
height: 24
}}
provider={provider}
customLogos={providerLogos}
/>
<ProviderItemName className="text-nowrap">{getFancyProviderName(provider)}</ProviderItemName>
{provider.enabled && (
<Tag color="green" style={{ marginLeft: 'auto', marginRight: 0, borderRadius: 16 }}>
@ -466,10 +435,6 @@ 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;