refactor: migrate Flex from antd to custom Flex component (#10083)

* refactor(components): rename HStack and VStack to RowFlex and ColFlex for clarity

rename HStack to RowFlex and VStack to ColFlex across all components to better reflect their purpose as flex containers with row and column directions. This improves code readability and maintainability while keeping the same functionality. All references to these components have been updated accordingly.

* refactor(layout): migrate layout components from .ts to .tsx

The layout components have been moved from TypeScript (.ts) to TypeScript with JSX (.tsx) to better support JSX syntax and improve type safety. The functionality remains unchanged.

* refactor(Layout): convert styled Box component to functional component

Improve maintainability by converting styled-component to a functional component with explicit style props. This provides better type safety through CSSProperties interface and makes the component easier to debug.

* refactor(Layout): restructure Box component and convert styled components to functional

- Replace styled-components with functional components for Stack and Center
- Rename style variable to _style in Box component to avoid naming conflict
- Add style prop to Box component to allow external style overrides

* refactor(components): rename HSpaceBetweenStack to SpaceBetweenRowFlex for clarity

* refactor(Layout): pass through props in Stack components

Allow additional props to be passed to Stack and its variants for better flexibility. Convert RowFlex from styled component to regular component for consistency.

* refactor(Layout): convert SpaceBetweenRowFlex from styled to component

Improve maintainability by converting styled component to a regular component that explicitly passes justifyContent prop

* refactor(Layout): convert ColFlex to component and type RowFlex props

Improve type safety by explicitly omitting flexDirection from StackProps and convert ColFlex from styled component to regular component for consistency

* refactor(Layout): convert BaseTypography from styled to component

Improve type safety and maintainability by converting styled component to regular React component with TypeScript interface

* refactor(Layout): remove unused BaseTypography component

* refactor(Layout): remove unused Container component and interface

* refactor(layout): rename Stack to Flex and use CSSProperties types

The Stack component was renamed to Flex to better reflect its purpose and align with common naming conventions. The interface properties were also updated to use CSSProperties types for better type safety and consistency with CSS standards.

* refactor(Layout): move FlexProps interface and comment out unused ButtonProps

Clean up component interfaces by moving FlexProps closer to its usage and commenting out unused ButtonProps interface to reduce clutter

* refactor(layout): standardize flex props from alignItems/justifyContent to align/justify

The changes standardize the flex-related props in the Layout component and across multiple files from using alignItems/justifyContent to the shorter align/justify. This improves consistency and reduces verbosity in the codebase while maintaining the same functionality.

All instances of alignItems have been replaced with align and justifyContent with justify in Flex, RowFlex, ColFlex and related components. The changes are purely syntactic and do not affect the actual layout behavior.

This refactoring makes the code more maintainable by using a consistent naming convention for flex properties throughout the application.

* refactor(Layout): extend BoxProps with React div props

* feat(Layout): add flexWrap prop to Flex component interface

Add flexWrap property to FlexProps interface to support CSS flex-wrap functionality. Also replace antd Flex with custom Flex component in TagFilterSection.

* refactor(components): replace antd Flex with custom Layout components

Consolidate Flex component usage across multiple files by replacing antd's Flex with custom Layout components (Flex, ColFlex, RowFlex) for better maintainability and consistency

* refactor(components):  migrate antd Flex tu custom Flex

* refactor(components): update layout component usage for consistency

replace RowFlex with ColFlex where appropriate and align prop names

* refactor(tests): rename HStack to RowFlex in test components

Update test snapshots and mock components to reflect the component name change from HStack to RowFlex
Remove unused data-vertical attribute from preview container

* refactor(Layout): pass through props and merge styles in Box and Flex components

Improve component flexibility by allowing additional props to pass through and properly merging style objects in both Box and Flex components

* refactor(Layout): make Flex component props optional with undefined defaults

* test: update TagFilterSection snapshot to include wrap style

* perf(Layout): optimize Box component style calculation with useMemo

* docs: fix typo in Layout component comment

* refactor(Layout): update BoxProps to use CSSProperties types

Standardize prop types by using CSSProperties for style-related props to improve type safety and consistency with React's style system

* feat(Layout): add wrap prop to Flex component

* style(TagFilterSection): update snapshot styling to use flex-wrap property

* refactor(Layout): simplify layout components by using Tailwind CSS classes

Remove custom style calculations and props in favor of Tailwind utility classes

* refactor: replace inline styles with Tailwind CSS classes for consistent styling

style: update spacing and alignment utilities across components

* refactor(tests): update test snapshots to use tailwind classes

Replace inline styles with tailwind classes in test snapshots and update test assertions to match. This improves consistency with the codebase's styling approach.

* style: adjust spacing and gaps in UI components for consistency

* style: replace inline styles with tailwind classes for consistency

Refactor various components to use tailwind classes instead of inline styles to maintain consistency and improve readability. Changes include:
- Replacing style attributes with tailwind classes for spacing, margins, and padding
- Standardizing gap sizes across components
- Using tailwind for width, height, and other layout properties
- Updating test files to match new class names

* style(settings): replace inline styles with tailwind classes for consistency

Refactor settings components to use tailwind gap utility instead of inline styles for better maintainability and consistency across the codebase

* refactor(styles): replace inline styles with tailwind classes for consistency

* feat(eslint): add rule to restrict antd Flex imports

Enforce using custom Layout components instead of antd's Flex by adding a restricted import rule

* refactor: migrate flex layout from antd props to tailwind classes

- Replace antd Flex component props with tailwind classes for consistency
- Update gap and alignment values to use tailwind's spacing scale
- Remove unused antd Flex imports to clean up dependencies

* style(settings): adjust spacing and layout in various settings components

- Add gap spacing between elements in multiple settings components
- Remove redundant flex class in some components
- Standardize gap sizes across related components

* refactor(ui): replace inline styles with tailwind classes for consistency

Replace various inline style attributes with equivalent tailwind classes across multiple components to maintain consistent styling approach. Changes include margin, padding, width, and flex properties.

- Convert style attributes to tailwind classes
- Standardize spacing values using tailwind's spacing scale
- Improve maintainability by using utility classes

* style(ui): adjust spacing in model list group header

* style(css): wrap base styles in @layer for better organization

* style(css): fix indentation and nesting in global styles

* style(settings): adjust spacing in model list and convert subtitle to cn

Refactor SettingSubtitle to use cn utility for better className handling
Add gap spacing between model list items for improved layout

* style(css): move some styles from base layer to outer in index.css

* refactor(components): replace HStack with RowFlex for consistency

Update layout components to use RowFlex instead of HStack to maintain consistent naming

* style: reorder imports in useAppInit and BaseApiClient files

* fix(MinAppTabsPool): wrong import path

* refactor: update style file extensions from scss to css

Update file extensions and comments to reflect the change from SCSS to CSS stylesheets

* feat(layout): add Flex component and its variants

Introduce new Flex component with Box, RowFlex, SpaceBetweenRowFlex, ColFlex and Center variants to provide reusable layout components

* refactor: migrate layout components to @cherrystudio/ui package

This commit updates all imports of layout components (Box, Flex, RowFlex, ColFlex, etc.) from '@renderer/components/Layout' to '@cherrystudio/ui' across the codebase. The change also includes updating related test files and eslint configuration to reflect this migration. This refactoring aims to centralize layout components in a shared package for better maintainability and consistency.

* docs(eslint): update comment and restricted imports rule

Update comment to clarify the purpose of the rule and add a TODO note for future migration

* docs(ui): update migration status for layout components

Mark Layout/* components as migrated and refactored in both Chinese and English documentation

* docs: update migration status for Layout components
This commit is contained in:
Phantom 2025-09-17 18:17:17 +08:00 committed by GitHub
parent aab941d89c
commit 4f746842a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
136 changed files with 924 additions and 968 deletions

View File

@ -110,6 +110,26 @@ export default defineConfig([
'i18n/no-template-in-t': 'warn'
}
},
{
// Component Rules - prevent importing antd components when migration completed
files: ['src/**/*.{ts,tsx,js,jsx}'],
rules: {
'no-restricted-imports': [
'error',
{
paths: [
{
name: 'antd',
// TODO: migrate message again
importNames: ['Flex'],
message:
'❌ Do not import Flex from antd. Use our custom Layout components instead: import { Flex } from "@cherrystudio/ui"'
}
]
}
]
}
},
{
ignores: [
'node_modules/**',

View File

@ -55,71 +55,71 @@ function MyComponent() {
## 组件状态表
| Category | Component Name | Migration Status | Refactoring Status | Description |
|----------|----------------|------------------|--------------------|-------------|
| **base** | | | | 基础组件 |
| | CopyButton | ✅ | ✅ | 复制按钮 |
| | CustomTag | ✅ | ✅ | 自定义标签 |
| | DividerWithText | ✅ | ✅ | 带文本的分隔线 |
| | EmojiIcon | ✅ | ✅ | 表情图标 |
| | ErrorBoundary | ✅ | ✅ | 错误边界 (通过 props 解耦) |
| | StatusTag | ✅ | ✅ | 统一状态标签(合并了 ErrorTag、SuccessTag、WarnTag、InfoTag|
| | IndicatorLight | ✅ | ✅ | 指示灯 |
| | Spinner | ✅ | ✅ | 加载动画 |
| | TextBadge | ✅ | ✅ | 文本徽标 |
| | CustomCollapse | ✅ | ✅ | 自定义折叠面板 |
| **display** | | | | 显示组件 |
| | Ellipsis | ✅ | ✅ | 文本省略 |
| | ExpandableText | ✅ | ✅ | 可展开文本 |
| | ThinkingEffect | ✅ | ✅ | 思考效果动画 |
| | EmojiAvatar | ✅ | ✅ | 表情头像 |
| | ListItem | ✅ | ✅ | 列表项 |
| | MaxContextCount | ✅ | ✅ | 最大上下文数显示 |
| | ProviderAvatar | ✅ | ✅ | 提供者头像 |
| | CodeViewer | ❌ | ❌ | 代码查看器 (外部依赖) |
| | OGCard | ❌ | ❌ | OG 卡片 |
| | MarkdownShadowDOMRenderer | ❌ | ❌ | Markdown 渲染器 |
| | Preview/* | ❌ | ❌ | 预览组件 |
| **layout** | | | | 布局组件 |
| | HorizontalScrollContainer | ✅ | ❌ | 水平滚动容器 |
| | Scrollbar | ✅ | ❌ | 滚动条 |
| | Layout/* | ❌ | ❌ | 布局组件 |
| | Tab/* | ❌ | ❌ | 标签页 (Redux 依赖) |
| | TopView | ❌ | ❌ | 顶部视图 (window.api 依赖) |
| **icons** | | | | 图标组件 |
| | Icon | ✅ | ✅ | 图标工厂函数和预定义图标(合并了 CopyIcon、DeleteIcon、EditIcon、RefreshIcon、ResetIcon、ToolIcon、VisionIcon、WebSearchIcon、WrapIcon、UnWrapIcon、OcrIcon|
| | FileIcons | ✅ | ❌ | 文件图标 (FileSvgIcon、FilePngIcon) |
| | ReasoningIcon | ✅ | ❌ | 推理图标 |
| | SvgSpinners180Ring | ✅ | ❌ | 旋转加载图标 |
| | ToolsCallingIcon | ✅ | ❌ | 工具调用图标 |
| **interactive** | | | | 交互组件 |
| | InfoTooltip | ✅ | ❌ | 信息提示 |
| | HelpTooltip | ✅ | ❌ | 帮助提示 |
| | WarnTooltip | ✅ | ❌ | 警告提示 |
| | EditableNumber | ✅ | ❌ | 可编辑数字 |
| | InfoPopover | ✅ | ❌ | 信息弹出框 |
| | CollapsibleSearchBar | ✅ | ❌ | 可折叠搜索栏 |
| | ImageToolButton | ✅ | ❌ | 图片工具按钮 |
| | DraggableList | ✅ | ❌ | 可拖拽列表 |
| | CodeEditor | ✅ | ❌ | 代码编辑器 |
| | EmojiPicker | ❌ | ❌ | 表情选择器 (useTheme 依赖) |
| | Selector | ✅ | ❌ | 选择器 (i18n 依赖) |
| | ModelSelector | ❌ | ❌ | 模型选择器 (Redux 依赖) |
| | LanguageSelect | ❌ | ❌ | 语言选择 |
| | TranslateButton | ❌ | ❌ | 翻译按钮 (window.api 依赖) |
| **composite** | | | | 复合组件 |
| | - | - | - | 暂无复合组件 |
| **未分类** | | | | 需要分类的组件 |
| | Popups/* (16+ 文件) | ❌ | ❌ | 弹窗组件 (业务耦合) |
| | RichEditor/* (30+ 文件) | ❌ | ❌ | 富文本编辑器 |
| | MarkdownEditor/* | ❌ | ❌ | Markdown 编辑器 |
| | MinApp/* | ❌ | ❌ | 迷你应用 (Redux 依赖) |
| | Avatar/* | ❌ | ❌ | 头像组件 |
| | ActionTools/* | ❌ | ❌ | 操作工具 |
| | CodeBlockView/* | ❌ | ❌ | 代码块视图 (window.api 依赖) |
| | ContextMenu | ❌ | ❌ | 右键菜单 (Electron API) |
| | WindowControls | ❌ | ❌ | 窗口控制 (Electron API) |
| | ErrorBoundary | ❌ | ❌ | 错误边界 (window.api 依赖) |
| Category | Component Name | Migration Status | Refactoring Status | Description |
| --------------- | ------------------------- | ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **base** | | | | 基础组件 |
| | CopyButton | ✅ | ✅ | 复制按钮 |
| | CustomTag | ✅ | ✅ | 自定义标签 |
| | DividerWithText | ✅ | ✅ | 带文本的分隔线 |
| | EmojiIcon | ✅ | ✅ | 表情图标 |
| | ErrorBoundary | ✅ | ✅ | 错误边界 (通过 props 解耦) |
| | StatusTag | ✅ | ✅ | 统一状态标签(合并了 ErrorTag、SuccessTag、WarnTag、InfoTag |
| | IndicatorLight | ✅ | ✅ | 指示灯 |
| | Spinner | ✅ | ✅ | 加载动画 |
| | TextBadge | ✅ | ✅ | 文本徽标 |
| | CustomCollapse | ✅ | ✅ | 自定义折叠面板 |
| **display** | | | | 显示组件 |
| | Ellipsis | ✅ | ✅ | 文本省略 |
| | ExpandableText | ✅ | ✅ | 可展开文本 |
| | ThinkingEffect | ✅ | ✅ | 思考效果动画 |
| | EmojiAvatar | ✅ | ✅ | 表情头像 |
| | ListItem | ✅ | ✅ | 列表项 |
| | MaxContextCount | ✅ | ✅ | 最大上下文数显示 |
| | ProviderAvatar | ✅ | ✅ | 提供者头像 |
| | CodeViewer | ❌ | ❌ | 代码查看器 (外部依赖) |
| | OGCard | ❌ | ❌ | OG 卡片 |
| | MarkdownShadowDOMRenderer | ❌ | ❌ | Markdown 渲染器 |
| | Preview/* | ❌ | ❌ | 预览组件 |
| **layout** | | | | 布局组件 |
| | HorizontalScrollContainer | ✅ | ❌ | 水平滚动容器 |
| | Scrollbar | ✅ | ❌ | 滚动条 |
| | Layout/* | ✅ | ✅ | 布局组件 |
| | Tab/* | ❌ | ❌ | 标签页 (Redux 依赖) |
| | TopView | ❌ | ❌ | 顶部视图 (window.api 依赖) |
| **icons** | | | | 图标组件 |
| | Icon | ✅ | ✅ | 图标工厂函数和预定义图标(合并了 CopyIcon、DeleteIcon、EditIcon、RefreshIcon、ResetIcon、ToolIcon、VisionIcon、WebSearchIcon、WrapIcon、UnWrapIcon、OcrIcon |
| | FileIcons | ✅ | ❌ | 文件图标 (FileSvgIcon、FilePngIcon) |
| | ReasoningIcon | ✅ | ❌ | 推理图标 |
| | SvgSpinners180Ring | ✅ | ❌ | 旋转加载图标 |
| | ToolsCallingIcon | ✅ | ❌ | 工具调用图标 |
| **interactive** | | | | 交互组件 |
| | InfoTooltip | ✅ | ❌ | 信息提示 |
| | HelpTooltip | ✅ | ❌ | 帮助提示 |
| | WarnTooltip | ✅ | ❌ | 警告提示 |
| | EditableNumber | ✅ | ❌ | 可编辑数字 |
| | InfoPopover | ✅ | ❌ | 信息弹出框 |
| | CollapsibleSearchBar | ✅ | ❌ | 可折叠搜索栏 |
| | ImageToolButton | ✅ | ❌ | 图片工具按钮 |
| | DraggableList | ✅ | ❌ | 可拖拽列表 |
| | CodeEditor | ✅ | ❌ | 代码编辑器 |
| | EmojiPicker | ❌ | ❌ | 表情选择器 (useTheme 依赖) |
| | Selector | ✅ | ❌ | 选择器 (i18n 依赖) |
| | ModelSelector | ❌ | ❌ | 模型选择器 (Redux 依赖) |
| | LanguageSelect | ❌ | ❌ | 语言选择 |
| | TranslateButton | ❌ | ❌ | 翻译按钮 (window.api 依赖) |
| **composite** | | | | 复合组件 |
| | - | - | - | 暂无复合组件 |
| **未分类** | | | | 需要分类的组件 |
| | Popups/* (16+ 文件) | ❌ | ❌ | 弹窗组件 (业务耦合) |
| | RichEditor/* (30+ 文件) | ❌ | ❌ | 富文本编辑器 |
| | MarkdownEditor/* | ❌ | ❌ | Markdown 编辑器 |
| | MinApp/* | ❌ | ❌ | 迷你应用 (Redux 依赖) |
| | Avatar/* | ❌ | ❌ | 头像组件 |
| | ActionTools/* | ❌ | ❌ | 操作工具 |
| | CodeBlockView/* | ❌ | ❌ | 代码块视图 (window.api 依赖) |
| | ContextMenu | ❌ | ❌ | 右键菜单 (Electron API) |
| | WindowControls | ❌ | ❌ | 窗口控制 (Electron API) |
| | ErrorBoundary | ❌ | ❌ | 错误边界 (window.api 依赖) |
## 迁移步骤

View File

@ -54,71 +54,71 @@ When submitting PRs, please place components in the correct directory based on t
## Component Status Table
| Category | Component Name | Migration Status | Refactoring Status | Description |
|----------|----------------|------------------|--------------------|-------------|
| **base** | | | | Base components |
| | CopyButton | ✅ | ✅ | Copy button |
| | CustomTag | ✅ | ✅ | Custom tag |
| | DividerWithText | ✅ | ✅ | Divider with text |
| | EmojiIcon | ✅ | ✅ | Emoji icon |
| | ErrorBoundary | ✅ | ✅ | Error boundary (decoupled via props) |
| | StatusTag | ✅ | ✅ | Unified status tag (merged ErrorTag, SuccessTag, WarnTag, InfoTag) |
| | IndicatorLight | ✅ | ✅ | Indicator light |
| | Spinner | ✅ | ✅ | Loading spinner |
| | TextBadge | ✅ | ✅ | Text badge |
| | CustomCollapse | ✅ | ✅ | Custom collapse panel |
| **display** | | | | Display components |
| | Ellipsis | ✅ | ✅ | Text ellipsis |
| | ExpandableText | ✅ | ✅ | Expandable text |
| | ThinkingEffect | ✅ | ✅ | Thinking effect animation |
| | EmojiAvatar | ✅ | ✅ | Emoji avatar |
| | ListItem | ✅ | ✅ | List item |
| | MaxContextCount | ✅ | ✅ | Max context count display |
| | ProviderAvatar | ✅ | ✅ | Provider avatar |
| | CodeViewer | ❌ | ❌ | Code viewer (external deps) |
| | OGCard | ❌ | ❌ | OG card |
| | MarkdownShadowDOMRenderer | ❌ | ❌ | Markdown renderer |
| | Preview/* | ❌ | ❌ | Preview components |
| **layout** | | | | Layout components |
| | HorizontalScrollContainer | ✅ | ❌ | Horizontal scroll container |
| | Scrollbar | ✅ | ❌ | Scrollbar |
| | Layout/* | ❌ | ❌ | Layout components |
| | Tab/* | ❌ | ❌ | Tab (Redux dependency) |
| | TopView | ❌ | ❌ | Top view (window.api dependency) |
| **icons** | | | | Icon components |
| | Icon | ✅ | ✅ | Icon factory function and predefined icons (merged CopyIcon, DeleteIcon, EditIcon, RefreshIcon, ResetIcon, ToolIcon, VisionIcon, WebSearchIcon, WrapIcon, UnWrapIcon, OcrIcon) |
| | FileIcons | ✅ | ❌ | File icons (FileSvgIcon, FilePngIcon) |
| | ReasoningIcon | ✅ | ❌ | Reasoning icon |
| | SvgSpinners180Ring | ✅ | ❌ | Spinner loading icon |
| | ToolsCallingIcon | ✅ | ❌ | Tools calling icon |
| **interactive** | | | | Interactive components |
| | InfoTooltip | ✅ | ❌ | Info tooltip |
| | HelpTooltip | ✅ | ❌ | Help tooltip |
| | WarnTooltip | ✅ | ❌ | Warning tooltip |
| | EditableNumber | ✅ | ❌ | Editable number |
| | InfoPopover | ✅ | ❌ | Info popover |
| | CollapsibleSearchBar | ✅ | ❌ | Collapsible search bar |
| | ImageToolButton | ✅ | ❌ | Image tool button |
| | DraggableList | ✅ | ❌ | Draggable list |
| | CodeEditor | ✅ | ❌ | Code editor |
| | EmojiPicker | ❌ | ❌ | Emoji picker (useTheme dependency) |
| | Selector | ✅ | ❌ | Selector (i18n dependency) |
| | ModelSelector | ❌ | ❌ | Model selector (Redux dependency) |
| | LanguageSelect | ❌ | ❌ | Language select |
| | TranslateButton | ❌ | ❌ | Translate button (window.api dependency) |
| **composite** | | | | Composite components |
| | - | - | - | No composite components yet |
| **Uncategorized** | | | | Components needing categorization |
| | Popups/* (16+ files) | ❌ | ❌ | Popup components (business coupled) |
| | RichEditor/* (30+ files) | ❌ | ❌ | Rich text editor |
| | MarkdownEditor/* | ❌ | ❌ | Markdown editor |
| | MinApp/* | ❌ | ❌ | Mini app (Redux dependency) |
| | Avatar/* | ❌ | ❌ | Avatar components |
| | ActionTools/* | ❌ | ❌ | Action tools |
| | CodeBlockView/* | ❌ | ❌ | Code block view (window.api dependency) |
| | ContextMenu | ❌ | ❌ | Context menu (Electron API) |
| | WindowControls | ❌ | ❌ | Window controls (Electron API) |
| | ErrorBoundary | ❌ | ❌ | Error boundary (window.api dependency) |
| Category | Component Name | Migration Status | Refactoring Status | Description |
| ----------------- | ------------------------- | ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **base** | | | | Base components |
| | CopyButton | ✅ | ✅ | Copy button |
| | CustomTag | ✅ | ✅ | Custom tag |
| | DividerWithText | ✅ | ✅ | Divider with text |
| | EmojiIcon | ✅ | ✅ | Emoji icon |
| | ErrorBoundary | ✅ | ✅ | Error boundary (decoupled via props) |
| | StatusTag | ✅ | ✅ | Unified status tag (merged ErrorTag, SuccessTag, WarnTag, InfoTag) |
| | IndicatorLight | ✅ | ✅ | Indicator light |
| | Spinner | ✅ | ✅ | Loading spinner |
| | TextBadge | ✅ | ✅ | Text badge |
| | CustomCollapse | ✅ | ✅ | Custom collapse panel |
| **display** | | | | Display components |
| | Ellipsis | ✅ | ✅ | Text ellipsis |
| | ExpandableText | ✅ | ✅ | Expandable text |
| | ThinkingEffect | ✅ | ✅ | Thinking effect animation |
| | EmojiAvatar | ✅ | ✅ | Emoji avatar |
| | ListItem | ✅ | ✅ | List item |
| | MaxContextCount | ✅ | ✅ | Max context count display |
| | ProviderAvatar | ✅ | ✅ | Provider avatar |
| | CodeViewer | ❌ | ❌ | Code viewer (external deps) |
| | OGCard | ❌ | ❌ | OG card |
| | MarkdownShadowDOMRenderer | ❌ | ❌ | Markdown renderer |
| | Preview/* | ❌ | ❌ | Preview components |
| **layout** | | | | Layout components |
| | HorizontalScrollContainer | ✅ | ❌ | Horizontal scroll container |
| | Scrollbar | ✅ | ❌ | Scrollbar |
| | Layout/* | ✅ | ✅ | Layout components |
| | Tab/* | ❌ | ❌ | Tab (Redux dependency) |
| | TopView | ❌ | ❌ | Top view (window.api dependency) |
| **icons** | | | | Icon components |
| | Icon | ✅ | ✅ | Icon factory function and predefined icons (merged CopyIcon, DeleteIcon, EditIcon, RefreshIcon, ResetIcon, ToolIcon, VisionIcon, WebSearchIcon, WrapIcon, UnWrapIcon, OcrIcon) |
| | FileIcons | ✅ | ❌ | File icons (FileSvgIcon, FilePngIcon) |
| | ReasoningIcon | ✅ | ❌ | Reasoning icon |
| | SvgSpinners180Ring | ✅ | ❌ | Spinner loading icon |
| | ToolsCallingIcon | ✅ | ❌ | Tools calling icon |
| **interactive** | | | | Interactive components |
| | InfoTooltip | ✅ | ❌ | Info tooltip |
| | HelpTooltip | ✅ | ❌ | Help tooltip |
| | WarnTooltip | ✅ | ❌ | Warning tooltip |
| | EditableNumber | ✅ | ❌ | Editable number |
| | InfoPopover | ✅ | ❌ | Info popover |
| | CollapsibleSearchBar | ✅ | ❌ | Collapsible search bar |
| | ImageToolButton | ✅ | ❌ | Image tool button |
| | DraggableList | ✅ | ❌ | Draggable list |
| | CodeEditor | ✅ | ❌ | Code editor |
| | EmojiPicker | ❌ | ❌ | Emoji picker (useTheme dependency) |
| | Selector | ✅ | ❌ | Selector (i18n dependency) |
| | ModelSelector | ❌ | ❌ | Model selector (Redux dependency) |
| | LanguageSelect | ❌ | ❌ | Language select |
| | TranslateButton | ❌ | ❌ | Translate button (window.api dependency) |
| **composite** | | | | Composite components |
| | - | - | - | No composite components yet |
| **Uncategorized** | | | | Components needing categorization |
| | Popups/* (16+ files) | ❌ | ❌ | Popup components (business coupled) |
| | RichEditor/* (30+ files) | ❌ | ❌ | Rich text editor |
| | MarkdownEditor/* | ❌ | ❌ | Markdown editor |
| | MinApp/* | ❌ | ❌ | Mini app (Redux dependency) |
| | Avatar/* | ❌ | ❌ | Avatar components |
| | ActionTools/* | ❌ | ❌ | Action tools |
| | CodeBlockView/* | ❌ | ❌ | Code block view (window.api dependency) |
| | ContextMenu | ❌ | ❌ | Context menu (Electron API) |
| | WindowControls | ❌ | ❌ | Window controls (Electron API) |
| | ErrorBoundary | ❌ | ❌ | Error boundary (window.api dependency) |
## Migration Steps

View File

@ -22,6 +22,7 @@ export { ProviderAvatar } from './display/ProviderAvatar'
export { default as ThinkingEffect } from './display/ThinkingEffect'
// Layout Components
export { Box, Center, ColFlex, Flex, RowFlex, SpaceBetweenRowFlex } from './layout/Flex'
export { default as HorizontalScrollContainer } from './layout/HorizontalScrollContainer'
export { default as Scrollbar } from './layout/Scrollbar'

View File

@ -0,0 +1,63 @@
import React from 'react'
import { cn } from '../../../utils'
export interface BoxProps extends React.ComponentProps<'div'> {}
export const Box = ({ children, className, ...props }: BoxProps & { children?: React.ReactNode }) => {
return (
<div className={cn('box-border', className)} {...props}>
{children}
</div>
)
}
export interface FlexProps extends BoxProps {}
export const Flex = ({ children, className, ...props }: FlexProps & { children?: React.ReactNode }) => {
return (
<Box className={cn('flex', className)} {...props}>
{children}
</Box>
)
}
export const RowFlex = ({ children, className, ...props }: FlexProps & { children?: React.ReactNode }) => {
return (
<Flex className={cn('flex-row', className)} {...props}>
{children}
</Flex>
)
}
export const SpaceBetweenRowFlex = ({ children, className, ...props }: FlexProps & { children?: React.ReactNode }) => {
return (
<RowFlex className={cn('justify-between', className)} {...props}>
{children}
</RowFlex>
)
}
export const ColFlex = ({ children, className, ...props }: FlexProps & { children?: React.ReactNode }) => {
return (
<Flex className={cn('flex-col', className)} {...props}>
{children}
</Flex>
)
}
export const Center = ({ children, className, ...props }: FlexProps & { children?: React.ReactNode }) => {
return (
<Flex className={cn('items-center justify-center', className)} {...props}>
{children}
</Flex>
)
}
export default {
Box,
Flex,
RowFlex,
SpaceBetweenRowFlex,
ColFlex,
Center
}

View File

@ -11,18 +11,19 @@
@import '../fonts/ubuntu/ubuntu.css';
@import '../fonts/country-flag-fonts/flag.css';
*,
*::before,
*::after {
box-sizing: border-box;
/* margin: 0; */
font-weight: normal;
@layer base {
*,
*::before,
*::after {
box-sizing: border-box;
/* margin: 0; */
font-weight: normal;
}
}
*:focus {
outline: none;
}
* {
-webkit-tap-highlight-color: transparent;
}

View File

@ -1,4 +1,4 @@
import { Flex } from 'antd'
import { Flex } from '@cherrystudio/ui'
import type { FC, ReactNode } from 'react'
import { memo } from 'react'
import styled from 'styled-components'

View File

@ -19,7 +19,7 @@ const mocks = vi.hoisted(() => ({
{children}
</div>
)),
HStack: vi.fn(({ children, className }) => (
RowFlex: vi.fn(({ children, className }) => (
<div data-testid="hstack" className={className}>
{children}
</div>
@ -43,8 +43,8 @@ vi.mock('antd', () => ({
Tooltip: mocks.Tooltip
}))
vi.mock('@renderer/components/Layout', () => ({
HStack: mocks.HStack
vi.mock('@cherrystudio/ui', () => ({
RowFlex: mocks.RowFlex
}))
vi.mock('./styles', () => ({

View File

@ -1,5 +1,5 @@
import { RowFlex } from '@cherrystudio/ui'
import type { ActionTool } from '@renderer/components/ActionTools'
import { HStack } from '@renderer/components/Layout'
import { Tooltip } from 'antd'
import { EllipsisVertical } from 'lucide-react'
import { memo, useMemo, useState } from 'react'
@ -61,7 +61,7 @@ const StickyWrapper = styled.div`
z-index: 10;
`
const ToolbarWrapper = styled(HStack)`
const ToolbarWrapper = styled(RowFlex)`
position: absolute;
align-items: center;
bottom: 0.3rem;

View File

@ -1,5 +1,6 @@
import { CheckCircleFilled, CloseCircleFilled, ExclamationCircleFilled, LoadingOutlined } from '@ant-design/icons'
import { Flex, Tooltip, Typography } from 'antd'
import { Flex } from '@cherrystudio/ui'
import { Tooltip, Typography } from 'antd'
import React, { memo } from 'react'
import styled from 'styled-components'
@ -48,7 +49,7 @@ const HealthStatusIndicator: React.FC<HealthStatusIndicatorProps> = ({
}
return (
<Flex align="center" gap={6}>
<Flex className="items-center gap-1.5">
{latencyText && <LatencyText type="secondary">{latencyText}</LatencyText>}
<Tooltip title={tooltip} styles={{ body: { userSelect: 'text' } }}>
<IndicatorWrapper $type={overallStatus}>{icon}</IndicatorWrapper>

View File

@ -1,5 +1,5 @@
import { Flex } from '@cherrystudio/ui'
import { HealthStatus } from '@renderer/types/healthCheck'
import { Flex } from 'antd'
import React from 'react'
import { useTranslation } from 'react-i18next'
@ -77,7 +77,7 @@ export const useHealthStatus = ({ results, showLatency = false }: UseHealthStatu
return (
<li key={idx} style={{ marginBottom: idx === results.length - 1 ? 0 : '10px' }}>
<Flex align="center" justify="space-between">
<Flex className="items-center justify-between">
<strong style={{ color: statusColor }}>{statusText}</strong>
{result.label}
</Flex>

View File

@ -1,159 +0,0 @@
import styled from 'styled-components'
interface ContainerProps {
padding?: string
}
type PxValue = number | string
export interface BoxProps {
width?: PxValue
height?: PxValue
w?: PxValue
h?: PxValue
color?: string
background?: string
flex?: string | number
position?: string
left?: PxValue
top?: PxValue
right?: PxValue
bottom?: PxValue
opacity?: string | number
borderRadius?: PxValue
border?: string
gap?: PxValue
mt?: PxValue
marginTop?: PxValue
mb?: PxValue
marginBottom?: PxValue
ml?: PxValue
marginLeft?: PxValue
mr?: PxValue
marginRight?: PxValue
m?: string
margin?: string
pt?: PxValue
paddingTop?: PxValue
pb?: PxValue
paddingBottom?: PxValue
pl?: PxValue
paddingLeft?: PxValue
pr?: PxValue
paddingRight?: PxValue
p?: string
padding?: string
}
export interface StackProps extends BoxProps {
justifyContent?: 'center' | 'flex-start' | 'flex-end' | 'space-between'
alignItems?: 'center' | 'flex-start' | 'flex-end' | 'space-between'
flexDirection?: 'row' | 'row-reverse' | 'column' | 'column-reverse'
}
export interface ButtonProps extends StackProps {
color?: string
isDisabled?: boolean
isLoading?: boolean
background?: string
border?: string
fontSize?: string
}
const cssRegex = /(px|vw|vh|%|auto)$/g
const getElementValue = (value?: PxValue) => {
if (!value) {
return value
}
if (typeof value === 'number') {
return value + 'px'
}
if (value.match(cssRegex)) {
return value
}
return value + 'px'
}
export const Box = styled.div<BoxProps>`
width: ${(props) => (props.width || props.w ? getElementValue(props.width ?? props.w) : 'auto')};
height: ${(props) => (props.height || props.h ? getElementValue(props.height || props.h) : 'auto')};
color: ${(props) => props.color || 'default'};
background: ${(props) => props.background || 'default'};
flex: ${(props) => props.flex || 'none'};
position: ${(props) => props.position || 'default'};
left: ${(props) => getElementValue(props.left) || 'auto'};
right: ${(props) => getElementValue(props.right) || 'auto'};
bottom: ${(props) => getElementValue(props.bottom) || 'auto'};
top: ${(props) => getElementValue(props.top) || 'auto'};
gap: ${(p) => (p.gap ? getElementValue(p.gap) : 0)};
opacity: ${(props) => props.opacity ?? 1};
border-radius: ${(props) => getElementValue(props.borderRadius) || 0};
box-sizing: border-box;
border: ${(props) => props?.border || 'none'};
gap: ${(p) => (p.gap ? getElementValue(p.gap) : 0)};
margin: ${(props) => (props.m || props.margin ? (props.m ?? props.margin) : 'none')};
margin-top: ${(props) => (props.mt || props.marginTop ? getElementValue(props.mt || props.marginTop) : 'default')};
margin-bottom: ${(props) =>
props.mb || props.marginBottom ? getElementValue(props.mb ?? props.marginBottom) : 'default'};
margin-left: ${(props) => (props.ml || props.marginLeft ? getElementValue(props.ml ?? props.marginLeft) : 'default')};
margin-right: ${(props) =>
props.mr || props.marginRight ? getElementValue(props.mr ?? props.marginRight) : 'default'};
padding: ${(props) => (props.p || props.padding ? (props.p ?? props.padding) : 'none')};
padding-top: ${(props) => (props.pt || props.paddingTop ? getElementValue(props.pt ?? props.paddingTop) : 'auto')};
padding-bottom: ${(props) =>
props.pb || props.paddingBottom ? getElementValue(props.pb ?? props.paddingBottom) : 'auto'};
padding-left: ${(props) => (props.pl || props.paddingLeft ? getElementValue(props.pl ?? props.paddingLeft) : 'auto')};
padding-right: ${(props) =>
props.pr || props.paddingRight ? getElementValue(props.pr ?? props.paddingRight) : 'auto'};
`
export const Stack = styled(Box)<StackProps>`
display: flex;
justify-content: ${(props) => props.justifyContent ?? 'flex-start'};
align-items: ${(props) => props.alignItems ?? 'flex-start'};
flex-direction: ${(props) => props.flexDirection ?? 'row'};
`
export const Center = styled(Stack)<StackProps>`
justify-content: center;
align-items: center;
`
export const HStack = styled(Stack)<StackProps>`
flex-direction: row;
`
export const HSpaceBetweenStack = styled(HStack)<StackProps>`
justify-content: space-between;
`
export const VStack = styled(Stack)<StackProps>`
flex-direction: column;
`
export const BaseTypography = styled(Box)<{
fontSize?: number
lineHeight?: string
fontWeigth?: number | string
color?: string
textAlign?: string
}>`
font-size: ${(props) => (props.fontSize ? getElementValue(props.fontSize) : '16px')};
line-height: ${(props) => (props.lineHeight ? getElementValue(props.lineHeight) : 'normal')};
font-weight: ${(props) => props.fontWeigth || 'normal'};
color: ${(props) => props.color || '#fff'};
text-align: ${(props) => props.textAlign || 'left'};
`
export const Container = styled.main<ContainerProps>`
display: flex;
flex-direction: column;
width: 100%;
box-sizing: border-box;
flex: 1;
padding: ${(p) => p.padding ?? '0 18px'};
`

View File

@ -1,3 +1,4 @@
import { RowFlex } from '@cherrystudio/ui'
import { loggerService } from '@logger'
import { FolderIcon as NutstoreFolderIcon } from '@renderer/components/Icons/NutstoreIcons'
import { Button, Input } from 'antd'
@ -5,8 +6,6 @@ import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { HStack } from './Layout'
interface NewFolderProps {
onConfirm: (name: string) => void
onCancel: () => void
@ -215,7 +214,7 @@ export function NutstorePathSelector(props: Props) {
)
}
const FooterContainer = styled(HStack)`
const FooterContainer = styled(RowFlex)`
background: transparent;
margin-top: 12px;
padding: 0;
@ -233,21 +232,21 @@ interface FooterProps {
export function NustorePathSelectorFooter(props: FooterProps) {
const { t } = useTranslation()
return (
<FooterContainer justifyContent="space-between">
<HStack gap={8} alignItems="center">
<FooterContainer className="justify-between">
<RowFlex className="items-center gap-2">
<Button onClick={props.returnPrev}>{t('settings.data.nutstore.pathSelector.return')}</Button>
<Button size="small" type="link" onClick={props.mkdir}>
{t('settings.data.nutstore.new_folder.button.label')}
</Button>
</HStack>
<HStack gap={8} alignItems="center">
</RowFlex>
<RowFlex className="items-center gap-2">
<Button type="default" onClick={props.cancel}>
{t('settings.data.nutstore.new_folder.button.cancel')}
</Button>
<Button type="primary" onClick={props.confirm}>
{t('backup.confirm.button')}
</Button>
</HStack>
</RowFlex>
</FooterContainer>
)
}

View File

@ -1,3 +1,4 @@
import { RowFlex } from '@cherrystudio/ui'
import { TopView } from '@renderer/components/TopView'
import { useAgents } from '@renderer/hooks/useAgents'
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
@ -16,7 +17,6 @@ import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import EmojiIcon from '../EmojiIcon'
import { HStack } from '../Layout'
import Scrollbar from '../Scrollbar'
interface Props {
@ -174,7 +174,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
}}
closeIcon={null}
footer={null}>
<HStack style={{ padding: '0 12px', marginTop: 5 }}>
<RowFlex className="mt-[5px] px-3">
<Input
prefix={
<SearchIcon>
@ -191,7 +191,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
variant="borderless"
size="middle"
/>
</HStack>
</RowFlex>
<Divider style={{ margin: 0, marginTop: 4, borderBlockStartWidth: 0.5 }} />
<Container ref={containerRef}>
{take(agents, 100).map((agent, index) => (
@ -200,10 +200,10 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
onClick={() => onCreateAssistant(agent)}
className={`agent-item ${agent.id === 'default' ? 'default' : ''} ${index === selectedIndex ? 'keyboard-selected' : ''}`}
onMouseEnter={() => setSelectedIndex(index)}>
<HStack alignItems="center" gap={5} style={{ overflow: 'hidden', maxWidth: '100%' }}>
<RowFlex className="max-w-full items-center gap-[5px] overflow-hidden">
<EmojiIcon emoji={agent.emoji || ''} />
<span className="text-nowrap">{agent.name}</span>
</HStack>
</RowFlex>
{agent.id === 'default' && <Tag color="green">{t('agents.tag.system')}</Tag>}
{agent.type === 'agent' && <Tag color="orange">{t('agents.tag.agent')}</Tag>}
{agent.id === 'new' && <Tag color="green">{t('agents.tag.new')}</Tag>}

View File

@ -1,10 +1,11 @@
import { Flex } from '@cherrystudio/ui'
import { type HealthResult, HealthStatusIndicator } from '@renderer/components/HealthStatusIndicator'
import { EditIcon } from '@renderer/components/Icons'
import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
import type { ApiKeyWithStatus } from '@renderer/types/healthCheck'
import { maskApiKey } from '@renderer/utils/api'
import type { InputRef } from 'antd'
import { Button, Flex, Input, List, Popconfirm, Tooltip, Typography } from 'antd'
import { Button, Input, List, Popconfirm, Tooltip, Typography } from 'antd'
import { Check, Minus, X } from 'lucide-react'
import type { FC } from 'react'
import { memo, useEffect, useRef, useState } from 'react'
@ -104,7 +105,7 @@ const ApiKeyItem: FC<ApiKeyItemProps> = ({
spellCheck={false}
disabled={disabled}
/>
<Flex gap={0} align="center">
<Flex className="items-center gap-0">
<Tooltip title={t('common.save')}>
<Button
type={hasUnsavedChanges ? 'primary' : 'text'}
@ -133,10 +134,10 @@ const ApiKeyItem: FC<ApiKeyItemProps> = ({
<span style={{ cursor: 'help' }}>{maskApiKey(keyStatus.key)}</span>
</Tooltip>
<Flex gap={10} align="center">
<Flex className="items-center gap-2.5">
<HealthStatusIndicator results={healthResults} loading={false} />
<Flex gap={0} align="center">
<Flex className="items-center gap-0">
{showHealthCheck && (
<Tooltip title={t('settings.provider.check')} mouseLeaveDelay={0}>
<Button

View File

@ -1,3 +1,4 @@
import { Flex } from '@cherrystudio/ui'
import { DeleteIcon } from '@renderer/components/Icons'
import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
import Scrollbar from '@renderer/components/Scrollbar'
@ -9,7 +10,7 @@ import { isProviderSupportAuth } from '@renderer/services/ProviderService'
import type { PreprocessProviderId, WebSearchProviderId } from '@renderer/types'
import type { ApiKeyWithStatus } from '@renderer/types/healthCheck'
import { HealthStatus } from '@renderer/types/healthCheck'
import { Button, Card, Flex, List, Popconfirm, Space, Tooltip, Typography } from 'antd'
import { Button, Card, List, Popconfirm, Space, Tooltip, Typography } from 'antd'
import { Plus } from 'lucide-react'
import type { FC } from 'react'
import { useState } from 'react'
@ -126,7 +127,7 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, show
)}
</Card>
<Flex dir="row" align="center" justify="space-between" style={{ marginTop: 15 }}>
<Flex className="mt-[15px] flex-row items-center justify-between">
{/* 帮助文本 */}
<SettingHelpText>{t('settings.provider.api_key.tip')}</SettingHelpText>

View File

@ -1,9 +1,9 @@
import { Box } from '@cherrystudio/ui'
import { Input, Modal } from 'antd'
import type { TextAreaProps } from 'antd/es/input'
import type { ReactNode } from 'react'
import { useRef, useState } from 'react'
import { Box } from '../Layout'
import { TopView } from '../TopView'
interface PromptPopupShowParams {
@ -69,7 +69,7 @@ const PromptPopupContainer: React.FC<Props> = ({
afterOpenChange={handleAfterOpenChange}
transitionName="animation-move-down"
centered>
<Box mb={8}>{message}</Box>
<Box className="mb-2">{message}</Box>
<Input.TextArea
ref={textAreaRef}
placeholder={inputPlaceholder}

View File

@ -1,3 +1,4 @@
import { ColFlex, Flex } from '@cherrystudio/ui'
import { loggerService } from '@logger'
import CustomTag from '@renderer/components/Tags/CustomTag'
import { TopView } from '@renderer/components/TopView'
@ -13,7 +14,7 @@ import {
processMessageContent,
processTopicContent
} from '@renderer/utils/knowledge'
import { Flex, Form, Modal, Select, Tooltip, Typography } from 'antd'
import { Form, Modal, Select, Tooltip, Typography } from 'antd'
import { Check, CircleHelp } from 'lucide-react'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -318,14 +319,13 @@ const PopupContainer: React.FC<Props> = ({ source, title, resolve }) => {
? 'chat.save.topic.knowledge.select.content.label'
: 'chat.save.knowledge.select.content.title'
)}>
<Flex gap={8} style={{ flexDirection: 'column' }}>
<ColFlex className="gap-2">
{contentTypeOptions.map((option) => (
<ContentTypeItem
key={option.type}
align="center"
justify="space-between"
className="items-center justify-between"
onClick={() => handleContentTypeToggle(option.type)}>
<Flex align="center" gap={8}>
<Flex className="items-center gap-2">
<CustomTag
color={selectedTypes.includes(option.type) ? TAG_COLORS.SELECTED : TAG_COLORS.UNSELECTED}
size={12}>
@ -339,7 +339,7 @@ const PopupContainer: React.FC<Props> = ({ source, title, resolve }) => {
{selectedTypes.includes(option.type) && <Check size={16} color={TAG_COLORS.SELECTED} />}
</ContentTypeItem>
))}
</Flex>
</ColFlex>
</Form.Item>
)}
</Form>

View File

@ -1,3 +1,4 @@
import { Flex } from '@cherrystudio/ui'
import { loggerService } from '@logger'
import {
EmbeddingTag,
@ -9,7 +10,6 @@ import {
WebSearchTag
} from '@renderer/components/Tags/Model'
import type { ModelTag } from '@renderer/types'
import { Flex } from 'antd'
import React, { startTransition, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -48,7 +48,7 @@ const TagFilterSection: React.FC<TagFilterSectionProps> = ({ availableTags, tagS
return (
<FilterContainer>
<Flex wrap="wrap" gap={4}>
<Flex className="flex-wrap gap-1">
<FilterText>{t('models.filter.by_tag')}</FilterText>
{availableTags.map((tag) => {
const TagElement = tagComponents[tag]

View File

@ -15,60 +15,64 @@ exports[`TagFilterSection > rendering > should match snapshot 1`] = `
<div
class="c0"
>
<span
class="c1"
<div
class="box-border flex flex-wrap gap-1"
>
models.filter.by_tag
</span>
<button
aria-label="tag-vision"
data-inactive="false"
type="button"
>
vision
</button>
<button
aria-label="tag-embedding"
data-inactive="false"
type="button"
>
embedding
</button>
<button
aria-label="tag-reasoning"
data-inactive="false"
type="button"
>
reasoning
</button>
<button
aria-label="tag-function_calling"
data-inactive="false"
type="button"
>
function_calling
</button>
<button
aria-label="tag-web_search"
data-inactive="false"
type="button"
>
web_search
</button>
<button
aria-label="tag-rerank"
data-inactive="false"
type="button"
>
rerank
</button>
<button
aria-label="tag-free"
data-inactive="false"
type="button"
>
free
</button>
<span
class="c1"
>
models.filter.by_tag
</span>
<button
aria-label="tag-vision"
data-inactive="false"
type="button"
>
vision
</button>
<button
aria-label="tag-embedding"
data-inactive="false"
type="button"
>
embedding
</button>
<button
aria-label="tag-reasoning"
data-inactive="false"
type="button"
>
reasoning
</button>
<button
aria-label="tag-function_calling"
data-inactive="false"
type="button"
>
function_calling
</button>
<button
aria-label="tag-web_search"
data-inactive="false"
type="button"
>
web_search
</button>
<button
aria-label="tag-rerank"
data-inactive="false"
type="button"
>
rerank
</button>
<button
aria-label="tag-free"
data-inactive="false"
type="button"
>
free
</button>
</div>
</div>
</div>
`;

View File

@ -1,4 +1,4 @@
import { HStack } from '@renderer/components/Layout'
import { RowFlex } from '@cherrystudio/ui'
import type { InputRef } from 'antd'
import { Input } from 'antd'
import { Search } from 'lucide-react'
@ -34,7 +34,7 @@ const SelectModelSearchBar: React.FC<SelectModelSearchBarProps> = ({ onSearch })
}, [])
return (
<HStack style={{ padding: '0 12px', marginTop: 5 }}>
<RowFlex className="mt-[5px] px-3">
<Input
prefix={
<SearchIcon>
@ -59,7 +59,7 @@ const SelectModelSearchBar: React.FC<SelectModelSearchBarProps> = ({ onSearch })
}
}}
/>
</HStack>
</RowFlex>
)
}

View File

@ -1,4 +1,4 @@
import { Box } from '@renderer/components/Layout'
import { Box } from '@cherrystudio/ui'
import { TopView } from '@renderer/components/TopView'
import { Modal } from 'antd'
import { useState } from 'react'
@ -37,7 +37,7 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
afterClose={onClose}
transitionName="animation-move-down"
centered>
<Box mb={8}>Name</Box>
<Box className="mb-2">Name</Box>
</Modal>
)
}

View File

@ -1,3 +1,4 @@
import { Center, ColFlex, RowFlex } from '@cherrystudio/ui'
import { cacheService } from '@data/CacheService'
import { usePreference } from '@data/hooks/usePreference'
import DefaultAvatar from '@renderer/assets/images/avatar.png'
@ -11,7 +12,6 @@ import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import EmojiPicker from '../EmojiPicker'
import { Center, HStack, VStack } from '../Layout'
import { TopView } from '../TopView'
interface Props {
@ -128,8 +128,8 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
afterClose={onClose}
transitionName="animation-move-down"
centered>
<Center mt="30px">
<VStack alignItems="center" gap="10px">
<Center className="mt-[30px]">
<ColFlex className="items-center gap-2.5">
<Dropdown
menu={{ items }}
trigger={['click']}
@ -162,9 +162,9 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
)}
</Popover>
</Dropdown>
</VStack>
</ColFlex>
</Center>
<HStack alignItems="center" gap="10px" p="20px">
<RowFlex className="items-center gap-2.5 p-5">
<Input
placeholder={t('settings.general.user_name.placeholder')}
value={userName}
@ -172,7 +172,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
style={{ flex: 1, textAlign: 'center', width: '100%' }}
maxLength={30}
/>
</HStack>
</RowFlex>
</Modal>
)
}

View File

@ -48,7 +48,7 @@ const ImagePreviewLayout = ({
return (
<Spin spinning={loading} indicator={<LoadingIcon color="var(--color-text-2)" />}>
<PreviewContainer vertical className={`image-preview-layout ${className ?? ''}`}>
<PreviewContainer className={`image-preview-layout flex-col ${className ?? ''}`}>
{error && <PreviewError>{error}</PreviewError>}
{children}
{!error && enableToolbar && <ImageToolbar pan={pan} zoom={zoom} dialog={dialog} />}

View File

@ -7,7 +7,6 @@ exports[`ImagePreviewLayout > should match snapshot 1`] = `
>
<div
data-testid="preview-container"
data-vertical="true"
>
<div>
Test Content

View File

@ -1,4 +1,4 @@
import { Flex } from 'antd'
import { Flex } from '@cherrystudio/ui'
import { styled } from 'styled-components'
export const PreviewError = styled.div`

View File

@ -1,10 +1,10 @@
import { RightOutlined } from '@ant-design/icons'
import { Flex } from '@cherrystudio/ui'
import { DynamicVirtualList, type DynamicVirtualListRef } from '@renderer/components/VirtualList'
import { isMac } from '@renderer/config/constant'
import { useTimer } from '@renderer/hooks/useTimer'
import useUserTheme from '@renderer/hooks/useUserTheme'
import { classNames } from '@renderer/utils'
import { Flex } from 'antd'
import { t } from 'i18next'
import { debounce } from 'lodash'
import { Check } from 'lucide-react'
@ -608,13 +608,11 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
<QuickPanelFooterTips $footerWidth={footerWidth}>
<span>ESC {t('settings.quickPanel.close')}</span>
<Flex align="center" gap={4}>
{t('settings.quickPanel.select')}
</Flex>
<Flex className="items-center gap-1"> {t('settings.quickPanel.select')}</Flex>
{footerWidth >= 500 && (
<>
<Flex align="center" gap={4}>
<Flex className="items-center gap-1">
<span style={{ color: isAssistiveKeyPressed ? 'var(--color-primary)' : 'var(--color-text-3)' }}>
{ASSISTIVE_KEY}
</span>
@ -622,7 +620,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
</Flex>
{canForwardAndBackward && (
<Flex align="center" gap={4}>
<Flex className="items-center gap-1">
<span style={{ color: isAssistiveKeyPressed ? 'var(--color-primary)' : 'var(--color-text-3)' }}>
{ASSISTIVE_KEY}
</span>
@ -632,9 +630,7 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
</>
)}
<Flex align="center" gap={4}>
{t('settings.quickPanel.confirm')}
</Flex>
<Flex className="items-center gap-1"> {t('settings.quickPanel.confirm')}</Flex>
</QuickPanelFooterTips>
</QuickPanelFooter>
</QuickPanelBody>

View File

@ -1,5 +1,6 @@
import { InboxOutlined, LinkOutlined, LoadingOutlined, UploadOutlined } from '@ant-design/icons'
import { Button, Flex, Input, message, Modal, Spin, Tabs, Upload } from 'antd'
import { Flex } from '@cherrystudio/ui'
import { Button, Input, message, Modal, Spin, Tabs, Upload } from 'antd'
const { Dragger } = Upload
import type { RcFile } from 'antd/es/upload'
@ -24,20 +25,20 @@ const TabContent = styled.div`
const UrlInput = styled(Input)`
.ant-input {
padding: 12px 16px
font-size: 14px
border-radius: 4px
border: 1px solid #dadce0
transition: all 0.2s ease
background: #ffffff
padding: 12px 16px;
font-size: 14px;
border-radius: 4px;
border: 1px solid #dadce0;
transition: all 0.2s ease;
background: #ffffff;
&:hover {
border-color: #4285f4
border-color: #4285f4;
}
&:focus {
border-color: #4285f4
box-shadow: 0 0 0 1px rgba(66, 133, 244, 0.3)
border-color: #4285f4;
box-shadow: 0 0 0 1px rgba(66, 133, 244, 0.3);
}
}
`
@ -164,7 +165,7 @@ export const ImageUploader: React.FC<ImageUploaderProps> = ({ onImageSelect, vis
),
children: (
<TabContent>
<Flex gap={12} justify="center">
<Flex className="justify-center gap-3">
<UrlInput
placeholder={t('richEditor.imageUploader.urlPlaceholder')}
value={urlInput}

View File

@ -1,5 +1,6 @@
import { Flex } from '@cherrystudio/ui'
import { useTheme } from '@renderer/context/ThemeProvider'
import { Button, Flex, Input } from 'antd'
import { Button, Input } from 'antd'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -142,7 +143,7 @@ const LinkEditor: React.FC<LinkEditorProps> = ({
<Input value={href} placeholder="https://example.com" onChange={(e) => setHref(e.target.value)} size="small" />
</div>
<Flex justify="space-between" align="center">
<Flex className="items-center justify-between">
<div>
{showRemove && (
<Button size="small" danger type="text" onClick={onRemove} style={{ padding: '0 8px' }}>
@ -150,7 +151,7 @@ const LinkEditor: React.FC<LinkEditorProps> = ({
</Button>
)}
</div>
<Flex gap={6}>
<Flex className="gap-1.5">
<Button size="small" onClick={onCancel}>
{t('common.cancel')}
</Button>

View File

@ -1,5 +1,6 @@
import { Flex } from '@cherrystudio/ui'
import { useTheme } from '@renderer/context/ThemeProvider'
import { Button, Flex, Input } from 'antd'
import { Button, Input } from 'antd'
import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -146,7 +147,7 @@ const MathInputDialog: React.FC<MathInputDialogProps> = ({
onKeyDown={handleKeyDown}
style={{ marginBottom: 12, fontFamily: 'monospace' }}
/>
<Flex justify="flex-end" gap={8}>
<Flex className="justify-end gap-2">
<Button size="small" onClick={onCancel}>
{t('common.cancel')}
</Button>

View File

@ -1,4 +1,5 @@
// import { loggerService } from '@logger'
import { Box } from '@cherrystudio/ui'
import TopViewMinappContainer from '@renderer/components/MinApp/TopViewMinappContainer'
import { useAppInit } from '@renderer/hooks/useAppInit'
import { useShortcuts } from '@renderer/hooks/useShortcuts'
@ -6,7 +7,6 @@ import { Modal } from 'antd'
import type { PropsWithChildren } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { Box } from '../Layout'
import { getToastUtilities } from './toast'
let onPop = () => {}
@ -72,8 +72,8 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
const FullScreenContainer: React.FC<PropsWithChildren> = useCallback(({ children }) => {
return (
<Box flex={1} position="absolute" w="100%" h="100%" className="topview-fullscreen-container">
<Box position="absolute" w="100%" h="100%" onClick={onPop} />
<Box className="topview-fullscreen-container absolute h-full w-full flex-1">
<Box className="absolute h-full w-full" onClick={onPop} />
{children}
</Box>
)

View File

@ -1,6 +1,6 @@
import { ImportOutlined, PlusOutlined } from '@ant-design/icons'
import { ColFlex, Flex, RowFlex } from '@cherrystudio/ui'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout'
import ListItem from '@renderer/components/ListItem'
import Scrollbar from '@renderer/components/Scrollbar'
import CustomTag from '@renderer/components/Tags/CustomTag'
@ -9,7 +9,7 @@ import { useNavbarPosition } from '@renderer/hooks/useNavbar'
import { createAssistantFromAgent } from '@renderer/services/AssistantService'
import type { Agent } from '@renderer/types'
import { uuid } from '@renderer/utils'
import { Button, Empty, Flex, Input } from 'antd'
import { Button, Empty, Input } from 'antd'
import { omit } from 'lodash'
import { Search } from 'lucide-react'
import type { FC } from 'react'
@ -71,7 +71,7 @@ const AgentsPage: FC = () => {
window.modal.confirm({
title: agent.name,
content: (
<Flex gap={16} vertical style={{ width: 'calc(100% + 12px)' }}>
<ColFlex className="gap-4" style={{ width: 'calc(100% + 12px)' }}>
{agent.description && <AgentDescription>{agent.description}</AgentDescription>}
{agent.prompt && (
@ -79,7 +79,7 @@ const AgentsPage: FC = () => {
<ReactMarkdown>{agent.prompt}</ReactMarkdown>
</AgentPrompt>
)}
</Flex>
</ColFlex>
),
width: 600,
icon: null,
@ -206,17 +206,17 @@ const AgentsPage: FC = () => {
active={activeGroup === group && !search.trim()}
key={group}
title={
<Flex gap={16} align="center" justify="space-between">
<Flex gap={10} align="center">
<Flex className="items-center justify-between gap-4">
<Flex className="items-center gap-2.5">
<AgentGroupIcon groupName={group} />
{getLocalizedGroupName(group)}
</Flex>
{
<HStack alignItems="center" justifyContent="center" style={{ minWidth: 40 }}>
<RowFlex className="min-w-10 items-center justify-center">
<CustomTag color="#A0A0A0" size={8}>
{agentGroups[group].length}
</CustomTag>
</HStack>
</RowFlex>
}
</Flex>
}
@ -246,7 +246,7 @@ const AgentsPage: FC = () => {
</CustomTag>
}
</AgentsListTitle>
<Flex gap={8}>
<Flex className="gap-2">
{isSearchExpanded ? (
<Input
placeholder={t('common.search')}

View File

@ -1,3 +1,4 @@
import { Flex } from '@cherrystudio/ui'
import { TopView } from '@renderer/components/TopView'
import { useAgents } from '@renderer/hooks/useAgents'
import { useTimer } from '@renderer/hooks/useTimer'
@ -5,7 +6,7 @@ import { getDefaultModel } from '@renderer/services/AssistantService'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import type { Agent } from '@renderer/types'
import { uuid } from '@renderer/utils'
import { Button, Flex, Form, Input, Modal, Radio } from 'antd'
import { Button, Form, Input, Modal, Radio } from 'antd'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -98,7 +99,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
onCancel={onCancel}
maskClosable={false}
footer={
<Flex justify="end" gap={8}>
<Flex className="justify-end gap-2">
<Button onClick={onCancel}>{t('common.cancel')}</Button>
<Button type="primary" onClick={() => form.submit()} loading={loading}>
{t('agents.import.button')}

View File

@ -1,6 +1,6 @@
import { MenuOutlined } from '@ant-design/icons'
import { Box, RowFlex } from '@cherrystudio/ui'
import { DraggableList } from '@renderer/components/DraggableList'
import { Box, HStack } from '@renderer/components/Layout'
import { TopView } from '@renderer/components/TopView'
import { useAgents } from '@renderer/hooks/useAgents'
import { Empty, Modal } from 'antd'
@ -46,12 +46,12 @@ const PopupContainer: React.FC = () => {
<DraggableList list={agents} onUpdate={updateAgents}>
{(item) => (
<AgentItem>
<Box mr={8}>
<Box className="mr-8">
{item.emoji} {item.name}
</Box>
<HStack gap="15px">
<RowFlex className="gap-[15px]">
<MenuOutlined style={{ cursor: 'move' }} />
</HStack>
</RowFlex>
</AgentItem>
)}
</DraggableList>

View File

@ -13,8 +13,8 @@ import {
LinkOutlined,
VideoCameraFilled
} from '@ant-design/icons'
import { ColFlex } from '@cherrystudio/ui'
import { videoExts } from '@shared/config/constant'
import { Flex } from 'antd'
import React, { memo } from 'react'
import styled from 'styled-components'
@ -88,10 +88,10 @@ const FileItem: React.FC<FileItemProps> = ({ fileInfo, style }) => {
<FileItemCard style={style}>
<CardContent>
<FileIcon>{icon || getFileIcon(ext)}</FileIcon>
<Flex vertical justify="center" gap={0} flex={1} style={{ width: '0px' }}>
<ColFlex className="w-0 flex-1 justify-center gap-0">
<FileName>{name}</FileName>
{extra && <FileInfo>{extra}</FileInfo>}
</Flex>
</ColFlex>
<FileActions>{actions}</FileActions>
</CardContent>
</FileItemCard>

View File

@ -1,4 +1,5 @@
import { ExclamationCircleOutlined } from '@ant-design/icons'
import { Flex } from '@cherrystudio/ui'
import { loggerService } from '@logger'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
@ -11,7 +12,7 @@ import store from '@renderer/store'
import type { FileMetadata } from '@renderer/types'
import { FileTypes } from '@renderer/types'
import { formatFileSize } from '@renderer/utils'
import { Button, Checkbox, Dropdown, Empty, Flex, Popconfirm } from 'antd'
import { Button, Checkbox, Dropdown, Empty, Popconfirm } from 'antd'
import dayjs from 'dayjs'
import { useLiveQuery } from 'dexie-react-hooks'
import {
@ -112,7 +113,7 @@ const FilesPage: FC = () => {
created_at: dayjs(file.created_at).format('MM-DD HH:mm'),
created_at_unix: dayjs(file.created_at).unix(),
actions: (
<Flex align="center" gap={0} style={{ opacity: 0.7 }}>
<Flex className="items-center gap-0 opacity-70">
<Button type="text" icon={<EditIcon size={14} />} onClick={() => handleRename(file.id)} />
<Popconfirm
title={t('files.delete.title')}
@ -162,7 +163,7 @@ const FilesPage: FC = () => {
</SideNav>
<MainContent>
<SortContainer>
<Flex gap={8} align="center">
<Flex className="items-center gap-2">
{(['created_at', 'size', 'name'] as const).map((field) => (
<SortButton
key={field}

View File

@ -1,4 +1,4 @@
import { HStack } from '@renderer/components/Layout'
import { RowFlex } from '@cherrystudio/ui'
import { useAppDispatch } from '@renderer/store'
import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk'
import type { Topic } from '@renderer/types'
@ -80,7 +80,7 @@ const HistoryPage: FC = () => {
return (
<Container>
<HStack style={{ padding: '0 12px', marginTop: 8 }}>
<RowFlex className="mt-2 px-3">
<Input
prefix={
stack.length > 1 ? (
@ -106,7 +106,7 @@ const HistoryPage: FC = () => {
size="middle"
onPressEnter={onSearch}
/>
</HStack>
</RowFlex>
<Divider style={{ margin: 0, marginTop: 4, borderBlockStartWidth: 0.5 }} />
<TopicsHistory

View File

@ -1,4 +1,4 @@
import { HStack } from '@renderer/components/Layout'
import { RowFlex } from '@cherrystudio/ui'
import { MessageEditingProvider } from '@renderer/context/MessageEditingContext'
import { getTopicById } from '@renderer/hooks/useTopic'
import { default as MessageItem } from '@renderer/pages/home/Messages/Message'
@ -52,11 +52,11 @@ const SearchMessage: FC<Props> = ({ message, ...props }) => {
onClick={() => locateToMessage(navigate, message)}
icon={<Forward size={16} />}
/>
<HStack mt="10px" justifyContent="center">
<RowFlex className="mt-[10px] justify-center">
<Button onClick={() => locateToMessage(navigate, message)} icon={<Forward size={16} />}>
{t('history.locate.message')}
</Button>
</HStack>
</RowFlex>
</ContainerWrapper>
</MessagesContainer>
</MessageEditingProvider>

View File

@ -1,6 +1,6 @@
import { MessageOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import SearchPopup from '@renderer/components/Popups/SearchPopup'
import { MessageEditingProvider } from '@renderer/context/MessageEditingContext'
import { modelGenerating } from '@renderer/hooks/useModel'
@ -75,11 +75,11 @@ const TopicMessages: FC<Props> = ({ topic: _topic, ...props }) => {
))}
{isEmpty && <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
{!isEmpty && (
<HStack justifyContent="center">
<RowFlex className="justify-center">
<Button onClick={() => onContinueChat(topic)} icon={<MessageOutlined />}>
{t('history.continue_chat')}
</Button>
</HStack>
</RowFlex>
)}
</ContainerWrapper>
</MessagesContainer>

View File

@ -1,5 +1,5 @@
import { SearchOutlined } from '@ant-design/icons'
import { VStack } from '@renderer/components/Layout'
import { ColFlex } from '@cherrystudio/ui'
import useScrollPosition from '@renderer/hooks/useScrollPosition'
import { selectAllTopics } from '@renderer/store/assistants'
import type { Topic } from '@renderer/types'
@ -38,12 +38,12 @@ const TopicsHistory: React.FC<Props> = ({ keywords, onClick, onSearch, ...props
if (isEmpty(filteredTopics)) {
return (
<ListContainer {...props}>
<VStack alignItems="center">
<ColFlex className="items-center">
<Empty description={t('history.search.topics.empty')} />
<Button style={{ width: 200, marginTop: 20 }} type="primary" onClick={onSearch} icon={<SearchOutlined />}>
{t('history.search.messages')}
</Button>
</VStack>
</ColFlex>
</ListContainer>
)
}

View File

@ -1,8 +1,8 @@
import { ColFlex, RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import type { ContentSearchRef } from '@renderer/components/ContentSearch'
import { ContentSearch } from '@renderer/components/ContentSearch'
import { HStack } from '@renderer/components/Layout'
import MultiSelectActionPopup from '@renderer/components/Popups/MultiSelectionPopup'
import PromptPopup from '@renderer/components/Popups/PromptPopup'
import { QuickPanelProvider } from '@renderer/components/QuickPanel'
@ -15,7 +15,6 @@ import { useTimer } from '@renderer/hooks/useTimer'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import type { Assistant, Topic } from '@renderer/types'
import { classNames } from '@renderer/utils'
import { Flex } from 'antd'
import { debounce } from 'lodash'
import { AnimatePresence, motion } from 'motion/react'
import type { FC } from 'react'
@ -152,13 +151,11 @@ const Chat: FC<Props> = (props) => {
position="left"
/>
)}
<HStack>
<RowFlex>
<Main
ref={mainRef}
id="chat-main"
vertical
flex={1}
justify="space-between"
className="flex-1 justify-between"
style={{ maxWidth: chatMaxWidth, height: mainHeight }}>
<QuickPanelProvider>
<Messages
@ -199,7 +196,7 @@ const Chat: FC<Props> = (props) => {
</motion.div>
)}
</AnimatePresence>
</HStack>
</RowFlex>
</Container>
)
}
@ -230,7 +227,7 @@ const Container = styled.div`
}
`
const Main = styled(Flex)`
const Main = styled(ColFlex)`
[navbar-position='left'] & {
height: calc(100vh - var(--navbar-height));
}

View File

@ -1,6 +1,6 @@
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { NavbarHeader } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout'
import SearchPopup from '@renderer/components/Popups/SearchPopup'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { modelGenerating } from '@renderer/hooks/useModel'
@ -66,7 +66,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
return (
<NavbarHeader className="home-navbar">
<HStack alignItems="center">
<RowFlex className="items-center">
{showAssistants && (
<Tooltip title={t('navbar.hide_sidebar')} mouseEnterDelay={0.8}>
<NavbarIcon onClick={toggleShowAssistants}>
@ -95,8 +95,8 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
)}
</AnimatePresence>
<SelectModelButton assistant={assistant} />
</HStack>
<HStack alignItems="center" gap={8}>
</RowFlex>
<RowFlex className="items-center gap-2">
<UpdateAppButton />
<Tooltip title={t('navbar.expand')} mouseEnterDelay={0.8}>
<NarrowIcon onClick={handleNarrowModeToggle}>
@ -122,7 +122,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
</NavbarIcon>
</Tooltip>
)}
</HStack>
</RowFlex>
</NavbarHeader>
)
}

View File

@ -12,12 +12,13 @@ import {
GlobalOutlined,
LinkOutlined
} from '@ant-design/icons'
import { ColFlex } from '@cherrystudio/ui'
import CustomTag from '@renderer/components/Tags/CustomTag'
import { useAttachment } from '@renderer/hooks/useAttachment'
import FileManager from '@renderer/services/FileManager'
import type { FileMetadata } from '@renderer/types'
import { formatFileSize } from '@renderer/utils'
import { Flex, Image, Tooltip } from 'antd'
import { Image, Tooltip } from 'antd'
import { isEmpty } from 'lodash'
import type { FC } from 'react'
import { useState } from 'react'
@ -101,7 +102,7 @@ export const FileNameRender: FC<{ file: FileMetadata }> = ({ file }) => {
}}
fresh
title={
<Flex vertical gap={2} align="center">
<ColFlex className="items-center gap-0.5">
{isImage(file.ext) && (
<Image
style={{ width: 80, maxHeight: 200 }}
@ -115,7 +116,7 @@ export const FileNameRender: FC<{ file: FileMetadata }> = ({ file }) => {
)}
<span style={{ wordBreak: 'break-all' }}>{fullName}</span>
{formatFileSize(file.size)}
</Flex>
</ColFlex>
}>
<FileName
onClick={() => {

View File

@ -1,5 +1,5 @@
import { ColFlex, RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { HStack, VStack } from '@renderer/components/Layout'
import MaxContextCount from '@renderer/components/MaxContextCount'
import { Divider, Popover } from 'antd'
import { ArrowUp, MenuIcon } from 'lucide-react'
@ -23,44 +23,44 @@ const TokenCount: FC<Props> = ({ estimateTokenCount, inputTokenCount, contextCou
const PopoverContent = () => {
return (
<VStack w="185px" background="100%">
<HStack justifyContent="space-between" w="100%">
<ColFlex className="w-full" style={{ width: '185px', background: '100%' }}>
<RowFlex className="w-full justify-between">
<Text>{t('chat.input.context_count.tip')}</Text>
<Text>
<HStack style={{ alignItems: 'center' }}>
<RowFlex className="items-center">
{contextCount.current}
<SlashSeparatorSpan>/</SlashSeparatorSpan>
<MaxContextCount maxContext={contextCount.max} />
</HStack>
</RowFlex>
</Text>
</HStack>
</RowFlex>
<Divider style={{ margin: '5px 0' }} />
<HStack justifyContent="space-between" w="100%">
<RowFlex className="w-full justify-between">
<Text>{t('chat.input.estimated_tokens.tip')}</Text>
<Text>{estimateTokenCount}</Text>
</HStack>
</VStack>
</RowFlex>
</ColFlex>
)
}
return (
<Container>
<Popover content={PopoverContent} arrow={false}>
<HStack>
<HStack style={{ alignItems: 'center' }}>
<RowFlex>
<RowFlex className="items-center">
<MenuIcon size={12} className="icon" />
{contextCount.current}
<SlashSeparatorSpan>/</SlashSeparatorSpan>
<MaxContextCount maxContext={contextCount.max} />
</HStack>
</RowFlex>
<Divider type="vertical" style={{ marginTop: 3, marginLeft: 5, marginRight: 3 }} />
<HStack style={{ alignItems: 'center' }}>
<RowFlex className="items-center">
<ArrowUp size={12} className="icon" />
{inputTokenCount}
<SlashSeparatorSpan>/</SlashSeparatorSpan>
{estimateTokenCount}
</HStack>
</HStack>
</RowFlex>
</RowFlex>
</Popover>
</Container>
)

View File

@ -1,3 +1,4 @@
import { Flex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { getModelUniqId } from '@renderer/services/ModelService'
import type { RootState } from '@renderer/store'
@ -5,7 +6,6 @@ import { selectFormattedCitationsByBlockId } from '@renderer/store/messageBlock'
import { type Model } from '@renderer/types'
import type { MainTextMessageBlock, Message } from '@renderer/types/newMessage'
import { determineCitationSource, withCitationTags } from '@renderer/utils/citation'
import { Flex } from 'antd'
import React, { useCallback } from 'react'
import { useSelector } from 'react-redux'
import styled from 'styled-components'
@ -44,7 +44,7 @@ const MainTextBlock: React.FC<Props> = ({ block, citationBlockId, role, mentions
<>
{/* Render mentions associated with the message */}
{mentions && mentions.length > 0 && (
<Flex gap="8px" wrap style={{ marginBottom: 10 }}>
<Flex className="mb-2.5 flex-wrap gap-2">
{mentions.map((m) => (
<MentionTag key={getModelUniqId(m)}>{'@' + m.name}</MentionTag>
))}

View File

@ -262,12 +262,11 @@ describe('MainTextBlock', () => {
const mentionElement = screen.getByText('@Test Model')
expect(mentionElement).toHaveStyle({ color: 'var(--color-link)' })
// Check container layout
const container = mentionElement.closest('[style*="gap"]')
expect(container).toHaveStyle({
gap: '8px',
marginBottom: '10px'
})
// Check container layout - now using Tailwind classes
const container = mentionElement.closest('.gap-2')
expect(container).toHaveClass('gap-2')
expect(container).toHaveClass('flex-wrap')
expect(container).toHaveClass('mb-2.5')
})
})

View File

@ -1,6 +1,6 @@
import { Flex } from '@cherrystudio/ui'
import { getModelUniqId } from '@renderer/services/ModelService'
import type { Message } from '@renderer/types/newMessage'
import { Flex } from 'antd'
import { isEmpty } from 'lodash'
import React from 'react'
import styled from 'styled-components'
@ -14,7 +14,7 @@ const MessageContent: React.FC<Props> = ({ message }) => {
return (
<>
{!isEmpty(message.mentions) && (
<Flex gap="8px" wrap style={{ marginBottom: '10px' }}>
<Flex className="mb-2.5 flex-wrap gap-2">
{message.mentions?.map((model) => (
<MentionTag key={getModelUniqId(model)}>{'@' + model.name}</MentionTag>
))}

View File

@ -6,7 +6,7 @@ import {
NumberOutlined,
ReloadOutlined
} from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout'
import { RowFlex } from '@cherrystudio/ui'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
import type { Topic } from '@renderer/types'
@ -102,7 +102,7 @@ const MessageGroupMenuBar: FC<Props> = ({
return (
<GroupMenuBar $layout={multiModelMessageStyle} className="group-menu-bar">
<HStack style={{ alignItems: 'center', flex: 1, overflow: 'hidden' }}>
<RowFlex className="flex-1 items-center overflow-hidden">
<LayoutContainer>
{(['fold', 'vertical', 'horizontal', 'grid'] as const).map((layout) => (
<Tooltip
@ -133,7 +133,7 @@ const MessageGroupMenuBar: FC<Props> = ({
/>
)}
{multiModelMessageStyle === 'grid' && <MessageGroupSettings />}
</HStack>
</RowFlex>
{hasFailedMessages && (
<Tooltip title={t('message.group.retry_failed')} mouseEnterDelay={0.6}>
<Button

View File

@ -1,7 +1,7 @@
import { ArrowsAltOutlined, ShrinkOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
import { HStack } from '@renderer/components/Layout'
import Scrollbar from '@renderer/components/Scrollbar'
import type { Model } from '@renderer/types'
import { AssistantMessageStatus, type Message } from '@renderer/types/newMessage'
@ -104,7 +104,7 @@ const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, selec
)
}
const Container = styled(HStack)`
const Container = styled(RowFlex)`
flex: 1;
overflow: hidden;
align-items: center;

View File

@ -1,6 +1,6 @@
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar'
import { HStack } from '@renderer/components/Layout'
import UserPopup from '@renderer/components/Popups/UserPopup'
import { APP_NAME, AppLogo, isLocalAi } from '@renderer/config/env'
import { getModelLogo } from '@renderer/config/models'
@ -113,7 +113,7 @@ const MessageHeader: FC<Props> = memo(({ assistant, model, message, topic, isGro
</>
)}
<UserWrap>
<HStack alignItems="center">
<RowFlex className="items-center">
<UserName isBubbleStyle={isBubbleStyle} theme={theme}>
{username}
</UserName>
@ -122,7 +122,7 @@ const MessageHeader: FC<Props> = memo(({ assistant, model, message, topic, isGro
<Sparkle fill="var(--color-primary)" strokeWidth={0} size={18} />
</Tooltip>
)}
</HStack>
</RowFlex>
<InfoWrap className="message-header-info-wrap">
<MessageTime>{dayjs(message?.updatedAt ?? message.createdAt).format('MM/DD HH:mm')}</MessageTime>
</InfoWrap>

View File

@ -1,3 +1,4 @@
import { Flex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import { CopyIcon, LoadingIcon } from '@renderer/components/Icons'
@ -14,7 +15,6 @@ import {
Collapse,
ConfigProvider,
Dropdown,
Flex,
message as antdMessage,
Modal,
Progress,
@ -269,7 +269,7 @@ const MessageMcpTool: FC<Props> = ({ block }) => {
label: (
<MessageTitleLabel>
<TitleContent>
<ToolName align="center" gap={4}>
<ToolName className="items-center gap-1">
{tool.serverName} : {tool.name}
{isToolAutoApproved(tool) && (
<Tooltip title={t('message.tools.autoApproveEnabled')} mouseLeaveDelay={0}>

View File

@ -1,6 +1,6 @@
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout'
import SearchPopup from '@renderer/components/Popups/SearchPopup'
import { isLinux, isWin } from '@renderer/config/constant'
import { useAssistant } from '@renderer/hooks/useAssistant'
@ -104,9 +104,9 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
</AnimatePresence>
</NavbarLeft>
)}
<HStack alignItems="center" gap={6}>
<RowFlex className="items-center gap-1.5">
<SelectModelButton assistant={assistant} />
</HStack>
</RowFlex>
<NavbarRight
style={{
justifyContent: 'flex-end',
@ -115,7 +115,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
paddingRight: isWin || isLinux ? '144px' : '6px'
}}
className="home-navbar-right">
<HStack alignItems="center" gap={6}>
<RowFlex className="items-center gap-1.5">
<Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}>
<NarrowIcon onClick={() => SearchPopup.show()}>
<Search size={18} />
@ -141,7 +141,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
</NavbarIcon>
</Tooltip>
)}
</HStack>
</RowFlex>
</NavbarRight>
</Navbar>
)

View File

@ -1,6 +1,6 @@
import { RowFlex } from '@cherrystudio/ui'
import { useMultiplePreferences, usePreference } from '@data/hooks/usePreference'
import EditableNumber from '@renderer/components/EditableNumber'
import { HStack } from '@renderer/components/Layout'
import Scrollbar from '@renderer/components/Scrollbar'
import Selector from '@renderer/components/Selector'
import { HelpTooltip } from '@renderer/components/TooltipIcons'
@ -172,14 +172,14 @@ const SettingsTab: FC<Props> = (props) => {
title={t('assistants.settings.title')}
defaultExpanded={true}
extra={
<HStack alignItems="center" gap={2}>
<RowFlex className="items-center gap-0.5">
<Button
type="text"
size="small"
icon={<Settings2 size={16} />}
onClick={() => AssistantSettingsPopup.show({ assistant, tab: 'model' })}
/>
</HStack>
</RowFlex>
}>
<SettingGroup style={{ marginTop: 5 }}>
<Row align="middle">

View File

@ -1,6 +1,6 @@
import { Box } from '@cherrystudio/ui'
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
import { DeleteIcon } from '@renderer/components/Icons'
import { Box } from '@renderer/components/Layout'
import { TopView } from '@renderer/components/TopView'
import { useAssistants } from '@renderer/hooks/useAssistant'
import { useTags } from '@renderer/hooks/useTags'
@ -91,7 +91,7 @@ const PopupContainer: React.FC<Props> = ({ title, resolve }) => {
<Draggable key={tag} draggableId={tag} index={index}>
{(provided) => (
<TagItem ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<Box mr={8}>{tag}</Box>
<Box className="mr-2">{tag}</Box>
<Button
type="text"
icon={<DeleteIcon size={16} className="lucide-custom" />}

View File

@ -1,6 +1,6 @@
import { RedoOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { loggerService } from '@logger'
import { HStack } from '@renderer/components/Layout'
import CustomTag from '@renderer/components/Tags/CustomTag'
import { useKnowledge } from '@renderer/hooks/useKnowledge'
import { NavbarIcon } from '@renderer/pages/home/ChatNavbar'
@ -165,12 +165,12 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
)}
</div>
</ModelInfo>
<HStack gap={8} alignItems="center">
<RowFlex className="items-center gap-2">
{/* 使用selected base导致修改设置后没有响应式更新 */}
<NavbarIcon onClick={() => base && KnowledgeSearchPopup.show({ base: base })}>
<Search size={18} />
</NavbarIcon>
</HStack>
</RowFlex>
</HeaderContainer>
<StyledTabs activeKey={activeKey} onChange={setActiveKey} items={tabItems} type="line" size="small" />
</MainContainer>
@ -181,9 +181,9 @@ export const KnowledgeEmptyView = () => <Empty style={{ margin: 20 }} styles={{
export const ItemHeaderLabel = ({ label }: { label: string }) => {
return (
<HStack alignItems="center" gap={10}>
<RowFlex className="items-center gap-2.5">
<label style={{ fontWeight: 600 }}>{label}</label>
</HStack>
</RowFlex>
)
}

View File

@ -12,8 +12,8 @@ const mocks = vi.hoisted(() => ({
}))
// Mock HStack component
vi.mock('@renderer/components/Layout', () => ({
HStack: ({ children, ...props }: any) => (
vi.mock('@cherrystudio/ui', () => ({
RowFlex: ({ children, ...props }: any) => (
<div data-testid="hstack" {...props}>
{children}
</div>

View File

@ -84,8 +84,8 @@ exports[`KnowledgeBaseFormModal > basic rendering > should match snapshot 1`] =
data-testid="modal-body"
>
<div
class="h-full"
data-testid="hstack"
height="100%"
>
<div
class="c1"

View File

@ -1,3 +1,4 @@
import { ColFlex } from '@cherrystudio/ui'
import { loggerService } from '@logger'
import { nanoid } from '@reduxjs/toolkit'
import { TopView } from '@renderer/components/TopView'
@ -6,7 +7,6 @@ import { useKnowledgeBaseForm } from '@renderer/hooks/useKnowledgeBaseForm'
import { getModelUniqId } from '@renderer/services/ModelService'
import type { KnowledgeBase } from '@renderer/types'
import { formatErrorMessage } from '@renderer/utils/error'
import { Flex } from 'antd'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -67,7 +67,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ base: _base, resolve })
window.modal.confirm({
title: t('knowledge.migrate.confirm.title'),
content: (
<Flex vertical align="self-start">
<ColFlex className="items-start">
<span>{t('knowledge.migrate.confirm.content')}</span>
<span>{t('knowledge.embedding_model')}:</span>
<span style={{ paddingLeft: '1em' }}>{`${t('knowledge.migrate.source_model')}: ${base.model.name}`}</span>
@ -80,7 +80,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ base: _base, resolve })
style={{
paddingLeft: '1em'
}}>{`${t('knowledge.migrate.target_dimensions')}: ${newBase.dimensions}`}</span>
</Flex>
</ColFlex>
),
okText: t('knowledge.migrate.confirm.ok'),
centered: true,

View File

@ -1,5 +1,5 @@
import { RowFlex } from '@cherrystudio/ui'
import { loggerService } from '@logger'
import { HStack } from '@renderer/components/Layout'
import { TopView } from '@renderer/components/TopView'
import { searchKnowledgeBase } from '@renderer/services/KnowledgeService'
import type { FileMetadata, KnowledgeBase, KnowledgeSearchResult } from '@renderer/types'
@ -96,7 +96,7 @@ const PopupContainer: React.FC<Props> = ({ base, resolve }) => {
padding: 0
}
}}>
<HStack style={{ padding: '0 12px', marginTop: 8 }}>
<RowFlex className="mt-2 px-3">
<Input
ref={searchInputRef}
prefix={
@ -115,7 +115,7 @@ const PopupContainer: React.FC<Props> = ({ base, resolve }) => {
onChange={(e) => setSearchKeyword(e.target.value)}
onPressEnter={() => handleSearch(searchKeyword)}
/>
</HStack>
</RowFlex>
<Divider style={{ margin: 0, marginTop: 4, borderBlockStartWidth: 0.5 }} />
<ResultsContainer>

View File

@ -1,4 +1,4 @@
import { HStack } from '@renderer/components/Layout'
import { RowFlex } from '@cherrystudio/ui'
import type { ModalProps } from 'antd'
import { Menu, Modal } from 'antd'
import React, { useState } from 'react'
@ -42,7 +42,7 @@ const KnowledgeBaseFormModal: React.FC<KnowledgeBaseFormModalProps> = ({ panels,
}
}}
{...rest}>
<HStack height="100%">
<RowFlex className="h-full">
<LeftMenu>
<StyledMenu
defaultSelectedKeys={[selectedMenu]}
@ -52,7 +52,7 @@ const KnowledgeBaseFormModal: React.FC<KnowledgeBaseFormModalProps> = ({ panels,
/>
</LeftMenu>
<SettingsContentPanel>{activePanel}</SettingsContentPanel>
</HStack>
</RowFlex>
</StyledModal>
)
}

View File

@ -1,3 +1,4 @@
import { Flex } from '@cherrystudio/ui'
import { loggerService } from '@logger'
import AiProvider from '@renderer/aiCore'
import InputEmbeddingDimension from '@renderer/components/InputEmbeddingDimension'
@ -9,7 +10,7 @@ import { useProviders } from '@renderer/hooks/useProvider'
import { getModelUniqId } from '@renderer/services/ModelService'
import { selectMemoryConfig, updateMemoryConfig } from '@renderer/store/memory'
import type { Model } from '@renderer/types'
import { Flex, Form, Modal } from 'antd'
import { Form, Modal } from 'antd'
import { t } from 'i18next'
import type { FC } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
@ -167,7 +168,7 @@ const MemoriesSettingsModal: FC<MemoriesSettingsModalProps> = ({ visible, onSubm
return (
<Form.Item
label={
<Flex align="center" gap={4}>
<Flex className="items-center gap-1">
{t('memory.embedding_dimensions')}
<InfoTooltip title={t('knowledge.dimensions_size_tooltip')} />
</Flex>

View File

@ -1,7 +1,7 @@
import { RowFlex } from '@cherrystudio/ui'
import { BreadcrumbItem, Breadcrumbs } from '@heroui/react'
import { loggerService } from '@logger'
import { NavbarCenter, NavbarHeader, NavbarRight } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout'
import { useActiveNode } from '@renderer/hooks/useNotesQuery'
import { useNotesSettings } from '@renderer/hooks/useNotesSettings'
import { useShowWorkspace } from '@renderer/hooks/useShowWorkspace'
@ -161,7 +161,7 @@ const HeaderNavbar = ({ notesTree, getCurrentNoteContent, onToggleStar }) => {
<NavbarHeader
className="home-navbar"
style={{ justifyContent: 'flex-start', borderBottom: '0.5px solid var(--color-border)' }}>
<HStack alignItems="center" flex="0 0 auto">
<RowFlex className="flex-[0_0_auto] items-center">
{showWorkspace && (
<Tooltip title={t('navbar.hide_sidebar')} mouseEnterDelay={0.8}>
<NavbarIcon onClick={handleToggleShowWorkspace}>
@ -176,7 +176,7 @@ const HeaderNavbar = ({ notesTree, getCurrentNoteContent, onToggleStar }) => {
</NavbarIcon>
</Tooltip>
)}
</HStack>
</RowFlex>
<NavbarCenter style={{ flex: 1, minWidth: 0 }}>
<BreadcrumbsContainer>
<Breadcrumbs>

View File

@ -1,5 +1,5 @@
import { CodeEditor } from '@cherrystudio/ui'
import { HSpaceBetweenStack } from '@renderer/components/Layout'
import { SpaceBetweenRowFlex } from '@cherrystudio/ui'
import CodeEditor from '@renderer/components/CodeEditor'
import RichEditor from '@renderer/components/RichEditor'
import type { RichEditorRef } from '@renderer/components/RichEditor/types'
import Selector from '@renderer/components/Selector'
@ -69,7 +69,7 @@ const NotesEditor: FC<NotesEditorProps> = memo(
value={currentContent}
language="markdown"
onChange={onMarkdownChange}
height="100%"
className="h-full"
expanded={false}
style={{
height: '100%'
@ -88,14 +88,14 @@ const NotesEditor: FC<NotesEditorProps> = memo(
showTableOfContents={settings.showTableOfContents}
enableContentSearch
className="notes-rich-editor"
isFullWidth={settings.isFullWidth}
isFullWidth
fontFamily={settings.fontFamily}
fontSize={settings.fontSize}
/>
)}
</RichEditorContainer>
<BottomPanel>
<HSpaceBetweenStack width="100%" justifyContent="space-between" alignItems="center">
<SpaceBetweenRowFlex className="w-full items-center">
<TokenCount>
{t('notes.characters')}: {tokenCount}
</TokenCount>
@ -117,7 +117,7 @@ const NotesEditor: FC<NotesEditorProps> = memo(
]}
/>
</div>
</HSpaceBetweenStack>
</SpaceBetweenRowFlex>
</BottomPanel>
</>
)

View File

@ -1,11 +1,11 @@
import { PlusOutlined, RedoOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { useCache } from '@data/hooks/useCache'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import AiProvider from '@renderer/aiCore'
import IcImageUp from '@renderer/assets/images/paintings/ic_ImageUp.svg'
import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout'
import Scrollbar from '@renderer/components/Scrollbar'
import TranslateButton from '@renderer/components/TranslateButton'
import { isMac } from '@renderer/config/constant'
@ -745,12 +745,12 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => {
)
case 'switch':
return (
<HStack>
<RowFlex>
<Switch
checked={(painting[item.key!] || item.initialValue) as boolean}
onChange={(checked) => updatePaintingState({ [item.key!]: checked })}
/>
</HStack>
</RowFlex>
)
case 'image': {
return (

View File

@ -1,8 +1,8 @@
import { PlusOutlined, RedoOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { useCache } from '@data/hooks/useCache'
import DMXAPIToImg from '@renderer/assets/images/providers/DMXAPI-to-img.webp'
import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout'
import Scrollbar from '@renderer/components/Scrollbar'
import { isMac } from '@renderer/config/constant'
import { getProviderLogo } from '@renderer/config/providers'
@ -872,9 +872,9 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
return modelImageSizes.map((size) => {
return (
<Select.Option key={size.value} value={size.value}>
<HStack style={{ alignItems: 'center', gap: 8 }}>
<RowFlex className="items-center gap-2">
<span>{size.label}</span>
</HStack>
</RowFlex>
</Select.Option>
)
})
@ -882,9 +882,9 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
{/* 检查当前模型是否支持自定义尺寸 */}
{allModels.find((m) => m.id === painting.model)?.is_custom_size && (
<Select.Option value="custom" key="custom">
<HStack style={{ alignItems: 'center', gap: 8 }}>
<RowFlex className="items-center gap-2">
<span>{t('paintings.custom_size')}</span>
</HStack>
</RowFlex>
</Select.Option>
)}
</Select>
@ -892,7 +892,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
{/* 自定义尺寸输入框 */}
{isCustomSize && allModels.find((m) => m.id === painting.model)?.is_custom_size && (
<div style={{ marginTop: 10 }}>
<HStack style={{ gap: 8, alignItems: 'center' }}>
<RowFlex className="items-center gap-2">
<InputNumber
placeholder="W"
value={customWidth}
@ -913,7 +913,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
style={{ width: 80, flex: 1 }}
/>
<span style={{ color: 'var(--color-text-3)', fontSize: '11px' }}>px</span>
</HStack>
</RowFlex>
</div>
)}
@ -959,9 +959,9 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => {
<InfoIcon />
</Tooltip>
</SettingTitle>
<HStack>
<RowFlex>
<Switch checked={painting.autoCreate} onChange={(checked) => onChangeAutoCreate(checked)} />
</HStack>
</RowFlex>
</LeftContainer>
<MainContainer>
<ModeSegmentedContainer>

View File

@ -1,4 +1,5 @@
import { PlusOutlined, RedoOutlined } from '@ant-design/icons'
import { ColFlex, RowFlex } from '@cherrystudio/ui'
import { useCache } from '@data/hooks/useCache'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
@ -10,7 +11,6 @@ import ImageSize3_4 from '@renderer/assets/images/paintings/image-size-3-4.svg'
import ImageSize9_16 from '@renderer/assets/images/paintings/image-size-9-16.svg'
import ImageSize16_9 from '@renderer/assets/images/paintings/image-size-16-9.svg'
import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar'
import { HStack, VStack } from '@renderer/components/Layout'
import Scrollbar from '@renderer/components/Scrollbar'
import TranslateButton from '@renderer/components/TranslateButton'
import { isMac } from '@renderer/config/constant'
@ -389,10 +389,10 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
style={{ display: 'flex' }}>
{IMAGE_SIZES.map((size) => (
<RadioButton value={size.value} key={size.value}>
<VStack alignItems="center">
<ColFlex className="items-center">
<ImageSizeImage src={size.icon} theme={theme} />
<span>{size.label}</span>
</VStack>
</ColFlex>
</RadioButton>
))}
</Radio.Group>
@ -483,12 +483,12 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => {
<InfoIcon />
</Tooltip>
</SettingTitle>
<HStack>
<RowFlex>
<Switch
checked={painting.promptEnhancement}
onChange={(checked) => updatePaintingState({ promptEnhancement: checked })}
/>
</HStack>
</RowFlex>
</LeftContainer>
<MainContainer>
<Artboard

View File

@ -1,8 +1,8 @@
import { PlusOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { useCache } from '@data/hooks/useCache'
import AiProvider from '@renderer/aiCore'
import { Navbar, NavbarCenter, NavbarRight } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout'
import Scrollbar from '@renderer/components/Scrollbar'
import { isMac } from '@renderer/config/constant'
import { getProviderLogo } from '@renderer/config/providers'
@ -421,7 +421,7 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
{/* 自定义尺寸输入框 */}
{isCustomSize && (
<div style={{ marginTop: 10 }}>
<HStack style={{ gap: 8, alignItems: 'center' }}>
<RowFlex className="items-center gap-2">
<InputNumber
placeholder="W"
value={customWidth}
@ -442,7 +442,7 @@ const ZhipuPage: FC<{ Options: string[] }> = ({ Options }) => {
style={{ width: 80, flex: 1 }}
/>
<span style={{ color: 'var(--color-text-2)', fontSize: '12px' }}>px</span>
</HStack>
</RowFlex>
<div style={{ marginTop: 5, fontSize: '12px', color: 'var(--color-text-3)' }}>
512px-2048px之间, 16, 2^21px
</div>

View File

@ -1,7 +1,7 @@
import { GithubOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import IndicatorLight from '@renderer/components/IndicatorLight'
import { HStack } from '@renderer/components/Layout'
import { APP_NAME, AppLogo } from '@renderer/config/env'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useAppUpdateState } from '@renderer/hooks/useAppUpdate'
@ -186,11 +186,11 @@ const AboutSettings: FC = () => {
<SettingGroup theme={theme}>
<SettingTitle>
{t('settings.about.title')}
<HStack alignItems="center">
<RowFlex className="items-center">
<Link to="https://github.com/CherryHQ/cherry-studio">
<GithubOutlined style={{ marginRight: 4, color: 'var(--color-text)', fontSize: 20 }} />
</Link>
</HStack>
</RowFlex>
</SettingTitle>
<SettingDivider />
<AboutHeader>

View File

@ -1,5 +1,5 @@
import { CheckOutlined } from '@ant-design/icons'
import { Box } from '@renderer/components/Layout'
import { Box } from '@cherrystudio/ui'
import { useAppSelector } from '@renderer/store'
import type { Assistant, AssistantSettings } from '@renderer/types'
import type { SelectProps } from 'antd'
@ -31,9 +31,7 @@ const AssistantKnowledgeBaseSettings: React.FC<Props> = ({ assistant, updateAssi
return (
<Container>
<Box mb={8} style={{ fontWeight: 'bold' }}>
{t('common.knowledge_base')}
</Box>
<Box className="mb-2 font-bold">{t('common.knowledge_base')}</Box>
<Select
mode="multiple"
allowClear

View File

@ -1,5 +1,5 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { Box } from '@renderer/components/Layout'
import { Box } from '@cherrystudio/ui'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import type { Assistant, AssistantSettings } from '@renderer/types'
import { Empty, Switch, Tooltip } from 'antd'

View File

@ -1,6 +1,6 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { Box } from '@cherrystudio/ui'
import { loggerService } from '@logger'
import { Box } from '@renderer/components/Layout'
import MemoriesSettingsModal from '@renderer/pages/memory/settings-modal'
import MemoryService from '@renderer/services/MemoryService'
import { selectGlobalMemoryEnabled, selectMemoryConfig } from '@renderer/store/memory'

View File

@ -1,8 +1,8 @@
import { QuestionCircleOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
import EditableNumber from '@renderer/components/EditableNumber'
import { DeleteIcon, ResetIcon } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout'
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
import Selector from '@renderer/components/Selector'
import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE, MAX_CONTEXT_COUNT } from '@renderer/config/constant'
@ -216,9 +216,9 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
return (
<Container>
<HStack alignItems="center" justifyContent="space-between" style={{ marginBottom: 10 }}>
<RowFlex className="mb-2.5 items-center justify-between">
<Label>{t('assistants.settings.default_model')}</Label>
<HStack alignItems="center" gap={5}>
<RowFlex className="items-center gap-[5px]">
<ModelSelectButton
icon={defaultModel ? <ModelAvatar model={defaultModel} size={20} /> : <PlusIcon size={18} />}
onClick={onSelectModel}>
@ -236,19 +236,19 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
danger
/>
)}
</HStack>
</HStack>
</RowFlex>
</RowFlex>
<Divider style={{ margin: '10px 0' }} />
<SettingRow style={{ minHeight: 30 }}>
<HStack alignItems="center">
<RowFlex className="items-center">
<Label>
{t('chat.settings.temperature.label')}
<Tooltip title={t('chat.settings.temperature.tip')}>
<QuestionIcon />
</Tooltip>
</Label>
</HStack>
</RowFlex>
<Switch
checked={enableTemperature}
onChange={(enabled) => {
@ -291,12 +291,12 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
<Divider style={{ margin: '10px 0' }} />
<SettingRow style={{ minHeight: 30 }}>
<HStack alignItems="center">
<RowFlex className="items-center">
<Label>{t('chat.settings.top_p.label')}</Label>
<Tooltip title={t('chat.settings.top_p.tip')}>
<QuestionIcon />
</Tooltip>
</HStack>
</RowFlex>
<Switch
checked={enableTopP}
onChange={(enabled) => {
@ -380,12 +380,12 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
</Row>
<Divider style={{ margin: '10px 0' }} />
<SettingRow style={{ minHeight: 30 }}>
<HStack alignItems="center">
<RowFlex className="items-center">
<Label>{t('chat.settings.max_tokens.label')}</Label>
<Tooltip title={t('chat.settings.max_tokens.tip')}>
<QuestionIcon />
</Tooltip>
</HStack>
</RowFlex>
<Switch
checked={enableMaxTokens}
onChange={async (enabled) => {
@ -492,11 +492,11 @@ const AssistantModelSettings: FC<Props> = ({ assistant, updateAssistant, updateA
</Row>
))}
<Divider style={{ margin: '15px 0' }} />
<HStack justifyContent="flex-end">
<RowFlex className="justify-end">
<Button onClick={onReset} danger type="primary" icon={<ResetIcon size={16} />}>
{t('chat.settings.reset')}
</Button>
</HStack>
</RowFlex>
</Container>
)
}

View File

@ -1,10 +1,10 @@
import 'emoji-picker-element'
import { CloseCircleFilled } from '@ant-design/icons'
import { Box, RowFlex, SpaceBetweenRowFlex } from '@cherrystudio/ui'
import { CodeEditor } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import EmojiPicker from '@renderer/components/EmojiPicker'
import { Box, HSpaceBetweenStack, HStack } from '@renderer/components/Layout'
import type { RichEditorRef } from '@renderer/components/RichEditor/types'
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
import { usePromptProcessor } from '@renderer/hooks/usePromptProcessor'
@ -73,10 +73,8 @@ const AssistantPromptSettings: React.FC<Props> = ({ assistant, updateAssistant }
return (
<Container>
<Box mb={8} style={{ fontWeight: 'bold' }}>
{t('common.name')}
</Box>
<HStack gap={8} alignItems="center">
<Box className="mb-2 font-bold">{t('common.name')}</Box>
<RowFlex className="items-center gap-2">
<Popover content={<EmojiPicker onEmojiClick={handleEmojiSelect} />} arrow trigger="click">
<EmojiButtonWrapper>
<Button
@ -115,14 +113,14 @@ const AssistantPromptSettings: React.FC<Props> = ({ assistant, updateAssistant }
onBlur={onUpdate}
style={{ flex: 1 }}
/>
</HStack>
</RowFlex>
<SettingDivider />
<HStack mb={8} alignItems="center" gap={4}>
<RowFlex className="mb-2 items-center gap-1">
<Box style={{ fontWeight: 'bold' }}>{t('common.prompt')}</Box>
<Popover title={t('agents.add.prompt.variables.tip.title')} content={promptVarsContent}>
<HelpCircle size={14} color="var(--color-text-2)" />
</Popover>
</HStack>
</RowFlex>
<TextAreaContainer>
<RichEditorContainer>
{showPreview ? (
@ -141,7 +139,7 @@ const AssistantPromptSettings: React.FC<Props> = ({ assistant, updateAssistant }
value={prompt}
language="markdown"
onChange={setPrompt}
height="100%"
className="h-full"
expanded={false}
style={{
height: '100%'
@ -150,7 +148,7 @@ const AssistantPromptSettings: React.FC<Props> = ({ assistant, updateAssistant }
)}
</RichEditorContainer>
</TextAreaContainer>
<HSpaceBetweenStack width="100%" justifyContent="flex-end" mt="10px">
<SpaceBetweenRowFlex className="mt-2.5 w-full justify-end">
<TokenCount>Tokens: {tokenCount}</TokenCount>
<Button
type="primary"
@ -170,7 +168,7 @@ const AssistantPromptSettings: React.FC<Props> = ({ assistant, updateAssistant }
}}>
{showPreview ? t('common.edit') : t('common.save')}
</Button>
</HSpaceBetweenStack>
</SpaceBetweenRowFlex>
</Container>
)
}

View File

@ -1,9 +1,10 @@
import { ExclamationCircleOutlined } from '@ant-design/icons'
import { Flex } from '@cherrystudio/ui'
import { DraggableList } from '@renderer/components/DraggableList'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import FileItem from '@renderer/pages/files/FileItem'
import type { Assistant, QuickPhrase } from '@renderer/types'
import { Button, Flex, Input, Modal, Popconfirm, Space } from 'antd'
import { Button, Input, Modal, Popconfirm, Space } from 'antd'
import { PlusIcon } from 'lucide-react'
import type { FC } from 'react'
import { useEffect, useState } from 'react'
@ -104,7 +105,7 @@ const AssistantRegularPromptsSettings: FC<AssistantRegularPromptsSettingsProps>
ext: '.txt',
extra: prompt.content,
actions: (
<Flex gap={4} style={{ opacity: 0.6 }}>
<Flex className="gap-1 opacity-60">
<Button key="edit" type="text" icon={<EditIcon size={14} />} onClick={() => handleEdit(prompt)} />
<Popconfirm
title={t('assistants.settings.regular_phrases.delete', 'Delete Prompt')}

View File

@ -1,4 +1,4 @@
import { HStack } from '@renderer/components/Layout'
import { RowFlex } from '@cherrystudio/ui'
import { TopView } from '@renderer/components/TopView'
import { useAgent } from '@renderer/hooks/useAgents'
import { useAssistant } from '@renderer/hooks/useAssistant'
@ -111,7 +111,7 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, tab, ...prop
width="min(800px, 70vw)"
height="80vh"
centered>
<HStack>
<RowFlex>
<LeftMenu>
<StyledMenu
defaultSelectedKeys={[tab || 'prompt']}
@ -161,7 +161,7 @@ const AssistantSettingPopupContainer: React.FC<Props> = ({ resolve, tab, ...prop
/>
)}
</Settings>
</HStack>
</RowFlex>
</StyledModal>
)
}

View File

@ -1,4 +1,4 @@
import { HStack } from '@renderer/components/Layout'
import { RowFlex } from '@cherrystudio/ui'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store'
@ -29,15 +29,15 @@ const AgentsSubscribeUrlSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.tool.websearch.subscribe_url')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Input
type="text"
value={agentssubscribeUrl || ''}
onChange={handleAgentChange}
style={{ width: 315 }}
className="w-[315px]"
placeholder={t('settings.tool.websearch.subscribe_name.placeholder')}
/>
</HStack>
</RowFlex>
</SettingRow>
</SettingGroup>
)

View File

@ -5,10 +5,10 @@ import {
LoadingOutlined,
YuqueOutlined
} from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import DividerWithText from '@renderer/components/DividerWithText'
import { NutstoreIcon } from '@renderer/components/Icons/NutstoreIcons'
import { HStack } from '@renderer/components/Layout'
import ListItem from '@renderer/components/ListItem'
import BackupPopup from '@renderer/components/Popups/BackupPopup'
import RestorePopup from '@renderer/components/Popups/RestorePopup'
@ -600,14 +600,14 @@ const DataSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
<HStack gap="5px" justifyContent="space-between">
<RowFlex className="justify-between gap-[5px]">
<Button onClick={BackupPopup.show} icon={<SaveIcon size={14} />}>
{t('settings.general.backup.button')}
</Button>
<Button onClick={RestorePopup.show} icon={<FolderOpen size={14} />}>
{t('settings.general.restore.button')}
</Button>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>
@ -630,9 +630,9 @@ const DataSettings: FC = () => {
{appInfo?.appDataPath}
</PathText>
<StyledIcon onClick={() => handleOpenPath(appInfo?.appDataPath)} style={{ flexShrink: 0 }} />
<HStack gap="5px" style={{ marginLeft: '8px' }}>
<RowFlex className="ml-2 gap-[5px]">
<Button onClick={handleSelectAppDataPath}>{t('settings.data.app_data.select')}</Button>
</HStack>
</RowFlex>
</PathRow>
</SettingRow>
<SettingDivider />
@ -643,19 +643,19 @@ const DataSettings: FC = () => {
{appInfo?.logsPath}
</PathText>
<StyledIcon onClick={() => handleOpenPath(appInfo?.logsPath)} style={{ flexShrink: 0 }} />
<HStack gap="5px" style={{ marginLeft: '8px' }}>
<RowFlex className="ml-2 gap-[5px]">
<Button onClick={() => handleOpenPath(appInfo?.logsPath)}>
{t('settings.data.app_logs.button')}
</Button>
</HStack>
</RowFlex>
</PathRow>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.app_knowledge.label')}</SettingRowTitle>
<HStack alignItems="center" gap="5px">
<RowFlex className="items-center gap-[5px]">
<Button onClick={handleRemoveAllFiles}>{t('settings.data.app_knowledge.button.delete')}</Button>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>
@ -663,18 +663,18 @@ const DataSettings: FC = () => {
{t('settings.data.clear_cache.title')}
{cacheSize && <CacheText>({cacheSize}MB)</CacheText>}
</SettingRowTitle>
<HStack gap="5px">
<RowFlex className="gap-[5px]">
<Button onClick={handleClearCache}>{t('settings.data.clear_cache.button')}</Button>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.reset.title')}</SettingRowTitle>
<HStack gap="5px">
<RowFlex className="gap-[5px]">
<Button onClick={reset} danger>
{t('settings.general.reset.title')}
</Button>
</HStack>
</RowFlex>
</SettingRow>
</SettingGroup>
</>
@ -696,7 +696,7 @@ const DataSettings: FC = () => {
)
}
const Container = styled(HStack)`
const Container = styled(RowFlex)`
flex: 1;
`
@ -751,7 +751,7 @@ const PathText = styled(Typography.Text)`
cursor: pointer;
`
const PathRow = styled(HStack)`
const PathRow = styled(RowFlex)`
min-width: 0;
flex: 1;
width: 0;

View File

@ -1,6 +1,6 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { Button, Space, Switch, Tooltip } from 'antd'
@ -80,16 +80,16 @@ const JoplinSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.joplin.url')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Input
type="text"
value={joplinUrl || ''}
onChange={handleJoplinUrlChange}
onBlur={handleJoplinUrlBlur}
style={{ width: 315 }}
className="w-[315px]"
placeholder={t('settings.data.joplin.url_placeholder')}
/>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>
@ -102,7 +102,7 @@ const JoplinSettings: FC = () => {
/>
</Tooltip>
</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Space.Compact style={{ width: '100%' }}>
<Input.Password
value={joplinToken || ''}
@ -113,7 +113,7 @@ const JoplinSettings: FC = () => {
/>
<Button onClick={handleJoplinConnectionCheck}>{t('settings.data.joplin.check.button')}</Button>
</Space.Compact>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>

View File

@ -1,7 +1,7 @@
import { DeleteOutlined, FolderOpenOutlined, SaveOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import { HStack } from '@renderer/components/Layout'
import { LocalBackupManager } from '@renderer/components/LocalBackupManager'
import { LocalBackupModal, useLocalBackupModal } from '@renderer/components/LocalBackupModals'
import Selector from '@renderer/components/Selector'
@ -151,7 +151,7 @@ const LocalBackupSettings: React.FC = () => {
}
return (
<HStack gap="5px" alignItems="center">
<RowFlex className="items-center gap-[5px]">
{localBackupSync.syncing && <SyncOutlined spin />}
{!localBackupSync.syncing && localBackupSync.lastSyncError && (
<Tooltip title={`${t('settings.data.local.syncError')}: ${localBackupSync.lastSyncError}`}>
@ -163,7 +163,7 @@ const LocalBackupSettings: React.FC = () => {
{t('settings.data.local.lastSync')}: {dayjs(localBackupSync.lastSyncTime).format('HH:mm:ss')}
</span>
)}
</HStack>
</RowFlex>
)
}
@ -184,7 +184,7 @@ const LocalBackupSettings: React.FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.local.directory.label')}</SettingRowTitle>
<HStack gap="5px">
<RowFlex className="gap-[5px]">
<Input
value={localBackupDir}
onChange={(e) => setLocalBackupDir(e.target.value)}
@ -198,19 +198,19 @@ const LocalBackupSettings: React.FC = () => {
<Button icon={<DeleteOutlined />} onClick={handleClearDirectory} disabled={!localBackupDir} danger>
{t('common.clear')}
</Button>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
<HStack gap="5px" justifyContent="space-between">
<RowFlex className="justify-between gap-[5px]">
<Button onClick={showBackupModal} icon={<SaveOutlined />} loading={backuping} disabled={!localBackupDir}>
{t('settings.data.local.backup.button')}
</Button>
<Button onClick={showBackupManager} icon={<FolderOpenOutlined />} disabled={!localBackupDir}>
{t('settings.data.local.restore.button')}
</Button>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>

View File

@ -1,6 +1,6 @@
import { DeleteOutlined, FolderOpenOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { Button, Switch } from 'antd'
import Input from 'antd/es/input/Input'
@ -72,7 +72,7 @@ const MarkdownExportSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.markdown_export.path')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Input
type="text"
value={markdownExportPath || ''}
@ -88,7 +88,7 @@ const MarkdownExportSettings: FC = () => {
<Button onClick={handleSelectFolder} icon={<FolderOpenOutlined />}>
{t('settings.data.markdown_export.select')}
</Button>
</HStack>
</RowFlex>
</SettingRow>
<SettingRow>
<SettingHelpText>{t('settings.data.markdown_export.help')}</SettingHelpText>

View File

@ -1,7 +1,7 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { Client } from '@notionhq/client'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { Button, Space, Switch, Tooltip } from 'antd'
@ -84,35 +84,33 @@ const NotionSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.notion.database_id')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Input
type="text"
value={notionDatabaseID || ''}
onChange={handleNotionDatabaseIdChange}
onBlur={handleNotionDatabaseIdChange}
style={{ width: 315 }}
placeholder={t('settings.data.notion.database_id_placeholder')}
/>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.notion.page_name_key')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Input
type="text"
value={notionPageNameKey || ''}
onChange={handleNotionPageNameKeyChange}
onBlur={handleNotionPageNameKeyChange}
style={{ width: 315 }}
placeholder={t('settings.data.notion.page_name_key_placeholder')}
/>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.notion.api_key')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Space.Compact style={{ width: '100%' }}>
<Input.Password
value={notionApiKey || ''}
@ -123,7 +121,7 @@ const NotionSettings: FC = () => {
/>
<Button onClick={handleNotionConnectionCheck}>{t('settings.data.notion.check.button')}</Button>
</Space.Compact>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>

View File

@ -1,6 +1,6 @@
import { CheckOutlined, FolderOutlined, LoadingOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import NutstorePathPopup from '@renderer/components/Popups/NutsorePathPopup'
import Selector from '@renderer/components/Selector'
import { WebdavBackupManager } from '@renderer/components/WebdavBackupManager'
@ -173,7 +173,7 @@ const NutstoreSettings: FC = () => {
}
return (
<HStack gap="5px" alignItems="center">
<RowFlex className="items-center gap-[5px]">
{nutstoreSyncState.syncing && <SyncOutlined spin />}
{!nutstoreSyncState.syncing && nutstoreSyncState.lastSyncError && (
<Tooltip title={`${t('settings.data.webdav.syncError')}: ${nutstoreSyncState.lastSyncError}`}>
@ -185,7 +185,7 @@ const NutstoreSettings: FC = () => {
{t('settings.data.webdav.lastSync')}: {dayjs(nutstoreSyncState.lastSyncTime).format('HH:mm:ss')}
</span>
)}
</HStack>
</RowFlex>
)
}
@ -208,7 +208,7 @@ const NutstoreSettings: FC = () => {
{isLogin ? t('settings.data.nutstore.isLogin') : t('settings.data.nutstore.notLogin')}
</SettingRowTitle>
{isLogin ? (
<HStack gap="5px" justifyContent="space-between" alignItems="center">
<RowFlex className="items-center justify-between gap-[5px]">
<Button
type={nsConnected ? 'primary' : 'default'}
ghost={nsConnected}
@ -225,7 +225,7 @@ const NutstoreSettings: FC = () => {
<Button type="primary" danger onClick={handleLayout}>
{t('settings.data.nutstore.logout.button')}
</Button>
</HStack>
</RowFlex>
) : (
<Button onClick={handleClickNutstoreSSO}>{t('settings.data.nutstore.login.button')}</Button>
)}
@ -241,7 +241,7 @@ const NutstoreSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.nutstore.path.label')}</SettingRowTitle>
<HStack gap="4px" justifyContent="space-between">
<RowFlex className="justify-between gap-1">
<Input
placeholder={t('settings.data.nutstore.path.placeholder')}
style={{ width: 250 }}
@ -253,19 +253,19 @@ const NutstoreSettings: FC = () => {
<Button type="default" onClick={handleClickPathChange}>
<FolderOutlined />
</Button>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
<HStack gap="5px" justifyContent="space-between">
<RowFlex className="justify-between gap-[5px]">
<Button onClick={showBackupModal} loading={backuping}>
{t('settings.data.nutstore.backup.button')}
</Button>
<Button onClick={showBackupManager} disabled={!nutstoreToken}>
{t('settings.data.nutstore.restore.button')}
</Button>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>

View File

@ -1,6 +1,6 @@
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import { HStack } from '@renderer/components/Layout'
import { Empty, Select, Spin } from 'antd'
import type { FC } from 'react'
import { useEffect, useState } from 'react'
@ -62,7 +62,7 @@ const ObsidianSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.obsidian.default_vault')}</SettingRowTitle>
<HStack gap="5px">
<RowFlex className="gap-[5px]">
<Spin spinning={loading} size="small">
{vaults.length > 0 ? (
<Select
@ -87,7 +87,7 @@ const ObsidianSettings: FC = () => {
/>
)}
</Spin>
</HStack>
</RowFlex>
</SettingRow>
</SettingGroup>
)

View File

@ -1,6 +1,6 @@
import { FolderOpenOutlined, InfoCircleOutlined, SaveOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import { S3BackupManager } from '@renderer/components/S3BackupManager'
import { S3BackupModal, useS3BackupModal } from '@renderer/components/S3Modals'
import Selector from '@renderer/components/Selector'
@ -71,7 +71,7 @@ const S3Settings: FC = () => {
}
return (
<HStack gap="5px" alignItems="center">
<RowFlex className="items-center gap-[5px]">
{s3Sync?.syncing && <SyncOutlined spin />}
{!s3Sync?.syncing && s3Sync?.lastSyncError && (
<Tooltip title={t('settings.data.s3.syncStatus.error', { message: s3Sync.lastSyncError })}>
@ -83,7 +83,7 @@ const S3Settings: FC = () => {
{t('settings.data.s3.syncStatus.lastSync', { time: dayjs(s3Sync.lastSyncTime).format('HH:mm:ss') })}
</span>
)}
</HStack>
</RowFlex>
)
}
@ -177,7 +177,7 @@ const S3Settings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.s3.backup.operation')}</SettingRowTitle>
<HStack gap="5px" justifyContent="space-between">
<RowFlex className="justify-between gap-[5px]">
<Button
onClick={showBackupModal}
icon={<SaveOutlined />}
@ -191,7 +191,7 @@ const S3Settings: FC = () => {
disabled={!s3Endpoint || !s3Region || !s3Bucket || !s3AccessKeyId || !s3SecretAccessKey}>
{t('settings.data.s3.backup.manager.button')}
</Button>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>

View File

@ -1,7 +1,7 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { Button, Space, Tooltip } from 'antd'
@ -86,15 +86,14 @@ const SiyuanSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.siyuan.api_url')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Input
type="text"
value={siyuanApiUrl || ''}
onChange={handleApiUrlChange}
style={{ width: 315 }}
placeholder={t('settings.data.siyuan.api_url_placeholder')}
/>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>
@ -107,7 +106,7 @@ const SiyuanSettings: FC = () => {
/>
</Tooltip>
</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Space.Compact style={{ width: '100%' }}>
<Input.Password
value={siyuanToken || ''}
@ -118,33 +117,31 @@ const SiyuanSettings: FC = () => {
/>
<Button onClick={handleCheckConnection}>{t('settings.data.siyuan.check.button')}</Button>
</Space.Compact>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.siyuan.box_id')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Input
type="text"
value={siyuanBoxId || ''}
onChange={handleBoxIdChange}
style={{ width: 315 }}
placeholder={t('settings.data.siyuan.box_id_placeholder')}
/>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.siyuan.root_path')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Input
type="text"
value={siyuanRootPath || ''}
onChange={handleRootPathChange}
style={{ width: 315 }}
placeholder={t('settings.data.siyuan.root_path_placeholder')}
/>
</HStack>
</RowFlex>
</SettingRow>
</SettingGroup>
)

View File

@ -1,6 +1,6 @@
import { FolderOpenOutlined, SaveOutlined, SyncOutlined, WarningOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import Selector from '@renderer/components/Selector'
import { WebdavBackupManager } from '@renderer/components/WebdavBackupManager'
import { useWebdavBackupModal, WebdavBackupModal } from '@renderer/components/WebdavModals'
@ -67,7 +67,7 @@ const WebDavSettings: FC = () => {
}
return (
<HStack gap="5px" alignItems="center">
<RowFlex className="items-center gap-[5px]">
{webdavSync.syncing && <SyncOutlined spin />}
{!webdavSync.syncing && webdavSync.lastSyncError && (
<Tooltip title={`${t('settings.data.webdav.syncError')}: ${webdavSync.lastSyncError}`}>
@ -79,7 +79,7 @@ const WebDavSettings: FC = () => {
{t('settings.data.webdav.lastSync')}: {dayjs(webdavSync.lastSyncTime).format('HH:mm:ss')}
</span>
)}
</HStack>
</RowFlex>
)
}
@ -145,14 +145,14 @@ const WebDavSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
<HStack gap="5px" justifyContent="space-between">
<RowFlex className="justify-between gap-[5px]">
<Button onClick={showBackupModal} icon={<SaveOutlined />} loading={backuping}>
{t('settings.data.webdav.backup.button')}
</Button>
<Button onClick={showBackupManager} icon={<FolderOpenOutlined />} disabled={!webdavHost}>
{t('settings.data.webdav.restore.button')}
</Button>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>

View File

@ -1,6 +1,6 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { Button, Space, Tooltip } from 'antd'
@ -76,15 +76,14 @@ const YuqueSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.yuque.repo_url')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Input
type="text"
value={yuqueUrl || ''}
onChange={handleYuqueRepoUrlChange}
style={{ width: 315 }}
placeholder={t('settings.data.yuque.repo_url_placeholder')}
/>
</HStack>
</RowFlex>
</SettingRow>
<SettingDivider />
<SettingRow>
@ -97,7 +96,7 @@ const YuqueSettings: FC = () => {
/>
</Tooltip>
</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<RowFlex className="w-[315px] items-center gap-[5px]">
<Space.Compact style={{ width: '100%' }}>
<Input.Password
value={yuqueToken || ''}
@ -108,7 +107,7 @@ const YuqueSettings: FC = () => {
/>
<Button onClick={handleYuqueConnectionCheck}>{t('settings.data.yuque.check.button')}</Button>
</Space.Compact>
</HStack>
</RowFlex>
</SettingRow>
</SettingGroup>
)

View File

@ -1,7 +1,7 @@
import { RowFlex } from '@cherrystudio/ui'
import { CodeEditor } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { ResetIcon } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout'
import TextBadge from '@renderer/components/TextBadge'
import { isMac, THEME_COLOR_PRESETS } from '@renderer/config/constant'
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
@ -196,8 +196,8 @@ const DisplaySettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.theme.color_primary')}</SettingRowTitle>
<HStack gap="12px" alignItems="center">
<HStack gap="12px">
<RowFlex className="items-center gap-3">
<RowFlex className="gap-3">
{THEME_COLOR_PRESETS.map((color) => (
<ColorCircleWrapper key={color}>
<ColorCircle
@ -207,7 +207,7 @@ const DisplaySettings: FC = () => {
/>
</ColorCircleWrapper>
))}
</HStack>
</RowFlex>
<ColorPicker
style={{ fontFamily: 'inherit' }}
className="color-picker"
@ -222,7 +222,7 @@ const DisplaySettings: FC = () => {
}
]}
/>
</HStack>
</RowFlex>
</SettingRow>
{isMac && (
<>

View File

@ -1,11 +1,12 @@
// import { loggerService } from '@logger'
import { Flex } from '@cherrystudio/ui'
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
import { isMac, isWin } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useOcrProviders } from '@renderer/hooks/useOcrProvider'
import type { OcrProvider } from '@renderer/types'
import { isBuiltinOcrProvider, isOcrSystemProvider } from '@renderer/types'
import { Divider, Flex } from 'antd'
import { Divider } from 'antd'
import styled from 'styled-components'
import { SettingGroup, SettingTitle } from '..'
@ -47,7 +48,7 @@ const OcrProviderSettings = ({ provider }: Props) => {
return (
<SettingGroup theme={themeMode}>
<SettingTitle>
<Flex align="center" gap={8}>
<Flex className="items-center gap-2">
<OcrProviderLogo provider={provider} />
<ProviderName> {getOcrProviderName(provider)}</ProviderName>
</Flex>

View File

@ -1,4 +1,5 @@
// import { loggerService } from '@logger'
import { Flex } from '@cherrystudio/ui'
import { SuccessTag } from '@renderer/components/Tags/SuccessTag'
import { InfoTooltip } from '@renderer/components/TooltipIcons'
import { isMac, isWin } from '@renderer/config/constant'
@ -6,7 +7,7 @@ import { useOcrProvider } from '@renderer/hooks/useOcrProvider'
import useTranslate from '@renderer/hooks/useTranslate'
import type { TranslateLanguageCode } from '@renderer/types'
import { BuiltinOcrProviderIds, isOcrSystemProvider } from '@renderer/types'
import { Flex, Select } from 'antd'
import { Select } from 'antd'
import { startTransition, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -54,7 +55,7 @@ export const OcrSystemSettings = () => {
<>
<SettingRow>
<SettingRowTitle>
<Flex align="center" gap={4}>
<Flex className="items-center gap-1">
{t('settings.tool.ocr.common.langs')}
{isWin && <InfoTooltip title={t('settings.tool.ocr.system.win.langs_tooltip')} />}
</Flex>

View File

@ -1,4 +1,5 @@
// import { loggerService } from '@logger'
import { Flex } from '@cherrystudio/ui'
import CustomTag from '@renderer/components/Tags/CustomTag'
import { InfoTooltip } from '@renderer/components/TooltipIcons'
import { TESSERACT_LANG_MAP } from '@renderer/config/ocr'
@ -6,7 +7,7 @@ import { useOcrProvider } from '@renderer/hooks/useOcrProvider'
import useTranslate from '@renderer/hooks/useTranslate'
import type { TesseractLangCode } from '@renderer/types'
import { BuiltinOcrProviderIds, isOcrTesseractProvider } from '@renderer/types'
import { Flex, Select } from 'antd'
import { Select } from 'antd'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -63,7 +64,7 @@ export const OcrTesseractSettings = () => {
<>
<SettingRow>
<SettingRowTitle>
<Flex align="center" gap={4}>
<Flex className="items-center gap-1">
{t('settings.tool.ocr.common.langs')}
<InfoTooltip title={t('settings.tool.ocr.tesseract.langs_tooltip')} />
</Flex>

View File

@ -1,10 +1,11 @@
import { ExportOutlined } from '@ant-design/icons'
import { Flex } from '@cherrystudio/ui'
import { ApiKeyListPopup } from '@renderer/components/Popups/ApiKeyListPopup'
import { getPreprocessProviderLogo, PREPROCESS_PROVIDER_CONFIG } from '@renderer/config/preprocessProviders'
import { usePreprocessProvider } from '@renderer/hooks/usePreprocess'
import type { PreprocessProvider } from '@renderer/types'
import { formatApiKeys, hasObjectKey } from '@renderer/utils'
import { Avatar, Button, Divider, Flex, Input, Tooltip } from 'antd'
import { Avatar, Button, Divider, Input, Tooltip } from 'antd'
import Link from 'antd/es/typography/Link'
import { List } from 'lucide-react'
import type { FC } from 'react'
@ -70,7 +71,7 @@ const PreprocessProviderSettings: FC<Props> = ({ provider: _provider }) => {
return (
<>
<SettingTitle>
<Flex align="center" gap={8}>
<Flex className="items-center gap-2">
<ProviderLogo shape="square" src={getPreprocessProviderLogo(preprocessProvider.id)} size={16} />
<ProviderName> {preprocessProvider.name}</ProviderName>
@ -97,7 +98,7 @@ const PreprocessProviderSettings: FC<Props> = ({ provider: _provider }) => {
<Button type="text" size="small" onClick={openApiKeyList} icon={<List size={14} />} />
</Tooltip>
</SettingSubtitle>
<Flex gap={8}>
<Flex className="gap-2">
<Input.Password
value={apiKey}
placeholder={

View File

@ -1,6 +1,7 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui'
import { Flex } from '@cherrystudio/ui'
import { useMultiplePreferences, usePreference } from '@data/hooks/usePreference'
import { HStack } from '@renderer/components/Layout'
import Selector from '@renderer/components/Selector'
import { InfoTooltip } from '@renderer/components/TooltipIcons'
import { useTheme } from '@renderer/context/ThemeProvider'
@ -11,7 +12,7 @@ import { isValidProxyUrl } from '@renderer/utils'
import { formatErrorMessage } from '@renderer/utils/error'
import { defaultByPassRules, defaultLanguage } from '@shared/config/constant'
import type { LanguageVarious } from '@shared/data/preference/preferenceTypes'
import { Flex, Input, Switch, Tooltip } from 'antd'
import { Input, Switch, Tooltip } from 'antd'
import type { FC } from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -185,7 +186,7 @@ const GeneralSettings: FC = () => {
onChange={onSelectLanguage}
options={languagesOptions.map((lang) => ({
label: (
<Flex align="center" gap={8}>
<Flex className="items-center gap-2">
<span role="img" aria-label={lang.flag}>
{lang.flag}
</span>
@ -236,7 +237,7 @@ const GeneralSettings: FC = () => {
)}
<SettingDivider />
<SettingRow>
<HStack justifyContent="space-between" alignItems="center" style={{ flex: 1, marginRight: 16 }}>
<RowFlex className="mr-4 flex-1 items-center justify-between">
<SettingRowTitle>{t('settings.general.spell_check.label')}</SettingRowTitle>
{enableSpellCheck && (
<Selector<string>
@ -248,7 +249,7 @@ const GeneralSettings: FC = () => {
options={spellCheckLanguageOptions.map((lang) => ({
value: lang.value,
label: (
<Flex align="center" gap={8}>
<Flex className="items-center gap-2">
<span role="img" aria-label={lang.flag}>
{lang.flag}
</span>
@ -258,7 +259,7 @@ const GeneralSettings: FC = () => {
}))}
/>
)}
</HStack>
</RowFlex>
<Switch checked={enableSpellCheck} onChange={handleSpellCheckChange} />
</SettingRow>
<SettingDivider />
@ -334,7 +335,7 @@ const GeneralSettings: FC = () => {
<SettingTitle>{t('settings.developer.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<Flex align="center" gap={4}>
<Flex className="items-center gap-1">
<SettingRowTitle>{t('settings.developer.enable_developer_mode')}</SettingRowTitle>
<InfoTooltip title={t('settings.developer.help')} />
</Flex>

View File

@ -1,5 +1,5 @@
import { CheckCircleOutlined, QuestionCircleOutlined, WarningOutlined } from '@ant-design/icons'
import { Center, VStack } from '@renderer/components/Layout'
import { Center, ColFlex } from '@cherrystudio/ui'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setIsBunInstalled, setIsUvInstalled } from '@renderer/store/mcp'
import { Alert, Button } from 'antd'
@ -112,7 +112,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
banner
style={{ borderRadius: 'var(--list-item-border-radius)' }}
description={
<VStack>
<ColFlex>
<SettingRow style={{ width: '100%' }}>
<SettingSubtitle style={{ margin: 0, fontWeight: 'normal' }}>
{isUvInstalled ? 'UV Installed' : `UV ${t('settings.mcp.missingDependencies')}`}
@ -135,7 +135,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
{uvPath}
</SettingDescription>
</SettingRow>
</VStack>
</ColFlex>
}
/>
<Alert
@ -143,7 +143,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
banner
style={{ borderRadius: 'var(--list-item-border-radius)' }}
description={
<VStack>
<ColFlex>
<SettingRow style={{ width: '100%' }}>
<SettingSubtitle style={{ margin: 0, fontWeight: 'normal' }}>
{isBunInstalled ? 'Bun Installed' : `Bun ${t('settings.mcp.missingDependencies')}`}
@ -166,7 +166,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
{bunPath}
</SettingDescription>
</SettingRow>
</VStack>
</ColFlex>
}
/>
<Center>

View File

@ -1,5 +1,6 @@
import { ColFlex, Flex } from '@cherrystudio/ui'
import type { MCPPrompt } from '@renderer/types'
import { Collapse, Descriptions, Empty, Flex, Tooltip, Typography } from 'antd'
import { Collapse, Descriptions, Empty, Tooltip, Typography } from 'antd'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -22,7 +23,7 @@ const MCPPromptsSection = ({ prompts }: MCPPromptsSectionProps) => {
<Descriptions.Item
key={index}
label={
<Flex gap={4}>
<Flex className="gap-1">
<Typography.Text strong>{arg.name}</Typography.Text>
{arg.required && (
<Tooltip title="Required field">
@ -31,13 +32,13 @@ const MCPPromptsSection = ({ prompts }: MCPPromptsSectionProps) => {
)}
</Flex>
}>
<Flex vertical gap={4}>
<ColFlex className="gap-1">
{arg.description && (
<Typography.Paragraph type="secondary" style={{ marginBottom: 0, marginTop: 4 }}>
{arg.description}
</Typography.Paragraph>
)}
</Flex>
</ColFlex>
</Descriptions.Item>
))}
</Descriptions>
@ -54,8 +55,8 @@ const MCPPromptsSection = ({ prompts }: MCPPromptsSectionProps) => {
<Collapse.Panel
key={prompt.id || prompt.name}
header={
<Flex vertical align="flex-start">
<Flex align="center" style={{ width: '100%' }}>
<ColFlex className="items-start">
<Flex className="w-full items-center">
<Typography.Text strong>{prompt.name}</Typography.Text>
</Flex>
{prompt.description && (
@ -63,7 +64,7 @@ const MCPPromptsSection = ({ prompts }: MCPPromptsSectionProps) => {
{prompt.description}
</Typography.Text>
)}
</Flex>
</ColFlex>
}>
<SelectableContent>{renderPromptArguments(prompt)}</SelectableContent>
</Collapse.Panel>

View File

@ -1,5 +1,6 @@
import { ColFlex, Flex } from '@cherrystudio/ui'
import type { MCPResource } from '@renderer/types'
import { Collapse, Descriptions, Empty, Flex, Tag, Typography } from 'antd'
import { Collapse, Descriptions, Empty, Tag, Typography } from 'antd'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -61,8 +62,8 @@ const MCPResourcesSection = ({ resources }: MCPResourcesSectionProps) => {
<Collapse.Panel
key={resource.uri}
header={
<Flex vertical align="flex-start" style={{ width: '100%' }}>
<Flex align="center" style={{ width: '100%' }}>
<ColFlex className="w-full items-start">
<Flex className="w-full items-center">
<Typography.Text strong>{`${resource.name} (${resource.uri})`}</Typography.Text>
</Flex>
{resource.description && (
@ -72,7 +73,7 @@ const MCPResourcesSection = ({ resources }: MCPResourcesSectionProps) => {
: resource.description}
</Typography.Text>
)}
</Flex>
</ColFlex>
}>
<SelectableContent>{renderResourceProperties(resource)}</SelectableContent>
</Collapse.Panel>

View File

@ -1,3 +1,4 @@
import { Flex } from '@cherrystudio/ui'
import { loggerService } from '@logger'
import type { McpError } from '@modelcontextprotocol/sdk/types.js'
import { DeleteIcon } from '@renderer/components/Icons'
@ -7,7 +8,7 @@ import MCPDescription from '@renderer/pages/settings/MCPSettings/McpDescription'
import type { MCPPrompt, MCPResource, MCPServer, MCPTool } from '@renderer/types'
import { formatMcpError } from '@renderer/utils/error'
import type { TabsProps } from 'antd'
import { Badge, Button, Flex, Form, Input, Radio, Select, Switch, Tabs } from 'antd'
import { Badge, Button, Form, Input, Radio, Select, Switch, Tabs } from 'antd'
import TextArea from 'antd/es/input/TextArea'
import { ChevronDown, SaveIcon } from 'lucide-react'
import React, { useCallback, useEffect, useState } from 'react'
@ -731,8 +732,8 @@ const McpSettings: React.FC = () => {
<SettingContainer theme={theme} style={{ width: '100%', paddingTop: 55, backgroundColor: 'transparent' }}>
<SettingGroup style={{ marginBottom: 0, borderRadius: 'var(--list-item-border-radius)' }}>
<SettingTitle>
<Flex justify="space-between" align="center" gap={5} style={{ marginRight: 10 }}>
<Flex align="center" gap={8}>
<Flex className="mr-2.5 items-center justify-between gap-[5px]">
<Flex className="items-center gap-2">
<ServerName className="text-nowrap">{server?.name}</ServerName>
{serverVersion && <VersionBadge count={serverVersion} color="blue" />}
</Flex>
@ -743,7 +744,7 @@ const McpSettings: React.FC = () => {
onClick={() => onDeleteMcpServer(server)}
/>
</Flex>
<Flex align="center" gap={16}>
<Flex className="items-center gap-4">
<Switch
value={server.isActive}
key={server.id}

View File

@ -1,5 +1,5 @@
import { RowFlex } from '@cherrystudio/ui'
import { NavbarRight } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout'
import { isLinux, isWin } from '@renderer/config/constant'
import { useFullscreen } from '@renderer/hooks/useFullscreen'
import { Button } from 'antd'
@ -15,7 +15,7 @@ export const McpSettingsNavbar = () => {
return (
<NavbarRight style={{ paddingRight: useFullscreen() ? '12px' : isWin ? 150 : isLinux ? 120 : 12 }}>
<HStack alignItems="center" gap={5}>
<RowFlex className="items-center gap-[5px]">
<Button
size="small"
type="text"
@ -26,7 +26,7 @@ export const McpSettingsNavbar = () => {
{t('settings.mcp.searchNpx')}
</Button>
<InstallNpxUv mini />
</HStack>
</RowFlex>
</NavbarRight>
)
}

View File

@ -1,6 +1,7 @@
import { ColFlex, Flex } from '@cherrystudio/ui'
import type { MCPServer, MCPTool } from '@renderer/types'
import { isToolAutoApproved } from '@renderer/utils/mcp-tools'
import { Badge, Descriptions, Empty, Flex, Switch, Table, Tag, Tooltip, Typography } from 'antd'
import { Badge, Descriptions, Empty, Switch, Table, Tag, Tooltip, Typography } from 'antd'
import type { ColumnsType } from 'antd/es/table'
import { Hammer, Info, Zap } from 'lucide-react'
import { useTranslation } from 'react-i18next'
@ -58,7 +59,7 @@ const MCPToolsSection = ({ tools, server, onToggleTool, onToggleAutoApprove }: M
<Descriptions.Item
key={key}
label={
<Flex gap={4}>
<Flex className="gap-1">
<Typography.Text strong>{key}</Typography.Text>
{tool.inputSchema.required?.includes(key) && (
<Tooltip title="Required field">
@ -67,8 +68,8 @@ const MCPToolsSection = ({ tools, server, onToggleTool, onToggleAutoApprove }: M
)}
</Flex>
}>
<Flex vertical gap={4}>
<Flex align="center" gap={8}>
<ColFlex className="gap-1">
<Flex className="items-center gap-2">
{prop.type && (
// <Typography.Text type="secondary">{prop.type} </Typography.Text>
<Badge
@ -94,7 +95,7 @@ const MCPToolsSection = ({ tools, server, onToggleTool, onToggleAutoApprove }: M
</div>
</div>
)}
</Flex>
</ColFlex>
</Descriptions.Item>
))}
</Descriptions>
@ -113,8 +114,8 @@ const MCPToolsSection = ({ tools, server, onToggleTool, onToggleAutoApprove }: M
onFilter: (value, record) => record.name === value,
filterSearch: true,
render: (_, tool) => (
<Flex vertical align="flex-start" gap={4}>
<Flex align="center" gap={4}>
<ColFlex className="gap-1">
<Flex className="items-center gap-1">
<Typography.Text strong ellipsis={{ tooltip: tool.name }}>
{tool.name}
</Typography.Text>
@ -130,12 +131,12 @@ const MCPToolsSection = ({ tools, server, onToggleTool, onToggleAutoApprove }: M
{tool.description}
</Typography.Paragraph>
)}
</Flex>
</ColFlex>
)
},
{
title: (
<Flex align="center" justify="center" gap={4}>
<Flex className="items-center justify-center gap-1">
<Hammer size={14} color="orange" />
<Typography.Text strong>{t('settings.mcp.tools.enable')}</Typography.Text>
</Flex>
@ -149,7 +150,7 @@ const MCPToolsSection = ({ tools, server, onToggleTool, onToggleAutoApprove }: M
},
{
title: (
<Flex align="center" justify="center" gap={4}>
<Flex className="items-center justify-center gap-1">
<Zap size={14} color="red" />
<Typography.Text strong>{t('settings.mcp.tools.autoApprove.label')}</Typography.Text>
</Flex>

View File

@ -1,11 +1,12 @@
import { CheckOutlined, PlusOutlined } from '@ant-design/icons'
import { Center, RowFlex } from '@cherrystudio/ui'
import { Flex } from '@cherrystudio/ui'
import { nanoid } from '@reduxjs/toolkit'
import logo from '@renderer/assets/images/cherry-text-logo.svg'
import { Center, HStack } from '@renderer/components/Layout'
import { useMCPServers } from '@renderer/hooks/useMCPServers'
import type { MCPServer } from '@renderer/types'
import { getMcpConfigSampleFromReadme } from '@renderer/utils'
import { Button, Card, Flex, Input, Space, Spin, Tag, Typography } from 'antd'
import { Button, Card, Input, Space, Spin, Tag, Typography } from 'antd'
import { npxFinder } from 'npx-scope-finder'
import { type FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -103,7 +104,7 @@ const NpxSearch: FC = () => {
<Container>
<Center>
<Space direction="vertical" style={{ marginBottom: 25, width: 500 }}>
<Center style={{ marginBottom: 15 }}>
<Center className="mb-[15px]">
<img src={logo} alt="npm" width={120} />
</Center>
<Space.Compact style={{ width: '100%' }}>
@ -116,7 +117,7 @@ const NpxSearch: FC = () => {
styles={{ input: { borderRadius: 100 } }}
/>
</Space.Compact>
<HStack alignItems="center" justifyContent="center">
<RowFlex className="items-center justify-center">
{npmScopes.map((scope) => (
<Tag
key={scope}
@ -132,7 +133,7 @@ const NpxSearch: FC = () => {
{scope}
</Tag>
))}
</HStack>
</RowFlex>
</Space>
</Center>
{searchLoading && (

Some files were not shown because too many files have changed in this diff Show More