Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "standalone",

// 转译 ESM 模块(@lobehub/icons 需要)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

文档建议

建议添加更详细的注释说明为什么需要转译这个包,以及可能影响的范围:

// 转译 ESM 模块
// @lobehub/icons 是纯 ESM 包,需要转译才能在 Next.js 中正常使用
// 参考:https://nextjs.org/docs/app/api-reference/next-config-js/transpilePackages
transpilePackages: ["@lobehub/icons"],

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚡ 性能影响说明

transpilePackages 会在开发环境增加 Turbopack 编译时间。如果采用轻量级图标方案(如 lucide-react),可以移除此配置项。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 配置复杂度增加

添加 transpilePackages 是因为 @lobehub/icons 是 ESM 模块,需要额外的转译步骤。

影响

  • 增加构建时间
  • 增加配置复杂度
  • 可能与其他插件冲突

建议
如果采用轻量级图标方案(如 lucide-react 或自定义 SVG),可以移除此配置,保持构建流程简洁。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

构建性能影响transpilePackages 会增加构建时间。

建议验证:

  1. @lobehub/icons 是否真的需要转译(检查是否是 ESM 模块导致的问题)
  2. 如果可能,优先考虑使用已转译的版本或替代方案

可以通过以下方式测试:

# 移除 transpilePackages 配置后构建
pnpm build

如果构建失败,说明确实需要;如果成功,可以移除此配置。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 构建性能提示

transpilePackages 会增加构建时间,因为 Next.js 需要转译 ESM 模块。

确认这是必需的:

  • 如果 @lobehub/icons 是纯 ESM 包且没有提供 CJS 版本,则需要这个配置
  • 建议在本地测试构建时间变化

可以通过 ANALYZE=true pnpm build 查看打包体积分析。

transpilePackages: ["@lobehub/icons"],

// 文件上传大小限制(用于数据库备份导入)
// Next.js 15 通过 serverActions.bodySizeLimit 统一控制
experimental: {
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@bull-board/api": "^6.14.0",
"@bull-board/express": "^6.14.0",
"@hookform/resolvers": "^5.2.2",
"@lobehub/icons": "^2.43.1",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📦 依赖包体积警告

@lobehub/icons 会引入整个 antd 依赖(约 500KB gzipped),但项目仅使用了 4-5 个图标。

替代方案

  1. 使用 lucide-react(项目已依赖,零额外体积):

    import { Bot, Sparkles, Code2, Diamond, Network } from "lucide-react";
  2. 使用 @iconify/react(按需加载,约 5KB):

    pnpm add @iconify/react
  3. 自定义 SVG 组件(最轻量,约 2KB)

建议评估是否值得为 5 个图标引入如此大的依赖。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

依赖项过重

引入 @lobehub/iconsantd 会显著增加打包体积(估计 +500KB+),但实际只使用了少量图标组件。

影响分析

  • 📦 打包体积:+500KB+ (minified + gzipped)
  • ⏱️ 构建时间:增加 ESM 转译开销
  • 🔧 维护成本:额外的依赖管理

建议替代方案

方案 1:使用项目已有的 lucide-react

import { Bot, Sparkles, Code2, Diamond, Network } from 'lucide-react';

// 这些图标已经在项目中,无额外成本

方案 2:自定义 SVG 图标(最轻量)

const ClaudeIcon = () => (
  <svg viewBox="0 0 24 24" className="h-3 w-3" fill="currentColor">
    <path d="M12 2C6.48..." /> {/* Claude logo path */}
  </svg>
);

方案 3:使用 CSS + Unicode 字符

<span className="font-bold text-orange-600">C</span> // Claude

推荐方案 1,保持项目轻量化的同时满足功能需求。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

依赖包体积问题@lobehub/iconsantd 增加了约 5000+ 行的 lock 文件。

建议:

  1. 检查是否真的需要 antd(peerDependency),如果只用到图标可以排除
  2. 考虑使用项目已有的 lucide-react 替代方案
  3. 如果必须使用,配置 webpack/Next.js 进行按需加载

可以运行以下命令查看实际打包体积影响:

pnpm build
pnpm run analyze # 如果有配置 bundle analyzer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 依赖体积警告

@lobehub/iconsantd 会显著增加打包体积:

  • @lobehub/icons: ~5.2MB
  • antd: ~2.4MB

考虑替代方案:

  1. 使用项目现有的 lucide-react(已安装)来替代这些图标
  2. 如果必须使用 @lobehub/icons,确保 tree-shaking 正常工作
  3. 考虑按需导入而不是全量导入

建议运行 pnpm run build 并检查打包体积变化。

"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.3",
Expand All @@ -35,6 +36,7 @@
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-query": "^5.90.5",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ 重要问题:引入了整个 antd 包

这会显著增加打包体积(antd 未压缩约 2MB+)。根据代码分析,只使用了 @lobehub/icons,而该包依赖 antd。

建议

  1. 检查是否真的需要 antd 依赖
  2. 如果只是为了 @lobehub/icons,考虑以下方案:
    • 使用 tree-shaking 优化(Next.js 应该已支持)
    • 配置 modularizeImports 来按需加载 antd 组件
    • 或考虑使用更轻量的图标库(如 lucide-react

请确认打包体积是否可接受。

"antd": "^5.27.6",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 依赖体积警告

引入 antd 会显著增加打包体积(~2MB)。建议考虑以下替代方案:

  1. 使用独立的图标包@ant-design/icons 而非完整的 antd
  2. 使用项目现有图标库lucide-react 已经包含在项目中
  3. 直接使用 SVG:将图标作为 SVG 组件内联

示例(使用 lucide-react):

import { Bot, Sparkles, Code2, Diamond, Network } from "lucide-react";

请评估打包体积的影响(运行 pnpm build 并检查 .next 目录大小)。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 依赖项疑问:项目引入了完整的 antd (Ant Design),但项目主要使用 Shadcn UI。

问题

  • Ant Design 体积较大(~2MB),可能显著增加 bundle 大小
  • 是否只为了 @lobehub/icons 而引入?

建议

  1. 检查 @lobehub/iconspeerDependencies 配置
  2. 如果 antd 只是可选依赖,建议移除以减小体积
  3. 或者考虑使用其他轻量级图标库(如 lucide-react,项目已使用)

可以运行以下命令检查:

pnpm why antd

"bull": "^4.16.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand Down
5,344 changes: 5,255 additions & 89 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions src/app/settings/providers/_components/provider-list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Badge } from "@/components/ui/badge";
import { Edit, Globe, Key, RotateCcw, Copy, ServerCog } from "lucide-react";
import type { ProviderDisplay } from "@/types/provider";
import type { User } from "@/types/user";
import { getProviderTypeConfig } from "@/lib/provider-type-utils";
import { ProviderForm } from "./forms/provider-form";
import { AddProviderDialog } from "./add-provider-dialog";
import { Switch } from "@/components/ui/switch";
Expand Down Expand Up @@ -91,6 +92,10 @@ export function ProviderListItem({
handleConcurrentPopover,
} = useProviderEdit(item, canEdit);

// 获取供应商类型配置
const typeConfig = getProviderTypeConfig(item.providerType);
const TypeIcon = typeConfig.icon;

// 处理手动解除熔断
const handleResetCircuit = () => {
startResetTransition(async () => {
Expand Down Expand Up @@ -126,9 +131,20 @@ export function ProviderListItem({
>
</span>
{/* 供应商类型图标 */}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI 增强建议:供应商类型图标的位置很好,但建议添加 Tooltip 组件(来自 Radix UI)来显示类型描述,而不是仅使用 HTML title 属性。

Tooltip 提供更好的用户体验和自定义样式能力:

import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";

<TooltipProvider>
  <Tooltip>
    <TooltipTrigger asChild>
      <span className={`inline-flex h-5 w-5 items-center justify-center rounded-md ${typeConfig.bgColor}`}>
        <TypeIcon className={`h-3 w-3 ${typeConfig.iconColor}`} />
      </span>
    </TooltipTrigger>
    <TooltipContent>
      <p>{typeConfig.description}</p>
    </TooltipContent>
  </Tooltip>
</TooltipProvider>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI 实现优秀

图标和标签的布局合理,视觉层次清晰:

  1. 启用状态指示器(绿点)
  2. 供应商类型图标(带 tooltip)
  3. 供应商名称
  4. 类型标签

小建议
title 属性的 tooltip 在移动端可能无法显示,可以考虑使用 Radix UI 的 Tooltip 组件以获得更好的跨平台体验:

<Tooltip>
  <TooltipTrigger asChild>
    <span className={`... ${typeConfig.bgColor}`}>
      <TypeIcon className={`... ${typeConfig.iconColor}`} />
    </span>
  </TooltipTrigger>
  <TooltipContent>{typeConfig.description}</TooltipContent>
</Tooltip>

<span
className={`inline-flex h-5 w-5 items-center justify-center rounded-md ${typeConfig.bgColor}`}
Comment on lines +135 to +136
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♿ 可访问性增强建议

建议添加 ARIA 属性以提升屏幕阅读器体验:

<span
  className={`inline-flex h-5 w-5 items-center justify-center rounded-md ${typeConfig.bgColor}`}
 
  role="img"
 
>
  <TypeIcon className={`h-3 w-3 ${typeConfig.iconColor}`} />
</span>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同样的颜色类名拼接问题

这里也存在动态类名拼接的问题(${typeConfig.bgColor}${typeConfig.iconColor})。建议统一使用 cn 工具函数或添加到 Tailwind safelist。

<span
  className={cn(
    "inline-flex h-5 w-5 items-center justify-center rounded-md",
    typeConfig.bgColor
  )}
 
>
  <TypeIcon className={cn("h-3 w-3", typeConfig.iconColor)} />
</span>

title={typeConfig.description}
>
<TypeIcon className={`h-3 w-3 ${typeConfig.iconColor}`} />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tailwind CSS Purge 风险:动态拼接的 className 可能在生产构建时被 Tailwind 移除。

建议使用 clsxcn 工具函数:

<TypeIcon className={cn("h-3 w-3", typeConfig.iconColor)} />

并确保 typeConfig.iconColor 中的类名在 safelist 中(如果使用了 Tailwind purge)。

</span>
<h3 className="text-sm font-semibold text-foreground truncate tracking-tight">
{item.name}
</h3>
{/* 供应商类型标签 */}
<Badge variant="outline" className="text-[10px] h-4 px-1.5 font-normal">
{typeConfig.label}
</Badge>
Comment on lines +134 to +147
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 UI 设计建议

类型图标和标签的实现很清晰!一个小优化建议:

可以让 Badge 的颜色与图标颜色保持一致,提升视觉连贯性:

<Badge 
  variant="outline" 
  className={`text-[10px] h-4 px-1.5 font-normal border-${typeConfig.iconColor.replace('text-', '')} ${typeConfig.bgColor}`}
>
  {typeConfig.label}
</Badge>

不过当前的实现已经很好了,这只是一个可选优化。


{/* 熔断器状态徽章 */}
{healthStatus?.circuitState === "open" && (
Expand Down
25 changes: 23 additions & 2 deletions src/app/settings/providers/_components/provider-manager.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use client";
import { useState, useMemo } from "react";
import { ProviderList } from "./provider-list";
import type { ProviderDisplay } from "@/types/provider";
import { ProviderTypeFilter } from "./provider-type-filter";
import type { ProviderDisplay, ProviderType } from "@/types/provider";
import type { User } from "@/types/user";
import type { CurrencyCode } from "@/lib/utils/currency";

Expand Down Expand Up @@ -28,10 +30,29 @@ export function ProviderManager({
currencyCode = "USD",
enableMultiProviderTypes,
}: ProviderManagerProps) {
const [typeFilter, setTypeFilter] = useState<ProviderType | "all">("all");

// 根据类型筛选供应商
const filteredProviders = useMemo(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 良好实践:使用 useMemo 优化筛选性能,避免不必要的重新计算。这是正确的做法!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

性能优化做得很好

使用 useMemo 避免不必要的筛选计算,在供应商数量较多时可以提升性能。

补充建议
如果供应商数量超过 100+,可以考虑添加虚拟滚动(react-window)来优化渲染性能。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

性能优化 - 很好!

使用 useMemo 缓存筛选结果是正确的做法,避免了不必要的重新计算。

如果未来供应商数量非常大(>1000),可以考虑进一步优化:

  • 使用虚拟滚动(react-window)
  • 添加防抖搜索功能

if (typeFilter === "all") {
return providers;
}
return providers.filter((provider) => provider.providerType === typeFilter);
}, [providers, typeFilter]);
Comment on lines +36 to +41
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 性能优化得当

使用 useMemo 避免不必要的重新计算,性能优化做得很好!

可选:如果供应商列表非常大(>1000项),可以考虑添加虚拟滚动(如 react-window),但目前这个实现已经足够高效。


return (
<div className="space-y-4">
{/* 筛选条件 */}
<div className="flex items-center justify-between">
<ProviderTypeFilter value={typeFilter} onChange={setTypeFilter} />
<div className="text-sm text-muted-foreground">
显示 {filteredProviders.length} / {providers.length} 个供应商
</div>
</div>

{/* 供应商列表 */}
<ProviderList
providers={providers}
providers={filteredProviders}
currentUser={currentUser}
healthStatus={healthStatus}
currencyCode={currencyCode}
Expand Down
44 changes: 44 additions & 0 deletions src/app/settings/providers/_components/provider-type-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Filter } from "lucide-react";
import type { ProviderType } from "@/types/provider";
import { PROVIDER_TYPE_CONFIG, getAllProviderTypes } from "@/lib/provider-type-utils";

interface ProviderTypeFilterProps {
value: ProviderType | "all";
onChange: (value: ProviderType | "all") => void;
}

export function ProviderTypeFilter({ value, onChange }: ProviderTypeFilterProps) {
return (
<div className="flex items-center gap-2">
<Filter className="h-4 w-4 text-muted-foreground" />
<Select value={value} onValueChange={onChange}>
<SelectTrigger className="w-[200px]">
<SelectValue placeholder="筛选供应商类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">全部供应商</SelectItem>
{getAllProviderTypes().map((type) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

潜在问题

getAllProviderTypes() 每次渲染都会创建新数组,建议使用 useMemo 优化:

const providerTypes = useMemo(() => getAllProviderTypes(), []);

return (
  // ...
  {providerTypes.map((type) => {
    // ...
  })}
);

或者将 getAllProviderTypes() 的结果导出为常量:

// provider-type-utils.tsx
export const ALL_PROVIDER_TYPES: ProviderType[] = Object.keys(PROVIDER_TYPE_CONFIG) as ProviderType[];

const config = PROVIDER_TYPE_CONFIG[type];
const Icon = config.icon;
return (
<SelectItem key={type} value={type}>
<div className="flex items-center gap-2">
<Icon className={`h-3.5 w-3.5 ${config.iconColor}`} />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可访问性改进:建议为每个 SelectItem 添加 aria-label 以提高可访问性。

<SelectItem 
  key={type} 
  value={type}
  ${config.label} 类型的供应商`}
>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

样式一致性良好

很好地复用了 PROVIDER_TYPE_CONFIG 中的图标和颜色配置,保持了 UI 一致性!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

潜在的颜色类名拼接问题

使用字符串拼接 ${config.iconColor} 来应用 Tailwind 类名,这可能导致 Tailwind 无法在构建时检测到这些类名(如果使用 PurgeCSS)。

建议的解决方案:

  1. 使用 clsxcn 工具函数进行条件类名组合
  2. 或者在 tailwind.config.ts 中添加 safelist 确保这些颜色类被保留
// 方案 1: 使用 clsx/cn
<Icon className={cn("h-3.5 w-3.5", config.iconColor)} />

// 方案 2: 在 tailwind.config.ts 添加 safelist
safelist: [
  'text-orange-600',
  'text-purple-600',
  'text-blue-600',
  // ...
]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

无障碍性建议

图标显示良好,建议添加 aria-label 以提高无障碍性:

<Icon className={`h-3.5 w-3.5 ${config.iconColor}`} />

或者在父元素 SelectItem 上添加 title 属性。

<span>{config.label}</span>
</div>
</SelectItem>
);
})}
</SelectContent>
</Select>
</div>
);
}
78 changes: 78 additions & 0 deletions src/lib/provider-type-utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Claude, Anthropic, OpenAI, Gemini } from "@lobehub/icons";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

性能优化建议:考虑按需导入以减小打包体积。

// 建议使用具名导入或动态导入
import { Claude, Anthropic, OpenAI, Gemini } from "@lobehub/icons/es";

或者评估是否可以使用项目已有的 lucide-react 图标库(如 Bot, Sparkles, Code2, Diamond, Network)来替代,避免增加新的依赖。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

缺少 React 导入

这是一个 .tsx 文件,使用了 JSX 语法,但没有导入 React。虽然在 React 17+ 的新 JSX 转换中不需要显式导入,但为了类型安全和代码一致性,建议添加:

import React from "react";

特别是在第 5 行使用了 React.FC 类型,更应该显式导入。

import type { ProviderType } from "@/types/provider";

// Anthropic Avatar 橙色包装组件(与 Claude Code 颜色一致)
const AnthropicOrangeAvatar: React.FC<{ className?: string }> = ({
className,
}) => {
// 从 className 中提取尺寸,默认 12px(对应 h-3 w-3)
const sizeMatch = className?.match(/h-(\d+)/);
const size = sizeMatch ? parseInt(sizeMatch[1]) * 4 : 12; // Tailwind: h-3 = 12px
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

性能优化建议

这里的尺寸计算逻辑在每次渲染时都会执行正则匹配,建议使用 useMemo 或将计算逻辑移到组件外:

const getSizeFromClassName = (className?: string): number => {
  const sizeMatch = className?.match(/h-(\d+)/);
  return sizeMatch ? parseInt(sizeMatch[1]) * 4 : 12;
};

const AnthropicOrangeAvatar: React.FC<{ className?: string }> = ({ className }) => {
  const size = useMemo(() => getSizeFromClassName(className), [className]);
  // ...
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 尺寸计算逻辑问题

当前的简单乘法计算对于非标准 Tailwind 尺寸可能不准确。建议使用映射表:

const TAILWIND_SIZE_MAP: Record<string, number> = {
  '3': 12,   // h-3 (0.75rem)
  '3.5': 14, // h-3.5 (0.875rem)
  '4': 16,   // h-4 (1rem)
  '5': 20,   // h-5 (1.25rem)
};

const sizeMatch = className?.match(/h-([\d.]+)/);
const size = sizeMatch ? TAILWIND_SIZE_MAP[sizeMatch[1]] || 16 : 12;

这样可以处理小数尺寸(如 h-3.5)并确保准确性。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

潜在的性能问题:这个尺寸解析逻辑在每次渲染时都会执行正则匹配。

建议:

  1. 将尺寸解析提取为独立函数并添加 memoization
  2. 或者直接接受 size prop 而不是从 className 解析
const getSizeFromClassName = (className?: string): number => {
  const sizeMatch = className?.match(/h-(\d+)/);
  return sizeMatch ? parseInt(sizeMatch[1]) * 4 : 12;
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 代码简化建议

尺寸计算逻辑可以更简洁:

const size = className?.includes('h-') 
  ? parseInt(className.match(/h-(\d+)/)?.[1] || '3') * 4 
  : 12;

或者直接传递固定尺寸,避免从 className 解析:

<Anthropic.Avatar size={12} background="#ffffff" shape="circle" />

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 不推荐的实现方式

通过正则表达式解析 Tailwind 类名来计算尺寸存在以下问题:

  1. 脆弱性:依赖 className 字符串格式,容易因类名变化而失效
  2. 可维护性差:其他开发者难以理解这种间接的尺寸传递方式
  3. 类型安全:无法在编译时验证

建议改进

interface AnthropicOrangeAvatarProps {
  size?: number; // 直接接收数值,默认 12px
}

const AnthropicOrangeAvatar: React.FC<AnthropicOrangeAvatarProps> = ({ 
  size = 12 
}) => {
  return (
    <Anthropic.Avatar
      size={size}
      background="#ffffff" 
      shape="circle"
    />
  );
};

这样调用时更清晰:<AnthropicOrangeAvatar size={12} />

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议:添加注释说明尺寸计算逻辑。

// Tailwind CSS 尺寸单位转换:h-1 = 4px, h-2 = 8px, h-3 = 12px
const size = sizeMatch ? parseInt(sizeMatch[1]) * 4 : 12;

这样可以让其他开发者更容易理解这个魔术数字 4 的含义。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

尺寸计算逻辑问题

当前的尺寸计算逻辑有潜在问题:

  1. 正则表达式 /h-(\d+)/ 只能匹配单个数字(如 h-3),无法匹配 h-12 等双位数
  2. 应该使用 h-(\d+) 改为 h-(\d+(?:\.\d+)?)(支持小数)或至少 h-(\d+) 改为 h-(\d{1,2})

建议修改:

const sizeMatch = className?.match(/h-(\d+(?:\.\d+)?)/);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 性能优化建议:每次渲染都会执行正则匹配,建议优化:

const AnthropicOrangeAvatar: React.FC<{ className?: string; size?: number }> = ({
  className,
  size = 12, // 直接传递 size,避免正则解析
}) => {
  return (
    <Anthropic.Avatar
      size={size}
      background="#ffffff"
      shape="circle"
      className={className}
    />
  );
};

这样可以避免重复的字符串解析开销。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 尺寸计算建议

当前通过正则提取 Tailwind 类名并计算像素值的方式比较脆弱:

  • 假设 h-3 = 12px (3 * 4) 可能在 Tailwind v4 中不准确
  • 正则匹配可能失败导致使用默认值

建议改为:

const AnthropicOrangeAvatar: React.FC<{ size?: number }> = ({ size = 12 }) => {
  return (
    <Anthropic.Avatar
      size={size}
      background="#ffffff" 
      shape="circle"
    />
  );
};

然后在使用时直接传入数值而不是 className。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议优化:尺寸计算可能存在问题

当前实现从 className 中提取尺寸,但这种方式不够可靠:

  1. className 可能包含多个 h- 相关的类名
  2. 正则表达式只匹配数字,可能无法处理小数或其他单位

建议方案

// 方案 1: 支持常见的 Tailwind 尺寸类
const sizeMap: Record<string, number> = {
  'h-3': 12,
  'h-3.5': 14, 
  'h-4': 16,
  'h-5': 20,
  // ...
};

// 方案 2: 直接接收 size prop
const AnthropicOrangeAvatar: React.FC<{ size?: number; className?: string }> = ({
  size = 12,
  className,
}) => {
  return (
    <Anthropic.Avatar
      size={size}
      background="#ffffff" 
      shape="circle"
      className={className}
    />
  );
};


return (
<Anthropic.Avatar
size={size}
background="#ffffff"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

硬编码背景色问题

background="#ffffff" 硬编码了白色背景,这在暗色主题下可能会有显示问题。建议使用 CSS 变量或主题色:

background="currentColor" // 或者
background="var(--background)" // 使用主题背景色

shape="circle"
className={className}
/>
);
};

// 供应商类型配置
export const PROVIDER_TYPE_CONFIG: Record<
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

类型安全改进建议

建议显式导出 PROVIDER_TYPE_CONFIG 的类型以提高代码可维护性:

export type ProviderTypeConfig = {
  label: string;
  icon: React.ComponentType<{ className?: string }>;
  iconColor: string;
  bgColor: string;
  description: string;
};

export const PROVIDER_TYPE_CONFIG: Record<ProviderType, ProviderTypeConfig> = {
  // ...
};

ProviderType,
{
label: string;
icon: React.ComponentType<{ className?: string }>;
iconColor: string;
bgColor: string;
description: string;
}
> = {
claude: {
label: "Claude",
icon: Claude.Color,
iconColor: "text-orange-600",
bgColor: "bg-orange-500/15",
description: "Anthropic 官方 API",
},
"claude-auth": {
label: "Claude Auth",
icon: AnthropicOrangeAvatar, // Anthropic Avatar 橙色圆形图标
iconColor: "text-purple-600",
bgColor: "bg-purple-500/15",
description: "Claude 中转服务",
},
codex: {
label: "Codex",
icon: OpenAI, // OpenAI 无文字版本(默认 Mono)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

代码重复codexopenai-compatible 都使用了相同的 OpenAI 图标组件。

建议:如果这是预期行为,建议添加注释说明原因。如果不同,建议为 codex 使用独立的图标以增强视觉区分度。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

疑问:OpenAI 图标用于 Codex

Codex 是 OpenAI 的产品,但使用 OpenAI 图标可能导致混淆:

  • codex 类型使用 OpenAI 图标(蓝色)
  • openai-compatible 类型也使用 OpenAI 图标(青色)

建议:考虑使用不同的图标以避免视觉混淆,或者在注释中说明这样设计的原因。

iconColor: "text-blue-600",
bgColor: "bg-blue-500/15",
description: "Codex CLI API",
},
"gemini-cli": {
label: "Gemini CLI",
icon: Gemini.Color,
iconColor: "text-emerald-600",
bgColor: "bg-emerald-500/15",
description: "Gemini CLI API",
},
"openai-compatible": {
label: "OpenAI Compatible",
icon: OpenAI, // OpenAI 无文字版本(默认 Mono)
iconColor: "text-cyan-600",
bgColor: "bg-cyan-500/15",
description: "OpenAI 兼容 API",
},
Comment on lines +49 to +67
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 图标复用问题

codexopenai-compatible 都使用了 OpenAI 图标,可能导致用户混淆。建议:

  1. codex 使用不同的图标(例如 Code2 from lucide-react)
  2. 保持 openai-compatible 使用 OpenAI 图标

这样可以更清晰地区分不同的供应商类型。

};

// 获取供应商类型配置
export function getProviderTypeConfig(type: ProviderType) {
return PROVIDER_TYPE_CONFIG[type];
}

// 获取所有供应商类型
export function getAllProviderTypes(): ProviderType[] {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 类型安全改进

使用 as 类型断言会绕过 TypeScript 的类型检查。建议改为:

export function getAllProviderTypes(): ProviderType[] {
  return Object.keys(PROVIDER_TYPE_CONFIG) as Array<keyof typeof PROVIDER_TYPE_CONFIG>;
}

这样既保证类型安全,又让 TypeScript 能够推断正确的类型。

return Object.keys(PROVIDER_TYPE_CONFIG) as ProviderType[];
}
Loading