diff --git a/packages/ui/ICON_IMPLEMENTATION_GUIDE.md b/packages/ui/ICON_IMPLEMENTATION_GUIDE.md new file mode 100644 index 0000000000..d8a8ee7d6c --- /dev/null +++ b/packages/ui/ICON_IMPLEMENTATION_GUIDE.md @@ -0,0 +1,1321 @@ +# Cherry Studio UI - 彩色 Logo 图标系统实施方案 + +> 借鉴 Lucide IconNode 架构,为 Cherry Studio UI 库构建专门支持彩色品牌 Logo 的轻量级图标系统 + +## 📋 目录 + +- [项目概述](#项目概述) +- [架构设计](#架构设计) +- [核心概念](#核心概念) +- [实施步骤](#实施步骤) +- [使用示例](#使用示例) +- [工作流程](#工作流程) +- [常见问题](#常见问题) + +--- + +## 项目概述 + +### 背景 + +Cherry Studio 是一个 Electron + React 的 monorepo 项目,主包通过 Vite alias 直接引用 UI 包的源码。我们需要为 UI 库添加 40+ 个 **彩色品牌 Logo 图标**(如 Anthropic、OpenAI、DeepSeek、Cohere 等)。 + +### 图标特点 + +这些图标与传统线性图标(如 Lucide)有本质区别: + +| 特征 | 传统线性图标 | 我们的彩色 Logo | +|------|------------|----------------| +| **颜色方式** | `stroke="currentColor"` | `fill="#3F3FAA"` 等固定颜色 | +| **结构** | 简单 path | 复杂嵌套 (``, ``, ``) | +| **样式控制** | 可动态改变颜色/描边 | 必须保留原始颜色 | +| **使用场景** | UI 通用图标 | 品牌标识 | + +### 方案特点 + +- ✅ **保留原色**:完整保留 SVG 中的所有 fill 颜色 +- ✅ **支持嵌套**:处理复杂的 ``、``、`` 结构 +- ✅ **借鉴 Lucide**:采用 IconNode 数据结构,工厂模式创建组件 +- ✅ **轻量级实现**:简化构建流程,无需复杂工具链 +- ✅ **源码直连**:主包通过 alias 直接使用源码,支持热更新 +- ✅ **TypeScript 支持**:完整的类型推导和类型安全 +- ✅ **Tailwind 友好**:完美支持 Tailwind CSS 样式 +- ✅ **自动化生成**:一键从 SVG 生成 React 组件 + +### 技术栈 + +- React 19 +- TypeScript 5.8 +- SVGO 3.0 (SVG 优化) +- Tailwind CSS 4.1 + +--- + +## 架构设计 + +### 目录结构 + +``` +cherry-studio/ +├── packages/ui/ +│ ├── icons/ # ① 源 SVG 文件目录 +│ │ ├── arrow-right.svg +│ │ ├── check.svg +│ │ ├── close.svg +│ │ └── ... (40+ SVG 文件) +│ │ +│ ├── scripts/ +│ │ └── generate-icons.ts # ② 生成脚本 +│ │ +│ ├── src/ +│ │ └── components/ +│ │ └── icons/ +│ │ ├── Icon.tsx # ③ 基础组件 +│ │ ├── generated/ # ④ 自动生成的图标组件 +│ │ │ ├── ArrowRight.tsx +│ │ │ ├── Check.tsx +│ │ │ ├── Close.tsx +│ │ │ └── index.ts +│ │ └── index.ts # ⑤ 统一导出 +│ │ +│ └── package.json +│ +└── src/renderer/src/ # ⑦ 主包使用 + └── components/ + └── YourComponent.tsx +``` + +### 架构分层 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 用户层 │ +│ │ +└────────────────────────┬────────────────────────────────────┘ + │ +┌────────────────────────▼────────────────────────────────────┐ +│ 具体图标组件层 │ +│ ArrowRight = createIcon('ArrowRight', iconNode) │ +└────────────────────────┬────────────────────────────────────┘ + │ +┌────────────────────────▼────────────────────────────────────┐ +│ 工厂函数层 │ +│ createIcon(name, iconNode) → IconComponent │ +└────────────────────────┬────────────────────────────────────┘ + │ +┌────────────────────────▼────────────────────────────────────┐ +│ 基础组件层 │ +│ Icon: 渲染 SVG,处理 props,映射 IconNode │ +└────────────────────────┬────────────────────────────────────┘ + │ +┌────────────────────────▼────────────────────────────────────┐ +│ 数据层 │ +│ IconNode: [['path', { d: '...' }], ['circle', {...}]] │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 核心概念 + +### 1. IconNode 数据结构(支持嵌套) + +借鉴 Lucide 的核心设计,扩展为支持嵌套结构的数组格式: + +```typescript +type IconNode = [ + tag: string, // SVG 元素标签名 + attrs: Record, // 元素属性 + children?: IconNode // 子元素(支持嵌套) +][]; + +// 简单示例(平面结构) +const simpleIcon: IconNode = [ + ['path', { d: 'M5 12h14', fill: '#000' }], + ['circle', { cx: '12', cy: '12', r: '10', fill: '#fff' }] +]; + +// 复杂示例(嵌套结构,品牌 Logo 常见) +const complexIcon: IconNode = [ + ['g', { clipPath: 'url(#clip0)' }, [ + ['path', { d: 'M18 0H6...', fill: '#CA9F7B' }], + ['path', { d: 'M15.38 6.43...', fill: '#191918' }] + ]], + ['defs', {}, [ + ['clipPath', { id: 'clip0' }, [ + ['rect', { width: '24', height: '24', fill: 'white' }] + ]] + ]] +]; +``` + +**优势:** +- 📦 体积极小(只存储数据,不存储模板) +- 🔄 框架无关(可复用于 Vue/Svelte 等) +- ⚡ 渲染快速(直接 createElement,无需解析) +- 🎨 **保留原色**(完整保留 fill 等样式属性) +- 🌲 **支持嵌套**(处理复杂的品牌 Logo 结构) + +### 2. 工厂模式 + +使用 `createIcon` 函数统一创建图标组件: + +```typescript +export function createIcon( + componentName: string, + iconNode: IconNode +) { + const IconComponent = forwardRef( + (props, ref) => { + return ; + } + ); + + IconComponent.displayName = componentName; + return IconComponent; +} +``` + +**优势:** +- ✅ 代码复用(40+ 图标共享同一套逻辑) +- ✅ 统一行为(所有图标的 props 处理完全一致) +- ✅ 易于维护(修改一处,全部更新) + +### 3. 自动化生成 + +从 SVG 到 React 组件的自动化流程: + +``` +SVG 文件 → SVGO 优化 → 正则解析 → IconNode → 组件代码 → 写入文件 +``` + +--- + +## 实施步骤 + +### 步骤 1:创建基础 Icon 组件 + +创建文件:`packages/ui/src/components/icons/Icon.tsx` + +```tsx +import React, { forwardRef, memo } from 'react'; +import { cn } from '@/utils'; + +/** + * IconNode 数据结构(借鉴 lucide,扩展支持嵌套) + * 格式: [标签名, 属性对象, 子元素(可选)] + */ +export type IconNode = [ + tag: string, + attrs: Record, + children?: IconNode +][]; + +/** + * Icon 组件的 Props(专为彩色 Logo 优化) + */ +export interface IconProps extends React.SVGProps { + /** 图标大小,支持数字(px)或字符串(如 "1rem") */ + size?: number | string; + /** 自定义类名 */ + className?: string; + /** 子元素 */ + children?: React.ReactNode; +} + +/** + * Icon 组件内部 Props(包含 iconNode) + */ +interface IconComponentProps extends IconProps { + iconNode: IconNode; + /** 图标名称(用于 className) */ + iconName?: string; +} + +/** + * 递归渲染 IconNode(支持嵌套结构) + */ +function renderNodes(nodes: IconNode, keyPrefix = ''): React.ReactNode[] { + return nodes.map((node, index) => { + const [tag, attrs, children] = node; + const key = `${keyPrefix}${index}`; + + // 如果有子元素,递归渲染 + const childElements = children ? renderNodes(children, `${key}-`) : undefined; + + return React.createElement( + tag, + { key, ...attrs }, + childElements + ); + }); +} + +/** + * 基础 Icon 组件(专为彩色品牌 Logo 设计) + * - 保留 SVG 原始颜色(不强制 fill/stroke) + * - 支持嵌套结构(g, clipPath, defs 等) + * - 只控制 size 和 className + */ +export const Icon = memo( + forwardRef( + ( + { + iconNode, + iconName, + size = 24, + className, + children, + ...props + }, + ref + ) => { + return ( + + {/* 递归渲染 IconNode(支持嵌套) */} + {renderNodes(iconNode)} + {children} + + ); + } + ) +); + +Icon.displayName = 'Icon'; + +/** + * 工厂函数:创建具体的图标组件 + * @param componentName - 组件名称(PascalCase) + * @param iconNode - 图标数据 + * @returns 图标组件 + */ +export function createIcon( + componentName: string, + iconNode: IconNode +) { + const IconComponent = forwardRef( + (props, ref) => { + return ( + + ); + } + ); + + IconComponent.displayName = componentName; + return IconComponent; +} +``` + +--- + +### 步骤 2:创建生成脚本 + +创建文件:`packages/ui/scripts/generate-icons.ts` + +```typescript +import fs from 'fs/promises'; +import path from 'path'; +import { optimize } from 'svgo'; + +const ICONS_DIR = path.join(__dirname, '../icons'); +const OUTPUT_DIR = path.join(__dirname, '../src/components/icons/generated'); + +// SVGO 优化配置(专为彩色 Logo 优化) +const svgoConfig = { + plugins: [ + { + name: 'preset-default', + params: { + overrides: { + // 保留 viewBox(必须!) + removeViewBox: false, + // 不转换为 path(保持原始形状) + convertShapeToPath: false, + // 不移除隐藏元素(可能包含 defs) + removeHiddenElems: false, + }, + }, + }, + { + // 只移除 width 和 height(保留所有颜色和样式) + name: 'removeAttrs', + params: { + attrs: '(width|height)', + }, + }, + ], +}; + +/** + * 转换命名:kebab-case → PascalCase + * 例: arrow-right → ArrowRight, 302ai → Ai302 + */ +function toPascalCase(str: string): string { + // 处理数字开头的情况(如 302ai) + if (/^\d/.test(str)) { + // 提取开头的数字和后续部分 + const match = str.match(/^(\d+)(.*)$/); + if (match) { + const [, numbers, rest] = match; + // 将数字放在后面:302ai → Ai302 + str = rest + numbers; + } + } + + return str + .split('-') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(''); +} + +/** + * 解析 SVG 为 IconNode 格式(支持嵌套结构) + * 使用 svgson 库来可靠地处理复杂的嵌套结构 + */ +async function parseSvg(svgContent: string): Promise { + const { parse } = await import('svgson'); + + // 1. SVGO 优化 + const optimized = optimize(svgContent, svgoConfig); + const svgString = optimized.data; + + // 2. 使用 svgson 解析为 AST + const ast = await parse(svgString); + + // 3. 转换为 IconNode 格式 + const iconNode = convertToIconNode(ast.children); + + // 4. 格式化为 TypeScript 代码 + return formatIconNode(iconNode); +} + +/** + * 将 svgson AST 转换为 IconNode 格式 + */ +function convertToIconNode(nodes: any[]): any[] { + return nodes.map(node => { + const { name, attributes, children } = node; + + if (children && children.length > 0) { + const childNodes = convertToIconNode(children); + return [name, attributes, childNodes]; + } else { + return [name, attributes]; + } + }); +} + +/** + * 格式化 IconNode 为 TypeScript 代码 + */ +function formatIconNode(nodes: any[], indent = 0): string { + const indentStr = ' '.repeat(indent + 1); + + const items = nodes.map(node => { + const [tag, attrs, children] = node; + + if (children) { + const childrenStr = formatIconNode(children, indent + 1); + return `${indentStr}['${tag}', ${JSON.stringify(attrs)}, ${childrenStr}]`; + } else { + return `${indentStr}['${tag}', ${JSON.stringify(attrs)}]`; + } + }); + + if (indent === 0) { + return `[\n${items.join(',\n')}\n]`; + } else { + return `[\n${items.join(',\n')}\n${' '.repeat(indent)}]`; + } +} + +/** + * 生成单个图标组件文件 + */ +async function generateIconComponent( + iconName: string, + iconNode: string +): Promise { + const componentName = toPascalCase(iconName); + + return `import { forwardRef } from 'react'; +import { createIcon, type IconProps } from '../Icon'; +import type { IconNode } from '../Icon'; + +const iconNode: IconNode = ${iconNode}; + +/** + * ${componentName} icon component + * + * @example + * <${componentName} size={24} color="red" /> + * <${componentName} className="text-blue-500" /> + */ +export const ${componentName} = createIcon('${componentName}', iconNode); + +export default ${componentName}; +`; +} + +/** + * 主函数:生成所有图标 + */ +async function generateIcons() { + console.log('🚀 开始生成图标组件...\n'); + + try { + // 检查 icons 目录是否存在 + try { + await fs.access(ICONS_DIR); + } catch { + console.error(`❌ 错误: 找不到 icons 目录: ${ICONS_DIR}`); + console.log(`💡 提示: 请创建 ${ICONS_DIR} 目录并放入 SVG 文件`); + process.exit(1); + } + + // 确保输出目录存在 + await fs.mkdir(OUTPUT_DIR, { recursive: true }); + + // 读取所有 SVG 文件 + const files = await fs.readdir(ICONS_DIR); + const svgFiles = files.filter(f => f.endsWith('.svg')); + + if (svgFiles.length === 0) { + console.warn(`⚠️ 警告: ${ICONS_DIR} 目录中没有找到 SVG 文件`); + process.exit(0); + } + + console.log(`📁 找到 ${svgFiles.length} 个 SVG 文件\n`); + + const exports: string[] = []; + let successCount = 0; + let errorCount = 0; + + // 处理每个 SVG 文件 + for (const file of svgFiles) { + const iconName = file.replace('.svg', ''); + const componentName = toPascalCase(iconName); + + try { + console.log(`⚙️ 处理: ${iconName}`); + + // 读取 SVG 内容 + const svgPath = path.join(ICONS_DIR, file); + const svgContent = await fs.readFile(svgPath, 'utf-8'); + + // 解析为 IconNode + const iconNode = await parseSvg(svgContent); + + // 生成组件代码 + const componentCode = await generateIconComponent(iconName, iconNode); + + // 写入文件 + const outputPath = path.join(OUTPUT_DIR, `${componentName}.tsx`); + await fs.writeFile(outputPath, componentCode, 'utf-8'); + + // 收集导出语句 + exports.push(`export { ${componentName} } from './${componentName}';`); + successCount++; + } catch (error) { + console.error(`❌ 处理 ${iconName} 失败:`, error); + errorCount++; + } + } + + // 生成 index.ts(统一导出) + const indexContent = `/** + * 自动生成的图标导出文件 + * 请勿手动编辑 + * + * 生成时间: ${new Date().toISOString()} + * 图标数量: ${successCount} + */ + +${exports.sort().join('\n')} +`; + + await fs.writeFile( + path.join(OUTPUT_DIR, 'index.ts'), + indexContent, + 'utf-8' + ); + + // 输出结果 + console.log(`\n✅ 成功生成 ${successCount} 个图标组件!`); + if (errorCount > 0) { + console.log(`⚠️ 失败 ${errorCount} 个图标`); + } + console.log(`📦 输出目录: ${OUTPUT_DIR}`); + } catch (error) { + console.error('\n❌ 生成过程发生错误:', error); + process.exit(1); + } +} + +// 执行生成 +generateIcons(); +``` + +--- + +### 步骤 3:更新 package.json + +在 `packages/ui/package.json` 中添加: + +```json +{ + "scripts": { + "generate:icons": "tsx scripts/generate-icons.ts", + "build": "pnpm generate:icons && tsdown", + "dev": "tsc -w" + }, + "devDependencies": { + "svgo": "^3.0.0", + "svgson": "^5.3.1", + "tsx": "^4.20.5" + } +} +``` + +安装依赖: + +```bash +cd packages/ui +pnpm add -D svgo svgson tsx +``` + +--- + +### 步骤 4:创建统一导出 + +创建文件:`packages/ui/src/components/icons/index.ts` + +```typescript +/** + * Icons 模块统一导出 + */ + +// 导出基础组件和类型 +export { Icon, createIcon, type IconProps, type IconNode } from './Icon'; + +// 导出所有生成的图标 +export * from './generated'; +``` + +--- + +### 步骤 5:更新主导出文件 + +在 `packages/ui/src/components/index.ts` 中添加: + +```typescript +// Icons +export * from './icons'; +``` + +--- + +### 步骤 6:配置子路径导出(推荐) + +为了支持 `@cherrystudio/ui/icons` 导入路径,需要在 `package.json` 中配置 `exports` 字段: + +```json +{ + "name": "@cherrystudio/ui", + "version": "1.0.0", + "type": "module", + "exports": { + ".": { + "import": "./src/components/index.ts", + "types": "./src/components/index.ts" + }, + "./icons": { + "import": "./src/components/icons/index.ts", + "types": "./src/components/icons/index.ts" + } + } +} +``` + +**同时更新主包的 Vite 配置**(`electron.vite.config.ts`): + +```typescript +export default defineConfig({ + resolve: { + alias: { + '@cherrystudio/ui': resolve('packages/ui/src/components'), + '@cherrystudio/ui/icons': resolve('packages/ui/src/components/icons'), + } + } +}); +``` + +这样你就可以使用两种导入方式: + +```tsx +// 推荐:从 icons 子路径导入(84个图标) +import { Anthropic, Deepseek } from '@cherrystudio/ui/icons'; + +// 兼容:从主包导入 +import { Anthropic, Deepseek } from '@cherrystudio/ui'; +``` + +--- + +### 步骤 7:创建 icons 目录 + +```bash +cd packages/ui +mkdir icons +``` + +将你的 84 个 SVG 文件放入 `icons/` 目录。 + +--- + +## 使用示例 + +### 基础用法(彩色品牌 Logo) + +```tsx +// 推荐:从 icons 子路径导入(84个图标,语义更清晰) +import { Anthropic, Deepseek, Cohere, Ai302 } from '@cherrystudio/ui/icons'; + +// 也可以:从主包导入(兼容方式) +// import { Anthropic, Deepseek } from '@cherrystudio/ui'; + +export function BasicExample() { + return ( +
+ {/* 默认大小(24px) */} + + + {/* 自定义大小 */} + + + + {/* 使用字符串大小 */} + +
+ ); +} +``` + +### Tailwind CSS 集成 + +```tsx +import { Anthropic, Deepseek } from '@cherrystudio/ui/icons'; + +export function TailwindExample() { + return ( +
+ {/* 使用 Tailwind 控制大小 */} + + + {/* 悬停效果(注意:颜色不可更改,但可添加缩放、阴影等效果) */} + + + {/* 响应式设计 */} + + + {/* 添加阴影和圆角效果 */} + +
+ ); +} +``` + +**重要提示:** + +- ❌ **不要尝试修改图标颜色**(这些是品牌 Logo,颜色是固定的) +- ✅ 可以修改 `size`、`className` +- ✅ 可以使用 Tailwind 的 `scale`、`opacity`、`transform`、`shadow` 等效果 + +### 事件处理 + +```tsx +import { Anthropic, Deepseek } from '@cherrystudio/ui/icons'; + +export function EventExample() { + const handleClick = () => { + console.log('Logo clicked!'); + }; + + return ( +
+ {/* onClick 事件 */} + + + {/* 其他事件 */} + console.log('Mouse enter')} + onMouseLeave={() => console.log('Mouse leave')} + className="cursor-pointer transition-opacity hover:opacity-75" + /> +
+ ); +} +``` + +### 使用 ref + +```tsx +import { Anthropic } from '@cherrystudio/ui/icons'; +import { useRef, useEffect } from 'react'; + +export function RefExample() { + const iconRef = useRef(null); + + useEffect(() => { + if (iconRef.current) { + console.log('Logo SVG element:', iconRef.current); + // 可以执行 DOM 操作,如添加动画 + } + }, []); + + return ; +} +``` + +### 自定义彩色图标 + +如果你有自己的彩色 SVG 图标,可以手动创建: + +```tsx +import { createIcon, type IconNode } from '@cherrystudio/ui/icons'; + +// 定义自定义彩色图标数据(带嵌套和颜色) +const customLogoNode: IconNode = [ + ['g', { clipPath: 'url(#clip0)' }, [ + ['circle', { cx: '12', cy: '12', r: '10', fill: '#FF6B6B' }], + ['path', { d: 'M12 6v6l4 2', stroke: '#fff', strokeWidth: '2' }] + ]], + ['defs', {}, [ + ['clipPath', { id: 'clip0' }, [ + ['rect', { width: '24', height: '24', fill: 'white' }] + ]] + ]] +]; + +// 创建自定义图标组件 +const MyBrandLogo = createIcon('MyBrandLogo', customLogoNode); + +export function CustomIconExample() { + return ; +} +``` + +### 组合使用 + +```tsx +import { Anthropic, Deepseek, Cohere } from '@cherrystudio/ui/icons'; + +export function CompositeExample() { + return ( +
+ {/* AI 模型选择器 */} +
+ {[ + { Logo: Anthropic, name: 'Claude' }, + { Logo: Deepseek, name: 'DeepSeek' }, + { Logo: Cohere, name: 'Cohere' } + ].map(({ Logo, name }) => ( + + ))} +
+ + {/* Logo 网格展示 */} +
+
+ +
+
+ +
+
+ +
+
+
+ ); +} +``` + +--- + +## 工作流程 + +### 开发流程 + +```bash +# 1. 准备 SVG 文件 +# 将 SVG 文件放到 packages/ui/icons/ 目录 + +# 2. 生成图标组件 +cd packages/ui +pnpm generate:icons + +# 3. 在主包中使用 +# 主包会通过 Vite alias 自动识别,直接导入使用 +``` + +### 生成的文件 + +``` +packages/ui/src/components/icons/ +├── Icon.tsx # ✅ 手写(基础组件) +├── generated/ # ⚠️ 自动生成(不要手动编辑) +│ ├── ArrowRight.tsx +│ ├── Check.tsx +│ ├── Close.tsx +│ ├── ... (40+ 个文件) +│ └── index.ts +└── index.ts # ✅ 手写(统一导出) +``` + +### 构建流程 + +**开发模式:** +- 主包通过 Vite alias 直接引用 UI 包源码 +- 支持热更新(HMR) +- 无需构建 UI 包 + +**生产构建:** + +```bash +# UI 包单独构建(如需发布) +cd packages/ui +pnpm build # 会先 generate:icons,然后 tsdown 打包 + +# 主包构建 +cd cherry-studio +pnpm build # Vite 会处理 UI 包的源码 +``` + +--- + +## 常见问题 + +### Q1: 为什么主包可以直接使用源码? + +**A:** 因为主包的 Vite 配置了 alias: + +```typescript +// electron.vite.config.ts +'@cherrystudio/ui': resolve('packages/ui/src') +``` + +这样导入的是 `.tsx` 源文件,Vite 会像处理主包代码一样处理这些文件,支持热更新和 TypeScript 类型推导。 + +--- + +### Q2: 彩色 Logo SVG 文件有什么要求? + +**A:** 针对彩色品牌 Logo 的要求: + +- ✅ 使用标准的 24x24 viewBox(推荐) +- ✅ 保留所有 `fill` 颜色(会自动保留) +- ✅ 支持复杂嵌套结构(``, ``, `` 等) +- ✅ 文件名使用 kebab-case(如 `anthropic.svg`、`deep-seek.svg`) +- ✅ 数字开头的文件名会自动转换(如 `302ai.svg` → `Ai302` 组件) + +示例彩色 Logo SVG: +```xml + + + + + + + + + + + +``` + +**注意事项:** + +- ⚠️ 确保 SVG 格式正确(使用 Figma/Illustrator 导出时选择"优化") +- ⚠️ ID 属性可能需要全局唯一(如 `clip0` 改为 `clip-anthropic`) +- ⚠️ 过大的 SVG 文件(超过 100KB)建议先手动优化 + +--- + +### Q3: 如何调试生成错误? + +**A:** 如果某个图标生成失败: + +1. **检查 SVG 文件语法**:使用浏览器直接打开 SVG 文件,看是否正常显示 +2. **测试 SVGO 优化**: + ```bash + npx svgo icons/your-icon.svg -o test.svg + ``` +3. **查看生成脚本的错误日志**:运行 `pnpm generate:icons` 时会显示详细错误 +4. **验证 SVG 结构**:本方案支持所有标准 SVG 元素(通过 svgson 解析) +5. **检查文件编码**:确保 SVG 文件是 UTF-8 编码 + +--- + +### Q4: 如何添加新图标? + +**A:** 非常简单: + +```bash +# 1. 将新的 SVG 文件放入 icons/ 目录 +cp new-icon.svg packages/ui/icons/ + +# 2. 重新生成 +cd packages/ui +pnpm generate:icons + +# 3. 立即可用(无需重启开发服务器) +import { NewIcon } from '@cherrystudio/ui/icons'; +``` + +--- + +### Q5: 如何自定义生成的代码? + +**A:** 修改 `scripts/generate-icons.ts` 中的 `generateIconComponent` 函数: + +```typescript +async function generateIconComponent( + iconName: string, + iconNode: string +): Promise { + const componentName = toPascalCase(iconName); + + // 在这里自定义生成的代码模板 + return `...`; +} +``` + +--- + +### Q6: TypeScript 类型如何工作? + +**A:** 完全自动,无需手动声明: + +```typescript +// IconProps 继承自 React.SVGProps +// 所以支持所有 SVG 属性 + + {}} // SVGProps + onMouseEnter={() => {}} // SVGProps + style={{ opacity: 0.8 }} // SVGProps + aria-label="Anthropic" // SVGProps + // ... 所有 SVG 属性 +/> +``` + +**注意:** 彩色 Logo 版本移除了 `color` 和 `strokeWidth` 属性(因为颜色是固定的) + +--- + +### Q7: 生成的文件需要提交到 Git 吗? + +**A:** 推荐做法: + +- ✅ **提交** `generated/` 目录(方便团队协作) +- ✅ **在 CI/CD 中重新生成**(确保一致性) + +`.gitignore` 配置: +```gitignore +# 可选:不提交生成文件(需要在 CI 中生成) +# packages/ui/src/components/icons/generated/ + +# 必须提交 icons 源文件 +!packages/ui/icons/*.svg +``` + +--- + +### Q8: 能否修改 Logo 的颜色? + +**A:** ❌ **不建议修改品牌 Logo 的颜色** + +这些是品牌官方 Logo,颜色是品牌标识的一部分,**不应该修改**。 + +**替代方案:** + +```tsx +import { Anthropic } from '@cherrystudio/ui/icons'; + +// ✅ 可以调整透明度 + + + +// ✅ 可以添加滤镜效果(慎用) + // 灰度滤镜 + + +// ❌ 不要尝试修改颜色 +// // 无效 +// // 无效 +``` + +**为什么无法修改颜色?** + +- 颜色信息存储在 IconNode 数据中(如 `fill="#CA9F7B"`) +- 这是设计决策:保护品牌标识的完整性 +- 如果需要可变颜色的图标,应该使用 Lucide 等线性图标库 + +--- + +### Q9: 如何与 Lucide React 共存? + +**A:** 完全可以同时使用: + +```tsx +// 使用彩色品牌 Logo(84个) +import { Anthropic, Deepseek } from '@cherrystudio/ui/icons'; + +// 使用 Lucide 线性图标 +import { Heart, Settings, User } from 'lucide-react'; + +export function MixedExample() { + return ( +
+ {/* 品牌 Logo:固定颜色 */} + + + + {/* Lucide 图标:可变颜色 */} + + +
+ ); +} +``` + +两者的 API 基本一致(都继承自 Lucide 的设计),但用途不同: + +- **Cherry Studio Icons**:彩色品牌 Logo +- **Lucide Icons**:单色通用图标 + +--- + +### Q10: 性能如何? + +**A:** 性能优秀: + +- 📦 **体积小**:每个图标约 0.5-1.5KB(IconNode 数据) +- ⚡ **渲染快**:直接 `createElement`,无需解析 +- 🌲 **Tree-shaking**:只打包使用的图标 +- 💾 **无运行时**:零运行时依赖 +- 🎨 **保留细节**:完整保留彩色 Logo 的所有颜色和细节 + +对比: + +``` +传统方式(内联 SVG JSX):~2-3KB/Logo +IconNode 方式(彩色):~0.5-1.5KB/Logo +节省:~50-75% 体积 +``` + +**注意:** 彩色 Logo 比简单线性图标稍大,因为包含更多颜色和路径信息,但仍然非常高效。 + +--- + +### Q11: 如何批量更新图标? + +**A:** 直接替换 SVG 文件,然后重新生成: + +```bash +# 1. 更新 SVG 文件(替换现有的或添加新的) +cp new-logos/*.svg packages/ui/icons/ + +# 2. 重新生成组件 +cd packages/ui +pnpm generate:icons + +# 3. 所有使用该图标的地方自动更新(无需修改代码) +``` + +**批量处理技巧:** + +```bash +# 批量优化 SVG(使用 SVGO) +npx svggo -f icons/ -o icons-optimized/ + +# 批量重命名(确保 kebab-case) +# 使用 rename 工具或脚本处理 +``` + +--- + +## 附录 + +### A. 命名规范 + +**SVG 文件名(品牌 Logo):** + +- 使用 kebab-case:`anthropic.svg`、`deep-seek.svg` +- 只包含小写字母、数字、连字符 +- 使用品牌官方名称 +- 数字开头会自动处理:`302ai.svg` → `Ai302` + +**组件名(自动生成):** + +- 自动转换为 PascalCase:`Anthropic`、`DeepSeek`、`Ai302` +- 无需手动指定 + +### B. SVGO 配置说明(彩色 Logo 专用) + +```javascript +{ + plugins: [ + { + name: 'preset-default', + params: { + overrides: { + removeViewBox: false, // 保留 viewBox(必须) + convertShapeToPath: false, // 不转换为 path(保持原始形状) + removeHiddenElems: false, // 不移除隐藏元素(保留 defs) + }, + }, + }, + { + name: 'removeAttrs', + params: { + attrs: '(width|height)', // 只移除 width/height,保留所有颜色 + }, + }, + ], +} +``` + +**关键差异:** + +- ✅ **保留 fill**:不移除颜色属性(传统方案会移除) +- ✅ **保留 defs**:保留 clipPath、linearGradient 等定义 +- ✅ **保留嵌套**:完整保留 `` 嵌套结构 + +### C. 目录结构完整示例(彩色品牌 Logo) + +``` +packages/ui/ +├── icons/ # 彩色 Logo SVG 源文件 +│ ├── 302ai.svg +│ ├── aiOnly.svg +│ ├── aihubmix.svg +│ ├── anthropic.svg +│ ├── aws-bedrock.svg +│ ├── baichuan.svg +│ ├── baidu-cloud.svg +│ ├── bailian.svg +│ ├── bytedance.svg +│ ├── cephalon.svg +│ ├── cherryin.svg +│ ├── cohere.svg +│ ├── dashscope.svg +│ ├── deepseek.svg +│ └── ... (40+ 品牌 Logo) +│ +├── scripts/ +│ └── generate-icons.ts # 生成脚本(支持嵌套和彩色) +│ +├── src/ +│ └── components/ +│ └── icons/ +│ ├── Icon.tsx # 基础组件(支持嵌套渲染) +│ ├── index.ts # 统一导出 +│ └── generated/ # 自动生成的 Logo 组件 +│ ├── Ai302.tsx +│ ├── AiOnly.tsx +│ ├── Aihubmix.tsx +│ ├── Anthropic.tsx +│ ├── AwsBedrock.tsx +│ ├── Baichuan.tsx +│ ├── BaiduCloud.tsx +│ ├── Bailian.tsx +│ ├── Bytedance.tsx +│ ├── Cephalon.tsx +│ ├── Cherryin.tsx +│ ├── Cohere.tsx +│ ├── Dashscope.tsx +│ ├── Deepseek.tsx +│ ├── ... (40+ 组件) +│ └── index.ts +│ +└── package.json +``` + +--- + +## 总结 + +本方案借鉴 Lucide 的核心 IconNode 架构,专门为 Cherry Studio UI 库打造了一个轻量级、高性能的**彩色品牌 Logo 图标系统**: + +### 核心优势 + +✅ **保留原色**:完整保留品牌 Logo 的所有颜色和细节 +✅ **支持嵌套**:处理复杂的 SVG 结构(g, clipPath, defs 等) +✅ **自动生成**:一键从 SVG 生成 React 组件,无需手动编写 +✅ **高效轻量**:IconNode 数据结构,体积小、渲染快 +✅ **类型安全**:完整的 TypeScript 支持 +✅ **Tailwind 友好**:完美集成 Tailwind CSS +✅ **易于维护**:工厂模式,统一管理,易于扩展 + +### 与传统方案的区别 + +| 特性 | 传统线性图标(Lucide) | 本方案(彩色 Logo) | +|------|---------------------|------------------| +| **颜色** | 单色,可动态改变 | 多色,保留原色 | +| **复杂度** | 简单路径 | 支持嵌套结构 | +| **用途** | 通用 UI 图标 | 品牌 Logo 展示 | +| **体积** | ~0.3KB | ~0.5-1.5KB | + +### 快速开始 + +```bash +cd packages/ui + +# 1. 安装依赖 +pnpm add -D svgo svgson tsx + +# 2. 准备 SVG 文件(已有 84 个品牌 Logo) +# icons/ 目录已包含: anthropic.svg, deepseek.svg, cohere.svg... + +# 3. 生成组件 +pnpm generate:icons + +# 4. 在代码中使用(推荐使用 /icons 子路径) +# import { Anthropic, Deepseek } from '@cherrystudio/ui/icons'; +``` + +现在就可以在主包中使用彩色品牌 Logo 了!🎉 + +### 下一步 + +1. 阅读[使用示例](#使用示例)了解更多用法 +2. 查看[常见问题](#常见问题)解决疑惑 +3. 将更多品牌 Logo SVG 添加到 `icons/` 目录 +4. 运行 `pnpm generate:icons` 生成新组件 diff --git a/packages/ui/icons/302ai.svg b/packages/ui/icons/302ai.svg new file mode 100644 index 0000000000..5d9b55b3fa --- /dev/null +++ b/packages/ui/icons/302ai.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/ui/icons/DMXAPI-to-img.svg b/packages/ui/icons/DMXAPI-to-img.svg new file mode 100644 index 0000000000..a839782d66 --- /dev/null +++ b/packages/ui/icons/DMXAPI-to-img.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/DMXAPI.svg b/packages/ui/icons/DMXAPI.svg new file mode 100644 index 0000000000..d3ca99244e --- /dev/null +++ b/packages/ui/icons/DMXAPI.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/Tesseract.js.svg b/packages/ui/icons/Tesseract.js.svg new file mode 100644 index 0000000000..af2b0f877d --- /dev/null +++ b/packages/ui/icons/Tesseract.js.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/Voyage.svg b/packages/ui/icons/Voyage.svg new file mode 100644 index 0000000000..537376784c --- /dev/null +++ b/packages/ui/icons/Voyage.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/ui/icons/aiOnly.svg b/packages/ui/icons/aiOnly.svg new file mode 100644 index 0000000000..a2369ed410 --- /dev/null +++ b/packages/ui/icons/aiOnly.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/icons/aihubmix.svg b/packages/ui/icons/aihubmix.svg new file mode 100644 index 0000000000..98cdcadb4b --- /dev/null +++ b/packages/ui/icons/aihubmix.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/alayanew.svg b/packages/ui/icons/alayanew.svg new file mode 100644 index 0000000000..83a5071a0a --- /dev/null +++ b/packages/ui/icons/alayanew.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/anthropic.svg b/packages/ui/icons/anthropic.svg new file mode 100644 index 0000000000..7704cbf1c9 --- /dev/null +++ b/packages/ui/icons/anthropic.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/ui/icons/aws-bedrock.svg b/packages/ui/icons/aws-bedrock.svg new file mode 100644 index 0000000000..de14833d45 --- /dev/null +++ b/packages/ui/icons/aws-bedrock.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/azureai.svg b/packages/ui/icons/azureai.svg new file mode 100644 index 0000000000..6a6627d70c --- /dev/null +++ b/packages/ui/icons/azureai.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/baichuan.svg b/packages/ui/icons/baichuan.svg new file mode 100644 index 0000000000..8b6a98929b --- /dev/null +++ b/packages/ui/icons/baichuan.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/baidu-cloud.svg b/packages/ui/icons/baidu-cloud.svg new file mode 100644 index 0000000000..ad4dbda3f2 --- /dev/null +++ b/packages/ui/icons/baidu-cloud.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ui/icons/bailian.svg b/packages/ui/icons/bailian.svg new file mode 100644 index 0000000000..784ee64497 --- /dev/null +++ b/packages/ui/icons/bailian.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/bocha.svg b/packages/ui/icons/bocha.svg new file mode 100644 index 0000000000..44ff57220d --- /dev/null +++ b/packages/ui/icons/bocha.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/ui/icons/burncloud.svg b/packages/ui/icons/burncloud.svg new file mode 100644 index 0000000000..7185b8827d --- /dev/null +++ b/packages/ui/icons/burncloud.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/bytedance.svg b/packages/ui/icons/bytedance.svg new file mode 100644 index 0000000000..e0c21c49a6 --- /dev/null +++ b/packages/ui/icons/bytedance.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/ui/icons/cephalon.svg b/packages/ui/icons/cephalon.svg new file mode 100644 index 0000000000..c895385491 --- /dev/null +++ b/packages/ui/icons/cephalon.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/icons/cherryin.svg b/packages/ui/icons/cherryin.svg new file mode 100644 index 0000000000..74536e15df --- /dev/null +++ b/packages/ui/icons/cherryin.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/cohere.svg b/packages/ui/icons/cohere.svg new file mode 100644 index 0000000000..a9727f89f8 --- /dev/null +++ b/packages/ui/icons/cohere.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/ui/icons/dashscope.svg b/packages/ui/icons/dashscope.svg new file mode 100644 index 0000000000..3bb2e62176 --- /dev/null +++ b/packages/ui/icons/dashscope.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/deepseek.svg b/packages/ui/icons/deepseek.svg new file mode 100644 index 0000000000..c6632fb7b0 --- /dev/null +++ b/packages/ui/icons/deepseek.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/icons/dmxapi-logo-dark.svg b/packages/ui/icons/dmxapi-logo-dark.svg new file mode 100644 index 0000000000..571a918120 --- /dev/null +++ b/packages/ui/icons/dmxapi-logo-dark.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/dmxapi-logo.svg b/packages/ui/icons/dmxapi-logo.svg new file mode 100644 index 0000000000..a4660d2bff --- /dev/null +++ b/packages/ui/icons/dmxapi-logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/doc2x.svg b/packages/ui/icons/doc2x.svg new file mode 100644 index 0000000000..f19f56d195 --- /dev/null +++ b/packages/ui/icons/doc2x.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/icons/doubao.svg b/packages/ui/icons/doubao.svg new file mode 100644 index 0000000000..3efdfeeca4 --- /dev/null +++ b/packages/ui/icons/doubao.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/ui/icons/exa.svg b/packages/ui/icons/exa.svg new file mode 100644 index 0000000000..0d2a401e31 --- /dev/null +++ b/packages/ui/icons/exa.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/icons/fireworks.svg b/packages/ui/icons/fireworks.svg new file mode 100644 index 0000000000..85e6548c14 --- /dev/null +++ b/packages/ui/icons/fireworks.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/icons/gemini.svg b/packages/ui/icons/gemini.svg new file mode 100644 index 0000000000..5986406a09 --- /dev/null +++ b/packages/ui/icons/gemini.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/gitee-ai.svg b/packages/ui/icons/gitee-ai.svg new file mode 100644 index 0000000000..f561588527 --- /dev/null +++ b/packages/ui/icons/gitee-ai.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/icons/github.svg b/packages/ui/icons/github.svg new file mode 100644 index 0000000000..cf6b05f172 --- /dev/null +++ b/packages/ui/icons/github.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/icons/google.svg b/packages/ui/icons/google.svg new file mode 100644 index 0000000000..e207b34a48 --- /dev/null +++ b/packages/ui/icons/google.svg @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/gpustack.svg b/packages/ui/icons/gpustack.svg new file mode 100644 index 0000000000..1876692196 --- /dev/null +++ b/packages/ui/icons/gpustack.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/graph-rag.svg b/packages/ui/icons/graph-rag.svg new file mode 100644 index 0000000000..f948b88179 --- /dev/null +++ b/packages/ui/icons/graph-rag.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/ui/icons/grok.svg b/packages/ui/icons/grok.svg new file mode 100644 index 0000000000..97da4e29be --- /dev/null +++ b/packages/ui/icons/grok.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/ui/icons/groq.svg b/packages/ui/icons/groq.svg new file mode 100644 index 0000000000..f5b9b67284 --- /dev/null +++ b/packages/ui/icons/groq.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/ui/icons/huggingface.svg b/packages/ui/icons/huggingface.svg new file mode 100644 index 0000000000..e1175839c2 --- /dev/null +++ b/packages/ui/icons/huggingface.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/hyperbolic.svg b/packages/ui/icons/hyperbolic.svg new file mode 100644 index 0000000000..2354a47e4a --- /dev/null +++ b/packages/ui/icons/hyperbolic.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/icons/infini.svg b/packages/ui/icons/infini.svg new file mode 100644 index 0000000000..e5886fc4d3 --- /dev/null +++ b/packages/ui/icons/infini.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/ui/icons/intel.svg b/packages/ui/icons/intel.svg new file mode 100644 index 0000000000..bdb0a56be5 --- /dev/null +++ b/packages/ui/icons/intel.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages/ui/icons/jimeng.svg b/packages/ui/icons/jimeng.svg new file mode 100644 index 0000000000..2c38ede87f --- /dev/null +++ b/packages/ui/icons/jimeng.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/jina.svg b/packages/ui/icons/jina.svg new file mode 100644 index 0000000000..88a4958696 --- /dev/null +++ b/packages/ui/icons/jina.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/icons/lanyun.svg b/packages/ui/icons/lanyun.svg new file mode 100644 index 0000000000..fc0383eab4 --- /dev/null +++ b/packages/ui/icons/lanyun.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/lepton.svg b/packages/ui/icons/lepton.svg new file mode 100644 index 0000000000..5983db5fb1 --- /dev/null +++ b/packages/ui/icons/lepton.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/ui/icons/lmstudio.svg b/packages/ui/icons/lmstudio.svg new file mode 100644 index 0000000000..791b5e3e15 --- /dev/null +++ b/packages/ui/icons/lmstudio.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/ui/icons/longcat.svg b/packages/ui/icons/longcat.svg new file mode 100644 index 0000000000..18af084275 --- /dev/null +++ b/packages/ui/icons/longcat.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/ui/icons/macos.svg b/packages/ui/icons/macos.svg new file mode 100644 index 0000000000..1b8a606bbf --- /dev/null +++ b/packages/ui/icons/macos.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/icons/mcprouter.svg b/packages/ui/icons/mcprouter.svg new file mode 100644 index 0000000000..196ab78196 --- /dev/null +++ b/packages/ui/icons/mcprouter.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/meta.svg b/packages/ui/icons/meta.svg new file mode 100644 index 0000000000..9014995bcb --- /dev/null +++ b/packages/ui/icons/meta.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/mineru.svg b/packages/ui/icons/mineru.svg new file mode 100644 index 0000000000..e7cccfa096 --- /dev/null +++ b/packages/ui/icons/mineru.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/minimax.svg b/packages/ui/icons/minimax.svg new file mode 100644 index 0000000000..6f1f1e06ee --- /dev/null +++ b/packages/ui/icons/minimax.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/mistral.svg b/packages/ui/icons/mistral.svg new file mode 100644 index 0000000000..bee0c6ceab --- /dev/null +++ b/packages/ui/icons/mistral.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/packages/ui/icons/mixedbread-1.svg b/packages/ui/icons/mixedbread-1.svg new file mode 100644 index 0000000000..3b1a91f2bf --- /dev/null +++ b/packages/ui/icons/mixedbread-1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/mixedbread.svg b/packages/ui/icons/mixedbread.svg new file mode 100644 index 0000000000..180923f69c --- /dev/null +++ b/packages/ui/icons/mixedbread.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/moonshot.svg b/packages/ui/icons/moonshot.svg new file mode 100644 index 0000000000..c1e2349e71 --- /dev/null +++ b/packages/ui/icons/moonshot.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/icons/netease-youdao.svg b/packages/ui/icons/netease-youdao.svg new file mode 100644 index 0000000000..9fe8ee01b3 --- /dev/null +++ b/packages/ui/icons/netease-youdao.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/icons/newapi.svg b/packages/ui/icons/newapi.svg new file mode 100644 index 0000000000..00c52495ea --- /dev/null +++ b/packages/ui/icons/newapi.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/nomic.svg b/packages/ui/icons/nomic.svg new file mode 100644 index 0000000000..4261f28669 --- /dev/null +++ b/packages/ui/icons/nomic.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/nvidia.svg b/packages/ui/icons/nvidia.svg new file mode 100644 index 0000000000..71a18de8c9 --- /dev/null +++ b/packages/ui/icons/nvidia.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/icons/o3.svg b/packages/ui/icons/o3.svg new file mode 100644 index 0000000000..305fbd3cd5 --- /dev/null +++ b/packages/ui/icons/o3.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/ocoolai.svg b/packages/ui/icons/ocoolai.svg new file mode 100644 index 0000000000..162a027707 --- /dev/null +++ b/packages/ui/icons/ocoolai.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/icons/ollama.svg b/packages/ui/icons/ollama.svg new file mode 100644 index 0000000000..cf2773773a --- /dev/null +++ b/packages/ui/icons/ollama.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/icons/openai-1.svg b/packages/ui/icons/openai-1.svg new file mode 100644 index 0000000000..a0b49d226e --- /dev/null +++ b/packages/ui/icons/openai-1.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/icons/openai.svg b/packages/ui/icons/openai.svg new file mode 100644 index 0000000000..08932f01d2 --- /dev/null +++ b/packages/ui/icons/openai.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/icons/openrouter.svg b/packages/ui/icons/openrouter.svg new file mode 100644 index 0000000000..61305c11d3 --- /dev/null +++ b/packages/ui/icons/openrouter.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/icons/paddleocr.svg b/packages/ui/icons/paddleocr.svg new file mode 100644 index 0000000000..1642eff9e1 --- /dev/null +++ b/packages/ui/icons/paddleocr.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/perplexity-1.svg b/packages/ui/icons/perplexity-1.svg new file mode 100644 index 0000000000..5134503045 --- /dev/null +++ b/packages/ui/icons/perplexity-1.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/icons/perplexity.svg b/packages/ui/icons/perplexity.svg new file mode 100644 index 0000000000..5134503045 --- /dev/null +++ b/packages/ui/icons/perplexity.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/icons/ph8.svg b/packages/ui/icons/ph8.svg new file mode 100644 index 0000000000..1ca4605182 --- /dev/null +++ b/packages/ui/icons/ph8.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/ppio.svg b/packages/ui/icons/ppio.svg new file mode 100644 index 0000000000..2110f20962 --- /dev/null +++ b/packages/ui/icons/ppio.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/icons/qiniu.svg b/packages/ui/icons/qiniu.svg new file mode 100644 index 0000000000..0956d63500 --- /dev/null +++ b/packages/ui/icons/qiniu.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/icons/searxng.svg b/packages/ui/icons/searxng.svg new file mode 100644 index 0000000000..b1f52d42d5 --- /dev/null +++ b/packages/ui/icons/searxng.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/ui/icons/silicon.svg b/packages/ui/icons/silicon.svg new file mode 100644 index 0000000000..eed86a2360 --- /dev/null +++ b/packages/ui/icons/silicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/icons/sophnet.svg b/packages/ui/icons/sophnet.svg new file mode 100644 index 0000000000..3a657ec2fb --- /dev/null +++ b/packages/ui/icons/sophnet.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/ui/icons/step.svg b/packages/ui/icons/step.svg new file mode 100644 index 0000000000..86273c0f0b --- /dev/null +++ b/packages/ui/icons/step.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/ui/icons/tavily.svg b/packages/ui/icons/tavily.svg new file mode 100644 index 0000000000..13766953eb --- /dev/null +++ b/packages/ui/icons/tavily.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/tencent-cloud-ti.svg b/packages/ui/icons/tencent-cloud-ti.svg new file mode 100644 index 0000000000..387c86b4c0 --- /dev/null +++ b/packages/ui/icons/tencent-cloud-ti.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/ui/icons/together.svg b/packages/ui/icons/together.svg new file mode 100644 index 0000000000..7cf402a08c --- /dev/null +++ b/packages/ui/icons/together.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/icons/tokenflux.svg b/packages/ui/icons/tokenflux.svg new file mode 100644 index 0000000000..9033cd890b --- /dev/null +++ b/packages/ui/icons/tokenflux.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/ui/icons/vertexai.svg b/packages/ui/icons/vertexai.svg new file mode 100644 index 0000000000..2b40e4b90a --- /dev/null +++ b/packages/ui/icons/vertexai.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/ui/icons/volcengine.svg b/packages/ui/icons/volcengine.svg new file mode 100644 index 0000000000..5fee048ddd --- /dev/null +++ b/packages/ui/icons/volcengine.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/ui/icons/xirang.svg b/packages/ui/icons/xirang.svg new file mode 100644 index 0000000000..e0876dc540 --- /dev/null +++ b/packages/ui/icons/xirang.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/ui/icons/zero-one.svg b/packages/ui/icons/zero-one.svg new file mode 100644 index 0000000000..3a53979f9e --- /dev/null +++ b/packages/ui/icons/zero-one.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/ui/icons/zhipu.svg b/packages/ui/icons/zhipu.svg new file mode 100644 index 0000000000..88a45da6b0 --- /dev/null +++ b/packages/ui/icons/zhipu.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/ui/stories/tailwind.css b/packages/ui/stories/tailwind.css index d080a16fc4..8142a488b1 100644 --- a/packages/ui/stories/tailwind.css +++ b/packages/ui/stories/tailwind.css @@ -1,5 +1,7 @@ /* Storybook 专用的 Tailwind CSS 配置 */ @import 'tailwindcss'; +@import 'tw-animate-css'; + @import '../src/styles/theme.css'; /* 扫描组件文件 */ @@ -11,7 +13,6 @@ /* Dark mode support */ @custom-variant dark (&:is(.dark *)); -@import 'tw-animate-css'; :root { --icon: #00000099;