mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-23 18:10:26 +08:00
refactor: remove ProviderAvatar component and related files
- Deleted the ProviderAvatar component and its associated utility functions and stories to streamline the codebase. - Updated index.ts to remove the export of ProviderAvatar, ensuring a cleaner component structure.
This commit is contained in:
parent
d470fd8b88
commit
6c71b92d1d
@ -1,93 +0,0 @@
|
|||||||
// Original path: src/renderer/src/components/ProviderAvatar.tsx
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
import { Avatar } from '../../base/Avatar'
|
|
||||||
import { generateColorFromChar, getFirstCharacter, getForegroundColor } from './utils'
|
|
||||||
|
|
||||||
interface ProviderAvatarProps {
|
|
||||||
providerId: string
|
|
||||||
providerName: string
|
|
||||||
logoSrc?: string
|
|
||||||
size?: 'sm' | 'md' | 'lg' | number
|
|
||||||
className?: string
|
|
||||||
style?: React.CSSProperties
|
|
||||||
renderCustomLogo?: (providerId: string) => React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProviderAvatar: React.FC<ProviderAvatarProps> = ({
|
|
||||||
providerId,
|
|
||||||
providerName,
|
|
||||||
logoSrc,
|
|
||||||
size = 'md',
|
|
||||||
className = '',
|
|
||||||
style,
|
|
||||||
renderCustomLogo
|
|
||||||
}) => {
|
|
||||||
// Convert numeric size to HeroUI size props
|
|
||||||
const getAvatarSize = () => {
|
|
||||||
if (typeof size === 'number') {
|
|
||||||
// For custom numeric sizes, we'll use style override
|
|
||||||
return 'md'
|
|
||||||
}
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCustomStyle = () => {
|
|
||||||
const baseStyle: React.CSSProperties = {
|
|
||||||
...style
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof size === 'number') {
|
|
||||||
baseStyle.width = `${size}px`
|
|
||||||
baseStyle.height = `${size}px`
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if custom logo renderer is provided for special providers
|
|
||||||
if (renderCustomLogo) {
|
|
||||||
const customLogo = renderCustomLogo(providerId)
|
|
||||||
if (customLogo) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`flex items-center justify-center rounded-full border border-gray-200 dark:border-gray-700 ${className}`}
|
|
||||||
style={getCustomStyle()}>
|
|
||||||
<div className="w-4/5 h-4/5 flex items-center justify-center">{customLogo}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If logo source is provided, render image avatar
|
|
||||||
if (logoSrc) {
|
|
||||||
return (
|
|
||||||
<Avatar
|
|
||||||
src={logoSrc}
|
|
||||||
size={getAvatarSize()}
|
|
||||||
className={`border border-gray-200 dark:border-gray-700 ${className}`}
|
|
||||||
style={getCustomStyle()}
|
|
||||||
imgProps={{ draggable: false }}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default: generate avatar with first character and background color
|
|
||||||
const backgroundColor = generateColorFromChar(providerName)
|
|
||||||
const color = providerName ? getForegroundColor(backgroundColor) : 'white'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Avatar
|
|
||||||
name={getFirstCharacter(providerName)}
|
|
||||||
size={getAvatarSize()}
|
|
||||||
className={`border border-gray-200 dark:border-gray-700 ${className}`}
|
|
||||||
style={{
|
|
||||||
backgroundColor,
|
|
||||||
color,
|
|
||||||
...getCustomStyle()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProviderAvatar
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
// Utility functions for ProviderAvatar component
|
|
||||||
|
|
||||||
export function generateColorFromChar(char: string): string {
|
|
||||||
const seed = char.charCodeAt(0)
|
|
||||||
const a = 1664525
|
|
||||||
const c = 1013904223
|
|
||||||
const m = Math.pow(2, 32)
|
|
||||||
|
|
||||||
let r = (a * seed + c) % m
|
|
||||||
let g = (a * r + c) % m
|
|
||||||
let b = (a * g + c) % m
|
|
||||||
|
|
||||||
r = Math.floor((r / m) * 256)
|
|
||||||
g = Math.floor((g / m) * 256)
|
|
||||||
b = Math.floor((b / m) * 256)
|
|
||||||
|
|
||||||
const toHex = (n: number) => n.toString(16).padStart(2, '0')
|
|
||||||
return `#${toHex(r)}${toHex(g)}${toHex(b)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFirstCharacter(str: string): string {
|
|
||||||
for (const char of str) {
|
|
||||||
return char
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getForegroundColor(backgroundColor: string): string {
|
|
||||||
// Simple luminance calculation
|
|
||||||
const hex = backgroundColor.replace('#', '')
|
|
||||||
const r = parseInt(hex.substring(0, 2), 16) / 255
|
|
||||||
const g = parseInt(hex.substring(2, 4), 16) / 255
|
|
||||||
const b = parseInt(hex.substring(4, 6), 16) / 255
|
|
||||||
|
|
||||||
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
|
|
||||||
return luminance > 0.179 ? '#000000' : '#FFFFFF'
|
|
||||||
}
|
|
||||||
@ -21,7 +21,6 @@ export { default as Ellipsis } from './display/Ellipsis'
|
|||||||
export { default as ExpandableText } from './display/ExpandableText'
|
export { default as ExpandableText } from './display/ExpandableText'
|
||||||
export { default as ListItem } from './display/ListItem'
|
export { default as ListItem } from './display/ListItem'
|
||||||
export { default as MaxContextCount } from './display/MaxContextCount'
|
export { default as MaxContextCount } from './display/MaxContextCount'
|
||||||
export { ProviderAvatar } from './display/ProviderAvatar'
|
|
||||||
export { default as ThinkingEffect } from './display/ThinkingEffect'
|
export { default as ThinkingEffect } from './display/ThinkingEffect'
|
||||||
|
|
||||||
// Layout Components
|
// Layout Components
|
||||||
|
|||||||
@ -1,210 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react'
|
|
||||||
|
|
||||||
import { ProviderAvatar } from '../../../src/components/display/ProviderAvatar'
|
|
||||||
|
|
||||||
// 定义 Story 的元数据
|
|
||||||
const meta: Meta<typeof ProviderAvatar> = {
|
|
||||||
title: 'Display/ProviderAvatar',
|
|
||||||
component: ProviderAvatar,
|
|
||||||
parameters: {
|
|
||||||
layout: 'centered'
|
|
||||||
},
|
|
||||||
tags: ['autodocs'],
|
|
||||||
argTypes: {
|
|
||||||
size: {
|
|
||||||
control: { type: 'range', min: 16, max: 128, step: 4 },
|
|
||||||
description: '头像尺寸',
|
|
||||||
defaultValue: 40
|
|
||||||
},
|
|
||||||
providerId: {
|
|
||||||
control: 'text',
|
|
||||||
description: '提供商 ID'
|
|
||||||
},
|
|
||||||
providerName: {
|
|
||||||
control: 'text',
|
|
||||||
description: '提供商名称'
|
|
||||||
},
|
|
||||||
logoSrc: {
|
|
||||||
control: 'text',
|
|
||||||
description: '图片 Logo 地址'
|
|
||||||
},
|
|
||||||
className: {
|
|
||||||
control: 'text',
|
|
||||||
description: '自定义类名'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} satisfies Meta<typeof ProviderAvatar>
|
|
||||||
|
|
||||||
export default meta
|
|
||||||
type Story = StoryObj<typeof meta>
|
|
||||||
|
|
||||||
// 基础用法:文字头像
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {
|
|
||||||
providerId: 'openai',
|
|
||||||
providerName: 'OpenAI',
|
|
||||||
size: 40
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 带图片的头像
|
|
||||||
export const WithImage: Story = {
|
|
||||||
args: {
|
|
||||||
providerId: 'custom',
|
|
||||||
providerName: 'Custom Provider',
|
|
||||||
logoSrc: 'https://via.placeholder.com/150',
|
|
||||||
size: 40
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 不同尺寸展示
|
|
||||||
export const Sizes: Story = {
|
|
||||||
render: (args) => (
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<ProviderAvatar {...args} providerName="Small" size="sm" />
|
|
||||||
<ProviderAvatar {...args} providerName="Medium" size="md" />
|
|
||||||
<ProviderAvatar {...args} providerName="Large" size="lg" />
|
|
||||||
<ProviderAvatar {...args} providerName="24px" size={24} />
|
|
||||||
<ProviderAvatar {...args} providerName="48px" size={48} />
|
|
||||||
<ProviderAvatar {...args} providerName="72px" size={72} />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
args: {
|
|
||||||
providerId: 'size-demo'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 不同首字母的颜色生成
|
|
||||||
export const ColorGeneration: Story = {
|
|
||||||
args: {
|
|
||||||
providerId: 'azure',
|
|
||||||
providerName: 'Azure',
|
|
||||||
size: 40
|
|
||||||
},
|
|
||||||
render: (args) => (
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<ProviderAvatar {...args} providerId="azure" providerName="Azure" size={40} />
|
|
||||||
<ProviderAvatar {...args} providerId="anthropic" providerName="Anthropic" size={40} />
|
|
||||||
<ProviderAvatar {...args} providerId="baidu" providerName="Baidu" size={40} />
|
|
||||||
<ProviderAvatar {...args} providerId="google" providerName="Google" size={40} />
|
|
||||||
<ProviderAvatar {...args} providerId="meta" providerName="Meta" size={40} />
|
|
||||||
<ProviderAvatar {...args} providerId="openai" providerName="OpenAI" size={40} />
|
|
||||||
<ProviderAvatar {...args} providerId="perplexity" providerName="Perplexity" size={40} />
|
|
||||||
<ProviderAvatar {...args} providerId="zhipu" providerName="智谱" size={40} />
|
|
||||||
<ProviderAvatar {...args} providerId="alibaba" providerName="阿里云" size={40} />
|
|
||||||
<ProviderAvatar {...args} providerId="tencent" providerName="腾讯云" size={40} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自定义 SVG Logo
|
|
||||||
export const WithCustomSvg: Story = {
|
|
||||||
args: {
|
|
||||||
providerId: 'custom-svg',
|
|
||||||
providerName: 'Custom SVG',
|
|
||||||
size: 40,
|
|
||||||
renderCustomLogo: () => (
|
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
||||||
<path d="M12 2L2 7L12 12L22 7L12 2Z" />
|
|
||||||
<path d="M2 17L12 22L22 17L12 12L2 17Z" />
|
|
||||||
<path d="M2 12L12 17L22 12L12 7L2 12Z" />
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 混合展示
|
|
||||||
export const Mixed: Story = {
|
|
||||||
args: {
|
|
||||||
providerId: 'text',
|
|
||||||
providerName: 'Text Avatar',
|
|
||||||
size: 40
|
|
||||||
},
|
|
||||||
render: (args) => (
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
|
||||||
<ProviderAvatar {...args} providerId="text" providerName="Text Avatar" size={40} />
|
|
||||||
<ProviderAvatar
|
|
||||||
{...args}
|
|
||||||
providerId="image"
|
|
||||||
providerName="Image Avatar"
|
|
||||||
logoSrc="https://via.placeholder.com/150/0000FF/FFFFFF?text=IMG"
|
|
||||||
size={40}
|
|
||||||
/>
|
|
||||||
<ProviderAvatar
|
|
||||||
{...args}
|
|
||||||
providerId="svg"
|
|
||||||
providerName="SVG Avatar"
|
|
||||||
size={40}
|
|
||||||
renderCustomLogo={() => (
|
|
||||||
<svg viewBox="0 0 24 24" fill="#FF6B6B">
|
|
||||||
<circle cx="12" cy="12" r="10" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 空值处理
|
|
||||||
export const EmptyValues: Story = {
|
|
||||||
args: {
|
|
||||||
providerId: 'empty',
|
|
||||||
providerName: '',
|
|
||||||
size: 40
|
|
||||||
},
|
|
||||||
render: (args) => (
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
|
||||||
<div>
|
|
||||||
<p style={{ margin: '0 0 8px 0', fontSize: '12px', color: '#666' }}>空名称</p>
|
|
||||||
<ProviderAvatar {...args} providerId="empty" providerName="" size={40} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p style={{ margin: '0 0 8px 0', fontSize: '12px', color: '#666' }}>正常显示</p>
|
|
||||||
<ProviderAvatar {...args} providerId="normal" providerName="Normal" size={40} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自定义样式
|
|
||||||
export const CustomStyle: Story = {
|
|
||||||
args: {
|
|
||||||
providerId: 'custom-style',
|
|
||||||
providerName: 'Custom',
|
|
||||||
size: 40,
|
|
||||||
style: {
|
|
||||||
border: '2px solid #FF6B6B',
|
|
||||||
boxShadow: '0 4px 8px rgba(0,0,0,0.1)'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 响应式网格展示
|
|
||||||
export const ResponsiveGrid: Story = {
|
|
||||||
args: {
|
|
||||||
providerId: 'provider-a',
|
|
||||||
providerName: 'Provider A',
|
|
||||||
size: 48
|
|
||||||
},
|
|
||||||
render: (args) => (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(60px, 1fr))',
|
|
||||||
gap: '16px',
|
|
||||||
width: '400px'
|
|
||||||
}}>
|
|
||||||
{['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'].map((letter) => (
|
|
||||||
<div key={letter} style={{ textAlign: 'center' }}>
|
|
||||||
<ProviderAvatar
|
|
||||||
{...args}
|
|
||||||
providerId={`provider-${letter.toLowerCase()}`}
|
|
||||||
providerName={`Provider ${letter}`}
|
|
||||||
size={48}
|
|
||||||
/>
|
|
||||||
<div style={{ fontSize: '12px', marginTop: '4px', color: '#666' }}>{letter}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -1,10 +1,9 @@
|
|||||||
import { Avatar } from '@cherrystudio/ui'
|
import { Avatar, cn } from '@cherrystudio/ui'
|
||||||
import { PoeLogo } from '@renderer/components/Icons'
|
import { PoeLogo } from '@renderer/components/Icons'
|
||||||
import { getProviderLogo } from '@renderer/config/providers'
|
import { getProviderLogo } from '@renderer/config/providers'
|
||||||
import type { Provider } from '@renderer/types'
|
import type { Provider } from '@renderer/types'
|
||||||
import { generateColorFromChar, getFirstCharacter, getForegroundColor } from '@renderer/utils'
|
import { generateColorFromChar, getFirstCharacter, getForegroundColor } from '@renderer/utils'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled from 'styled-components'
|
|
||||||
|
|
||||||
interface ProviderAvatarPrimitiveProps {
|
interface ProviderAvatarPrimitiveProps {
|
||||||
providerId: string
|
providerId: string
|
||||||
@ -23,28 +22,6 @@ interface ProviderAvatarProps {
|
|||||||
style?: React.CSSProperties
|
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> = ({
|
export const ProviderAvatarPrimitive: React.FC<ProviderAvatarPrimitiveProps> = ({
|
||||||
providerId,
|
providerId,
|
||||||
providerName,
|
providerName,
|
||||||
@ -53,33 +30,42 @@ export const ProviderAvatarPrimitive: React.FC<ProviderAvatarPrimitiveProps> = (
|
|||||||
className,
|
className,
|
||||||
style
|
style
|
||||||
}) => {
|
}) => {
|
||||||
|
// Special handling for Poe provider
|
||||||
if (providerId === 'poe') {
|
if (providerId === 'poe') {
|
||||||
return (
|
return (
|
||||||
<ProviderSvgLogo className={className} style={style}>
|
<div
|
||||||
<PoeLogo fontSize={size} />
|
className={cn(
|
||||||
</ProviderSvgLogo>
|
'flex items-center justify-center rounded-full',
|
||||||
|
'border-[0.5px] border-[var(--color-border)]',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
style={{ width: size, height: size, ...style }}>
|
||||||
|
<PoeLogo fontSize={size ? size * 0.8 : undefined} />
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If logo source is provided, render image avatar
|
||||||
if (logoSrc) {
|
if (logoSrc) {
|
||||||
return (
|
return (
|
||||||
<ProviderLogo
|
<Avatar
|
||||||
draggable="false"
|
|
||||||
radius="full"
|
|
||||||
src={logoSrc}
|
src={logoSrc}
|
||||||
className={className}
|
radius="full"
|
||||||
|
className={cn('border-[0.5px] border-[var(--color-border)]', className)}
|
||||||
style={{ width: size, height: size, ...style }}
|
style={{ width: size, height: size, ...style }}
|
||||||
|
imgProps={{ draggable: false }}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default: generate avatar with first character and background color
|
||||||
const backgroundColor = generateColorFromChar(providerName)
|
const backgroundColor = generateColorFromChar(providerName)
|
||||||
const color = providerName ? getForegroundColor(backgroundColor) : 'white'
|
const color = providerName ? getForegroundColor(backgroundColor) : 'white'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProviderLogo
|
<Avatar
|
||||||
radius="full"
|
radius="full"
|
||||||
className={className}
|
className={cn('border-[0.5px] border-[var(--color-border)]', className)}
|
||||||
style={{
|
style={{
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
@ -88,7 +74,7 @@ export const ProviderAvatarPrimitive: React.FC<ProviderAvatarPrimitiveProps> = (
|
|||||||
...style
|
...style
|
||||||
}}>
|
}}>
|
||||||
{getFirstCharacter(providerName)}
|
{getFirstCharacter(providerName)}
|
||||||
</ProviderLogo>
|
</Avatar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user