refactor: migrate all antd Tooltip components to HeroUI Tooltip (#10295)

* refactor: migrate tooltip components to @cherrystudio/ui

- Replace all antd Tooltip + InfoCircleOutlined patterns with InfoTooltip component
- Replace all antd Tooltip + QuestionCircleOutlined patterns with HelpTooltip component
- Migrate all WarnTooltip imports to @cherrystudio/ui
- Add onClick support to InfoTooltip and HelpTooltip components
- Remove local tooltip components from renderer
- Update eslint config to restrict antd Tooltip imports
- Clean up unused imports and styled components

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: replace tooltip

* fix: yarn format

* fix: type check

* Update QuickModelPopup.tsx

* fix: yarn test

* fix: ci error

* Update TabContainer.tsx

* fix: ci error

* fix: ci error

* fix: issue

* fix: ci

* fix: again

* refactor(ui): replace Tooltip title prop with content for consistency

* refactor(Tooltip): improve Tooltip component by extending props and simplifying implementation

- Extend TooltipProps from HeroUITooltipProps instead of redefining
- Remove redundant props and use spread operator for classNames
- Export TooltipProps type for better type support

* refactor(HelpTooltip): rename title prop to content and simplify component

Update HelpTooltip component to use TooltipProps interface and rename title prop to content for consistency
Update all instances where HelpTooltip is used to reflect the prop name change

* refactor(IconTooltips): consolidate tooltip components into unified module

Move HelpTooltip, InfoTooltip, and WarnTooltip into a single IconTooltips directory with shared types
Update exports in components index to use new module structure

* refactor(tooltip): update InfoTooltip prop from title to content and simplify component

Consolidate tooltip props interface and update all instances to use content prop instead of title for consistency. Remove redundant interface definitions and simplify InfoTooltip component implementation.

* refactor(ui): rename WarnTooltip prop from title to content for consistency

Update all instances of WarnTooltip component to use content prop instead of title for better consistency with Tooltip component interface. Also simplify the component props by extending IconTooltipProps type.

* fix(tooltip): update tooltip usage

- Replace deprecated props like `mouseEnterDelay` and `mouseLeaveDelay` with `delay` and `closeDelay`
- Rename `arrow` prop to `showArrow` for better semantics
- Update styling props to use `classNames` instead of inline styles
- Remove unnecessary props like `fresh` and `destroyOnHidden`

* refactor(components): remove redundant placement="top" from Tooltip components

The placement="top" prop was removed from all Tooltip components since it's the default value and redundant. This change improves code cleanliness without affecting functionality.

* fix(HeaderNavbar): add tooltip placement for sidebar toggle buttons

* fix(ui): add delay to tooltip components for better user experience

* refactor(tooltip): adjust tooltip behavior and styling across components

- Remove default delay values from base Tooltip component
- Add delay and closeDelay props to specific tooltip instances
- Fix tooltip compatibility issue with Antd Dropdown
- Adjust tooltip placement and styling in various components

* fix(ui): set closeDelay to 0 for Tooltip components to improve responsiveness

Prevent tooltip delay from causing poor user experience by making them close immediately when mouse leaves the element

* refactor(ui): remove redundant tooltip placement prop

The 'placement="top"' prop was removed from Tooltip components as it's the default value and doesn't need to be explicitly set.

* fix(ui): adjust tooltip delays for better user experience

- Set consistent default delay of 1000ms for window controls
- Increase delay for sidebar toggle tooltips to 2000ms
- Adjust various message action tooltip delays between 600-1200ms

* fix(SelectModelPopup): add delay props to provider settings tooltip

Add delay and closeDelay props to Tooltip component to improve user experience by preventing accidental triggers

* style(HelpTooltip): add cursor help style to improve UX

* fix(components): add tooltip delay and placement props for better UX

Add delay prop to CustomTag and ModelIdWithTags tooltips to prevent flickering
Set placement prop for LocalBackupManager tooltip to top-start
Add closeDelay prop to HelpTooltip in SaveToKnowledgePopup for immediate closing

* refactor(ModelSelectButton): simplify tooltip props by using TooltipProps type

Replace individual tooltip placement props with TooltipProps type from ui library for better maintainability

* fix(ui): remove tooltip close delay for better user experience

* docs(tooltip): add jsdoc comments explaining tooltip wrapper behavior

* refactor(Tooltip): clarify showArrow prop

* fix(Inputbar): set closeDelay to 0 for pause tooltip to improve UX

Prevent tooltip from staying visible after interaction by removing the close delay

* style(InputbarTools): improve tooltip consistency and css formatting

- Add closeDelay to new topic tooltip for consistency
- Remove redundant line breaks in tooltip props
- Format css transition properties for better readability

* chore: add tailwindCSS class attributes to vscode settings

* fix(tooltips): improve tooltip behavior and styling across components

- Add closeDelay=0 to most tooltips for instant closing
- Add custom styling to CitationTooltip and ChatFlowHistory tooltips
- Adjust delay times for navigation tooltips
- Remove conflicting Tooltip wrappers around Popconfirm actions

* refactor(ui): adjust tooltip delays and placements across components

- Remove redundant isOpen prop from CustomNode tooltip
- Standardize tooltip delays and placements in MessageGroupMenuBar, MessageTokens, ChatNavbar
- Simplify tooltip wrapper structure in HeaderNavbar
- Add consistent tooltip delays in MessageGroupModelList
- Set tooltip placements in MinimalToolbar

* refactor(Tooltip): enhance tooltip structure and props

- Add className prop to Tooltip component for better customization
- Wrap children in a div with relative positioning to improve layout

* refactor(Tooltip): enhance props structure for improved customization

- Update Tooltip component to allow optional classNames with a placeholder property
- Modify child wrapper to utilize classNames for better styling control

* refactor(IconTooltips): consolidate icon props into single iconProps object

Replace individual icon styling props (iconColor, iconSize, iconStyle) with a unified iconProps object using LucideProps type. This simplifies the component API and improves maintainability by using a standardized props structure across all icon tooltip components.

* feat(JoplinSettings): add help button to open Joplin documentation

Add a help button in Joplin settings that opens the official Joplin documentation in a minapp popup when clicked. This provides users with quick access to Joplin's help resources.

* feat(NotionSettings): add help link click handler for notion title

Add click handler to open help documentation when clicking on Notion title in settings

* feat(S3Settings): add help link to S3 settings title

Add click handler to open documentation for S3 settings when title is clicked

* feat(settings): add help button for siyuan integration

Add click handler to open help documentation for siyuan integration settings

* feat(yuque-settings): add help button to open yuque token guide

Add a help button in Yuque settings that opens a minapp popup with Yuque's token guide. This helps users easily access documentation for generating API tokens.

* fix(ui): adjust tooltip delay settings for better user experience

Set closeDelay to 0 for reset button tooltip to prevent lingering
Add delay of 500ms for api key list tooltip to avoid accidental triggers

* fix(ModelList): set closeDelay to 0 for all Tooltip components

Prevent tooltips from staying open longer than necessary by immediately closing them on mouse leave

* fix(ui): improve tooltip placement and delay settings

adjust tooltip placement and delay for better user experience

* refactor(tests): update tooltip mock implementation and snapshots

- Consolidate tooltip mock to handle both title and content props
- Remove deprecated placement attributes from snapshots
- Clean up test tooltip content assertions

* refactor: remove unnecessary whitespace and simplify tooltip components

clean up code by removing redundant whitespace and simplifying tooltip component usage across multiple files

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: icarus <eurfelux@gmail.com>
Co-authored-by: MyPrototypeWhat <daoquqiexing@gmail.com>
This commit is contained in:
Pleasure1234 2025-10-05 11:33:21 +01:00 committed by GitHub
parent de5fb03efb
commit a00aba23bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
183 changed files with 1432 additions and 1350 deletions

View File

@ -48,5 +48,9 @@
"search.exclude": { "search.exclude": {
"**/dist/**": true, "**/dist/**": true,
".yarn/releases/**": true ".yarn/releases/**": true
} },
"tailwindCSS.classAttributes": [
"className",
"classNames",
]
} }

View File

@ -145,7 +145,7 @@ export default defineConfig([
paths: [ paths: [
{ {
name: 'antd', name: 'antd',
importNames: ['Flex', 'Switch', 'message', 'Button'], importNames: ['Flex', 'Switch', 'message', 'Button', 'Tooltip'],
message: message:
'❌ Do not import this component from antd. Use our custom components instead: import { ... } from "@cherrystudio/ui"' '❌ Do not import this component from antd. Use our custom components instead: import { ... } from "@cherrystudio/ui"'
}, },

View File

@ -69,7 +69,7 @@
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"storybook": "^9.1.6", "storybook": "^9.1.6",
"styled-components": "^6.1.15", "styled-components": "^6.1.15",
"tsdown": "^0.12.9", "tsdown": "^0.15.5",
"tsx": "^4.20.5", "tsx": "^4.20.5",
"typescript": "^5.6.2", "typescript": "^5.6.2",
"vitest": "^3.2.4" "vitest": "^3.2.4"

View File

@ -10,14 +10,7 @@ interface EmojiAvatarProps {
style?: React.CSSProperties style?: React.CSSProperties
} }
const EmojiAvatar = ({ const EmojiAvatar = ({ children, size = 31, fontSize, onClick, className, style }: EmojiAvatarProps) => (
children,
size = 31,
fontSize,
onClick,
className,
style
}: EmojiAvatarProps) => (
<div <div
onClick={onClick} onClick={onClick}
className={cn( className={cn(
@ -40,4 +33,4 @@ const EmojiAvatar = ({
EmojiAvatar.displayName = 'EmojiAvatar' EmojiAvatar.displayName = 'EmojiAvatar'
export default memo(EmojiAvatar) export default memo(EmojiAvatar)

View File

@ -76,7 +76,7 @@ const CustomTag: FC<CustomTagProps> = ({
) )
return tooltip ? ( return tooltip ? (
<Tooltip content={tooltip} placement="top" delay={300}> <Tooltip content={tooltip} delay={300}>
{tagContent} {tagContent}
</Tooltip> </Tooltip>
) : ( ) : (

View File

@ -0,0 +1,34 @@
import type { TooltipProps as HeroUITooltipProps } from '@heroui/react'
import { cn, Tooltip as HeroUITooltip } from '@heroui/react'
export interface TooltipProps extends HeroUITooltipProps {}
/**
* Tooltip wrapper that applies consistent styling and arrow display.
* Differences from raw HeroUI Tooltip:
* 1. Defaults showArrow={true}
* 2. Merges a default max-w-60 class into the content slot, capping width at 240px.
* All other HeroUI Tooltip props/behaviors remain unchanged.
*
* @see https://www.heroui.com/docs/components/tooltip
*/
export const Tooltip = ({
children,
classNames,
showArrow,
...rest
}: Omit<TooltipProps, 'classNames'> & {
classNames?: TooltipProps['classNames'] & { placeholder?: string }
}) => {
return (
<HeroUITooltip
classNames={{
...classNames,
content: cn('max-w-60', classNames?.content)
}}
showArrow={showArrow ?? true}
{...rest}>
<div className={cn('relative z-10', classNames?.placeholder)}>{children}</div>
</HeroUITooltip>
)
}

View File

@ -43,7 +43,7 @@ const ListItem = ({
<div className="flex items-center gap-0.5 overflow-hidden text-xs"> <div className="flex items-center gap-0.5 overflow-hidden text-xs">
{icon && <span className="flex items-center justify-center mr-2">{icon}</span>} {icon && <span className="flex items-center justify-center mr-2">{icon}</span>}
<div className="flex-1 flex flex-col overflow-hidden"> <div className="flex-1 flex flex-col overflow-hidden">
<Tooltip content={title} placement="top"> <Tooltip content={title}>
<div className="truncate text-gray-900 dark:text-gray-100" style={titleStyle}> <div className="truncate text-gray-900 dark:text-gray-100" style={titleStyle}>
{title} {title}
</div> </div>

View File

@ -14,7 +14,7 @@ interface ToolsCallingIconProps extends React.HTMLAttributes<HTMLDivElement> {
const ToolsCallingIcon = ({ className, iconClassName, TooltipProps, ...props }: ToolsCallingIconProps) => { const ToolsCallingIcon = ({ className, iconClassName, TooltipProps, ...props }: ToolsCallingIconProps) => {
return ( return (
<div className={cn('flex justify-center items-center', className)} {...props}> <div className={cn('flex justify-center items-center', className)} {...props}>
<Tooltip placement="top" {...TooltipProps}> <Tooltip {...TooltipProps}>
<Wrench className={cn('w-4 h-4 mr-1.5 text-[#00b96b]', iconClassName)} /> <Wrench className={cn('w-4 h-4 mr-1.5 text-[#00b96b]', iconClassName)} />
</Tooltip> </Tooltip>
</div> </div>

View File

@ -15,6 +15,7 @@ export { ErrorTag, InfoTag, StatusTag, SuccessTag, WarnTag } from './base/Status
export { DescriptionSwitch, Switch } from './base/Switch' export { DescriptionSwitch, Switch } from './base/Switch'
export { default as TextBadge } from './base/TextBadge' export { default as TextBadge } from './base/TextBadge'
export { getToastUtilities, type ToastUtilities } from './base/Toast' export { getToastUtilities, type ToastUtilities } from './base/Toast'
export { Tooltip, type TooltipProps } from './base/Tooltip'
// Display Components // Display Components
export { default as Ellipsis } from './display/Ellipsis' export { default as Ellipsis } from './display/Ellipsis'
@ -80,15 +81,12 @@ export { DraggableList, useDraggableReorder } from './interactive/DraggableList'
export type { EditableNumberProps } from './interactive/EditableNumber' export type { EditableNumberProps } from './interactive/EditableNumber'
// EditableNumber // EditableNumber
export { default as EditableNumber } from './interactive/EditableNumber' export { default as EditableNumber } from './interactive/EditableNumber'
export { default as HelpTooltip } from './interactive/HelpTooltip' // Tooltip variants
export { HelpTooltip, type IconTooltipProps, InfoTooltip, WarnTooltip } from './interactive/IconTooltips'
// ImageToolButton // ImageToolButton
export { default as ImageToolButton } from './interactive/ImageToolButton' export { default as ImageToolButton } from './interactive/ImageToolButton'
// InfoTooltip
export { default as InfoTooltip } from './interactive/InfoTooltip'
// Sortable // Sortable
export { Sortable } from './interactive/Sortable' export { Sortable } from './interactive/Sortable'
// WarnTooltip
export { default as WarnTooltip } from './interactive/WarnTooltip'
// Composite Components (复合组件) // Composite Components (复合组件)
// 暂无复合组件 // 暂无复合组件

View File

@ -1,10 +1,12 @@
// Original path: src/renderer/src/components/CollapsibleSearchBar.tsx // Original path: src/renderer/src/components/CollapsibleSearchBar.tsx
import type { InputRef } from 'antd' import type { InputRef } from 'antd'
import { Input, Tooltip } from 'antd' import { Input } from 'antd'
import { Search } from 'lucide-react' import { Search } from 'lucide-react'
import { motion } from 'motion/react' import { motion } from 'motion/react'
import React, { memo, useCallback, useEffect, useRef, useState } from 'react' import React, { memo, useCallback, useEffect, useRef, useState } from 'react'
import { Tooltip } from '../../base/Tooltip'
interface CollapsibleSearchBarProps { interface CollapsibleSearchBarProps {
onSearch: (text: string) => void onSearch: (text: string) => void
placeholder?: string placeholder?: string
@ -93,7 +95,7 @@ const CollapsibleSearchBar = ({
}} }}
style={{ cursor: 'pointer', display: 'flex' }} style={{ cursor: 'pointer', display: 'flex' }}
onClick={() => setSearchVisible(true)}> onClick={() => setSearchVisible(true)}>
<Tooltip title={tooltip} mouseEnterDelay={0.5} mouseLeaveDelay={0}> <Tooltip content={tooltip} delay={500} closeDelay={0}>
{icon} {icon}
</Tooltip> </Tooltip>
</motion.div> </motion.div>

View File

@ -1,4 +1,3 @@
import { Scrollbar } from '@cherrystudio/ui'
import type { import type {
DroppableProps, DroppableProps,
DropResult, DropResult,
@ -10,6 +9,7 @@ import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
import { type ScrollToOptions, useVirtualizer, type VirtualItem } from '@tanstack/react-virtual' import { type ScrollToOptions, useVirtualizer, type VirtualItem } from '@tanstack/react-virtual'
import { type Key, memo, useCallback, useImperativeHandle, useRef } from 'react' import { type Key, memo, useCallback, useImperativeHandle, useRef } from 'react'
import Scrollbar from '../../layout/Scrollbar'
import { droppableReorder } from './sort' import { droppableReorder } from './sort'
export interface DraggableVirtualListRef { export interface DraggableVirtualListRef {

View File

@ -1,22 +0,0 @@
// Original path: src/renderer/src/components/TooltipIcons/HelpTooltip.tsx
import type { TooltipProps } from 'antd'
import { Tooltip } from 'antd'
import { HelpCircle } from 'lucide-react'
type InheritedTooltipProps = Omit<TooltipProps, 'children'>
interface HelpTooltipProps extends InheritedTooltipProps {
iconColor?: string
iconSize?: string | number
iconStyle?: React.CSSProperties
}
const HelpTooltip = ({ iconColor = 'var(--color-text-2)', iconSize = 14, iconStyle, ...rest }: HelpTooltipProps) => {
return (
<Tooltip {...rest}>
<HelpCircle size={iconSize} color={iconColor} style={{ ...iconStyle }} role="img" aria-label="Help" />
</Tooltip>
)
}
export default HelpTooltip

View File

@ -0,0 +1,19 @@
// Original path: src/renderer/src/components/TooltipIcons/HelpTooltip.tsx
import { HelpCircle } from 'lucide-react'
import { Tooltip } from '../../base/Tooltip'
import type { IconTooltipProps } from './types'
export const HelpTooltip = ({ iconProps, ...rest }: IconTooltipProps) => {
return (
<Tooltip {...rest}>
<HelpCircle
size={iconProps?.size ?? 14}
color={iconProps?.color ?? 'var(--color-text-2)'}
role="img"
aria-label="Help"
{...iconProps}
/>
</Tooltip>
)
}

View File

@ -0,0 +1,19 @@
// Original: src/renderer/src/components/TooltipIcons/InfoTooltip.tsx
import { Info } from 'lucide-react'
import { Tooltip } from '../../base/Tooltip'
import type { IconTooltipProps } from './types'
export const InfoTooltip = ({ iconProps, ...rest }: IconTooltipProps) => {
return (
<Tooltip {...rest}>
<Info
size={iconProps?.size ?? 14}
color={iconProps?.color ?? 'var(--color-text-2)'}
role="img"
aria-label="Information"
{...iconProps}
/>
</Tooltip>
)
}

View File

@ -0,0 +1,19 @@
// Original path: src/renderer/src/components/TooltipIcons/WarnTooltip.tsx
import { AlertTriangle } from 'lucide-react'
import { Tooltip } from '../../base/Tooltip'
import type { IconTooltipProps } from './types'
export const WarnTooltip = ({ iconProps, ...rest }: IconTooltipProps) => {
return (
<Tooltip {...rest}>
<AlertTriangle
size={iconProps?.size ?? 14}
color={iconProps?.color ?? 'var(--color-status-warning)'}
role="img"
aria-label="Warning"
{...iconProps}
/>
</Tooltip>
)
}

View File

@ -0,0 +1,4 @@
export { HelpTooltip } from './HelpTooltip'
export { InfoTooltip } from './InfoTooltip'
export type { IconTooltipProps } from './types'
export { WarnTooltip } from './WarnTooltip'

View File

@ -0,0 +1,7 @@
import type { LucideProps } from 'lucide-react'
import type { TooltipProps } from '../../base/Tooltip'
export interface IconTooltipProps extends TooltipProps {
iconProps?: LucideProps
}

View File

@ -1,8 +1,9 @@
// Original path: src/renderer/src/components/Preview/ImageToolButton.tsx // Original path: src/renderer/src/components/Preview/ImageToolButton.tsx
import { Button } from '@cherrystudio/ui'
import { Tooltip } from 'antd'
import { memo } from 'react' import { memo } from 'react'
import Button from '../../base/Button'
import { Tooltip } from '../../base/Tooltip'
interface ImageToolButtonProps { interface ImageToolButtonProps {
tooltip: string tooltip: string
icon: React.ReactNode icon: React.ReactNode
@ -11,7 +12,7 @@ interface ImageToolButtonProps {
const ImageToolButton = ({ tooltip, icon, onPress }: ImageToolButtonProps) => { const ImageToolButton = ({ tooltip, icon, onPress }: ImageToolButtonProps) => {
return ( return (
<Tooltip title={tooltip} mouseEnterDelay={0.5} mouseLeaveDelay={0}> <Tooltip content={tooltip} delay={500} closeDelay={0}>
<Button radius="full" isIconOnly onPress={onPress} aria-label={tooltip}> <Button radius="full" isIconOnly onPress={onPress} aria-label={tooltip}>
{icon} {icon}
</Button> </Button>

View File

@ -1,22 +0,0 @@
// Original: src/renderer/src/components/TooltipIcons/InfoTooltip.tsx
import type { TooltipProps } from 'antd'
import { Tooltip } from 'antd'
import { Info } from 'lucide-react'
type InheritedTooltipProps = Omit<TooltipProps, 'children'>
interface InfoTooltipProps extends InheritedTooltipProps {
iconColor?: string
iconSize?: string | number
iconStyle?: React.CSSProperties
}
const InfoTooltip = ({ iconColor = 'var(--color-text-2)', iconSize = 14, iconStyle, ...rest }: InfoTooltipProps) => {
return (
<Tooltip {...rest}>
<Info size={iconSize} color={iconColor} style={{ ...iconStyle }} role="img" aria-label="Information" />
</Tooltip>
)
}
export default InfoTooltip

View File

@ -1,27 +0,0 @@
// Original path: src/renderer/src/components/TooltipIcons/WarnTooltip.tsx
import type { TooltipProps } from 'antd'
import { Tooltip } from 'antd'
import { AlertTriangle } from 'lucide-react'
type InheritedTooltipProps = Omit<TooltipProps, 'children'>
interface WarnTooltipProps extends InheritedTooltipProps {
iconColor?: string
iconSize?: string | number
iconStyle?: React.CSSProperties
}
const WarnTooltip = ({
iconColor = 'var(--color-status-warning)',
iconSize = 14,
iconStyle,
...rest
}: WarnTooltipProps) => {
return (
<Tooltip {...rest}>
<AlertTriangle size={iconSize} color={iconColor} style={{ ...iconStyle }} role="img" aria-label="Information" />
</Tooltip>
)
}
export default WarnTooltip

View File

@ -1,5 +1,5 @@
import { CodeEditor, type CodeEditorHandles } from '@cherrystudio/ui' import { CodeEditor, type CodeEditorHandles } from '@cherrystudio/ui'
import { Button } from '@cherrystudio/ui' import { Button, Tooltip } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference' import { usePreference } from '@data/hooks/usePreference'
import { CopyIcon, FilePngIcon } from '@renderer/components/Icons' import { CopyIcon, FilePngIcon } from '@renderer/components/Icons'
import { isMac } from '@renderer/config/constant' import { isMac } from '@renderer/config/constant'
@ -8,7 +8,7 @@ import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue'
import { classNames } from '@renderer/utils' import { classNames } from '@renderer/utils'
import { extractHtmlTitle, getFileNameFromHtmlTitle } from '@renderer/utils/formats' import { extractHtmlTitle, getFileNameFromHtmlTitle } from '@renderer/utils/formats'
import { captureScrollableIframeAsBlob, captureScrollableIframeAsDataURL } from '@renderer/utils/image' import { captureScrollableIframeAsBlob, captureScrollableIframeAsDataURL } from '@renderer/utils/image'
import { Dropdown, Modal, Splitter, Tooltip, Typography } from 'antd' import { Dropdown, Modal, Splitter, Typography } from 'antd'
import { Camera, Check, Code, Eye, Maximize2, Minimize2, SaveIcon, SquareSplitHorizontal, X } from 'lucide-react' import { Camera, Check, Code, Eye, Maximize2, Minimize2, SaveIcon, SquareSplitHorizontal, X } from 'lucide-react'
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -126,7 +126,7 @@ const HtmlArtifactsPopup: React.FC<HtmlArtifactsPopupProps> = ({ open, title, ht
} }
] ]
}}> }}>
<Tooltip title={t('html_artifacts.capture.label')} mouseLeaveDelay={0}> <Tooltip content={t('html_artifacts.capture.label')} closeDelay={0}>
<Button variant="light" startContent={<Camera size={16} />} isIconOnly className="nodrag" /> <Button variant="light" startContent={<Camera size={16} />} isIconOnly className="nodrag" />
</Tooltip> </Tooltip>
</Dropdown> </Dropdown>
@ -164,7 +164,7 @@ const HtmlArtifactsPopup: React.FC<HtmlArtifactsPopupProps> = ({ open, title, ht
}} }}
/> />
<ToolbarWrapper> <ToolbarWrapper>
<Tooltip title={t('code_block.edit.save.label')} mouseLeaveDelay={0}> <Tooltip content={t('code_block.edit.save.label')} closeDelay={0}>
<ToolbarButton radius="full" size="lg" isIconOnly onPress={handleSave}> <ToolbarButton radius="full" size="lg" isIconOnly onPress={handleSave}>
{saved ? ( {saved ? (
<Check size={16} color="var(--color-status-success)" /> <Check size={16} color="var(--color-status-success)" />

View File

@ -6,8 +6,8 @@ import CodeToolButton from '../button'
// Mock Antd components // Mock Antd components
const mocks = vi.hoisted(() => ({ const mocks = vi.hoisted(() => ({
Tooltip: vi.fn(({ children, title }) => ( Tooltip: vi.fn(({ children, title, content }) => (
<div data-testid="tooltip" data-title={title}> <div data-testid="tooltip" data-title={content || title}>
{children} {children}
</div> </div>
)), )),
@ -19,10 +19,13 @@ const mocks = vi.hoisted(() => ({
})) }))
vi.mock('antd', () => ({ vi.mock('antd', () => ({
Tooltip: mocks.Tooltip,
Dropdown: mocks.Dropdown Dropdown: mocks.Dropdown
})) }))
vi.mock('@cherrystudio/ui', () => ({
Tooltip: mocks.Tooltip
}))
// Mock ToolWrapper // Mock ToolWrapper
vi.mock('../styles', () => ({ vi.mock('../styles', () => ({
ToolWrapper: ({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) => ( ToolWrapper: ({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) => (

View File

@ -14,8 +14,8 @@ const mocks = vi.hoisted(() => ({
{tool.icon} {tool.icon}
</div> </div>
)), )),
Tooltip: vi.fn(({ children, title }) => ( Tooltip: vi.fn(({ children, title, content }) => (
<div data-testid="tooltip" data-title={title}> <div data-testid="tooltip" data-title={content || title}>
{children} {children}
</div> </div>
)), )),
@ -39,11 +39,8 @@ vi.mock('../button', () => ({
default: mocks.CodeToolButton default: mocks.CodeToolButton
})) }))
vi.mock('antd', () => ({
Tooltip: mocks.Tooltip
}))
vi.mock('@cherrystudio/ui', () => ({ vi.mock('@cherrystudio/ui', () => ({
Tooltip: mocks.Tooltip,
RowFlex: mocks.RowFlex RowFlex: mocks.RowFlex
})) }))

View File

@ -1,5 +1,6 @@
import { Tooltip } from '@cherrystudio/ui'
import type { ActionTool } from '@renderer/components/ActionTools' import type { ActionTool } from '@renderer/components/ActionTools'
import { Dropdown, Tooltip } from 'antd' import { Dropdown } from 'antd'
import { memo, useMemo } from 'react' import { memo, useMemo } from 'react'
import { ToolWrapper } from './styles' import { ToolWrapper } from './styles'
@ -11,7 +12,7 @@ interface CodeToolButtonProps {
const CodeToolButton = ({ tool }: CodeToolButtonProps) => { const CodeToolButton = ({ tool }: CodeToolButtonProps) => {
const mainTool = useMemo( const mainTool = useMemo(
() => ( () => (
<Tooltip key={tool.id} title={tool.tooltip} mouseEnterDelay={0.5} mouseLeaveDelay={0}> <Tooltip key={tool.id} content={tool.tooltip} delay={500} closeDelay={0}>
<ToolWrapper onClick={tool.onClick}>{tool.icon}</ToolWrapper> <ToolWrapper onClick={tool.onClick}>{tool.icon}</ToolWrapper>
</Tooltip> </Tooltip>
), ),

View File

@ -1,6 +1,6 @@
import { RowFlex } from '@cherrystudio/ui' import { RowFlex } from '@cherrystudio/ui'
import { Tooltip } from '@cherrystudio/ui'
import type { ActionTool } from '@renderer/components/ActionTools' import type { ActionTool } from '@renderer/components/ActionTools'
import { Tooltip } from 'antd'
import { EllipsisVertical } from 'lucide-react' import { EllipsisVertical } from 'lucide-react'
import { memo, useMemo, useState } from 'react' import { memo, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -39,7 +39,7 @@ const CodeToolbar = ({ tools }: { tools: ActionTool[] }) => {
{/* 有多个快捷工具时通过 more 按钮展示 */} {/* 有多个快捷工具时通过 more 按钮展示 */}
{quickToolButtons} {quickToolButtons}
{quickTools.length > 1 && ( {quickTools.length > 1 && (
<Tooltip title={t('code_block.more')} mouseEnterDelay={0.5}> <Tooltip content={t('code_block.more')} delay={500}>
<ToolWrapper onClick={() => setShowQuickTools(!showQuickTools)} className={showQuickTools ? 'active' : ''}> <ToolWrapper onClick={() => setShowQuickTools(!showQuickTools)} className={showQuickTools ? 'active' : ''}>
<EllipsisVertical className="tool-icon" /> <EllipsisVertical className="tool-icon" />
</ToolWrapper> </ToolWrapper>

View File

@ -1,6 +1,7 @@
import { Tooltip } from '@cherrystudio/ui'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import type { InputRef } from 'antd' import type { InputRef } from 'antd'
import { Input, Tooltip } from 'antd' import { Input } from 'antd'
import { Search } from 'lucide-react' import { Search } from 'lucide-react'
import { motion } from 'motion/react' import { motion } from 'motion/react'
import React, { memo, useCallback, useEffect, useRef, useState } from 'react' import React, { memo, useCallback, useEffect, useRef, useState } from 'react'
@ -93,7 +94,7 @@ const CollapsibleSearchBar = ({
}} }}
style={{ cursor: 'pointer', display: 'flex' }} style={{ cursor: 'pointer', display: 'flex' }}
onClick={() => setSearchVisible(true)}> onClick={() => setSearchVisible(true)}>
<Tooltip title={tooltip} mouseEnterDelay={0.5} mouseLeaveDelay={0}> <Tooltip content={tooltip} delay={500} closeDelay={0}>
{icon} {icon}
</Tooltip> </Tooltip>
</motion.div> </motion.div>

View File

@ -1,6 +1,6 @@
import { Tooltip } from '@cherrystudio/ui'
import { ActionIconButton } from '@renderer/components/Buttons' import { ActionIconButton } from '@renderer/components/Buttons'
import NarrowLayout from '@renderer/pages/home/Messages/NarrowLayout' import NarrowLayout from '@renderer/pages/home/Messages/NarrowLayout'
import { Tooltip } from 'antd'
import { debounce } from 'lodash' import { debounce } from 'lodash'
import { CaseSensitive, ChevronDown, ChevronUp, User, WholeWord, X } from 'lucide-react' import { CaseSensitive, ChevronDown, ChevronUp, User, WholeWord, X } from 'lucide-react'
import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
@ -363,17 +363,17 @@ export const ContentSearch = React.forwardRef<ContentSearchRef, Props>(
/> />
<ToolBar> <ToolBar>
{showUserToggle && ( {showUserToggle && (
<Tooltip title={t('button.includes_user_questions')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip placement="bottom" content={t('button.includes_user_questions')} delay={800}>
<ActionIconButton <ActionIconButton
onPress={userOutlinedButtonOnClick} onPress={userOutlinedButtonOnClick}
isIconOnly isIconOnly
icon={ icon={
<User size={18} style={{ color: includeUser ? 'var(--color-link)' : 'var(--color-icon)' }} /> <User size={18} style={{ color: includeUser ? 'var(--color-link)' : 'var(--color-icon)' }} />
} }
/> />{' '}
</Tooltip> </Tooltip>
)} )}
<Tooltip title={t('button.case_sensitive')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip placement="bottom" content={t('button.case_sensitive')} delay={800}>
<ActionIconButton <ActionIconButton
onPress={caseSensitiveButtonOnClick} onPress={caseSensitiveButtonOnClick}
icon={ icon={
@ -382,9 +382,9 @@ export const ContentSearch = React.forwardRef<ContentSearchRef, Props>(
style={{ color: isCaseSensitive ? 'var(--color-link)' : 'var(--color-icon)' }} style={{ color: isCaseSensitive ? 'var(--color-link)' : 'var(--color-icon)' }}
/> />
} }
/> />{' '}
</Tooltip> </Tooltip>
<Tooltip title={t('button.whole_word')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip placement="bottom" content={t('button.whole_word')} delay={800}>
<ActionIconButton <ActionIconButton
onPress={wholeWordButtonOnClick} onPress={wholeWordButtonOnClick}
icon={ icon={

View File

@ -1,4 +1,4 @@
import { Tooltip } from 'antd' import { Tooltip } from '@cherrystudio/ui'
import { Copy } from 'lucide-react' import { Copy } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -47,7 +47,7 @@ const CopyButton: FC<CopyButtonProps> = ({
) )
if (tooltip) { if (tooltip) {
return <Tooltip title={tooltip}>{button}</Tooltip> return <Tooltip content={tooltip}>{button}</Tooltip>
} }
return button return button

View File

@ -20,7 +20,8 @@ exports[`DraggableVirtualList > snapshot > should match snapshot with custom sty
</div> </div>
</div> </div>
<div <div
class="ScrollBarContainer-eGlIoO jgKpou virtual-scroller" class="virtual-scroller"
data-testid="scrollbar"
style="height: 100%; width: 100%; overflow-y: auto; position: relative;" style="height: 100%; width: 100%; overflow-y: auto; position: relative;"
> >
<div <div

View File

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

View File

@ -1,4 +1,4 @@
import { Tooltip } from 'antd' import { Tooltip } from '@cherrystudio/ui'
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -9,7 +9,7 @@ const ReasoningIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement
return ( return (
<Container> <Container>
<Tooltip title={t('models.type.reasoning')} placement="top"> <Tooltip content={t('models.type.reasoning')}>
<Icon className="iconfont icon-thinking" {...(props as any)} /> <Icon className="iconfont icon-thinking" {...(props as any)} />
</Tooltip> </Tooltip>
</Container> </Container>

View File

@ -279,7 +279,7 @@ export function PoeLogo(props: SVGProps<SVGSVGElement>) {
y1="7.303" y1="7.303"
y2="27.715"> y2="27.715">
<stop stopColor="#46A6F7"></stop> <stop stopColor="#46A6F7"></stop>
<stop offset="1" stop-color="#8364FF"></stop> <stop offset="1" stopColor="#8364FF"></stop>
</linearGradient> </linearGradient>
<linearGradient <linearGradient
gradientUnits="userSpaceOnUse" gradientUnits="userSpaceOnUse"
@ -289,7 +289,7 @@ export function PoeLogo(props: SVGProps<SVGSVGElement>) {
y1="23.511" y1="23.511"
y2="9.464"> y2="9.464">
<stop stopColor="#FF44D3"></stop> <stop stopColor="#FF44D3"></stop>
<stop offset="1" stop-color="#CF4BFF"></stop> <stop offset="1" stopColor="#CF4BFF"></stop>
</linearGradient> </linearGradient>
</defs> </defs>
</svg> </svg>

View File

@ -1,5 +1,5 @@
import { ToolOutlined } from '@ant-design/icons' import { ToolOutlined } from '@ant-design/icons'
import { Tooltip } from 'antd' import { Tooltip } from '@cherrystudio/ui'
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -10,7 +10,7 @@ const ToolsCallingIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElem
return ( return (
<Container> <Container>
<Tooltip title={t('models.function_calling')} placement="top"> <Tooltip content={t('models.function_calling')}>
<Icon {...(props as any)} /> <Icon {...(props as any)} />
</Tooltip> </Tooltip>
</Container> </Container>

View File

@ -1,4 +1,4 @@
import { Tooltip } from 'antd' import { Tooltip } from '@cherrystudio/ui'
import { ImageIcon } from 'lucide-react' import { ImageIcon } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
@ -10,7 +10,7 @@ const VisionIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>,
return ( return (
<Container> <Container>
<Tooltip title={t('models.type.vision')} placement="top"> <Tooltip content={t('models.type.vision')}>
<Icon size={15} {...(props as any)} /> <Icon size={15} {...(props as any)} />
</Tooltip> </Tooltip>
</Container> </Container>

View File

@ -1,5 +1,5 @@
import { GlobalOutlined } from '@ant-design/icons' import { GlobalOutlined } from '@ant-design/icons'
import { Tooltip } from 'antd' import { Tooltip } from '@cherrystudio/ui'
import type { FC } from 'react' import type { FC } from 'react'
import React from 'react' import React from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -10,7 +10,7 @@ const WebSearchIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement
return ( return (
<Container> <Container>
<Tooltip title={t('models.type.websearch')} placement="top"> <Tooltip content={t('models.type.websearch')}>
<Icon {...(props as any)} /> <Icon {...(props as any)} />
</Tooltip> </Tooltip>
</Container> </Container>

View File

@ -1,11 +1,11 @@
import { Button } from '@cherrystudio/ui' import { Button, Tooltip } from '@cherrystudio/ui'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import AiProvider from '@renderer/aiCore' import AiProvider from '@renderer/aiCore'
import { RefreshIcon } from '@renderer/components/Icons' import { RefreshIcon } from '@renderer/components/Icons'
import { useProvider } from '@renderer/hooks/useProvider' import { useProvider } from '@renderer/hooks/useProvider'
import type { Model } from '@renderer/types' import type { Model } from '@renderer/types'
import { getErrorMessage } from '@renderer/utils' import { getErrorMessage } from '@renderer/utils'
import { InputNumber, Space, Tooltip } from 'antd' import { InputNumber, Space } from 'antd'
import { memo, useCallback, useMemo, useState } from 'react' import { memo, useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -74,7 +74,7 @@ const InputEmbeddingDimension = ({
onChange={onChange} onChange={onChange}
disabled={disabled} disabled={disabled}
/> />
<Tooltip title={t('knowledge.dimensions_auto_set')}> <Tooltip content={t('knowledge.dimensions_auto_set')}>
<Button <Button
role="button" role="button"
aria-label="Get embedding dimension" aria-label="Get embedding dimension"

View File

@ -1,8 +1,8 @@
import { DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined } from '@ant-design/icons' import { DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined } from '@ant-design/icons'
import { Button, Flex } from '@cherrystudio/ui' import { Button, Flex, Tooltip } from '@cherrystudio/ui'
import { restoreFromLocal } from '@renderer/services/BackupService' import { restoreFromLocal } from '@renderer/services/BackupService'
import { formatFileSize } from '@renderer/utils' import { formatFileSize } from '@renderer/utils'
import { Modal, Table, Tooltip } from 'antd' import { Modal, Table } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -168,7 +168,7 @@ export function LocalBackupManager({ visible, onClose, localBackupDir, restoreMe
showTitle: false showTitle: false
}, },
render: (fileName: string) => ( render: (fileName: string) => (
<Tooltip placement="topLeft" title={fileName}> <Tooltip content={fileName} placement="top-start">
{fileName} {fileName}
</Tooltip> </Tooltip>
) )

View File

@ -10,8 +10,7 @@ import {
PushpinOutlined, PushpinOutlined,
ReloadOutlined ReloadOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import { Button } from '@cherrystudio/ui' import { Avatar, Button, Tooltip } from '@cherrystudio/ui'
import { Avatar } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference' import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import WindowControls from '@renderer/components/WindowControls' import WindowControls from '@renderer/components/WindowControls'
@ -26,7 +25,7 @@ import { useTimer } from '@renderer/hooks/useTimer'
import type { MinAppType } from '@renderer/types' import type { MinAppType } from '@renderer/types'
import { delay } from '@renderer/utils' import { delay } from '@renderer/utils'
import { clearWebviewState, getWebviewLoaded, setWebviewLoaded } from '@renderer/utils/webviewStateManager' import { clearWebviewState, getWebviewLoaded, setWebviewLoaded } from '@renderer/utils/webviewStateManager'
import { Alert, Drawer, Tooltip } from 'antd' import { Alert, Drawer } from 'antd'
import type { WebviewTag } from 'electron' import type { WebviewTag } from 'electron'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -401,24 +400,19 @@ const MinappPopupContainer: React.FC = () => {
return ( return (
<TitleContainer style={{ backgroundColor: backgroundColor }}> <TitleContainer style={{ backgroundColor: backgroundColor }}>
<Tooltip <Tooltip
title={ placement="right-end"
className="max-w-100"
content={
<TitleTextTooltip> <TitleTextTooltip>
{url ?? appInfo.url} <br /> {url ?? appInfo.url} <br />
<CopyOutlined className="icon-copy" /> <CopyOutlined className="icon-copy" />
{t('minapp.popup.rightclick_copyurl')} {t('minapp.popup.rightclick_copyurl')}
</TitleTextTooltip> </TitleTextTooltip>
} }>
mouseEnterDelay={0.8}
placement="rightBottom"
styles={{
root: {
maxWidth: '400px'
}
}}>
<TitleText onContextMenu={(e) => handleCopyUrl(e, url ?? appInfo.url)}>{appInfo.name}</TitleText> <TitleText onContextMenu={(e) => handleCopyUrl(e, url ?? appInfo.url)}>{appInfo.name}</TitleText>
</Tooltip> </Tooltip>
{appInfo.canOpenExternalLink && ( {appInfo.canOpenExternalLink && (
<Tooltip title={t('minapp.popup.openExternal')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip placement="bottom" content={t('minapp.popup.openExternal')} delay={800}>
<TitleButton onClick={() => handleOpenLink(url ?? appInfo.url)}> <TitleButton onClick={() => handleOpenLink(url ?? appInfo.url)}>
<ExportOutlined /> <ExportOutlined />
</TitleButton> </TitleButton>
@ -429,24 +423,24 @@ const MinappPopupContainer: React.FC = () => {
className={isWin || isLinux ? 'windows' : ''} className={isWin || isLinux ? 'windows' : ''}
style={{ marginRight: isWin || isLinux ? '140px' : 0 }} style={{ marginRight: isWin || isLinux ? '140px' : 0 }}
isTopNavbar={isTopNavbar}> isTopNavbar={isTopNavbar}>
<Tooltip title={t('minapp.popup.goBack')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip placement="bottom" content={t('minapp.popup.goBack')} delay={800}>
<TitleButton onClick={() => handleGoBack(appInfo.id)}> <TitleButton onClick={() => handleGoBack(appInfo.id)}>
<ArrowLeftOutlined /> <ArrowLeftOutlined />
</TitleButton> </TitleButton>
</Tooltip> </Tooltip>
<Tooltip title={t('minapp.popup.goForward')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip placement="bottom" content={t('minapp.popup.goForward')} delay={800}>
<TitleButton onClick={() => handleGoForward(appInfo.id)}> <TitleButton onClick={() => handleGoForward(appInfo.id)}>
<ArrowRightOutlined /> <ArrowRightOutlined />
</TitleButton> </TitleButton>
</Tooltip> </Tooltip>
<Tooltip title={t('minapp.popup.refresh')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip placement="bottom" content={t('minapp.popup.refresh')} delay={800}>
<TitleButton onClick={() => handleReload(appInfo.id)}> <TitleButton onClick={() => handleReload(appInfo.id)}>
<ReloadOutlined /> <ReloadOutlined />
</TitleButton> </TitleButton>
</Tooltip> </Tooltip>
{appInfo.canPinned && ( {appInfo.canPinned && (
<Tooltip <Tooltip
title={ content={
appInfo.isPinned appInfo.isPinned
? isTopNavbar ? isTopNavbar
? t('minapp.remove_from_launchpad') ? t('minapp.remove_from_launchpad')
@ -455,40 +449,40 @@ const MinappPopupContainer: React.FC = () => {
? t('minapp.add_to_launchpad') ? t('minapp.add_to_launchpad')
: t('minapp.add_to_sidebar') : t('minapp.add_to_sidebar')
} }
mouseEnterDelay={0.8} placement="bottom"
placement="bottom"> delay={800}>
<TitleButton onClick={() => handleTogglePin(appInfo.id)} className={appInfo.isPinned ? 'pinned' : ''}> <TitleButton onClick={() => handleTogglePin(appInfo.id)} className={appInfo.isPinned ? 'pinned' : ''}>
<PushpinOutlined style={{ fontSize: 16 }} /> <PushpinOutlined style={{ fontSize: 16 }} />
</TitleButton> </TitleButton>
</Tooltip> </Tooltip>
)} )}
<Tooltip <Tooltip
title={ content={
minappsOpenLinkExternal minappsOpenLinkExternal
? t('minapp.popup.open_link_external_on') ? t('minapp.popup.open_link_external_on')
: t('minapp.popup.open_link_external_off') : t('minapp.popup.open_link_external_off')
} }
mouseEnterDelay={0.8} placement="bottom"
placement="bottom"> delay={800}>
<TitleButton onClick={handleToggleOpenExternal} className={minappsOpenLinkExternal ? 'open-external' : ''}> <TitleButton onClick={handleToggleOpenExternal} className={minappsOpenLinkExternal ? 'open-external' : ''}>
<LinkOutlined /> <LinkOutlined />
</TitleButton> </TitleButton>
</Tooltip> </Tooltip>
{isDev && ( {isDev && (
<Tooltip title={t('minapp.popup.devtools')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip placement="bottom" content={t('minapp.popup.devtools')} delay={800}>
<TitleButton onClick={() => handleOpenDevTools(appInfo.id)}> <TitleButton onClick={() => handleOpenDevTools(appInfo.id)}>
<CodeOutlined /> <CodeOutlined />
</TitleButton> </TitleButton>
</Tooltip> </Tooltip>
)} )}
{canMinimize && ( {canMinimize && (
<Tooltip title={t('minapp.popup.minimize')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip placement="bottom" content={t('minapp.popup.minimize')} delay={800}>
<TitleButton onClick={() => handlePopupMinimize()}> <TitleButton onClick={() => handlePopupMinimize()}>
<MinusOutlined /> <MinusOutlined />
</TitleButton> </TitleButton>
</Tooltip> </Tooltip>
)} )}
<Tooltip title={t('minapp.popup.close')} mouseEnterDelay={0.8} placement="bottom"> <Tooltip placement="bottom" content={t('minapp.popup.close')} delay={800}>
<TitleButton onClick={() => handlePopupClose(appInfo.id)}> <TitleButton onClick={() => handlePopupClose(appInfo.id)}>
<CloseOutlined /> <CloseOutlined />
</TitleButton> </TitleButton>

View File

@ -1,5 +1,6 @@
import { Tooltip } from '@cherrystudio/ui'
import type { Model } from '@renderer/types' import type { Model } from '@renderer/types'
import { Tooltip, Typography } from 'antd' import { Typography } from 'antd'
import { memo } from 'react' import { memo } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
@ -20,20 +21,13 @@ const ModelIdWithTags = ({
return ( return (
<ListItemName ref={ref} $fontSize={fontSize} style={style}> <ListItemName ref={ref} $fontSize={fontSize} style={style}>
<Tooltip <Tooltip
styles={{ content={
root: {
width: 'auto',
maxWidth: '500px'
}
}}
destroyOnHidden
title={
<Typography.Text style={{ color: 'white' }} copyable={{ text: model.id }}> <Typography.Text style={{ color: 'white' }} copyable={{ text: model.id }}>
{model.id} {model.id}
</Typography.Text> </Typography.Text>
} }
mouseEnterDelay={0.5} className="w-auto max-w-125"
placement="top"> delay={500}>
<NameSpan>{model.name}</NameSpan> <NameSpan>{model.name}</NameSpan>
</Tooltip> </Tooltip>
<ModelTagsWithLabel model={model} size={11} style={{ flexShrink: 0 }} /> <ModelTagsWithLabel model={model} size={11} style={{ flexShrink: 0 }} />

View File

@ -1,7 +1,6 @@
import { Button } from '@cherrystudio/ui' import type { TooltipProps } from '@cherrystudio/ui'
import { Button, Tooltip } from '@cherrystudio/ui'
import type { Model } from '@renderer/types' import type { Model } from '@renderer/types'
import type { TooltipProps } from 'antd'
import { Tooltip } from 'antd'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import ModelAvatar from './Avatar/ModelAvatar' import ModelAvatar from './Avatar/ModelAvatar'
@ -39,7 +38,7 @@ const ModelSelectButton = ({ model, onSelectModel, modelFilter, noTooltip, toolt
return button return button
} else { } else {
return ( return (
<Tooltip title={model.name} {...tooltipProps}> <Tooltip content={model.name} {...tooltipProps}>
{button} {button}
</Tooltip> </Tooltip>
) )

View File

@ -1,12 +1,11 @@
import { Flex } from '@cherrystudio/ui' import { Button, Flex, Tooltip } from '@cherrystudio/ui'
import { Button } from '@cherrystudio/ui'
import { type HealthResult, HealthStatusIndicator } from '@renderer/components/HealthStatusIndicator' import { type HealthResult, HealthStatusIndicator } from '@renderer/components/HealthStatusIndicator'
import { EditIcon } from '@renderer/components/Icons' import { EditIcon } from '@renderer/components/Icons'
import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon' import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
import type { ApiKeyWithStatus } from '@renderer/types/healthCheck' import type { ApiKeyWithStatus } from '@renderer/types/healthCheck'
import { maskApiKey } from '@renderer/utils/api' import { maskApiKey } from '@renderer/utils/api'
import type { InputRef } from 'antd' import type { InputRef } from 'antd'
import { Input, List, Popconfirm, Tooltip, Typography } from 'antd' import { Input, List, Popconfirm, Typography } from 'antd'
import { Check, Minus, X } from 'lucide-react' import { Check, Minus, X } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { memo, useEffect, useRef, useState } from 'react' import { memo, useEffect, useRef, useState } from 'react'
@ -108,7 +107,7 @@ const ApiKeyItem: FC<ApiKeyItemProps> = ({
disabled={disabled} disabled={disabled}
/> />
<Flex className="items-center gap-0"> <Flex className="items-center gap-0">
<Tooltip title={t('common.save')}> <Tooltip content={t('common.save')}>
<Button <Button
color={hasUnsavedChanges ? 'primary' : 'default'} color={hasUnsavedChanges ? 'primary' : 'default'}
variant={hasUnsavedChanges ? 'solid' : 'light'} variant={hasUnsavedChanges ? 'solid' : 'light'}
@ -118,7 +117,7 @@ const ApiKeyItem: FC<ApiKeyItemProps> = ({
isIconOnly isIconOnly
/> />
</Tooltip> </Tooltip>
<Tooltip title={t('common.cancel')}> <Tooltip content={t('common.cancel')}>
<Button <Button
variant="light" variant="light"
startContent={<X size={16} />} startContent={<X size={16} />}
@ -132,15 +131,12 @@ const ApiKeyItem: FC<ApiKeyItemProps> = ({
) : ( ) : (
<> <>
<Tooltip <Tooltip
title={ content={
<Typography.Text style={{ color: 'white' }} copyable={{ text: keyStatus.key }}> <Typography.Text style={{ color: 'white' }} copyable={{ text: keyStatus.key }}>
{keyStatus.key} {keyStatus.key}
</Typography.Text> </Typography.Text>
} }
mouseEnterDelay={0.5} delay={500}>
placement="top"
// 确保不留下明文
destroyOnHidden>
<span style={{ cursor: 'help' }}>{maskApiKey(keyStatus.key)}</span> <span style={{ cursor: 'help' }}>{maskApiKey(keyStatus.key)}</span>
</Tooltip> </Tooltip>
@ -149,7 +145,7 @@ const ApiKeyItem: FC<ApiKeyItemProps> = ({
<Flex className="items-center gap-0"> <Flex className="items-center gap-0">
{showHealthCheck && ( {showHealthCheck && (
<Tooltip title={t('settings.provider.check')} mouseLeaveDelay={0}> <Tooltip content={t('settings.provider.check')} closeDelay={0}>
<Button <Button
variant="light" variant="light"
startContent={<StreamlineGoodHealthAndWellBeing size={18} isActive={keyStatus.checking} />} startContent={<StreamlineGoodHealthAndWellBeing size={18} isActive={keyStatus.checking} />}
@ -159,7 +155,7 @@ const ApiKeyItem: FC<ApiKeyItemProps> = ({
/> />
</Tooltip> </Tooltip>
)} )}
<Tooltip title={t('common.edit')} mouseLeaveDelay={0}> <Tooltip content={t('common.edit')} closeDelay={0}>
<Button <Button
variant="light" variant="light"
startContent={<EditIcon size={16} />} startContent={<EditIcon size={16} />}
@ -175,7 +171,7 @@ const ApiKeyItem: FC<ApiKeyItemProps> = ({
okText={t('common.confirm')} okText={t('common.confirm')}
cancelText={t('common.cancel')} cancelText={t('common.cancel')}
okButtonProps={{ color: 'danger' }}> okButtonProps={{ color: 'danger' }}>
<Tooltip title={t('common.delete')} mouseLeaveDelay={0}> <Tooltip content={t('common.delete')} closeDelay={0}>
<Button variant="light" startContent={<Minus size={16} />} isDisabled={disabled} isIconOnly /> <Button variant="light" startContent={<Minus size={16} />} isDisabled={disabled} isIconOnly />
</Tooltip> </Tooltip>
</Popconfirm> </Popconfirm>

View File

@ -1,5 +1,4 @@
import { Flex } from '@cherrystudio/ui' import { Button, Flex, Tooltip } from '@cherrystudio/ui'
import { Button } from '@cherrystudio/ui'
import { DeleteIcon } from '@renderer/components/Icons' import { DeleteIcon } from '@renderer/components/Icons'
import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon' import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon'
import Scrollbar from '@renderer/components/Scrollbar' import Scrollbar from '@renderer/components/Scrollbar'
@ -11,7 +10,7 @@ import { isProviderSupportAuth } from '@renderer/services/ProviderService'
import type { PreprocessProviderId, WebSearchProviderId } from '@renderer/types' import type { PreprocessProviderId, WebSearchProviderId } from '@renderer/types'
import type { ApiKeyWithStatus } from '@renderer/types/healthCheck' import type { ApiKeyWithStatus } from '@renderer/types/healthCheck'
import { HealthStatus } from '@renderer/types/healthCheck' import { HealthStatus } from '@renderer/types/healthCheck'
import { Card, List, Popconfirm, Space, Tooltip, Typography } from 'antd' import { Card, List, Popconfirm, Space, Typography } from 'antd'
import { Plus } from 'lucide-react' import { Plus } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useState } from 'react' import { useState } from 'react'
@ -143,7 +142,7 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, show
okText={t('common.confirm')} okText={t('common.confirm')}
cancelText={t('common.cancel')} cancelText={t('common.cancel')}
okButtonProps={{ color: 'danger' }}> okButtonProps={{ color: 'danger' }}>
<Tooltip title={t('settings.provider.remove_invalid_keys')} placement="top" mouseLeaveDelay={0}> <Tooltip content={t('settings.provider.remove_invalid_keys')} closeDelay={0}>
<Button <Button
variant="light" variant="light"
startContent={<DeleteIcon size={16} className="lucide-custom" />} startContent={<DeleteIcon size={16} className="lucide-custom" />}
@ -155,7 +154,7 @@ export const ApiKeyList: FC<ApiKeyListProps> = ({ provider, updateProvider, show
</Popconfirm> </Popconfirm>
{/* 批量检查 */} {/* 批量检查 */}
<Tooltip title={t('settings.provider.check_all_keys')} placement="top" mouseLeaveDelay={0}> <Tooltip content={t('settings.provider.check_all_keys')} closeDelay={0}>
<Button <Button
variant="light" variant="light"
startContent={<StreamlineGoodHealthAndWellBeing size={'1.2em'} />} startContent={<StreamlineGoodHealthAndWellBeing size={'1.2em'} />}

View File

@ -1,8 +1,7 @@
import { Button } from '@cherrystudio/ui' import { Button, Tooltip } from '@cherrystudio/ui'
import { CopyIcon, DeleteIcon } from '@renderer/components/Icons' import { CopyIcon, DeleteIcon } from '@renderer/components/Icons'
import { useChatContext } from '@renderer/hooks/useChatContext' import { useChatContext } from '@renderer/hooks/useChatContext'
import type { Topic } from '@renderer/types' import type { Topic } from '@renderer/types'
import { Tooltip } from 'antd'
import { Save, X } from 'lucide-react' import { Save, X } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -36,7 +35,7 @@ const MultiSelectActionPopup: FC<Props> = ({ topic }) => {
<ActionBar> <ActionBar>
<SelectionCount>{t('common.selectedMessages', { count: selectedMessageIds.length })}</SelectionCount> <SelectionCount>{t('common.selectedMessages', { count: selectedMessageIds.length })}</SelectionCount>
<ActionButtons> <ActionButtons>
<Tooltip title={t('common.save')}> <Tooltip content={t('common.save')}>
<Button <Button
radius="full" radius="full"
variant="light" variant="light"
@ -46,7 +45,7 @@ const MultiSelectActionPopup: FC<Props> = ({ topic }) => {
isIconOnly isIconOnly
/> />
</Tooltip> </Tooltip>
<Tooltip title={t('common.copy')}> <Tooltip content={t('common.copy')}>
<Button <Button
radius="full" radius="full"
variant="light" variant="light"
@ -56,7 +55,7 @@ const MultiSelectActionPopup: FC<Props> = ({ topic }) => {
isIconOnly isIconOnly
/> />
</Tooltip> </Tooltip>
<Tooltip title={t('common.delete')}> <Tooltip content={t('common.delete')}>
<Button <Button
radius="full" radius="full"
color="danger" color="danger"
@ -67,7 +66,7 @@ const MultiSelectActionPopup: FC<Props> = ({ topic }) => {
/> />
</Tooltip> </Tooltip>
</ActionButtons> </ActionButtons>
<Tooltip title={t('chat.navigation.close')}> <Tooltip content={t('chat.navigation.close')}>
<Button radius="full" variant="light" startContent={<X size={16} />} onPress={handleClose} isIconOnly /> <Button radius="full" variant="light" startContent={<X size={16} />} onPress={handleClose} isIconOnly />
</Tooltip> </Tooltip>
</ActionBar> </ActionBar>
@ -92,7 +91,7 @@ const ActionBar = styled.div`
background-color: var(--color-background); background-color: var(--color-background);
padding: 4px 4px; padding: 4px 4px;
border-radius: 99px; border-radius: 99px;
box-shadow: 0px 2px 8px 0px rgb(128 128 128 / 20%); box-shadow: 0 2px 8px 0 rgb(128 128 128 / 20%);
border: 0.5px solid var(--color-border); border: 0.5px solid var(--color-border);
gap: 16px; gap: 16px;
` `

View File

@ -1,4 +1,4 @@
import { ColFlex, Flex } from '@cherrystudio/ui' import { ColFlex, Flex, HelpTooltip } from '@cherrystudio/ui'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import CustomTag from '@renderer/components/Tags/CustomTag' import CustomTag from '@renderer/components/Tags/CustomTag'
import { TopView } from '@renderer/components/TopView' import { TopView } from '@renderer/components/TopView'
@ -14,8 +14,8 @@ import {
processMessageContent, processMessageContent,
processTopicContent processTopicContent
} from '@renderer/utils/knowledge' } from '@renderer/utils/knowledge'
import { Form, Modal, Select, Tooltip, Typography } from 'antd' import { Form, Modal, Select, Typography } from 'antd'
import { Check, CircleHelp } from 'lucide-react' import { Check } from 'lucide-react'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -375,9 +375,7 @@ const PopupContainer: React.FC<Props> = ({ source, title, resolve }) => {
{option.count} {option.count}
</CustomTag> </CustomTag>
<span>{option.label}</span> <span>{option.label}</span>
<Tooltip title={option.description} mouseLeaveDelay={0}> <HelpTooltip content={option.description} closeDelay={0} />
<CircleHelp size={16} style={{ cursor: 'help' }} />
</Tooltip>
</Flex> </Flex>
{selectedTypes.includes(option.type) && <Check size={16} color={TAG_COLORS.SELECTED} />} {selectedTypes.includes(option.type) && <Check size={16} color={TAG_COLORS.SELECTED} />}
</ContentTypeItem> </ContentTypeItem>

View File

@ -16,7 +16,8 @@ exports[`TagFilterSection > rendering > should match snapshot 1`] = `
class="c0" class="c0"
> >
<div <div
class="box-border flex flex-wrap gap-1" class="flex-wrap gap-1"
data-testid="flex"
> >
<span <span
class="c1" class="c1"

View File

@ -1,4 +1,5 @@
import { PushpinOutlined } from '@ant-design/icons' import { PushpinOutlined } from '@ant-design/icons'
import { Tooltip } from '@cherrystudio/ui'
import { Flex } from '@cherrystudio/ui' import { Flex } from '@cherrystudio/ui'
import { Avatar } from '@cherrystudio/ui' import { Avatar } from '@cherrystudio/ui'
import { FreeTrialModelTag } from '@renderer/components/FreeTrialModelTag' import { FreeTrialModelTag } from '@renderer/components/FreeTrialModelTag'
@ -13,7 +14,7 @@ import type { Model, ModelType, Provider } from '@renderer/types'
import { objectEntries } from '@renderer/types' import { objectEntries } from '@renderer/types'
import { classNames, filterModelsByKeywords, getFancyProviderName } from '@renderer/utils' import { classNames, filterModelsByKeywords, getFancyProviderName } from '@renderer/utils'
import { getModelTags } from '@renderer/utils/model' import { getModelTags } from '@renderer/utils/model'
import { Divider, Empty, Modal, Tooltip } from 'antd' import { Divider, Empty, Modal } from 'antd'
import { first, sortBy } from 'lodash' import { first, sortBy } from 'lodash'
import { Settings2 } from 'lucide-react' import { Settings2 } from 'lucide-react'
import React, { import React, {
@ -183,7 +184,7 @@ const PopupContainer: React.FC<Props> = ({ model, filter: baseFilter, showTagFil
type: 'group', type: 'group',
name: getFancyProviderName(p), name: getFancyProviderName(p),
actions: p.id !== 'cherryai' && ( actions: p.id !== 'cherryai' && (
<Tooltip title={t('navigate.provider_settings')} mouseEnterDelay={0.5} mouseLeaveDelay={0}> <Tooltip content={t('navigate.provider_settings')} delay={500} closeDelay={0}>
<Settings2 <Settings2
size={12} size={12}
color="var(--color-text)" color="var(--color-text)"

View File

@ -1,5 +1,4 @@
import { Button } from '@cherrystudio/ui' import { Button, Tooltip } from '@cherrystudio/ui'
import { Tooltip } from 'antd'
import { memo } from 'react' import { memo } from 'react'
interface ImageToolButtonProps { interface ImageToolButtonProps {
@ -10,7 +9,7 @@ interface ImageToolButtonProps {
const ImageToolButton = ({ tooltip, icon, onPress }: ImageToolButtonProps) => { const ImageToolButton = ({ tooltip, icon, onPress }: ImageToolButtonProps) => {
return ( return (
<Tooltip title={tooltip} mouseEnterDelay={0.5} mouseLeaveDelay={0}> <Tooltip content={tooltip} delay={500} closeDelay={0}>
<Button radius="full" startContent={icon} onPress={onPress} isIconOnly aria-label={tooltip} /> <Button radius="full" startContent={icon} onPress={onPress} isIconOnly aria-label={tooltip} />
</Tooltip> </Tooltip>
) )

View File

@ -3,14 +3,23 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import ImageToolButton from '../ImageToolButton' import ImageToolButton from '../ImageToolButton'
// Mock antd components // Mock components
vi.mock('antd', () => ({ vi.mock('antd', () => ({
Button: vi.fn(({ children, onClick, ...props }) => ( Button: vi.fn(({ children, onClick, ...props }) => (
<button type="button" data-testid="custom-button" onClick={onClick} {...props}> <button type="button" data-testid="custom-button" onClick={onClick} {...props}>
{children} {children}
</button> </button>
)), ))
Tooltip: vi.fn(({ children, title }) => <div title={title}>{children}</div>) }))
vi.mock('@cherrystudio/ui', () => ({
Button: ({ children, onPress, disabled, isDisabled, startContent, ...props }: any) => (
<button type="button" data-testid="button" onClick={onPress} disabled={disabled || isDisabled} {...props}>
{startContent}
{children}
</button>
),
Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}</>
})) }))
describe('ImageToolButton', () => { describe('ImageToolButton', () => {

View File

@ -2,24 +2,17 @@
exports[`ImageToolButton > should match snapshot 1`] = ` exports[`ImageToolButton > should match snapshot 1`] = `
<DocumentFragment> <DocumentFragment>
<div <button
title="Test tooltip" aria-label="Test tooltip"
data-testid="button"
radius="full"
type="button"
> >
<button <span
aria-label="Test tooltip" data-testid="test-icon"
class="z-0 group relative inline-flex items-center justify-center box-border appearance-none select-none whitespace-nowrap font-normal subpixel-antialiased overflow-hidden tap-highlight-transparent transform-gpu data-[pressed=true]:scale-[0.97] cursor-pointer outline-solid outline-transparent data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 text-small gap-2 rounded-full px-0 !gap-0 transition-transform-colors-opacity motion-reduce:transition-none bg-default text-default-foreground min-w-10 w-10 h-10 data-[hover=true]:opacity-hover"
data-react-aria-pressable="true"
tabindex="0"
type="button"
> >
<span Icon
aria-hidden="true" </span>
data-testid="test-icon" </button>
focusable="false"
>
Icon
</span>
</button>
</div>
</DocumentFragment> </DocumentFragment>
`; `;

View File

@ -1,8 +1,9 @@
import { SearchOutlined } from '@ant-design/icons' import { SearchOutlined } from '@ant-design/icons'
import { Tooltip } from '@cherrystudio/ui'
import { ProviderAvatarPrimitive } from '@renderer/components/ProviderAvatar' import { ProviderAvatarPrimitive } from '@renderer/components/ProviderAvatar'
import { PROVIDER_LOGO_MAP } from '@renderer/config/providers' import { PROVIDER_LOGO_MAP } from '@renderer/config/providers'
import { getProviderLabel } from '@renderer/i18n/label' import { getProviderLabel } from '@renderer/i18n/label'
import { Input, Tooltip } from 'antd' import { Input } from 'antd'
import type { FC } from 'react' import type { FC } from 'react'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
@ -51,9 +52,14 @@ const ProviderLogoPicker: FC<Props> = ({ onProviderClick }) => {
</SearchContainer> </SearchContainer>
<LogoGrid> <LogoGrid>
{filteredProviders.map(({ id, name, logo }) => ( {filteredProviders.map(({ id, name, logo }) => (
<Tooltip key={id} title={name} placement="top" mouseLeaveDelay={0}> <Tooltip key={id} content={name} closeDelay={0}>
<LogoItem onClick={(e) => handleProviderClick(e, id)}> <LogoItem onClick={(e) => handleProviderClick(e, id)}>
<ProviderAvatarPrimitive providerId={id} style={{ width: '52px', height: '52px' }} providerName={name} logoSrc={logo} /> <ProviderAvatarPrimitive
providerId={id}
style={{ width: '52px', height: '52px' }}
providerName={name}
logoSrc={logo}
/>
</LogoItem> </LogoItem>
</Tooltip> </Tooltip>
))} ))}

View File

@ -1,8 +1,8 @@
import { CopyOutlined } from '@ant-design/icons' import { CopyOutlined } from '@ant-design/icons'
import { Button } from '@cherrystudio/ui' import { Button, Tooltip } from '@cherrystudio/ui'
import { DEFAULT_LANGUAGES, getHighlighter, getShiki } from '@renderer/utils/shiki' import { DEFAULT_LANGUAGES, getHighlighter, getShiki } from '@renderer/utils/shiki'
import { NodeViewContent, NodeViewWrapper, type ReactNodeViewProps, ReactNodeViewRenderer } from '@tiptap/react' import { NodeViewContent, NodeViewWrapper, type ReactNodeViewProps, ReactNodeViewRenderer } from '@tiptap/react'
import { Select, Tooltip } from 'antd' import { Select } from 'antd'
import type { FC } from 'react' import type { FC } from 'react'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
@ -66,7 +66,7 @@ const CodeBlockNodeView: FC<ReactNodeViewProps> = (props) => {
options={languageOptions.map((lang) => ({ value: lang, label: lang }))} options={languageOptions.map((lang) => ({ value: lang, label: lang }))}
style={{ minWidth: 90 }} style={{ minWidth: 90 }}
/> />
<Tooltip title="Copy"> <Tooltip content="Copy">
<Button <Button
size="sm" size="sm"
variant="light" variant="light"

View File

@ -1,8 +1,8 @@
import { Tooltip } from '@cherrystudio/ui'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { ContentSearch, type ContentSearchRef } from '@renderer/components/ContentSearch' import { ContentSearch, type ContentSearchRef } from '@renderer/components/ContentSearch'
import DragHandle from '@tiptap/extension-drag-handle-react' import DragHandle from '@tiptap/extension-drag-handle-react'
import { EditorContent } from '@tiptap/react' import { EditorContent } from '@tiptap/react'
import { Tooltip } from 'antd'
import { t } from 'i18next' import { t } from 'i18next'
import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, GripVertical, Plus, Trash2 } from 'lucide-react' import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, GripVertical, Plus, Trash2 } from 'lucide-react'
import React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react' import React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'
@ -402,12 +402,12 @@ const RichEditor = ({
<Scrollbar ref={scrollContainerRef} style={{ flex: 1, display: 'flex' }}> <Scrollbar ref={scrollContainerRef} style={{ flex: 1, display: 'flex' }}>
<StyledEditorContent> <StyledEditorContent>
<PlusButton editor={editor} onElementClick={handlePlusButtonClick}> <PlusButton editor={editor} onElementClick={handlePlusButtonClick}>
<Tooltip title={t('richEditor.plusButton')}> <Tooltip content={t('richEditor.plusButton')}>
<Plus /> <Plus />
</Tooltip> </Tooltip>
</PlusButton> </PlusButton>
<DragHandle editor={editor} onElementDragEnd={handleDragEnd}> <DragHandle editor={editor} onElementDragEnd={handleDragEnd}>
<Tooltip title={t('richEditor.dragHandle')}> <Tooltip content={t('richEditor.dragHandle')}>
<GripVertical /> <GripVertical />
</Tooltip> </Tooltip>
</DragHandle> </DragHandle>

View File

@ -1,4 +1,4 @@
import { Tooltip } from 'antd' import { Tooltip } from '@cherrystudio/ui'
import type { TFunction } from 'i18next' import type { TFunction } from 'i18next'
import type { LucideProps } from 'lucide-react' import type { LucideProps } from 'lucide-react'
import type { ForwardRefExoticComponent, RefAttributes } from 'react' import type { ForwardRefExoticComponent, RefAttributes } from 'react'
@ -177,7 +177,7 @@ export const Toolbar: React.FC<ToolbarProps> = ({ editor, formattingState, onCom
) )
return ( return (
<Tooltip key={item.id} title={tooltipText} placement="top"> <Tooltip key={item.id} content={tooltipText}>
{buttonElement} {buttonElement}
</Tooltip> </Tooltip>
) )

View File

@ -1,9 +1,9 @@
import { DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined } from '@ant-design/icons' import { DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined } from '@ant-design/icons'
import { Button } from '@cherrystudio/ui' import { Button, Tooltip } from '@cherrystudio/ui'
import { restoreFromS3 } from '@renderer/services/BackupService' import { restoreFromS3 } from '@renderer/services/BackupService'
import type { S3Config } from '@renderer/types' import type { S3Config } from '@renderer/types'
import { formatFileSize } from '@renderer/utils' import { formatFileSize } from '@renderer/utils'
import { Modal, Table, Tooltip } from 'antd' import { Modal, Table } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -207,7 +207,7 @@ export function S3BackupManager({ visible, onClose, s3Config, restoreMethod }: S
showTitle: false showTitle: false
}, },
render: (fileName: string) => ( render: (fileName: string) => (
<Tooltip placement="topLeft" title={fileName}> <Tooltip placement="top-start" content={fileName}>
{fileName} {fileName}
</Tooltip> </Tooltip>
) )

View File

@ -1,5 +1,6 @@
import { PlusOutlined } from '@ant-design/icons' import { PlusOutlined } from '@ant-design/icons'
import { Sortable, useDndReorder } from '@cherrystudio/ui' import { Sortable, useDndReorder } from '@cherrystudio/ui'
import { Tooltip } from '@cherrystudio/ui'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer' import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer'
import { isMac } from '@renderer/config/constant' import { isMac } from '@renderer/config/constant'
@ -16,7 +17,6 @@ import { addTab, removeTab, setActiveTab, setTabs } from '@renderer/store/tabs'
import type { MinAppType } from '@renderer/types' import type { MinAppType } from '@renderer/types'
import { classNames } from '@renderer/utils' import { classNames } from '@renderer/utils'
import { ThemeMode } from '@shared/data/preference/preferenceTypes' import { ThemeMode } from '@shared/data/preference/preferenceTypes'
import { Tooltip } from 'antd'
import type { LRUCache } from 'lru-cache' import type { LRUCache } from 'lru-cache'
import { import {
FileSearch, FileSearch,
@ -263,9 +263,9 @@ const TabsContainer: React.FC<TabsContainerProps> = ({ children }) => {
</HorizontalScrollContainer> </HorizontalScrollContainer>
<RightButtonsContainer> <RightButtonsContainer>
<Tooltip <Tooltip
title={t('settings.theme.title') + ': ' + getThemeModeLabel(settedTheme)} placement="bottom"
mouseEnterDelay={0.8} content={t('settings.theme.title') + ': ' + getThemeModeLabel(settedTheme)}
placement="bottom"> delay={800}>
<ThemeButton onClick={toggleTheme}> <ThemeButton onClick={toggleTheme}>
{settedTheme === ThemeMode.dark ? ( {settedTheme === ThemeMode.dark ? (
<Moon size={16} /> <Moon size={16} />

View File

@ -1,5 +1,5 @@
import { CloseOutlined } from '@ant-design/icons' import { CloseOutlined } from '@ant-design/icons'
import { Tooltip } from 'antd' import { Tooltip } from '@cherrystudio/ui'
import type { CSSProperties, FC, MouseEventHandler } from 'react' import type { CSSProperties, FC, MouseEventHandler } from 'react'
import { memo, useMemo } from 'react' import { memo, useMemo } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
@ -61,7 +61,7 @@ const CustomTag: FC<CustomTagProps> = ({
) )
return tooltip ? ( return tooltip ? (
<Tooltip title={tooltip} placement="top" mouseEnterDelay={0.3}> <Tooltip content={tooltip} delay={300}>
{tagContent} {tagContent}
</Tooltip> </Tooltip>
) : ( ) : (

View File

@ -1,28 +0,0 @@
import type { TooltipProps } from 'antd'
import { Tooltip } from 'antd'
import { HelpCircle } from 'lucide-react'
type InheritedTooltipProps = Omit<TooltipProps, 'children'>
interface HelpTooltipProps extends InheritedTooltipProps {
iconColor?: string
iconSize?: string | number
iconStyle?: React.CSSProperties
}
const HelpTooltip = ({ iconColor = 'var(--color-text-2)', iconSize = 14, iconStyle, ...rest }: HelpTooltipProps) => {
return (
<Tooltip {...rest}>
<HelpCircle
size={iconSize}
color={iconColor}
style={{ ...iconStyle, cursor: 'help' }}
role="img"
aria-label="Help"
className="relative z-10"
/>
</Tooltip>
)
}
export default HelpTooltip

View File

@ -1,21 +0,0 @@
import type { TooltipProps } from 'antd'
import { Tooltip } from 'antd'
import { Info } from 'lucide-react'
type InheritedTooltipProps = Omit<TooltipProps, 'children'>
interface InfoTooltipProps extends InheritedTooltipProps {
iconColor?: string
iconSize?: string | number
iconStyle?: React.CSSProperties
}
const InfoTooltip = ({ iconColor = 'var(--color-text-2)', iconSize = 14, iconStyle, ...rest }: InfoTooltipProps) => {
return (
<Tooltip {...rest}>
<Info size={iconSize} color={iconColor} style={{ ...iconStyle }} role="img" aria-label="Information" />
</Tooltip>
)
}
export default InfoTooltip

View File

@ -1,26 +0,0 @@
import type { TooltipProps } from 'antd'
import { Tooltip } from 'antd'
import { AlertTriangle } from 'lucide-react'
type InheritedTooltipProps = Omit<TooltipProps, 'children'>
interface WarnTooltipProps extends InheritedTooltipProps {
iconColor?: string
iconSize?: string | number
iconStyle?: React.CSSProperties
}
const WarnTooltip = ({
iconColor = 'var(--color-status-warning)',
iconSize = 14,
iconStyle,
...rest
}: WarnTooltipProps) => {
return (
<Tooltip {...rest}>
<AlertTriangle size={iconSize} color={iconColor} style={{ ...iconStyle }} role="img" aria-label="Information" />
</Tooltip>
)
}
export default WarnTooltip

View File

@ -1,38 +0,0 @@
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import InfoTooltip from '../InfoTooltip'
vi.mock('antd', () => ({
Tooltip: ({ children, title }: { children: React.ReactNode; title: string }) => (
<div>
{children}
{title && <div>{title}</div>}
</div>
)
}))
vi.mock('lucide-react', () => ({
Info: ({ ref, ...props }) => (
<div {...props} ref={ref} role="img" aria-label="Information">
Info
</div>
)
}))
describe('InfoTooltip', () => {
it('should match snapshot', () => {
const { container } = render(
<InfoTooltip title="Test tooltip" placement="top" iconColor="#1890ff" iconStyle={{ fontSize: '16px' }} />
)
expect(container.firstChild).toMatchSnapshot()
})
it('should pass title prop to the underlying Tooltip component', () => {
const tooltipText = 'This is helpful information'
render(<InfoTooltip title={tooltipText} />)
expect(screen.getByRole('img', { name: 'Information' })).toBeInTheDocument()
expect(screen.getByText(tooltipText)).toBeInTheDocument()
})
})

View File

@ -1,18 +0,0 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`InfoTooltip > should match snapshot 1`] = `
<div>
<div
aria-label="Information"
color="#1890ff"
role="img"
size="14"
style="font-size: 16px;"
>
Info
</div>
<div>
Test tooltip
</div>
</div>
`;

View File

@ -1,3 +0,0 @@
export { default as HelpTooltip } from './HelpTooltip'
export { default as InfoTooltip } from './InfoTooltip'
export { default as WarnTooltip } from './WarnTooltip'

View File

@ -1,10 +1,9 @@
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
import { Button } from '@cherrystudio/ui' import { Button, Tooltip } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference' import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import useTranslate from '@renderer/hooks/useTranslate' import useTranslate from '@renderer/hooks/useTranslate'
import { translateText } from '@renderer/services/TranslateService' import { translateText } from '@renderer/services/TranslateService'
import { Tooltip } from 'antd'
import { Languages } from 'lucide-react' import { Languages } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
@ -66,10 +65,8 @@ const TranslateButton: FC<Props> = ({ text, onTranslated, disabled, style, isLoa
return ( return (
<Tooltip <Tooltip
placement="top" content={t('chat.input.translate', { target_language: getLanguageByLangcode(targetLanguage).label() })}
title={t('chat.input.translate', { target_language: getLanguageByLangcode(targetLanguage).label() })} closeDelay={0}>
mouseLeaveDelay={0}
arrow>
<Button <Button
onPress={handleTranslate} onPress={handleTranslate}
isDisabled={disabled || isTranslating} isDisabled={disabled || isTranslating}

View File

@ -1,8 +1,8 @@
import { DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined } from '@ant-design/icons' import { DeleteOutlined, ExclamationCircleOutlined, ReloadOutlined } from '@ant-design/icons'
import { Button } from '@cherrystudio/ui' import { Button, Tooltip } from '@cherrystudio/ui'
import { restoreFromWebdav } from '@renderer/services/BackupService' import { restoreFromWebdav } from '@renderer/services/BackupService'
import { formatFileSize } from '@renderer/utils' import { formatFileSize } from '@renderer/utils'
import { Modal, Table, Tooltip } from 'antd' import { Modal, Table } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -214,7 +214,7 @@ export function WebdavBackupManager({
showTitle: false showTitle: false
}, },
render: (fileName: string) => ( render: (fileName: string) => (
<Tooltip placement="topLeft" title={fileName}> <Tooltip placement="top-start" content={fileName}>
{fileName} {fileName}
</Tooltip> </Tooltip>
) )

View File

@ -1,5 +1,5 @@
import { Tooltip } from '@cherrystudio/ui'
import { isLinux, isWin } from '@renderer/config/constant' import { isLinux, isWin } from '@renderer/config/constant'
import { Tooltip } from 'antd'
import { Minus, Square, X } from 'lucide-react' import { Minus, Square, X } from 'lucide-react'
import type { SVGProps } from 'react' import type { SVGProps } from 'react'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
@ -11,6 +11,8 @@ interface WindowRestoreIconProps extends SVGProps<SVGSVGElement> {
size?: string | number size?: string | number
} }
const DEFAULT_DELAY = 1000
export const WindowRestoreIcon = ({ size = '1.1em', ...props }: WindowRestoreIconProps) => ( export const WindowRestoreIcon = ({ size = '1.1em', ...props }: WindowRestoreIconProps) => (
<svg <svg
width={size} width={size}
@ -44,8 +46,6 @@ export const WindowRestoreIcon = ({ size = '1.1em', ...props }: WindowRestoreIco
</svg> </svg>
) )
const DEFAULT_DELAY = 1
const WindowControls: React.FC = () => { const WindowControls: React.FC = () => {
const [isMaximized, setIsMaximized] = useState(false) const [isMaximized, setIsMaximized] = useState(false)
const { t } = useTranslation() const { t } = useTranslation()
@ -85,20 +85,20 @@ const WindowControls: React.FC = () => {
return ( return (
<WindowControlsContainer> <WindowControlsContainer>
<Tooltip title={t('navbar.window.minimize')} placement="bottom" mouseEnterDelay={DEFAULT_DELAY}> <Tooltip placement="bottom" content={t('navbar.window.minimize')} delay={DEFAULT_DELAY}>
<ControlButton onClick={handleMinimize} aria-label="Minimize"> <ControlButton onClick={handleMinimize} aria-label="Minimize">
<Minus size={14} /> <Minus size={14} />
</ControlButton> </ControlButton>
</Tooltip> </Tooltip>
<Tooltip <Tooltip
title={isMaximized ? t('navbar.window.restore') : t('navbar.window.maximize')}
placement="bottom" placement="bottom"
mouseEnterDelay={DEFAULT_DELAY}> content={isMaximized ? t('navbar.window.restore') : t('navbar.window.maximize')}
delay={DEFAULT_DELAY}>
<ControlButton onClick={handleMaximize} aria-label={isMaximized ? 'Restore' : 'Maximize'}> <ControlButton onClick={handleMaximize} aria-label={isMaximized ? 'Restore' : 'Maximize'}>
{isMaximized ? <WindowRestoreIcon size={14} /> : <Square size={14} />} {isMaximized ? <WindowRestoreIcon size={14} /> : <Square size={14} />}
</ControlButton> </ControlButton>
</Tooltip> </Tooltip>
<Tooltip title={t('navbar.window.close')} placement="bottom" mouseEnterDelay={DEFAULT_DELAY}> <Tooltip placement="bottom" content={t('navbar.window.close')} delay={DEFAULT_DELAY}>
<ControlButton $isClose onClick={handleClose} aria-label="Close"> <ControlButton $isClose onClick={handleClose} aria-label="Close">
<X size={17} /> <X size={17} />
</ControlButton> </ControlButton>

View File

@ -28,18 +28,16 @@ describe('CustomTag', () => {
reasoning reasoning
</CustomTag> </CustomTag>
) )
// 鼠标悬停触发 Tooltip // 鼠标悬停触发 Tooltipmock 直接渲染 tooltip 内容)
await userEvent.hover(screen.getByText('reasoning')) await userEvent.hover(screen.getByText('reasoning'))
expect(await screen.findByText('reasoning model')).toBeInTheDocument() expect(screen.getByTestId('tooltip-content')).toHaveTextContent('reasoning model')
}) })
it('should not render Tooltip when tooltip is not set', () => { it('should not render Tooltip when tooltip is not set', () => {
render(<CustomTag color="#ff0000">no tooltip</CustomTag>) render(<CustomTag color="#ff0000">no tooltip</CustomTag>)
expect(screen.getByText('no tooltip')).toBeInTheDocument() expect(screen.getByText('no tooltip')).toBeInTheDocument()
// 不应有 tooltip 相关内容 expect(screen.queryByTestId('tooltip-content')).not.toBeInTheDocument()
expect(document.querySelector('.ant-tooltip')).toBeNull()
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument()
}) })
it('should not allow click when disabled', async () => { it('should not allow click when disabled', async () => {

View File

@ -54,19 +54,26 @@ vi.mock('antd', () => {
</button> </button>
) )
const MockTooltip: React.FC<React.PropsWithChildren<{ title: string }>> = ({ children, title }) => ( return {
Button: MockButton,
InputNumber: MockInputNumber,
Space: { Compact: MockSpaceCompact }
}
})
vi.mock('@cherrystudio/ui', () => ({
Button: ({ children, onPress, disabled, isDisabled, startContent, ...props }: any) => (
<button type="button" data-testid="button" onClick={onPress} disabled={disabled || isDisabled} {...props}>
{startContent}
{children}
</button>
),
Tooltip: ({ children, title }: { children: React.ReactNode; title: React.ReactNode }) => (
<div data-testid="tooltip" data-title={title}> <div data-testid="tooltip" data-title={title}>
{children} {children}
</div> </div>
) )
}))
return {
Button: MockButton,
InputNumber: MockInputNumber,
Space: { Compact: MockSpaceCompact },
Tooltip: MockTooltip
}
})
// Mock dependencies // Mock dependencies
vi.mock('@renderer/aiCore', () => ({ vi.mock('@renderer/aiCore', () => ({

View File

@ -14,22 +14,17 @@ exports[`InputEmbeddingDimension > basic rendering > should match snapshot with
/> />
<div <div
data-testid="tooltip" data-testid="tooltip"
data-title="自动设置维度"
> >
<button <button
aria-label="Get embedding dimension" aria-label="Get embedding dimension"
class="z-0 group relative inline-flex items-center justify-center box-border appearance-none select-none whitespace-nowrap font-normal subpixel-antialiased overflow-hidden tap-highlight-transparent transform-gpu data-[pressed=true]:scale-[0.97] cursor-pointer outline-solid outline-transparent data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 text-tiny gap-2 rounded-small px-0 !gap-0 transition-transform-colors-opacity motion-reduce:transition-none bg-default text-default-foreground min-w-8 w-8 h-8 data-[hover=true]:opacity-hover" data-testid="button"
data-react-aria-pressable="true"
role="button" role="button"
tabindex="0"
type="button" type="button"
> >
<svg <svg
aria-hidden="true"
aria-label="refresh" aria-label="refresh"
class="" class=""
data-testid="refresh-icon" data-testid="refresh-icon"
focusable="false"
role="img" role="img"
size="16" size="16"
> >
@ -54,32 +49,23 @@ exports[`InputEmbeddingDimension > basic rendering > should match snapshot with
/> />
<div <div
data-testid="tooltip" data-testid="tooltip"
data-title="自动设置维度"
> >
<button <button
aria-label="Get embedding dimension" aria-label="Get embedding dimension"
class="z-0 group relative inline-flex items-center justify-center box-border appearance-none select-none whitespace-nowrap font-normal subpixel-antialiased overflow-hidden tap-highlight-transparent transform-gpu data-[pressed=true]:scale-[0.97] cursor-pointer outline-solid outline-transparent data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 text-tiny gap-2 rounded-small opacity-disabled pointer-events-none px-0 !gap-0 transition-transform-colors-opacity motion-reduce:transition-none bg-default text-default-foreground min-w-8 w-8 h-8 data-[hover=true]:opacity-hover" data-testid="button"
data-disabled="true"
data-react-aria-pressable="true"
disabled="" disabled=""
role="button" role="button"
type="button" type="button"
> >
<svg <svg
aria-hidden="true"
aria-label="refresh" aria-label="refresh"
class="animation-rotate" class="animation-rotate"
data-testid="refresh-icon" data-testid="refresh-icon"
focusable="false"
role="img" role="img"
size="16" size="16"
> >
RefreshIcon RefreshIcon
</svg> </svg>
<span
class="heroui-ripple"
style="position: absolute; background-color: currentColor; border-radius: 100%; transform-origin: center; pointer-events: none; overflow: hidden; inset: 0; z-index: 0; top: 0px; left: 0px; width: 0px; height: 0px; transform: scale(0); opacity: 0.35;"
/>
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,3 +1,4 @@
import { Tooltip } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference' import { usePreference } from '@data/hooks/usePreference'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup' import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
@ -5,7 +6,7 @@ import { useMinapps } from '@renderer/hooks/useMinapps'
import { useNavbarPosition } from '@renderer/hooks/useNavbar' import { useNavbarPosition } from '@renderer/hooks/useNavbar'
import type { MinAppType } from '@renderer/types' import type { MinAppType } from '@renderer/types'
import type { MenuProps } from 'antd' import type { MenuProps } from 'antd'
import { Dropdown, Tooltip } from 'antd' import { Dropdown } from 'antd'
import type { FC } from 'react' import type { FC } from 'react'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -86,16 +87,18 @@ export const SidebarOpenedMinappTabs: FC = () => {
const isActive = minappShow && currentMinappId === app.id const isActive = minappShow && currentMinappId === app.id
return ( return (
<Tooltip key={app.id} title={app.name} mouseEnterDelay={0.8} placement="right"> <Dropdown
<Dropdown menu={{ items: menuItems }} trigger={['contextMenu']} overlayStyle={{ zIndex: 10000 }}> key={app.id}
<Icon menu={{ items: menuItems }}
theme={theme} trigger={['contextMenu']}
onClick={() => handleOnClick(app)} overlayStyle={{ zIndex: 10000 }}>
className={`${isActive ? 'opened-active' : ''}`}> {/* FIXME: Antd Dropdown is not compatible with HeroUI Tooltip */}
<MinAppIcon size={20} app={app} style={{ borderRadius: 6 }} sidebar /> {/* <Tooltip content={app.name} placement="right" delay={800}> */}
</Icon> <Icon theme={theme} onClick={() => handleOnClick(app)} className={`${isActive ? 'opened-active' : ''}`}>
</Dropdown> <MinAppIcon size={20} app={app} style={{ borderRadius: 6 }} sidebar />
</Tooltip> </Icon>
{/* </Tooltip> */}
</Dropdown>
) )
})} })}
</Menus> </Menus>
@ -126,7 +129,7 @@ export const SidebarPinnedApps: FC = () => {
] ]
const isActive = minappShow && currentMinappId === app.id const isActive = minappShow && currentMinappId === app.id
return ( return (
<Tooltip key={app.id} title={app.name} mouseEnterDelay={0.8} placement="right"> <Tooltip key={app.id} content={app.name} placement="right" delay={800}>
<Dropdown menu={{ items: menuItems }} trigger={['contextMenu']} overlayStyle={{ zIndex: 10000 }}> <Dropdown menu={{ items: menuItems }} trigger={['contextMenu']} overlayStyle={{ zIndex: 10000 }}>
<Icon <Icon
theme={theme} theme={theme}

View File

@ -1,4 +1,4 @@
import { Avatar, EmojiAvatar } from '@cherrystudio/ui' import { Avatar, EmojiAvatar, Tooltip } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference' import { usePreference } from '@data/hooks/usePreference'
import { isMac } from '@renderer/config/constant' import { isMac } from '@renderer/config/constant'
import { UserAvatar } from '@renderer/config/env' import { UserAvatar } from '@renderer/config/env'
@ -13,7 +13,6 @@ import { useSettings } from '@renderer/hooks/useSettings'
import { getSidebarIconLabel, getThemeModeLabel } from '@renderer/i18n/label' import { getSidebarIconLabel, getThemeModeLabel } from '@renderer/i18n/label'
import { isEmoji } from '@renderer/utils' import { isEmoji } from '@renderer/utils'
import { ThemeMode } from '@shared/data/preference/preferenceTypes' import { ThemeMode } from '@shared/data/preference/preferenceTypes'
import { Tooltip } from 'antd'
import { import {
Code, Code,
FileSearch, FileSearch,
@ -90,9 +89,9 @@ const Sidebar: FC = () => {
</MainMenusContainer> </MainMenusContainer>
<Menus> <Menus>
<Tooltip <Tooltip
title={t('settings.theme.title') + ': ' + getThemeModeLabel(settedTheme)} placement="right"
mouseEnterDelay={0.8} content={t('settings.theme.title') + ': ' + getThemeModeLabel(settedTheme)}
placement="right"> delay={800}>
<Icon theme={theme} onClick={toggleTheme}> <Icon theme={theme} onClick={toggleTheme}>
{settedTheme === ThemeMode.dark ? ( {settedTheme === ThemeMode.dark ? (
<Moon size={20} className="icon" /> <Moon size={20} className="icon" />
@ -103,7 +102,7 @@ const Sidebar: FC = () => {
)} )}
</Icon> </Icon>
</Tooltip> </Tooltip>
<Tooltip title={t('settings.title')} mouseEnterDelay={0.8} placement="right"> <Tooltip placement="right" content={t('settings.title')} delay={800}>
<StyledLink <StyledLink
onClick={async () => { onClick={async () => {
hideMinappPopup() hideMinappPopup()
@ -161,7 +160,7 @@ const MainMenus: FC = () => {
const isActive = path === '/' ? isRoute(path) : isRoutes(path) const isActive = path === '/' ? isRoute(path) : isRoutes(path)
return ( return (
<Tooltip key={icon} title={getSidebarIconLabel(icon)} mouseEnterDelay={0.8} placement="right"> <Tooltip key={icon} placement="right" content={getSidebarIconLabel(icon)} delay={800}>
<StyledLink <StyledLink
onClick={async () => { onClick={async () => {
hideMinappPopup() hideMinappPopup()

View File

@ -1,5 +1,4 @@
import { Button } from '@cherrystudio/ui' import { Avatar, Button, Tooltip } from '@cherrystudio/ui'
import { Avatar } from '@cherrystudio/ui'
import AiProvider from '@renderer/aiCore' import AiProvider from '@renderer/aiCore'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import ModelSelector from '@renderer/components/ModelSelector' import ModelSelector from '@renderer/components/ModelSelector'
@ -18,7 +17,7 @@ import { setIsBunInstalled } from '@renderer/store/mcp'
import type { EndpointType, Model } from '@renderer/types' import type { EndpointType, Model } from '@renderer/types'
import type { TerminalConfig } from '@shared/config/constant' import type { TerminalConfig } from '@shared/config/constant'
import { codeTools, terminalApps } from '@shared/config/constant' import { codeTools, terminalApps } from '@shared/config/constant'
import { Alert, Checkbox, Input, Popover, Select, Space, Tooltip } from 'antd' import { Alert, Checkbox, Input, Popover, Select, Space } from 'antd'
import { ArrowUpRight, Download, FolderOpen, HelpCircle, Terminal, X } from 'lucide-react' import { ArrowUpRight, Download, FolderOpen, HelpCircle, Terminal, X } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react' import { useCallback, useEffect, useMemo, useState } from 'react'
@ -458,7 +457,7 @@ const CodeToolsPage: FC = () => {
selectedTerminal !== terminalApps.cmd && selectedTerminal !== terminalApps.cmd &&
selectedTerminal !== terminalApps.powershell && selectedTerminal !== terminalApps.powershell &&
selectedTerminal !== terminalApps.windowsTerminal && ( selectedTerminal !== terminalApps.windowsTerminal && (
<Tooltip title={terminalCustomPaths[selectedTerminal] || t('code.set_custom_path')}> <Tooltip content={terminalCustomPaths[selectedTerminal] || t('code.set_custom_path')}>
<Button <Button
startContent={<FolderOpen size={16} />} startContent={<FolderOpen size={16} />}
isIconOnly isIconOnly

View File

@ -1,4 +1,5 @@
import { RowFlex } from '@cherrystudio/ui' import { RowFlex } from '@cherrystudio/ui'
import { Tooltip } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference' import { usePreference } from '@data/hooks/usePreference'
import { NavbarHeader } from '@renderer/components/app/Navbar' import { NavbarHeader } from '@renderer/components/app/Navbar'
import SearchPopup from '@renderer/components/Popups/SearchPopup' import SearchPopup from '@renderer/components/Popups/SearchPopup'
@ -8,7 +9,6 @@ import { useShortcut } from '@renderer/hooks/useShortcuts'
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore' import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import type { Assistant, Topic } from '@renderer/types' import type { Assistant, Topic } from '@renderer/types'
import { Tooltip } from 'antd'
import { t } from 'i18next' import { t } from 'i18next'
import { Menu, PanelLeftClose, PanelRightClose, Search } from 'lucide-react' import { Menu, PanelLeftClose, PanelRightClose, Search } from 'lucide-react'
import { AnimatePresence, motion } from 'motion/react' import { AnimatePresence, motion } from 'motion/react'
@ -68,14 +68,14 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
<NavbarHeader className="home-navbar"> <NavbarHeader className="home-navbar">
<RowFlex className="items-center"> <RowFlex className="items-center">
{showAssistants && ( {showAssistants && (
<Tooltip title={t('navbar.hide_sidebar')} mouseEnterDelay={0.8}> <Tooltip placement="bottom" content={t('navbar.hide_sidebar')} delay={800}>
<NavbarIcon onClick={toggleShowAssistants}> <NavbarIcon onClick={toggleShowAssistants}>
<PanelLeftClose size={18} /> <PanelLeftClose size={18} />
</NavbarIcon> </NavbarIcon>
</Tooltip> </Tooltip>
)} )}
{!showAssistants && ( {!showAssistants && (
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={0.8}> <Tooltip placement="bottom" content={t('navbar.show_sidebar')} delay={800}>
<NavbarIcon onClick={() => toggleShowAssistants()} style={{ marginRight: 8 }}> <NavbarIcon onClick={() => toggleShowAssistants()} style={{ marginRight: 8 }}>
<PanelRightClose size={18} /> <PanelRightClose size={18} />
</NavbarIcon> </NavbarIcon>
@ -98,25 +98,25 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
</RowFlex> </RowFlex>
<RowFlex className="items-center gap-2"> <RowFlex className="items-center gap-2">
<UpdateAppButton /> <UpdateAppButton />
<Tooltip title={t('navbar.expand')} mouseEnterDelay={0.8}> <Tooltip placement="bottom" content={t('navbar.expand')} delay={800}>
<NarrowIcon onClick={handleNarrowModeToggle}> <NarrowIcon onClick={handleNarrowModeToggle}>
<i className="iconfont icon-icon-adaptive-width"></i> <i className="iconfont icon-icon-adaptive-width"></i>
</NarrowIcon> </NarrowIcon>
</Tooltip> </Tooltip>
<Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}> <Tooltip placement="bottom" content={t('chat.assistant.search.placeholder')} delay={800}>
<NavbarIcon onClick={() => SearchPopup.show()}> <NavbarIcon onClick={() => SearchPopup.show()}>
<Search size={18} /> <Search size={18} />
</NavbarIcon> </NavbarIcon>
</Tooltip> </Tooltip>
{topicPosition === 'right' && !showTopics && ( {topicPosition === 'right' && !showTopics && (
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={2}> <Tooltip placement="bottom" content={t('navbar.show_sidebar')} delay={2000}>
<NavbarIcon onClick={toggleShowTopics}> <NavbarIcon onClick={toggleShowTopics}>
<PanelLeftClose size={18} /> <PanelLeftClose size={18} />
</NavbarIcon> </NavbarIcon>
</Tooltip> </Tooltip>
)} )}
{topicPosition === 'right' && showTopics && ( {topicPosition === 'right' && showTopics && (
<Tooltip title={t('navbar.hide_sidebar')} mouseEnterDelay={2}> <Tooltip placement="bottom" content={t('navbar.hide_sidebar')} delay={2000}>
<NavbarIcon onClick={toggleShowTopics}> <NavbarIcon onClick={toggleShowTopics}>
<PanelRightClose size={18} /> <PanelRightClose size={18} />
</NavbarIcon> </NavbarIcon>

View File

@ -1,9 +1,9 @@
import { Tooltip } from '@cherrystudio/ui'
import { ActionIconButton } from '@renderer/components/Buttons' import { ActionIconButton } from '@renderer/components/Buttons'
import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel' import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
import { useKnowledgeBases } from '@renderer/hooks/useKnowledge' import { useKnowledgeBases } from '@renderer/hooks/useKnowledge'
import type { FileType, KnowledgeBase, KnowledgeItem } from '@renderer/types' import type { FileType, KnowledgeBase, KnowledgeItem } from '@renderer/types'
import { filterSupportedFiles, formatFileSize } from '@renderer/utils/file' import { filterSupportedFiles, formatFileSize } from '@renderer/utils/file'
import { Tooltip } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { FileSearch, FileText, Paperclip, Upload } from 'lucide-react' import { FileSearch, FileText, Paperclip, Upload } from 'lucide-react'
import type { Dispatch, FC, SetStateAction } from 'react' import type { Dispatch, FC, SetStateAction } from 'react'
@ -144,10 +144,8 @@ const AttachmentButton: FC<Props> = ({ ref, couldAddImageFile, extensions, files
return ( return (
<Tooltip <Tooltip
placement="top" content={couldAddImageFile ? t('chat.input.upload.image_or_document') : t('chat.input.upload.document')}
title={couldAddImageFile ? t('chat.input.upload.image_or_document') : t('chat.input.upload.document')} closeDelay={0}>
mouseLeaveDelay={0}
arrow>
<ActionIconButton <ActionIconButton
onPress={openFileSelectDialog} onPress={openFileSelectDialog}
active={files.length > 0} active={files.length > 0}

View File

@ -13,12 +13,13 @@ import {
LinkOutlined LinkOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import { ColFlex } from '@cherrystudio/ui' import { ColFlex } from '@cherrystudio/ui'
import { Tooltip } from '@cherrystudio/ui'
import CustomTag from '@renderer/components/Tags/CustomTag' import CustomTag from '@renderer/components/Tags/CustomTag'
import { useAttachment } from '@renderer/hooks/useAttachment' import { useAttachment } from '@renderer/hooks/useAttachment'
import FileManager from '@renderer/services/FileManager' import FileManager from '@renderer/services/FileManager'
import type { FileMetadata } from '@renderer/types' import type { FileMetadata } from '@renderer/types'
import { formatFileSize } from '@renderer/utils' import { formatFileSize } from '@renderer/utils'
import { Image, Tooltip } from 'antd' import { Image } from 'antd'
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash'
import type { FC } from 'react' import type { FC } from 'react'
import { useState } from 'react' import { useState } from 'react'
@ -95,13 +96,10 @@ export const FileNameRender: FC<{ file: FileMetadata }> = ({ file }) => {
return ( return (
<Tooltip <Tooltip
styles={{ classNames={{
body: { content: 'p-1'
padding: 5
}
}} }}
fresh content={
title={
<ColFlex className="items-center gap-0.5"> <ColFlex className="items-center gap-0.5">
{isImage(file.ext) && ( {isImage(file.ext) && (
<Image <Image

View File

@ -1,7 +1,7 @@
import { Tooltip } from '@cherrystudio/ui'
import { ActionIconButton } from '@renderer/components/Buttons' import { ActionIconButton } from '@renderer/components/Buttons'
import { isGenerateImageModel } from '@renderer/config/models' import { isGenerateImageModel } from '@renderer/config/models'
import type { Assistant, Model } from '@renderer/types' import type { Assistant, Model } from '@renderer/types'
import { Tooltip } from 'antd'
import { Image } from 'lucide-react' import { Image } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -17,12 +17,9 @@ const GenerateImageButton: FC<Props> = ({ model, assistant, onEnableGenerateImag
return ( return (
<Tooltip <Tooltip
placement="top" content={
title={
isGenerateImageModel(model) ? t('chat.input.generate_image') : t('chat.input.generate_image_not_supported') isGenerateImageModel(model) ? t('chat.input.generate_image') : t('chat.input.generate_image_not_supported')
} }>
mouseLeaveDelay={0}
arrow>
<ActionIconButton <ActionIconButton
onPress={onEnableGenerateImage} onPress={onEnableGenerateImage}
active={assistant.enableGenerateImage} active={assistant.enableGenerateImage}

View File

@ -1,4 +1,5 @@
import { HolderOutlined } from '@ant-design/icons' import { HolderOutlined } from '@ant-design/icons'
import { Tooltip } from '@cherrystudio/ui'
import { useCache } from '@data/hooks/useCache' import { useCache } from '@data/hooks/useCache'
import { usePreference } from '@data/hooks/usePreference' import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger' import { loggerService } from '@logger'
@ -45,7 +46,6 @@ import {
} from '@renderer/utils/input' } from '@renderer/utils/input'
import { documentExts, imageExts, textExts } from '@shared/config/constant' import { documentExts, imageExts, textExts } from '@shared/config/constant'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { Tooltip } from 'antd'
import type { TextAreaRef } from 'antd/es/input/TextArea' import type { TextAreaRef } from 'antd/es/input/TextArea'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import { debounce, isEmpty } from 'lodash' import { debounce, isEmpty } from 'lodash'
@ -911,7 +911,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
<TranslateButton text={text} onTranslated={onTranslated} isLoading={isTranslating} /> <TranslateButton text={text} onTranslated={onTranslated} isLoading={isTranslating} />
<SendMessageButton sendMessage={sendMessage} disabled={inputEmpty} /> <SendMessageButton sendMessage={sendMessage} disabled={inputEmpty} />
{loading && ( {loading && (
<Tooltip placement="top" title={t('chat.input.pause')} mouseLeaveDelay={0} arrow> <Tooltip content={t('chat.input.pause')} closeDelay={0}>
<ActionIconButton <ActionIconButton
onClick={onPause} onClick={onPause}
className="mr-[-2px]" className="mr-[-2px]"

View File

@ -1,3 +1,4 @@
import { Tooltip } from '@cherrystudio/ui'
import type { DropResult } from '@hello-pangea/dnd' import type { DropResult } from '@hello-pangea/dnd'
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd' import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
import { loggerService } from '@logger' import { loggerService } from '@logger'
@ -23,7 +24,7 @@ import type { FileType, KnowledgeBase, Model } from '@renderer/types'
import { FileTypes } from '@renderer/types' import { FileTypes } from '@renderer/types'
import { classNames } from '@renderer/utils' import { classNames } from '@renderer/utils'
import { isPromptToolUse, isSupportedToolUse } from '@renderer/utils/mcp-tools' import { isPromptToolUse, isSupportedToolUse } from '@renderer/utils/mcp-tools'
import { Divider, Dropdown, Tooltip } from 'antd' import { Divider, Dropdown } from 'antd'
import type { ItemType } from 'antd/es/menu/interface' import type { ItemType } from 'antd/es/menu/interface'
import { import {
AtSign, AtSign,
@ -354,11 +355,7 @@ const InputbarTools = ({
key: 'new_topic', key: 'new_topic',
label: t('chat.input.new_topic', { Command: '' }), label: t('chat.input.new_topic', { Command: '' }),
component: ( component: (
<Tooltip <Tooltip content={t('chat.input.new_topic', { Command: newTopicShortcut })} closeDelay={0}>
placement="top"
title={t('chat.input.new_topic', { Command: newTopicShortcut })}
mouseLeaveDelay={0}
arrow>
<ActionIconButton onPress={addNewTopic} icon={<MessageSquareDiff size={19} />} /> <ActionIconButton onPress={addNewTopic} icon={<MessageSquareDiff size={19} />} />
</Tooltip> </Tooltip>
) )
@ -459,11 +456,7 @@ const InputbarTools = ({
key: 'clear_topic', key: 'clear_topic',
label: t('chat.input.clear.label', { Command: '' }), label: t('chat.input.clear.label', { Command: '' }),
component: ( component: (
<Tooltip <Tooltip content={t('chat.input.clear.label', { Command: clearTopicShortcut })} closeDelay={0} showArrow>
placement="top"
title={t('chat.input.clear.label', { Command: clearTopicShortcut })}
mouseLeaveDelay={0}
arrow>
<ActionIconButton onPress={clearTopic} icon={<PaintbrushVertical size={18} />} /> <ActionIconButton onPress={clearTopic} icon={<PaintbrushVertical size={18} />} />
</Tooltip> </Tooltip>
) )
@ -472,11 +465,7 @@ const InputbarTools = ({
key: 'toggle_expand', key: 'toggle_expand',
label: isExpended ? t('chat.input.collapse') : t('chat.input.expand'), label: isExpended ? t('chat.input.collapse') : t('chat.input.expand'),
component: ( component: (
<Tooltip <Tooltip content={isExpended ? t('chat.input.collapse') : t('chat.input.expand')} closeDelay={0} showArrow>
placement="top"
title={isExpended ? t('chat.input.collapse') : t('chat.input.expand')}
mouseLeaveDelay={0}
arrow>
<ActionIconButton <ActionIconButton
onPress={onToggleExpended} onPress={onToggleExpended}
icon={isExpended ? <Minimize size={18} /> : <Maximize size={18} />} icon={isExpended ? <Minimize size={18} /> : <Maximize size={18} />}
@ -655,10 +644,7 @@ const InputbarTools = ({
</DragDropContext> </DragDropContext>
{showCollapseButton && ( {showCollapseButton && (
<Tooltip <Tooltip content={isCollapse ? t('chat.input.tools.expand') : t('chat.input.tools.collapse')} showArrow>
placement="top"
title={isCollapse ? t('chat.input.tools.expand') : t('chat.input.tools.collapse')}
arrow>
<ActionIconButton <ActionIconButton
onPress={() => dispatch(setIsCollapsed(!isCollapse))} onPress={() => dispatch(setIsCollapsed(!isCollapse))}
icon={ icon={
@ -715,9 +701,10 @@ const ToolWrapper = styled.div`
width 0.2s, width 0.2s,
margin-right 0.2s, margin-right 0.2s,
opacity 0.2s; opacity 0.2s;
&.is-collapsed { &.is-collapsed {
width: 0px; width: 0;
margin-right: 0px; margin-right: 0;
overflow: hidden; overflow: hidden;
opacity: 0; opacity: 0;
} }

View File

@ -1,9 +1,9 @@
import { Tooltip } from '@cherrystudio/ui'
import { ActionIconButton } from '@renderer/components/Buttons' import { ActionIconButton } from '@renderer/components/Buttons'
import type { QuickPanelListItem } from '@renderer/components/QuickPanel' import type { QuickPanelListItem } from '@renderer/components/QuickPanel'
import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel' import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
import { useAppSelector } from '@renderer/store' import { useAppSelector } from '@renderer/store'
import type { KnowledgeBase } from '@renderer/types' import type { KnowledgeBase } from '@renderer/types'
import { Tooltip } from 'antd'
import { CircleX, FileSearch, Plus } from 'lucide-react' import { CircleX, FileSearch, Plus } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react' import { memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react'
@ -108,7 +108,7 @@ const KnowledgeBaseButton: FC<Props> = ({ ref, selectedBases, onSelect, disabled
})) }))
return ( return (
<Tooltip placement="top" title={t('chat.input.knowledge_base')} mouseLeaveDelay={0} arrow> <Tooltip content={t('chat.input.knowledge_base')} closeDelay={0}>
<ActionIconButton <ActionIconButton
onPress={handleOpenQuickPanel} onPress={handleOpenQuickPanel}
active={selectedBases && selectedBases.length > 0} active={selectedBases && selectedBases.length > 0}

View File

@ -1,3 +1,4 @@
import { Tooltip } from '@cherrystudio/ui'
import { ActionIconButton } from '@renderer/components/Buttons' import { ActionIconButton } from '@renderer/components/Buttons'
import type { QuickPanelListItem } from '@renderer/components/QuickPanel' import type { QuickPanelListItem } from '@renderer/components/QuickPanel'
import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel' import { QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
@ -10,7 +11,7 @@ import { getProviderByModel } from '@renderer/services/AssistantService'
import { EventEmitter } from '@renderer/services/EventService' import { EventEmitter } from '@renderer/services/EventService'
import type { MCPPrompt, MCPResource, MCPServer } from '@renderer/types' import type { MCPPrompt, MCPResource, MCPServer } from '@renderer/types'
import { isToolUseModeFunction } from '@renderer/utils/assistant' import { isToolUseModeFunction } from '@renderer/utils/assistant'
import { Form, Input, Tooltip } from 'antd' import { Form, Input } from 'antd'
import { CircleX, Hammer, Plus } from 'lucide-react' import { CircleX, Hammer, Plus } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
@ -487,7 +488,7 @@ const MCPToolsButton: FC<Props> = ({ ref, setInputValue, resizeTextArea, assista
})) }))
return ( return (
<Tooltip placement="top" title={t('settings.mcp.title')} mouseLeaveDelay={0} arrow> <Tooltip content={t('settings.mcp.title')} closeDelay={0}>
<ActionIconButton <ActionIconButton
onPress={handleOpenQuickPanel} onPress={handleOpenQuickPanel}
active={assistant.mcpServers && assistant.mcpServers.length > 0} active={assistant.mcpServers && assistant.mcpServers.length > 0}

View File

@ -1,4 +1,4 @@
import { Avatar } from '@cherrystudio/ui' import { Avatar, Tooltip } from '@cherrystudio/ui'
import { ActionIconButton } from '@renderer/components/Buttons' import { ActionIconButton } from '@renderer/components/Buttons'
import ModelTagsWithLabel from '@renderer/components/ModelTagsWithLabel' import ModelTagsWithLabel from '@renderer/components/ModelTagsWithLabel'
import { type QuickPanelListItem, QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel' import { type QuickPanelListItem, QuickPanelReservedSymbol, useQuickPanel } from '@renderer/components/QuickPanel'
@ -8,7 +8,6 @@ import { useProviders } from '@renderer/hooks/useProvider'
import { getModelUniqId } from '@renderer/services/ModelService' import { getModelUniqId } from '@renderer/services/ModelService'
import type { FileType, Model } from '@renderer/types' import type { FileType, Model } from '@renderer/types'
import { getFancyProviderName } from '@renderer/utils' import { getFancyProviderName } from '@renderer/utils'
import { Tooltip } from 'antd'
import { useLiveQuery } from 'dexie-react-hooks' import { useLiveQuery } from 'dexie-react-hooks'
import { first, sortBy } from 'lodash' import { first, sortBy } from 'lodash'
import { AtSign, CircleX, Plus } from 'lucide-react' import { AtSign, CircleX, Plus } from 'lucide-react'
@ -136,7 +135,7 @@ const MentionModelsButton: FC<Props> = ({
), ),
description: <ModelTagsWithLabel model={m} showLabel={false} size={10} style={{ opacity: 0.8 }} />, description: <ModelTagsWithLabel model={m} showLabel={false} size={10} style={{ opacity: 0.8 }} />,
icon: ( icon: (
<Avatar src={getModelLogo(m.id)} className="w-5 h-5"> <Avatar src={getModelLogo(m.id)} className="h-5 w-5">
{first(m.name)} {first(m.name)}
</Avatar> </Avatar>
), ),
@ -172,7 +171,7 @@ const MentionModelsButton: FC<Props> = ({
), ),
description: <ModelTagsWithLabel model={m} showLabel={false} size={10} style={{ opacity: 0.8 }} />, description: <ModelTagsWithLabel model={m} showLabel={false} size={10} style={{ opacity: 0.8 }} />,
icon: ( icon: (
<Avatar src={getModelLogo(m.id)} className="w-5 h-5"> <Avatar src={getModelLogo(m.id)} className="h-5 w-5">
{first(m.name)} {first(m.name)}
</Avatar> </Avatar>
), ),
@ -304,7 +303,7 @@ const MentionModelsButton: FC<Props> = ({
})) }))
return ( return (
<Tooltip placement="top" title={t('agents.edit.model.select.title')} mouseLeaveDelay={0} arrow> <Tooltip content={t('agents.edit.model.select.title')} closeDelay={0}>
<ActionIconButton <ActionIconButton
onPress={handleOpenQuickPanel} onPress={handleOpenQuickPanel}
active={mentionedModels.length > 0} active={mentionedModels.length > 0}

View File

@ -1,6 +1,6 @@
import { Tooltip } from '@cherrystudio/ui'
import { ActionIconButton } from '@renderer/components/Buttons' import { ActionIconButton } from '@renderer/components/Buttons'
import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts' import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts'
import { Tooltip } from 'antd'
import { Eraser } from 'lucide-react' import { Eraser } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -15,11 +15,7 @@ const NewContextButton: FC<Props> = ({ onNewContext }) => {
useShortcut('toggle_new_context', onNewContext) useShortcut('toggle_new_context', onNewContext)
return ( return (
<Tooltip <Tooltip content={t('chat.input.new.context', { Command: newContextShortcut })} closeDelay={0}>
placement="top"
title={t('chat.input.new.context', { Command: newContextShortcut })}
mouseLeaveDelay={0}
arrow>
<ActionIconButton onPress={onNewContext} icon={<Eraser size={18} />} /> <ActionIconButton onPress={onNewContext} icon={<Eraser size={18} />} />
</Tooltip> </Tooltip>
) )

View File

@ -1,3 +1,4 @@
import { Tooltip } from '@cherrystudio/ui'
import { ActionIconButton } from '@renderer/components/Buttons' import { ActionIconButton } from '@renderer/components/Buttons'
import { import {
type QuickPanelListItem, type QuickPanelListItem,
@ -9,7 +10,7 @@ import { useAssistant } from '@renderer/hooks/useAssistant'
import { useTimer } from '@renderer/hooks/useTimer' import { useTimer } from '@renderer/hooks/useTimer'
import QuickPhraseService from '@renderer/services/QuickPhraseService' import QuickPhraseService from '@renderer/services/QuickPhraseService'
import type { QuickPhrase } from '@renderer/types' import type { QuickPhrase } from '@renderer/types'
import { Input, Modal, Radio, Space, Tooltip } from 'antd' import { Input, Modal, Radio, Space } from 'antd'
import { BotMessageSquare, Plus, Zap } from 'lucide-react' import { BotMessageSquare, Plus, Zap } from 'lucide-react'
import { memo, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react' import { memo, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -156,7 +157,7 @@ const QuickPhrasesButton = ({ ref, setInputValue, resizeTextArea, assistantId }:
return ( return (
<> <>
<Tooltip placement="top" title={t('settings.quickPhrase.title')} mouseLeaveDelay={0} arrow> <Tooltip content={t('settings.quickPhrase.title')} closeDelay={0}>
<ActionIconButton onPress={handleOpenQuickPanel} icon={<Zap size={18} />} /> <ActionIconButton onPress={handleOpenQuickPanel} icon={<Zap size={18} />} />
</Tooltip> </Tooltip>

View File

@ -1,3 +1,4 @@
import { Tooltip } from '@cherrystudio/ui'
import { ActionIconButton } from '@renderer/components/Buttons' import { ActionIconButton } from '@renderer/components/Buttons'
import { import {
MdiLightbulbAutoOutline, MdiLightbulbAutoOutline,
@ -18,7 +19,6 @@ import {
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { getReasoningEffortOptionsLabel } from '@renderer/i18n/label' import { getReasoningEffortOptionsLabel } from '@renderer/i18n/label'
import type { Model, ThinkingOption } from '@renderer/types' import type { Model, ThinkingOption } from '@renderer/types'
import { Tooltip } from 'antd'
import type { FC, ReactElement } from 'react' import type { FC, ReactElement } from 'react'
import { useCallback, useImperativeHandle, useMemo } from 'react' import { useCallback, useImperativeHandle, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -132,14 +132,12 @@ const ThinkingButton: FC<Props> = ({ ref, model, assistantId }): ReactElement =>
return ( return (
<Tooltip <Tooltip
placement="top" content={
title={
isThinkingEnabled && supportedOptions.includes('off') isThinkingEnabled && supportedOptions.includes('off')
? t('common.close') ? t('common.close')
: t('assistants.settings.reasoning_effort.label') : t('assistants.settings.reasoning_effort.label')
} }
mouseLeaveDelay={0} closeDelay={0}>
arrow>
<ActionIconButton <ActionIconButton
onPress={handleOpenQuickPanel} onPress={handleOpenQuickPanel}
active={currentReasoningEffort !== 'off'} active={currentReasoningEffort !== 'off'}

View File

@ -1,8 +1,8 @@
import { Tooltip } from '@cherrystudio/ui'
import { ActionIconButton } from '@renderer/components/Buttons' import { ActionIconButton } from '@renderer/components/Buttons'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { useTimer } from '@renderer/hooks/useTimer' import { useTimer } from '@renderer/hooks/useTimer'
import { isToolUseModeFunction } from '@renderer/utils/assistant' import { isToolUseModeFunction } from '@renderer/utils/assistant'
import { Tooltip } from 'antd'
import { Link } from 'lucide-react' import { Link } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { memo, useCallback } from 'react' import { memo, useCallback } from 'react'
@ -47,7 +47,7 @@ const UrlContextButton: FC<Props> = ({ assistantId }) => {
}, [setTimeoutTimer, assistant, urlContentNewState, updateAssistant, t]) }, [setTimeoutTimer, assistant, urlContentNewState, updateAssistant, t])
return ( return (
<Tooltip placement="top" title={t('chat.input.url_context')} arrow> <Tooltip content={t('chat.input.url_context')}>
<ActionIconButton onPress={handleToggle} active={assistant.enableUrlContext} icon={<Link size={18} />} /> <ActionIconButton onPress={handleToggle} active={assistant.enableUrlContext} icon={<Link size={18} />} />
</Tooltip> </Tooltip>
) )

View File

@ -1,4 +1,5 @@
import { BaiduOutlined, GoogleOutlined } from '@ant-design/icons' import { BaiduOutlined, GoogleOutlined } from '@ant-design/icons'
import { Tooltip } from '@cherrystudio/ui'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { ActionIconButton } from '@renderer/components/Buttons' import { ActionIconButton } from '@renderer/components/Buttons'
import { BingLogo, BochaLogo, ExaLogo, SearXNGLogo, TavilyLogo, ZhipuLogo } from '@renderer/components/Icons' import { BingLogo, BochaLogo, ExaLogo, SearXNGLogo, TavilyLogo, ZhipuLogo } from '@renderer/components/Icons'
@ -19,7 +20,6 @@ import WebSearchService from '@renderer/services/WebSearchService'
import type { WebSearchProvider, WebSearchProviderId } from '@renderer/types' import type { WebSearchProvider, WebSearchProviderId } from '@renderer/types'
import { hasObjectKey } from '@renderer/utils' import { hasObjectKey } from '@renderer/utils'
import { isToolUseModeFunction } from '@renderer/utils/assistant' import { isToolUseModeFunction } from '@renderer/utils/assistant'
import { Tooltip } from 'antd'
import { Globe } from 'lucide-react' import { Globe } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { memo, useCallback, useImperativeHandle, useMemo } from 'react' import { memo, useCallback, useImperativeHandle, useMemo } from 'react'
@ -207,11 +207,7 @@ const WebSearchButton: FC<Props> = ({ ref, assistantId }) => {
})) }))
return ( return (
<Tooltip <Tooltip content={enableWebSearch ? t('common.close') : t('chat.input.web_search.label')} closeDelay={0}>
placement="top"
title={enableWebSearch ? t('common.close') : t('chat.input.web_search.label')}
mouseLeaveDelay={0}
arrow>
<ActionIconButton <ActionIconButton
onPress={onClick} onPress={onClick}
active={!!enableWebSearch} active={!!enableWebSearch}

View File

@ -1,5 +1,5 @@
import { Tooltip } from '@cherrystudio/ui'
import Favicon from '@renderer/components/Icons/FallbackFavicon' import Favicon from '@renderer/components/Icons/FallbackFavicon'
import { Tooltip } from 'antd'
import React, { memo, useCallback, useMemo } from 'react' import React, { memo, useCallback, useMemo } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { z } from 'zod' import { z } from 'zod'
@ -57,17 +57,9 @@ const CitationTooltip: React.FC<CitationTooltipProps> = ({ children, citation })
return ( return (
<Tooltip <Tooltip
arrow={false} content={tooltipContent}
overlay={tooltipContent} showArrow={false}
placement="top" className="rounded-[8px] border border-[var(--color-border)] bg-[var(--color-background)] p-3">
color="var(--color-background)"
styles={{
body: {
border: '1px solid var(--color-border)',
padding: '12px',
borderRadius: '8px'
}
}}>
{children} {children}
</Tooltip> </Tooltip>
) )

View File

@ -26,7 +26,6 @@ const Hyperlink: React.FC<HyperLinkProps> = ({ children, href }) => {
open={open} open={open}
onOpenChange={setOpen} onOpenChange={setOpen}
content={<OGCard link={link} show={open} />} content={<OGCard link={link} show={open} />}
placement="top"
styles={{ styles={{
body: { body: {
padding: 0, padding: 0,

View File

@ -1,8 +1,8 @@
import { Tooltip } from '@cherrystudio/ui'
import { CopyIcon } from '@renderer/components/Icons' import { CopyIcon } from '@renderer/components/Icons'
import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue' import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue'
import store from '@renderer/store' import store from '@renderer/store'
import { messageBlocksSelectors } from '@renderer/store/messageBlock' import { messageBlocksSelectors } from '@renderer/store/messageBlock'
import { Tooltip } from 'antd'
import { Check } from 'lucide-react' import { Check } from 'lucide-react'
import React, { memo, useCallback } from 'react' import React, { memo, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -40,7 +40,7 @@ const Table: React.FC<Props> = ({ children, node, blockId }) => {
<TableWrapper className="table-wrapper"> <TableWrapper className="table-wrapper">
<table>{children}</table> <table>{children}</table>
<ToolbarWrapper className="table-toolbar"> <ToolbarWrapper className="table-toolbar">
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}> <Tooltip content={t('common.copy')} delay={800}>
<ToolButton role="button" aria-label={t('common.copy')} onClick={handleCopyTable}> <ToolButton role="button" aria-label={t('common.copy')} onClick={handleCopyTable}>
{copied ? <Check size={14} color="var(--color-primary)" /> : <CopyIcon size={14} />} {copied ? <Check size={14} color="var(--color-primary)" /> : <CopyIcon size={14} />}
</ToolButton> </ToolButton>

View File

@ -12,20 +12,17 @@ vi.mock('@renderer/components/Icons/FallbackFavicon', () => ({
default: (props: any) => <div data-testid="mock-favicon" {...props} /> default: (props: any) => <div data-testid="mock-favicon" {...props} />
})) }))
vi.mock('antd', () => ({ const uiMocks = vi.hoisted(() => ({
Tooltip: ({ children, overlay, title, placement, color, styles, ...props }: any) => ( Tooltip: vi.fn(({ children, title, content, placement, ...props }: any) => (
<div <div data-testid="tooltip-wrapper" data-placement={placement} {...props}>
data-testid="tooltip-wrapper"
data-placement={placement}
data-color={color}
data-styles={JSON.stringify(styles)}
{...props}>
{children} {children}
<div data-testid="tooltip-content">{overlay || title}</div> <div data-testid="tooltip-content">{content || title}</div>
</div> </div>
) ))
})) }))
vi.mock('@cherrystudio/ui', () => uiMocks)
const originalWindowOpen = window.open const originalWindowOpen = window.open
describe('CitationTooltip', () => { describe('CitationTooltip', () => {
@ -87,22 +84,6 @@ describe('CitationTooltip', () => {
expect(favicon).toHaveAttribute('alt', 'Example Title') expect(favicon).toHaveAttribute('alt', 'Example Title')
}) })
it('should pass correct props to Tooltip component', () => {
const citation = createCitationData()
renderCitationTooltip(citation)
const tooltip = screen.getByTestId('tooltip-wrapper')
expect(tooltip).toHaveAttribute('data-placement', 'top')
expect(tooltip).toHaveAttribute('data-color', 'var(--color-background)')
const styles = JSON.parse(tooltip.getAttribute('data-styles') || '{}')
expect(styles.body).toEqual({
border: '1px solid var(--color-border)',
padding: '12px',
borderRadius: '8px'
})
})
it('should match snapshot', () => { it('should match snapshot', () => {
const citation = createCitationData() const citation = createCitationData()
const { container } = render( const { container } = render(

View File

@ -102,7 +102,6 @@ describe('Hyperlink', () => {
const popover = screen.getByTestId('popover') const popover = screen.getByTestId('popover')
expect(popover).toBeInTheDocument() expect(popover).toBeInTheDocument()
expect(popover).toHaveAttribute('data-arrow', 'false') expect(popover).toHaveAttribute('data-arrow', 'false')
expect(popover).toHaveAttribute('data-placement', 'top')
// Content includes decoded url text and favicon with hostname // Content includes decoded url text and favicon with hostname
expect(screen.getByTestId('favicon')).toHaveAttribute('data-hostname', 'domain.com') expect(screen.getByTestId('favicon')).toHaveAttribute('data-hostname', 'domain.com')

View File

@ -33,7 +33,10 @@ vi.mock('@renderer/components/Icons', () => ({
})) }))
vi.mock('lucide-react', () => ({ vi.mock('lucide-react', () => ({
Check: ({ size }: { size: number }) => <div data-testid="check-icon" style={{ width: size, height: size }} /> Check: ({ size }: { size: number }) => <div data-testid="check-icon" style={{ width: size, height: size }} />,
CheckIcon: ({ size }: { size: number }) => <div data-testid="check-icon" style={{ width: size, height: size }} />,
CircleXIcon: () => <span>error</span>,
AlertTriangleIcon: () => <span>alert</span>
})) }))
vi.mock('react-i18next', () => ({ vi.mock('react-i18next', () => ({
@ -42,9 +45,9 @@ vi.mock('react-i18next', () => ({
}) })
})) }))
vi.mock('antd', () => ({ vi.mock('@cherrystudio/ui', () => ({
Tooltip: ({ children, title }: any) => ( Tooltip: ({ children, title, content }: any) => (
<div data-testid="tooltip" title={title}> <div data-testid="tooltip" title={content || title}>
{children} {children}
</div> </div>
) )

View File

@ -47,9 +47,7 @@ exports[`CitationTooltip > basic rendering > should match snapshot 1`] = `
} }
<div <div
data-color="var(--color-background)" class="rounded-[8px] border border-[var(--color-border)] bg-[var(--color-background)] p-3"
data-placement="top"
data-styles="{"body":{"border":"1px solid var(--color-border)","padding":"12px","borderRadius":"8px"}}"
data-testid="tooltip-wrapper" data-testid="tooltip-wrapper"
> >
<span> <span>

View File

@ -4,7 +4,6 @@ exports[`Hyperlink > should match snapshot for normal url 1`] = `
<div> <div>
<div <div
data-arrow="false" data-arrow="false"
data-placement="top"
data-styles="{"body":{"padding":0,"borderRadius":"8px","overflow":"hidden"}}" data-styles="{"body":{"padding":0,"borderRadius":"8px","overflow":"hidden"}}"
data-testid="popover" data-testid="popover"
> >

View File

@ -1,10 +1,11 @@
import { CheckOutlined } from '@ant-design/icons' import { CheckOutlined } from '@ant-design/icons'
import { Tooltip } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference' import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import ThinkingEffect from '@renderer/components/ThinkingEffect' import ThinkingEffect from '@renderer/components/ThinkingEffect'
import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue' import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue'
import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage' import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage'
import { Collapse, Tooltip } from 'antd' import { Collapse } from 'antd'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -81,7 +82,7 @@ const ThinkingBlock: React.FC<Props> = ({ block }) => {
fontSize fontSize
}}> }}>
{!isThinking && ( {!isThinking && (
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}> <Tooltip content={t('common.copy')} delay={800}>
<ActionButton <ActionButton
className="message-action-button" className="message-action-button"
onClick={(e) => { onClick={(e) => {

View File

@ -42,17 +42,20 @@ vi.mock('antd', () => ({
))} ))}
</div> </div>
), ),
Tooltip: ({ title, children, mouseEnterDelay }: any) => (
<div data-testid="tooltip" title={title} data-mouse-enter-delay={mouseEnterDelay}>
{children}
</div>
),
message: { message: {
success: vi.fn(), success: vi.fn(),
error: vi.fn() error: vi.fn()
} }
})) }))
vi.mock('@cherrystudio/ui', () => ({
Tooltip: ({ title, children, mouseEnterDelay }: any) => (
<div data-testid="tooltip" title={title} data-mouse-enter-delay={mouseEnterDelay}>
{children}
</div>
)
}))
// Mock icons // Mock icons
vi.mock('@ant-design/icons', () => ({ vi.mock('@ant-design/icons', () => ({
CheckOutlined: ({ style }: any) => ( CheckOutlined: ({ style }: any) => (
@ -68,7 +71,10 @@ vi.mock('lucide-react', () => ({
💡 💡
</span> </span>
), ),
ChevronRight: (props: any) => <svg data-testid="chevron-right-icon" {...props} /> ChevronRight: (props: any) => <svg data-testid="chevron-right-icon" {...props} />,
CheckIcon: () => <span>check</span>,
CircleXIcon: () => <span>error</span>,
AlertTriangleIcon: () => <span>alert</span>
})) }))
// Mock motion // Mock motion

View File

@ -85,9 +85,7 @@ exports[`ThinkingBlock > basic rendering > should match snapshot 1`] = `
style="font-family: var(--font-family); font-size: 14px;" style="font-family: var(--font-family); font-size: 14px;"
> >
<div <div
data-mouse-enter-delay="0.8"
data-testid="tooltip" data-testid="tooltip"
title="Copy"
> >
<button <button
aria-label="Copy" aria-label="Copy"

View File

@ -1,8 +1,7 @@
import '@xyflow/react/dist/style.css' import '@xyflow/react/dist/style.css'
import { RobotOutlined, UserOutlined } from '@ant-design/icons' import { RobotOutlined, UserOutlined } from '@ant-design/icons'
import { Avatar } from '@cherrystudio/ui' import { Avatar, EmojiAvatar, Tooltip } from '@cherrystudio/ui'
import { EmojiAvatar } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference' import { usePreference } from '@data/hooks/usePreference'
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar' import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
import { getModelLogo } from '@renderer/config/models' import { getModelLogo } from '@renderer/config/models'
@ -18,7 +17,7 @@ import { getMainTextContent } from '@renderer/utils/messageUtils/find'
import type { Edge, Node, NodeTypes } from '@xyflow/react' import type { Edge, Node, NodeTypes } from '@xyflow/react'
import { Controls, Handle, MiniMap, ReactFlow, ReactFlowProvider } from '@xyflow/react' import { Controls, Handle, MiniMap, ReactFlow, ReactFlowProvider } from '@xyflow/react'
import { Position, useEdgesState, useNodesState } from '@xyflow/react' import { Position, useEdgesState, useNodesState } from '@xyflow/react'
import { Spin, Tooltip } from 'antd' import { Spin } from 'antd'
import { isEqual } from 'lodash' import { isEqual } from 'lodash'
import type { FC } from 'react' import type { FC } from 'react'
import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { memo, useCallback, useEffect, useMemo, useState } from 'react'
@ -91,13 +90,7 @@ const CustomNode: FC<{ data: any }> = ({ data }) => {
avatar = <ModelAvatar model={data.modelInfo} size={32} /> avatar = <ModelAvatar model={data.modelInfo} size={32} />
} else if (data.modelId) { } else if (data.modelId) {
const modelLogo = getModelLogo(data.modelId) const modelLogo = getModelLogo(data.modelId)
avatar = ( avatar = <Avatar src={modelLogo} icon={!modelLogo ? <RobotOutlined /> : undefined} className="bg-primary" />
<Avatar
src={modelLogo}
icon={!modelLogo ? <RobotOutlined /> : undefined}
className="bg-primary"
/>
)
} else { } else {
avatar = <Avatar icon={<RobotOutlined />} className="bg-primary" /> avatar = <Avatar icon={<RobotOutlined />} className="bg-primary" />
} }
@ -141,18 +134,16 @@ const CustomNode: FC<{ data: any }> = ({ data }) => {
return ( return (
<Tooltip <Tooltip
title={ content={
<TooltipContent> <TooltipContent>
<TooltipTitle>{title}</TooltipTitle> <TooltipTitle>{title}</TooltipTitle>
<TooltipBody>{data.content}</TooltipBody> <TooltipBody>{data.content}</TooltipBody>
<TooltipFooter>{t('chat.history.click_to_navigate')}</TooltipFooter> <TooltipFooter>{t('chat.history.click_to_navigate')}</TooltipFooter>
</TooltipContent> </TooltipContent>
} }
placement="top" classNames={{ content: 'bg-[#000000d8] text-gray-200 text-sm' }}
color="rgba(0, 0, 0, 0.85)" delay={300}
mouseEnterDelay={0.3} closeDelay={100}>
mouseLeaveDelay={0.1}
destroyOnHidden>
<CustomNodeContainer <CustomNodeContainer
style={{ style={{
borderColor, borderColor,

View File

@ -6,11 +6,11 @@ import {
VerticalAlignBottomOutlined, VerticalAlignBottomOutlined,
VerticalAlignTopOutlined VerticalAlignTopOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
// import { selectCurrentTopicId } from '@renderer/store/newMessage' import { Button, Tooltip } from '@cherrystudio/ui'
import { Button } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference' import { usePreference } from '@data/hooks/usePreference'
import type { RootState } from '@renderer/store' import type { RootState } from '@renderer/store'
import { Drawer, Tooltip } from 'antd' // import { selectCurrentTopicId } from '@renderer/store/newMessage'
import { Drawer } from 'antd'
import type { FC } from 'react' import type { FC } from 'react'
import { useCallback, useEffect, useRef, useState } from 'react' import { useCallback, useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -337,7 +337,7 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
<> <>
<NavigationContainer $isVisible={isVisible} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}> <NavigationContainer $isVisible={isVisible} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<ButtonGroup> <ButtonGroup>
<Tooltip title={t('chat.navigation.close')} placement="left" mouseEnterDelay={0.5}> <Tooltip placement="left" content={t('chat.navigation.close')} delay={500}>
<NavigationButton <NavigationButton
variant="light" variant="light"
startContent={<CloseOutlined />} startContent={<CloseOutlined />}
@ -346,7 +346,7 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
/> />
</Tooltip> </Tooltip>
<Divider /> <Divider />
<Tooltip title={t('chat.navigation.top')} placement="left" mouseEnterDelay={0.5}> <Tooltip placement="left" content={t('chat.navigation.top')} delay={500}>
<NavigationButton <NavigationButton
variant="light" variant="light"
startContent={<VerticalAlignTopOutlined />} startContent={<VerticalAlignTopOutlined />}
@ -355,7 +355,7 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
/> />
</Tooltip> </Tooltip>
<Divider /> <Divider />
<Tooltip title={t('chat.navigation.prev')} placement="left" mouseEnterDelay={0.5}> <Tooltip placement="left" content={t('chat.navigation.prev')} delay={500}>
<NavigationButton <NavigationButton
variant="light" variant="light"
startContent={<ArrowUpOutlined />} startContent={<ArrowUpOutlined />}
@ -364,7 +364,7 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
/> />
</Tooltip> </Tooltip>
<Divider /> <Divider />
<Tooltip title={t('chat.navigation.next')} placement="left" mouseEnterDelay={0.5}> <Tooltip placement="left" content={t('chat.navigation.next')} delay={500}>
<NavigationButton <NavigationButton
variant="light" variant="light"
startContent={<ArrowDownOutlined />} startContent={<ArrowDownOutlined />}
@ -373,7 +373,7 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
/> />
</Tooltip> </Tooltip>
<Divider /> <Divider />
<Tooltip title={t('chat.navigation.bottom')} placement="left" mouseEnterDelay={0.5}> <Tooltip placement="left" content={t('chat.navigation.bottom')} delay={500}>
<NavigationButton <NavigationButton
variant="light" variant="light"
startContent={<VerticalAlignBottomOutlined />} startContent={<VerticalAlignBottomOutlined />}
@ -382,7 +382,7 @@ const ChatNavigation: FC<ChatNavigationProps> = ({ containerId }) => {
/> />
</Tooltip> </Tooltip>
<Divider /> <Divider />
<Tooltip title={t('chat.navigation.history')} placement="left" mouseEnterDelay={0.5}> <Tooltip placement="left" content={t('chat.navigation.history')} delay={500}>
<NavigationButton <NavigationButton
variant="light" variant="light"
startContent={<HistoryOutlined />} startContent={<HistoryOutlined />}

View File

@ -1,3 +1,4 @@
import { Tooltip } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference' import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { ActionIconButton } from '@renderer/components/Buttons' import { ActionIconButton } from '@renderer/components/Buttons'
@ -19,7 +20,7 @@ import { getFilesFromDropEvent, isSendMessageKeyPressed } from '@renderer/utils/
import { createFileBlock, createImageBlock } from '@renderer/utils/messageUtils/create' import { createFileBlock, createImageBlock } from '@renderer/utils/messageUtils/create'
import { findAllBlocks } from '@renderer/utils/messageUtils/find' import { findAllBlocks } from '@renderer/utils/messageUtils/find'
import { documentExts, imageExts, textExts } from '@shared/config/constant' import { documentExts, imageExts, textExts } from '@shared/config/constant'
import { Space, Tooltip } from 'antd' import { Space } from 'antd'
import type { TextAreaRef } from 'antd/es/input/TextArea' import type { TextAreaRef } from 'antd/es/input/TextArea'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import { Save, Send, X } from 'lucide-react' import { Save, Send, X } from 'lucide-react'
@ -359,14 +360,14 @@ const MessageBlockEditor: FC<Props> = ({ message, topicId, onSave, onResend, onC
</ActionBarLeft> </ActionBarLeft>
<ActionBarMiddle /> <ActionBarMiddle />
<ActionBarRight> <ActionBarRight>
<Tooltip title={t('common.cancel')}> <Tooltip content={t('common.cancel')}>
<ActionIconButton onPress={onCancel} icon={<X size={16} />} /> <ActionIconButton onPress={onCancel} icon={<X size={16} />} />
</Tooltip> </Tooltip>
<Tooltip title={t('common.save')}> <Tooltip content={t('common.save')}>
<ActionIconButton onPress={handleSave} icon={<Save size={16} />} /> <ActionIconButton onPress={handleSave} icon={<Save size={16} />} />
</Tooltip> </Tooltip>
{message.role === 'user' && ( {message.role === 'user' && (
<Tooltip title={t('chat.resend')}> <Tooltip content={t('chat.resend')}>
<ActionIconButton onPress={handleResend} icon={<Send size={16} />} /> <ActionIconButton onPress={handleResend} icon={<Send size={16} />} />
</Tooltip> </Tooltip>
)} )}

View File

@ -7,7 +7,7 @@ import {
ReloadOutlined ReloadOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import { RowFlex } from '@cherrystudio/ui' import { RowFlex } from '@cherrystudio/ui'
import { Button } from '@cherrystudio/ui' import { Button, Tooltip } from '@cherrystudio/ui'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { useMessageOperations } from '@renderer/hooks/useMessageOperations' import { useMessageOperations } from '@renderer/hooks/useMessageOperations'
import type { Topic } from '@renderer/types' import type { Topic } from '@renderer/types'
@ -15,7 +15,6 @@ import type { Message } from '@renderer/types/newMessage'
import { AssistantMessageStatus } from '@renderer/types/newMessage' import { AssistantMessageStatus } from '@renderer/types/newMessage'
import { getMainTextContent } from '@renderer/utils/messageUtils/find' import { getMainTextContent } from '@renderer/utils/messageUtils/find'
import type { MultiModelMessageStyle } from '@shared/data/preference/preferenceTypes' import type { MultiModelMessageStyle } from '@shared/data/preference/preferenceTypes'
import { Tooltip } from 'antd'
import type { FC } from 'react' import type { FC } from 'react'
import { memo } from 'react' import { memo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -107,9 +106,11 @@ const MessageGroupMenuBar: FC<Props> = ({
<LayoutContainer> <LayoutContainer>
{(['fold', 'vertical', 'horizontal', 'grid'] as const).map((layout) => ( {(['fold', 'vertical', 'horizontal', 'grid'] as const).map((layout) => (
<Tooltip <Tooltip
mouseEnterDelay={0.5} delay={500}
key={layout} key={layout}
title={t('message.message.multi_model_style.label') + ': ' + multiModelMessageStyleTextByLayout[layout]}> content={
t('message.message.multi_model_style.label') + ': ' + multiModelMessageStyleTextByLayout[layout]
}>
<LayoutOption <LayoutOption
$active={multiModelMessageStyle === layout} $active={multiModelMessageStyle === layout}
onClick={() => setMultiModelMessageStyle(layout)}> onClick={() => setMultiModelMessageStyle(layout)}>
@ -136,7 +137,7 @@ const MessageGroupMenuBar: FC<Props> = ({
{multiModelMessageStyle === 'grid' && <MessageGroupSettings />} {multiModelMessageStyle === 'grid' && <MessageGroupSettings />}
</RowFlex> </RowFlex>
{hasFailedMessages && ( {hasFailedMessages && (
<Tooltip title={t('message.group.retry_failed')} mouseEnterDelay={0.6}> <Tooltip content={t('message.group.retry_failed')} delay={600}>
<Button <Button
variant="light" variant="light"
size="sm" size="sm"

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