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:
MyPrototypeWhat 2025-09-29 18:58:46 +08:00
parent d470fd8b88
commit 6c71b92d1d
5 changed files with 20 additions and 375 deletions

View File

@ -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

View File

@ -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'
}

View File

@ -21,7 +21,6 @@ export { default as Ellipsis } from './display/Ellipsis'
export { default as ExpandableText } from './display/ExpandableText'
export { default as ListItem } from './display/ListItem'
export { default as MaxContextCount } from './display/MaxContextCount'
export { ProviderAvatar } from './display/ProviderAvatar'
export { default as ThinkingEffect } from './display/ThinkingEffect'
// Layout Components

View File

@ -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>
)
}

View File

@ -1,10 +1,9 @@
import { Avatar } from '@cherrystudio/ui'
import { Avatar, cn } from '@cherrystudio/ui'
import { PoeLogo } from '@renderer/components/Icons'
import { getProviderLogo } from '@renderer/config/providers'
import type { Provider } from '@renderer/types'
import { generateColorFromChar, getFirstCharacter, getForegroundColor } from '@renderer/utils'
import React from 'react'
import styled from 'styled-components'
interface ProviderAvatarPrimitiveProps {
providerId: string
@ -23,28 +22,6 @@ interface ProviderAvatarProps {
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,
@ -53,33 +30,42 @@ export const ProviderAvatarPrimitive: React.FC<ProviderAvatarPrimitiveProps> = (
className,
style
}) => {
// Special handling for Poe provider
if (providerId === 'poe') {
return (
<ProviderSvgLogo className={className} style={style}>
<PoeLogo fontSize={size} />
</ProviderSvgLogo>
<div
className={cn(
'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) {
return (
<ProviderLogo
draggable="false"
radius="full"
<Avatar
src={logoSrc}
className={className}
radius="full"
className={cn('border-[0.5px] border-[var(--color-border)]', className)}
style={{ width: size, height: size, ...style }}
imgProps={{ draggable: false }}
/>
)
}
// Default: generate avatar with first character and background color
const backgroundColor = generateColorFromChar(providerName)
const color = providerName ? getForegroundColor(backgroundColor) : 'white'
return (
<ProviderLogo
<Avatar
radius="full"
className={className}
className={cn('border-[0.5px] border-[var(--color-border)]', className)}
style={{
width: size,
height: size,
@ -88,7 +74,7 @@ export const ProviderAvatarPrimitive: React.FC<ProviderAvatarPrimitiveProps> = (
...style
}}>
{getFirstCharacter(providerName)}
</ProviderLogo>
</Avatar>
)
}