mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-26 03:31:24 +08:00
feat: add migration status documentation and new UI components
- Introduced migration status documentation in both English and Chinese to track the progress of the UI component library migration. - Added new UI components including CopyButton, DividerWithText, EmojiIcon, IndicatorLight, Spinner, TextBadge, and various display and icon components. - Updated the index.ts file to export the newly added components for easier access. - Enhanced the directory structure and component classification guidelines for better organization and clarity.
This commit is contained in:
parent
bf2f6ddd7f
commit
2e07b4ea58
104
packages/ui/MIGRATION_STATUS.md
Normal file
104
packages/ui/MIGRATION_STATUS.md
Normal file
@ -0,0 +1,104 @@
|
||||
# UI 组件库迁移状态
|
||||
|
||||
## 使用示例
|
||||
|
||||
```typescript
|
||||
// 从 @cherrystudio/ui 导入组件
|
||||
import { Spinner, DividerWithText, InfoTooltip } from '@cherrystudio/ui'
|
||||
|
||||
// 在组件中使用
|
||||
function MyComponent() {
|
||||
return (
|
||||
<div>
|
||||
<Spinner size={24} />
|
||||
<DividerWithText text="分隔文本" />
|
||||
<InfoTooltip content="提示信息" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 目录结构说明
|
||||
|
||||
```
|
||||
@packages/ui/
|
||||
├── src/
|
||||
│ ├── components/ # 组件主目录
|
||||
│ │ ├── base/ # 基础组件(按钮、输入框、标签等)
|
||||
│ │ ├── display/ # 显示组件(卡片、列表、表格等)
|
||||
│ │ ├── layout/ # 布局组件(容器、网格、间距等)
|
||||
│ │ ├── icons/ # 图标组件
|
||||
│ │ ├── interactive/ # 交互组件(弹窗、提示、下拉等)
|
||||
│ │ └── composite/ # 复合组件(多个基础组件组合而成)
|
||||
│ ├── hooks/ # 自定义 React Hooks
|
||||
│ └── types/ # TypeScript 类型定义
|
||||
```
|
||||
|
||||
### 组件分类指南
|
||||
|
||||
提交 PR 时,请根据组件功能将其放入正确的目录:
|
||||
|
||||
- **base**: 最基础的 UI 元素,如按钮、输入框、开关、标签等
|
||||
- **display**: 用于展示内容的组件,如卡片、列表、表格、标签页等
|
||||
- **layout**: 用于页面布局的组件,如容器、网格系统、分隔符等
|
||||
- **icons**: 所有图标相关的组件
|
||||
- **interactive**: 需要用户交互的组件,如模态框、抽屉、提示框、下拉菜单等
|
||||
- **composite**: 复合组件,由多个基础组件组合而成
|
||||
|
||||
## 迁移概览
|
||||
|
||||
- **总组件数**: 236
|
||||
- **已迁移**: 18
|
||||
- **已重构**: 0
|
||||
- **待迁移**: 218
|
||||
|
||||
## 组件状态表
|
||||
|
||||
| 组件名称 | 原路径 | 分类 | 迁移状态 | 重构状态 |
|
||||
|---------|--------|------|---------|---------|
|
||||
| CopyButton | src/renderer/src/components/CopyButton.tsx | base | ✅ | ❌ |
|
||||
| DividerWithText | src/renderer/src/components/DividerWithText.tsx | base | ✅ | ❌ |
|
||||
| EmojiIcon | src/renderer/src/components/EmojiIcon.tsx | base | ✅ | ❌ |
|
||||
| IndicatorLight | src/renderer/src/components/IndicatorLight.tsx | base | ✅ | ❌ |
|
||||
| Spinner | src/renderer/src/components/Spinner.tsx | base | ✅ | ❌ |
|
||||
| TextBadge | src/renderer/src/components/TextBadge.tsx | base | ✅ | ❌ |
|
||||
| Ellipsis | src/renderer/src/components/Ellipsis/index.tsx | display | ✅ | ❌ |
|
||||
| ExpandableText | src/renderer/src/components/ExpandableText.tsx | display | ✅ | ❌ |
|
||||
| ThinkingEffect | src/renderer/src/components/ThinkingEffect.tsx | display | ✅ | ❌ |
|
||||
| HorizontalScrollContainer | src/renderer/src/components/HorizontalScrollContainer/index.tsx | layout | ✅ | ❌ |
|
||||
| Scrollbar | src/renderer/src/components/Scrollbar/index.tsx | layout | ✅ | ❌ |
|
||||
| VisionIcon | src/renderer/src/components/Icons/VisionIcon.tsx | icons | ✅ | ❌ |
|
||||
| WebSearchIcon | src/renderer/src/components/Icons/WebSearchIcon.tsx | icons | ✅ | ❌ |
|
||||
| ToolsCallingIcon | src/renderer/src/components/Icons/ToolsCallingIcon.tsx | icons | ✅ | ❌ |
|
||||
| FileIcons | src/renderer/src/components/Icons/FileIcons.tsx | icons | ✅ | ❌ |
|
||||
| SvgSpinners180Ring | src/renderer/src/components/Icons/SvgSpinners180Ring.tsx | icons | ✅ | ❌ |
|
||||
| ReasoningIcon | src/renderer/src/components/Icons/ReasoningIcon.tsx | icons | ✅ | ❌ |
|
||||
| InfoTooltip | src/renderer/src/components/TooltipIcons/InfoTooltip.tsx | interactive | ✅ | ❌ |
|
||||
|
||||
## 迁移步骤
|
||||
|
||||
### 第一阶段:复制迁移(当前阶段)
|
||||
- 将组件原样复制到 @packages/ui
|
||||
- 保留原有依赖(antd、styled-components 等)
|
||||
- 在文件顶部添加原路径注释
|
||||
|
||||
### 第二阶段:重构优化
|
||||
- 移除 antd 依赖,替换为 HeroUI
|
||||
- 移除 styled-components,替换为 Tailwind CSS
|
||||
- 优化组件 API 和类型定义
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **不迁移**包含以下依赖的组件**(解耦后可迁移)**:
|
||||
- window.api 调用
|
||||
- Redux(useSelector、useDispatch 等)
|
||||
- 其他外部数据源
|
||||
|
||||
2. **可迁移**但需要后续解耦的组件:
|
||||
- 使用 i18n 的组件(将 i18n 改为 props 传入)
|
||||
- 使用 antd 的组件(后续替换为 HeroUI)
|
||||
|
||||
3. **提交规范**:
|
||||
- 每次 PR 专注于一个类别的组件
|
||||
- 确保所有迁移的组件都有导出
|
||||
- 更新此文档的迁移状态
|
||||
106
packages/ui/MIGRATION_STATUS_EN.md
Normal file
106
packages/ui/MIGRATION_STATUS_EN.md
Normal file
@ -0,0 +1,106 @@
|
||||
# UI Component Library Migration Status
|
||||
|
||||
## Usage Example
|
||||
|
||||
```typescript
|
||||
// Import components from @cherrystudio/ui
|
||||
import { Spinner, DividerWithText, InfoTooltip } from '@cherrystudio/ui'
|
||||
|
||||
// Use in components
|
||||
function MyComponent() {
|
||||
return (
|
||||
<div>
|
||||
<Spinner size={24} />
|
||||
<DividerWithText text="Divider Text" />
|
||||
<InfoTooltip content="Tooltip message" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
@packages/ui/
|
||||
├── src/
|
||||
│ ├── components/ # Main components directory
|
||||
│ │ ├── base/ # Basic components (buttons, inputs, labels, etc.)
|
||||
│ │ ├── display/ # Display components (cards, lists, tables, etc.)
|
||||
│ │ ├── layout/ # Layout components (containers, grids, spacing, etc.)
|
||||
│ │ ├── icons/ # Icon components
|
||||
│ │ ├── interactive/ # Interactive components (modals, tooltips, dropdowns, etc.)
|
||||
│ │ └── composite/ # Composite components (made from multiple base components)
|
||||
│ ├── hooks/ # Custom React Hooks
|
||||
│ └── types/ # TypeScript type definitions
|
||||
```
|
||||
|
||||
### Component Classification Guide
|
||||
|
||||
When submitting PRs, please place components in the correct directory based on their function:
|
||||
|
||||
- **base**: Most basic UI elements like buttons, inputs, switches, labels, etc.
|
||||
- **display**: Components for displaying content like cards, lists, tables, tabs, etc.
|
||||
- **layout**: Components for page layout like containers, grid systems, dividers, etc.
|
||||
- **icons**: All icon-related components
|
||||
- **interactive**: Components requiring user interaction like modals, drawers, tooltips, dropdowns, etc.
|
||||
- **composite**: Composite components made from multiple base components
|
||||
|
||||
## Migration Overview
|
||||
|
||||
- **Total Components**: 236
|
||||
- **Migrated**: 18
|
||||
- **Refactored**: 0
|
||||
- **Pending Migration**: 218
|
||||
|
||||
## Component Status Table
|
||||
|
||||
| Component Name | Original Path | Category | Migration Status | Refactoring Status |
|
||||
|---------------|---------------|----------|------------------|-------------------|
|
||||
| CopyButton | src/renderer/src/components/CopyButton.tsx | base | ✅ | ❌ |
|
||||
| DividerWithText | src/renderer/src/components/DividerWithText.tsx | base | ✅ | ❌ |
|
||||
| EmojiIcon | src/renderer/src/components/EmojiIcon.tsx | base | ✅ | ❌ |
|
||||
| IndicatorLight | src/renderer/src/components/IndicatorLight.tsx | base | ✅ | ❌ |
|
||||
| Spinner | src/renderer/src/components/Spinner.tsx | base | ✅ | ❌ |
|
||||
| TextBadge | src/renderer/src/components/TextBadge.tsx | base | ✅ | ❌ |
|
||||
| Ellipsis | src/renderer/src/components/Ellipsis/index.tsx | display | ✅ | ❌ |
|
||||
| ExpandableText | src/renderer/src/components/ExpandableText.tsx | display | ✅ | ❌ |
|
||||
| ThinkingEffect | src/renderer/src/components/ThinkingEffect.tsx | display | ✅ | ❌ |
|
||||
| HorizontalScrollContainer | src/renderer/src/components/HorizontalScrollContainer/index.tsx | layout | ✅ | ❌ |
|
||||
| Scrollbar | src/renderer/src/components/Scrollbar/index.tsx | layout | ✅ | ❌ |
|
||||
| VisionIcon | src/renderer/src/components/Icons/VisionIcon.tsx | icons | ✅ | ❌ |
|
||||
| WebSearchIcon | src/renderer/src/components/Icons/WebSearchIcon.tsx | icons | ✅ | ❌ |
|
||||
| ToolsCallingIcon | src/renderer/src/components/Icons/ToolsCallingIcon.tsx | icons | ✅ | ❌ |
|
||||
| FileIcons | src/renderer/src/components/Icons/FileIcons.tsx | icons | ✅ | ❌ |
|
||||
| SvgSpinners180Ring | src/renderer/src/components/Icons/SvgSpinners180Ring.tsx | icons | ✅ | ❌ |
|
||||
| ReasoningIcon | src/renderer/src/components/Icons/ReasoningIcon.tsx | icons | ✅ | ❌ |
|
||||
| InfoTooltip | src/renderer/src/components/TooltipIcons/InfoTooltip.tsx | interactive | ✅ | ❌ |
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### Phase 1: Copy Migration (Current Phase)
|
||||
|
||||
- Copy components as-is to @packages/ui
|
||||
- Retain original dependencies (antd, styled-components, etc.)
|
||||
- Add original path comment at file top
|
||||
|
||||
### Phase 2: Refactor and Optimize
|
||||
|
||||
- Remove antd dependencies, replace with HeroUI
|
||||
- Remove styled-components, replace with Tailwind CSS
|
||||
- Optimize component APIs and type definitions
|
||||
|
||||
## Notes
|
||||
|
||||
1. **Do NOT migrate** components with these dependencies:
|
||||
- window.api calls
|
||||
- Redux (useSelector, useDispatch, etc.)
|
||||
- Other external data sources
|
||||
|
||||
2. **Can migrate** but need decoupling later:
|
||||
- Components using i18n (change i18n to props)
|
||||
- Components using antd (replace with HeroUI later)
|
||||
|
||||
3. **Submission Guidelines**:
|
||||
- Each PR should focus on one category of components
|
||||
- Ensure all migrated components are exported
|
||||
- Update migration status in this document
|
||||
69
packages/ui/src/components/base/CopyButton/index.tsx
Normal file
69
packages/ui/src/components/base/CopyButton/index.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
// Original path: src/renderer/src/components/CopyButton.tsx
|
||||
import { Tooltip } from 'antd'
|
||||
import { Copy } from 'lucide-react'
|
||||
import { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface CopyButtonProps {
|
||||
tooltip?: string
|
||||
label?: string
|
||||
color?: string
|
||||
hoverColor?: string
|
||||
size?: number
|
||||
}
|
||||
|
||||
interface ButtonContainerProps {
|
||||
$color: string
|
||||
$hoverColor: string
|
||||
}
|
||||
|
||||
const CopyButton: FC<CopyButtonProps> = ({
|
||||
tooltip,
|
||||
label,
|
||||
color = 'var(--color-text-2)',
|
||||
hoverColor = 'var(--color-primary)',
|
||||
size = 14,
|
||||
...props
|
||||
}) => {
|
||||
const button = (
|
||||
<ButtonContainer $color={color} $hoverColor={hoverColor} {...props}>
|
||||
<Copy size={size} className="copy-icon" />
|
||||
{label && <RightText size={size}>{label}</RightText>}
|
||||
</ButtonContainer>
|
||||
)
|
||||
|
||||
if (tooltip) {
|
||||
return <Tooltip title={tooltip}>{button}</Tooltip>
|
||||
}
|
||||
|
||||
return button
|
||||
}
|
||||
|
||||
const ButtonContainer = styled.div<ButtonContainerProps>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
cursor: pointer;
|
||||
color: ${(props) => props.$color};
|
||||
transition: color 0.2s;
|
||||
|
||||
.copy-icon {
|
||||
color: ${(props) => props.$color};
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: ${(props) => props.$hoverColor};
|
||||
|
||||
.copy-icon {
|
||||
color: ${(props) => props.$hoverColor};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const RightText = styled.span<{ size: number }>`
|
||||
font-size: ${(props) => props.size}px;
|
||||
`
|
||||
|
||||
export default CopyButton
|
||||
49
packages/ui/src/components/base/EmojiIcon/index.tsx
Normal file
49
packages/ui/src/components/base/EmojiIcon/index.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
// Original path: src/renderer/src/components/EmojiIcon.tsx
|
||||
import { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface EmojiIconProps {
|
||||
emoji: string
|
||||
className?: string
|
||||
size?: number
|
||||
fontSize?: number
|
||||
}
|
||||
|
||||
const EmojiIcon: FC<EmojiIconProps> = ({ emoji, className, size = 26, fontSize = 15 }) => {
|
||||
return (
|
||||
<Container className={className} $size={size} $fontSize={fontSize}>
|
||||
<EmojiBackground>{emoji || '⭐️'}</EmojiBackground>
|
||||
{emoji}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div<{ $size: number; $fontSize: number }>`
|
||||
width: ${({ $size }) => $size}px;
|
||||
height: ${({ $size }) => $size}px;
|
||||
border-radius: ${({ $size }) => $size / 2}px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
font-size: ${({ $fontSize }) => $fontSize}px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin-right: 3px;
|
||||
`
|
||||
|
||||
const EmojiBackground = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 200%;
|
||||
transform: scale(1.5);
|
||||
filter: blur(5px);
|
||||
opacity: 0.4;
|
||||
`
|
||||
|
||||
export default EmojiIcon
|
||||
@ -0,0 +1,38 @@
|
||||
import type { Variants } from 'motion/react'
|
||||
export const lightbulbVariants: Variants = {
|
||||
active: {
|
||||
opacity: [1, 0.2, 1],
|
||||
transition: {
|
||||
duration: 1.2,
|
||||
ease: 'easeInOut',
|
||||
times: [0, 0.5, 1],
|
||||
repeat: Infinity
|
||||
}
|
||||
},
|
||||
idle: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 0.3,
|
||||
ease: 'easeInOut'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const lightbulbSoftVariants: Variants = {
|
||||
active: {
|
||||
opacity: [1, 0.5, 1],
|
||||
transition: {
|
||||
duration: 2,
|
||||
ease: 'easeInOut',
|
||||
times: [0, 0.5, 1],
|
||||
repeat: Infinity
|
||||
}
|
||||
},
|
||||
idle: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: 0.3,
|
||||
ease: 'easeInOut'
|
||||
}
|
||||
}
|
||||
}
|
||||
190
packages/ui/src/components/display/ThinkingEffect/index.tsx
Normal file
190
packages/ui/src/components/display/ThinkingEffect/index.tsx
Normal file
@ -0,0 +1,190 @@
|
||||
// Original path: src/renderer/src/components/ThinkingEffect.tsx
|
||||
import { isEqual } from 'lodash'
|
||||
import { ChevronRight, Lightbulb } from 'lucide-react'
|
||||
import { motion } from 'motion/react'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { lightbulbVariants } from './defaultVariants'
|
||||
|
||||
interface Props {
|
||||
isThinking: boolean
|
||||
thinkingTimeText: React.ReactNode
|
||||
content: string
|
||||
expanded: boolean
|
||||
}
|
||||
|
||||
const ThinkingEffect: React.FC<Props> = ({ isThinking, thinkingTimeText, content, expanded }) => {
|
||||
const [messages, setMessages] = useState<string[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
const allLines = (content || '').split('\n')
|
||||
const newMessages = isThinking ? allLines.slice(0, -1) : allLines
|
||||
const validMessages = newMessages.filter((line) => line.trim() !== '')
|
||||
|
||||
if (!isEqual(messages, validMessages)) {
|
||||
setMessages(validMessages)
|
||||
}
|
||||
}, [content, isThinking, messages])
|
||||
|
||||
const showThinking = useMemo(() => {
|
||||
return isThinking && !expanded
|
||||
}, [expanded, isThinking])
|
||||
|
||||
const LINE_HEIGHT = 14
|
||||
|
||||
const containerHeight = useMemo(() => {
|
||||
if (!showThinking || messages.length < 1) return 38
|
||||
return Math.min(75, Math.max(messages.length + 1, 2) * LINE_HEIGHT + 25)
|
||||
}, [showThinking, messages.length])
|
||||
|
||||
return (
|
||||
<ThinkingContainer style={{ height: containerHeight }} className={expanded ? 'expanded' : ''}>
|
||||
<LoadingContainer>
|
||||
<motion.div variants={lightbulbVariants} animate={isThinking ? 'active' : 'idle'} initial="idle">
|
||||
<Lightbulb
|
||||
size={!showThinking || messages.length < 2 ? 20 : 30}
|
||||
style={{ transition: 'width,height, 150ms' }}
|
||||
/>
|
||||
</motion.div>
|
||||
</LoadingContainer>
|
||||
|
||||
<TextContainer>
|
||||
<Title className={!showThinking || !messages.length ? 'showThinking' : ''}>{thinkingTimeText}</Title>
|
||||
|
||||
{showThinking && (
|
||||
<Content>
|
||||
<Messages
|
||||
style={{
|
||||
height: messages.length * LINE_HEIGHT
|
||||
}}
|
||||
initial={{
|
||||
y: -2
|
||||
}}
|
||||
animate={{
|
||||
y: -messages.length * LINE_HEIGHT - 2
|
||||
}}
|
||||
transition={{
|
||||
duration: 0.15,
|
||||
ease: 'linear'
|
||||
}}>
|
||||
{messages.map((message, index) => {
|
||||
if (index < messages.length - 5) return null
|
||||
|
||||
return <Message key={index}>{message}</Message>
|
||||
})}
|
||||
</Messages>
|
||||
</Content>
|
||||
)}
|
||||
</TextContainer>
|
||||
<ArrowContainer className={expanded ? 'expanded' : ''}>
|
||||
<ChevronRight size={20} color="var(--color-text-3)" strokeWidth={1} />
|
||||
</ArrowContainer>
|
||||
</ThinkingContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const ThinkingContainer = styled.div`
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 0.5px solid var(--color-border);
|
||||
transition: height, border-radius, 150ms;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
&.expanded {
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
`
|
||||
|
||||
const Title = styled.div`
|
||||
position: absolute;
|
||||
inset: 0 0 auto 0;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
font-weight: 500;
|
||||
padding: 10px 0;
|
||||
z-index: 99;
|
||||
transition: padding-top 150ms;
|
||||
&.showThinking {
|
||||
padding-top: 12px;
|
||||
}
|
||||
`
|
||||
|
||||
const LoadingContainer = styled.div`
|
||||
width: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
padding-left: 5px;
|
||||
transition: width 150ms;
|
||||
> div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
padding: 5px 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const Content = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
mask: linear-gradient(
|
||||
to bottom,
|
||||
rgb(0 0 0 / 0%) 0%,
|
||||
rgb(0 0 0 / 0%) 35%,
|
||||
rgb(0 0 0 / 25%) 40%,
|
||||
rgb(0 0 0 / 100%) 90%,
|
||||
rgb(0 0 0 / 100%) 100%
|
||||
);
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const Messages = styled(motion.div)`
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
`
|
||||
|
||||
const Message = styled.div`
|
||||
width: 100%;
|
||||
line-height: 14px;
|
||||
font-size: 11px;
|
||||
color: var(--color-text-2);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`
|
||||
|
||||
const ArrowContainer = styled.div`
|
||||
width: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
color: var(--color-border);
|
||||
transition: transform 150ms;
|
||||
&.expanded {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
`
|
||||
|
||||
export default ThinkingEffect
|
||||
71
packages/ui/src/components/icons/FileIcons/index.tsx
Normal file
71
packages/ui/src/components/icons/FileIcons/index.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
// Original path: src/renderer/src/components/Icons/FileIcons.tsx
|
||||
import { CSSProperties, SVGProps } from 'react'
|
||||
|
||||
interface BaseFileIconProps extends SVGProps<SVGSVGElement> {
|
||||
size?: string | number
|
||||
text?: string
|
||||
}
|
||||
|
||||
const textStyle: CSSProperties = {
|
||||
fontStyle: 'italic',
|
||||
fontSize: '7.70985px',
|
||||
lineHeight: 0.8,
|
||||
fontFamily: "'Times New Roman'",
|
||||
textAlign: 'center',
|
||||
writingMode: 'horizontal-tb',
|
||||
direction: 'ltr',
|
||||
textAnchor: 'middle',
|
||||
fill: 'none',
|
||||
stroke: '#000000',
|
||||
strokeWidth: '0.289119',
|
||||
strokeLinejoin: 'round',
|
||||
strokeDasharray: 'none'
|
||||
}
|
||||
|
||||
const tspanStyle: CSSProperties = {
|
||||
fontStyle: 'normal',
|
||||
fontVariant: 'normal',
|
||||
fontWeight: 'normal',
|
||||
fontStretch: 'condensed',
|
||||
fontSize: '7.70985px',
|
||||
lineHeight: 0.8,
|
||||
fontFamily: 'Arial',
|
||||
fill: '#000000',
|
||||
fillOpacity: 1,
|
||||
strokeWidth: '0.289119',
|
||||
strokeDasharray: 'none'
|
||||
}
|
||||
|
||||
const BaseFileIcon = ({ size = '1.1em', text = 'SVG', ...props }: BaseFileIconProps) => (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}>
|
||||
<defs id="defs4" />
|
||||
<path d="m 14,2 v 4 a 2,2 0 0 0 2,2 h 4" id="path3" />
|
||||
<path d="M 15,2 H 6 A 2,2 0 0 0 4,4 v 16 a 2,2 0 0 0 2,2 h 12 a 2,2 0 0 0 2,-2 V 7 Z" id="path4" />
|
||||
<text
|
||||
xmlSpace="preserve"
|
||||
style={textStyle}
|
||||
x="12.478625"
|
||||
y="17.170216"
|
||||
id="text4"
|
||||
transform="scale(0.96196394,1.03954)">
|
||||
<tspan id="tspan4" x="12.478625" y="17.170216" style={tspanStyle}>
|
||||
{text}
|
||||
</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const FileSvgIcon = (props: Omit<BaseFileIconProps, 'text'>) => <BaseFileIcon text="SVG" {...props} />
|
||||
export const FilePngIcon = (props: Omit<BaseFileIconProps, 'text'>) => <BaseFileIcon text="PNG" {...props} />
|
||||
31
packages/ui/src/components/icons/ReasoningIcon/index.tsx
Normal file
31
packages/ui/src/components/icons/ReasoningIcon/index.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
// Original path: src/renderer/src/components/Icons/ReasoningIcon.tsx
|
||||
import { Tooltip } from 'antd'
|
||||
import React, { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const ReasoningIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Tooltip title={t('models.type.reasoning')} placement="top">
|
||||
<Icon className="iconfont icon-thinking" {...(props as any)} />
|
||||
</Tooltip>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Icon = styled.i`
|
||||
color: var(--color-link);
|
||||
font-size: 16px;
|
||||
margin-right: 6px;
|
||||
`
|
||||
|
||||
export default ReasoningIcon
|
||||
@ -0,0 +1,22 @@
|
||||
// Original path: src/renderer/src/components/Icons/SvgSpinners180Ring.tsx
|
||||
import { SVGProps } from 'react'
|
||||
|
||||
export function SvgSpinners180Ring(props: SVGProps<SVGSVGElement> & { size?: number | string }) {
|
||||
const { size = '1em', ...svgProps } = props
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
{...svgProps}
|
||||
className={`animation-rotate ${svgProps.className || ''}`.trim()}>
|
||||
{/* Icon from SVG Spinners by Utkarsh Verma - https://github.com/n3r4zzurr0/svg-spinners/blob/main/LICENSE */}
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
export default SvgSpinners180Ring
|
||||
@ -1,5 +1,7 @@
|
||||
// Base Components
|
||||
export { default as CopyButton } from './base/CopyButton'
|
||||
export { default as DividerWithText } from './base/DividerWithText'
|
||||
export { default as EmojiIcon } from './base/EmojiIcon'
|
||||
export { default as IndicatorLight } from './base/IndicatorLight'
|
||||
export { default as Spinner } from './base/Spinner'
|
||||
export { default as TextBadge } from './base/TextBadge'
|
||||
@ -7,15 +9,22 @@ export { default as TextBadge } from './base/TextBadge'
|
||||
// Display Components
|
||||
export { default as Ellipsis } from './display/Ellipsis'
|
||||
export { default as ExpandableText } from './display/ExpandableText'
|
||||
export { default as ThinkingEffect } from './display/ThinkingEffect'
|
||||
|
||||
// Layout Components
|
||||
export { default as HorizontalScrollContainer } from './layout/HorizontalScrollContainer'
|
||||
export { default as Scrollbar } from './layout/Scrollbar'
|
||||
|
||||
// Icon Components
|
||||
export { FilePngIcon, FileSvgIcon } from './icons/FileIcons'
|
||||
export { default as ReasoningIcon } from './icons/ReasoningIcon'
|
||||
export { default as SvgSpinners180Ring } from './icons/SvgSpinners180Ring'
|
||||
export { default as ToolsCallingIcon } from './icons/ToolsCallingIcon'
|
||||
export { default as VisionIcon } from './icons/VisionIcon'
|
||||
export { default as WebSearchIcon } from './icons/WebSearchIcon'
|
||||
|
||||
// Interactive Components
|
||||
export { default as InfoTooltip } from './interactive/InfoTooltip'
|
||||
|
||||
// Composite Components (复合组件)
|
||||
// 暂无复合组件
|
||||
|
||||
@ -18,4 +18,4 @@ const InfoTooltip = ({ iconColor = 'var(--color-text-2)', iconSize = 14, iconSty
|
||||
)
|
||||
}
|
||||
|
||||
export default InfoTooltip
|
||||
export default InfoTooltip
|
||||
@ -3,7 +3,7 @@ import { ChevronRight } from 'lucide-react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Scrollbar from './Scrollbar'
|
||||
import Scrollbar from '../Scrollbar'
|
||||
|
||||
/**
|
||||
* 水平滚动容器
|
||||
Loading…
Reference in New Issue
Block a user