mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-07 22:10:21 +08:00
fix: 对齐模型设置中 avatar 的样式 (#9829)
* fix: 对齐模型设置中 avatar 的样式 * update * update * fix: 修复上传弹出两次文件夹的问题 * update
This commit is contained in:
parent
b1a39e9b38
commit
507a653d81
144
src/renderer/src/components/ProviderAvatar.tsx
Normal file
144
src/renderer/src/components/ProviderAvatar.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { SearchOutlined } from '@ant-design/icons'
|
import { SearchOutlined } from '@ant-design/icons'
|
||||||
|
import { ProviderAvatarPrimitive } from '@renderer/components/ProviderAvatar'
|
||||||
import { PROVIDER_LOGO_MAP } from '@renderer/config/providers'
|
import { PROVIDER_LOGO_MAP } from '@renderer/config/providers'
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
import { getProviderLabel } from '@renderer/i18n/label'
|
||||||
import { Input, Tooltip } from 'antd'
|
import { Input, Tooltip } from 'antd'
|
||||||
@ -48,10 +49,10 @@ const ProviderLogoPicker: FC<Props> = ({ onProviderClick }) => {
|
|||||||
/>
|
/>
|
||||||
</SearchContainer>
|
</SearchContainer>
|
||||||
<LogoGrid>
|
<LogoGrid>
|
||||||
{filteredProviders.map(({ id, logo, name }) => (
|
{filteredProviders.map(({ id, name, logo }) => (
|
||||||
<Tooltip key={id} title={name} placement="top" mouseLeaveDelay={0}>
|
<Tooltip key={id} title={name} placement="top" mouseLeaveDelay={0}>
|
||||||
<LogoItem onClick={(e) => handleProviderClick(e, id)}>
|
<LogoItem onClick={(e) => handleProviderClick(e, id)}>
|
||||||
<img src={logo} alt={name} draggable={false} />
|
<ProviderAvatarPrimitive providerId={id} size={52} providerName={name} logoSrc={logo} />
|
||||||
</LogoItem>
|
</LogoItem>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
@ -86,11 +87,12 @@ const LogoGrid = styled.div`
|
|||||||
const LogoItem = styled.div`
|
const LogoItem = styled.div`
|
||||||
width: 52px;
|
width: 52px;
|
||||||
height: 52px;
|
height: 52px;
|
||||||
|
border-radius: 100%;
|
||||||
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
background: var(--color-background-soft);
|
background: var(--color-background-soft);
|
||||||
border: 0.5px solid var(--color-border);
|
border: 0.5px solid var(--color-border);
|
||||||
@ -102,8 +104,8 @@ const LogoItem = styled.div`
|
|||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 32px;
|
width: 100%;
|
||||||
height: 32px;
|
height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-drag: none;
|
-webkit-user-drag: none;
|
||||||
|
|||||||
@ -661,7 +661,7 @@ export const PROVIDER_LOGO_MAP: AtLeast<SystemProviderId, string> = {
|
|||||||
vertexai: VertexAIProviderLogo,
|
vertexai: VertexAIProviderLogo,
|
||||||
'new-api': NewAPIProviderLogo,
|
'new-api': NewAPIProviderLogo,
|
||||||
'aws-bedrock': AwsProviderLogo,
|
'aws-bedrock': AwsProviderLogo,
|
||||||
poe: 'svg' // use svg icon component
|
poe: 'poe' // use svg icon component
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export function getProviderLogo(providerId: string) {
|
export function getProviderLogo(providerId: string) {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { Center, VStack } from '@renderer/components/Layout'
|
import { Center, VStack } from '@renderer/components/Layout'
|
||||||
|
import { ProviderAvatarPrimitive } from '@renderer/components/ProviderAvatar'
|
||||||
import ProviderLogoPicker from '@renderer/components/ProviderLogoPicker'
|
import ProviderLogoPicker from '@renderer/components/ProviderLogoPicker'
|
||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
import { PROVIDER_LOGO_MAP } from '@renderer/config/providers'
|
import { PROVIDER_LOGO_MAP } from '@renderer/config/providers'
|
||||||
@ -143,7 +144,6 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
})
|
})
|
||||||
setLogo(tempUrl)
|
setLogo(tempUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
setDropdownOpen(false)
|
setDropdownOpen(false)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
window.message.error(error.message)
|
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>
|
<MenuItem ref={uploadRef}>{t('settings.general.image_upload')}</MenuItem>
|
||||||
</Upload>
|
</Upload>
|
||||||
),
|
),
|
||||||
onClick: () => {
|
onClick: (e: any) => {
|
||||||
|
e.stopPropagation()
|
||||||
uploadRef.current?.click()
|
uploadRef.current?.click()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -215,7 +216,9 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
}}
|
}}
|
||||||
placement="bottom">
|
placement="bottom">
|
||||||
{logo ? (
|
{logo ? (
|
||||||
<ProviderLogo src={logo} />
|
<ProviderLogo>
|
||||||
|
<ProviderAvatarPrimitive providerId={logo} providerName={name} logoSrc={logo} size={60} />
|
||||||
|
</ProviderLogo>
|
||||||
) : (
|
) : (
|
||||||
<ProviderInitialsLogo style={name ? { backgroundColor, color } : undefined}>
|
<ProviderInitialsLogo style={name ? { backgroundColor, color } : undefined}>
|
||||||
{getInitials()}
|
{getInitials()}
|
||||||
@ -258,16 +261,17 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProviderLogo = styled.img`
|
const ProviderLogo = styled.div`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
border-radius: 12px;
|
border-radius: 100%;
|
||||||
object-fit: contain;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
background-color: var(--color-background-soft);
|
|
||||||
padding: 5px;
|
|
||||||
border: 0.5px solid var(--color-border);
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
@ -277,7 +281,7 @@ const ProviderInitialsLogo = styled.div`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
border-radius: 12px;
|
border-radius: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@ -5,22 +5,14 @@ import {
|
|||||||
type DraggableVirtualListRef,
|
type DraggableVirtualListRef,
|
||||||
useDraggableReorder
|
useDraggableReorder
|
||||||
} from '@renderer/components/DraggableList'
|
} from '@renderer/components/DraggableList'
|
||||||
import { DeleteIcon, EditIcon, PoeLogo } from '@renderer/components/Icons'
|
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
||||||
import { getProviderLogo } from '@renderer/config/providers'
|
import { ProviderAvatar } from '@renderer/components/ProviderAvatar'
|
||||||
import { useAllProviders, useProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders, useProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import ImageStorage from '@renderer/services/ImageStorage'
|
import ImageStorage from '@renderer/services/ImageStorage'
|
||||||
import { isSystemProvider, Provider, ProviderType } from '@renderer/types'
|
import { isSystemProvider, Provider, ProviderType } from '@renderer/types'
|
||||||
import {
|
import { getFancyProviderName, matchKeywordsInModel, matchKeywordsInProvider, uuid } from '@renderer/utils'
|
||||||
generateColorFromChar,
|
import { Button, Dropdown, Input, MenuProps, Tag } from 'antd'
|
||||||
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 { GripVertical, PlusIcon, Search, UserPen } from 'lucide-react'
|
||||||
import { FC, startTransition, useCallback, useEffect, useRef, useState } from 'react'
|
import { FC, startTransition, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 filteredProviders = providers.filter((provider) => {
|
||||||
const keywords = searchText.toLowerCase().split(/\s+/).filter(Boolean)
|
const keywords = searchText.toLowerCase().split(/\s+/).filter(Boolean)
|
||||||
const isProviderMatch = matchKeywordsInProvider(keywords, provider)
|
const isProviderMatch = matchKeywordsInProvider(keywords, provider)
|
||||||
@ -382,7 +344,14 @@ const ProviderList: FC = () => {
|
|||||||
<DragHandle>
|
<DragHandle>
|
||||||
<GripVertical size={12} />
|
<GripVertical size={12} />
|
||||||
</DragHandle>
|
</DragHandle>
|
||||||
{getProviderAvatar(provider)}
|
<ProviderAvatar
|
||||||
|
style={{
|
||||||
|
width: 24,
|
||||||
|
height: 24
|
||||||
|
}}
|
||||||
|
provider={provider}
|
||||||
|
customLogos={providerLogos}
|
||||||
|
/>
|
||||||
<ProviderItemName className="text-nowrap">{getFancyProviderName(provider)}</ProviderItemName>
|
<ProviderItemName className="text-nowrap">{getFancyProviderName(provider)}</ProviderItemName>
|
||||||
{provider.enabled && (
|
{provider.enabled && (
|
||||||
<Tag color="green" style={{ marginLeft: 'auto', marginRight: 0, borderRadius: 16 }}>
|
<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`
|
const ProviderItemName = styled.div`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user