refactor(web): remove tooltip-plus and migrate to ui tooltip

This commit is contained in:
yyh 2026-03-25 13:35:43 +08:00
parent e94c689dd3
commit 720c950f9e
No known key found for this signature in database
21 changed files with 434 additions and 348 deletions

View File

@ -12,7 +12,7 @@ import Input from '@/app/components/base/input'
import TabSliderNew from '@/app/components/base/tab-slider-new'
import TagFilter from '@/app/components/base/tag-management/filter'
import { useStore as useTagStore } from '@/app/components/base/tag-management/store'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useGlobalPublicStore } from '@/context/global-public-context'
@ -261,13 +261,16 @@ const List: FC<Props> = ({
onChange={e => handleKeywordsChange(e.target.value)}
onClear={() => handleKeywordsChange('')}
/>
<Tooltip
popupContent={t('skills.comingSoon', { ns: 'app' })}
>
<Button className="cursor-not-allowed">
<span className="i-ri-folder-6-line mr-1 h-[14px] w-[14px]" />
Skills
</Button>
<Tooltip>
<TooltipTrigger
render={(
<Button className="cursor-not-allowed">
<span className="i-ri-folder-6-line mr-1 h-[14px] w-[14px]" />
{t('skills.title', { ns: 'app' })}
</Button>
)}
/>
<TooltipContent>{t('skills.comingSoon', { ns: 'app' })}</TooltipContent>
</Tooltip>
</div>
</div>

View File

@ -1,8 +0,0 @@
'use client'
/**
* Re-exports the legacy default Tooltip API (popupContent, etc.) without using
* the restricted `@/app/components/base/tooltip` import path at call sites.
* Prefer migrating to `@/app/components/base/ui/tooltip` when touching UI.
*/
export { default } from '../tooltip'

View File

@ -1,8 +1,7 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { useProviderContext } from '@/context/provider-context'
import { cn } from '@/utils/classnames'
import ModelIcon from '../model-icon'
@ -42,8 +41,11 @@ const ModelTrigger: FC<ModelTriggerProps> = ({
</div>
<div className="flex shrink-0 items-center justify-center">
{showWarnIcon && (
<Tooltip popupContent={t('modelProvider.deprecated', { ns: 'common' })}>
<AlertTriangle className="h-4 w-4 text-text-warning-secondary" />
<Tooltip>
<TooltipTrigger>
<AlertTriangle className="h-4 w-4 text-text-warning-secondary" />
</TooltipTrigger>
<TooltipContent>{t('modelProvider.deprecated', { ns: 'common' })}</TooltipContent>
</Tooltip>
)}
</div>

View File

@ -16,7 +16,6 @@ import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Input from '@/app/components/base/input'
import Switch from '@/app/components/base/switch'
import Tooltip from '@/app/components/base/tooltip-plus'
import {
Select,
SelectContent,
@ -24,6 +23,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/app/components/base/ui/select'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
@ -163,15 +163,19 @@ const ReasoningConfigForm: React.FC<Props> = ({
const canUseAuto = ![FormTypeEnum.modelSelector, FormTypeEnum.appSelector].includes(type)
const auto = canUseAuto ? value[variable]?.auto : 0
const tooltipContent = (tooltip && (
<Tooltip
popupContent={(
<Tooltip>
<TooltipTrigger
delay={0}
className="ml-0.5 flex h-4 w-4 items-center justify-center"
>
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
</TooltipTrigger>
<TooltipContent>
<div className="w-[200px]">
{tooltip[language] || tooltip.en_US}
</div>
)}
triggerClassName="ml-0.5 w-4 h-4"
asChild={false}
/>
</TooltipContent>
</Tooltip>
))
const varInput = value[variable].value
const isString = type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput
@ -233,20 +237,18 @@ const ReasoningConfigForm: React.FC<Props> = ({
<span className="mx-1 text-text-quaternary system-xs-regular">·</span>
<span className="text-text-tertiary system-xs-regular">{targetVarType()}</span>
{isShowJSONEditor && (
<Tooltip
popupContent={(
<div className="text-text-secondary system-xs-medium">
{t('nodes.agent.clickToViewParameterSchema', { ns: 'workflow' })}
</div>
)}
asChild={false}
>
<div
<Tooltip>
<TooltipTrigger
className="ml-0.5 cursor-pointer rounded-[4px] p-px text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary"
onClick={() => showSchema(input_schema as SchemaRoot, label[language] || label.en_US)}
>
<RiBracesLine className="size-3.5" />
</div>
</TooltipTrigger>
<TooltipContent>
<div className="text-text-secondary system-xs-medium">
{t('nodes.agent.clickToViewParameterSchema', { ns: 'workflow' })}
</div>
</TooltipContent>
</Tooltip>
)}
@ -323,7 +325,7 @@ const ReasoningConfigForm: React.FC<Props> = ({
{isShowJSONEditor && isConstant && (
<div className="mt-1 w-full">
<CodeEditor
title="JSON"
title={t('common.json', { ns: 'workflow' })}
value={varInput?.value as any}
isExpand
isInNode

View File

@ -6,9 +6,8 @@ import copy from 'copy-to-clipboard'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import Tooltip from '@/app/components/base/tooltip-plus'
import { toast } from '@/app/components/base/ui/toast'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
type IResultHeaderProps = {
result: string
@ -32,7 +31,7 @@ const Header: FC<IResultHeaderProps> = ({
className="h-7 p-[2px] pr-2"
onClick={() => {
copy(result)
toast.success('copied')
toast.success(t('actionMsg.copySuccessfully', { ns: 'common' }))
}}
>
<>
@ -42,70 +41,66 @@ const Header: FC<IResultHeaderProps> = ({
</Button>
{showFeedback && feedback.rating && feedback.rating === 'like' && (
<Tooltip
popupContent="Undo Great Rating"
>
<div
<Tooltip>
<TooltipTrigger
className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border border-primary-200 bg-primary-100 !text-primary-600 hover:border-primary-300 hover:bg-primary-200"
onClick={() => {
onFeedback({
rating: null,
})
}}
className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border border-primary-200 bg-primary-100 !text-primary-600 hover:border-primary-300 hover:bg-primary-200"
>
<HandThumbUpIcon width={16} height={16} />
</div>
</TooltipTrigger>
<TooltipContent>{t('generation.feedback.undoLike', { ns: 'share' })}</TooltipContent>
</Tooltip>
)}
{showFeedback && feedback.rating && feedback.rating === 'dislike' && (
<Tooltip
popupContent="Undo Undesirable Response"
>
<div
<Tooltip>
<TooltipTrigger
className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border border-red-200 bg-red-100 !text-red-600 hover:border-red-300 hover:bg-red-200"
onClick={() => {
onFeedback({
rating: null,
})
}}
className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-md border border-red-200 bg-red-100 !text-red-600 hover:border-red-300 hover:bg-red-200"
>
<HandThumbDownIcon width={16} height={16} />
</div>
</TooltipTrigger>
<TooltipContent>{t('generation.feedback.undoDislike', { ns: 'share' })}</TooltipContent>
</Tooltip>
)}
{showFeedback && !feedback.rating && (
<div className="flex space-x-1 rounded-lg border border-gray-200 p-[1px]">
<Tooltip
popupContent="Great Rating"
needsDelay={false}
>
<div
<Tooltip>
<TooltipTrigger
delay={0}
className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-gray-100"
onClick={() => {
onFeedback({
rating: 'like',
})
}}
className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-gray-100"
>
<HandThumbUpIcon width={16} height={16} />
</div>
</TooltipTrigger>
<TooltipContent>{t('generation.feedback.like', { ns: 'share' })}</TooltipContent>
</Tooltip>
<Tooltip
popupContent="Undesirable Response"
needsDelay={false}
>
<div
<Tooltip>
<TooltipTrigger
delay={0}
className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-gray-100"
onClick={() => {
onFeedback({
rating: 'dislike',
})
}}
className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-gray-100"
>
<HandThumbDownIcon width={16} height={16} />
</div>
</TooltipTrigger>
<TooltipContent>{t('generation.feedback.dislike', { ns: 'share' })}</TooltipContent>
</Tooltip>
</div>
)}

View File

@ -10,8 +10,7 @@ import { Avatar } from '@/app/components/base/avatar'
import Divider from '@/app/components/base/divider'
import InlineDeleteConfirm from '@/app/components/base/inline-delete-confirm'
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { getUserColor } from '@/app/components/workflow/collaboration/utils/user-color'
import { useAppContext } from '@/context/app-context'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
@ -375,66 +374,86 @@ export const CommentThread: FC<CommentThreadProps> = memo(({
{t('comments.panelTitle', { ns: 'workflow' })}
</div>
<div className="flex items-center gap-1">
<Tooltip
popupContent={t('comments.aria.deleteComment', { ns: 'workflow' })}
position="top"
popupClassName="!px-2 !py-1.5"
>
<button
type="button"
disabled={loading}
className={cn('flex h-6 w-6 items-center justify-center rounded-lg text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive disabled:cursor-not-allowed disabled:text-text-disabled disabled:hover:bg-transparent disabled:hover:text-text-disabled')}
onClick={onDelete}
aria-label={t('comments.aria.deleteComment', { ns: 'workflow' })}
>
<RiDeleteBinLine className="h-4 w-4" />
</button>
<Tooltip>
<TooltipTrigger
render={(
<span className="inline-flex">
<button
type="button"
disabled={loading}
className={cn('flex h-6 w-6 items-center justify-center rounded-lg text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive disabled:cursor-not-allowed disabled:text-text-disabled disabled:hover:bg-transparent disabled:hover:text-text-disabled')}
onClick={onDelete}
aria-label={t('comments.aria.deleteComment', { ns: 'workflow' })}
>
<RiDeleteBinLine className="h-4 w-4" />
</button>
</span>
)}
/>
<TooltipContent placement="top" popupClassName="!px-2 !py-1.5">
{t('comments.aria.deleteComment', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
<Tooltip
popupContent={t('comments.aria.resolveComment', { ns: 'workflow' })}
position="top"
popupClassName="!px-2 !py-1.5"
>
<button
type="button"
disabled={comment.resolved || loading}
className={cn('flex h-6 w-6 items-center justify-center rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary disabled:cursor-not-allowed disabled:text-text-disabled disabled:hover:bg-transparent disabled:hover:text-text-disabled')}
onClick={onResolve}
aria-label={t('comments.aria.resolveComment', { ns: 'workflow' })}
>
{comment.resolved ? <RiCheckboxCircleFill className="h-4 w-4" /> : <RiCheckboxCircleLine className="h-4 w-4" />}
</button>
<Tooltip>
<TooltipTrigger
render={(
<span className="inline-flex">
<button
type="button"
disabled={comment.resolved || loading}
className={cn('flex h-6 w-6 items-center justify-center rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary disabled:cursor-not-allowed disabled:text-text-disabled disabled:hover:bg-transparent disabled:hover:text-text-disabled')}
onClick={onResolve}
aria-label={t('comments.aria.resolveComment', { ns: 'workflow' })}
>
{comment.resolved ? <RiCheckboxCircleFill className="h-4 w-4" /> : <RiCheckboxCircleLine className="h-4 w-4" />}
</button>
</span>
)}
/>
<TooltipContent placement="top" popupClassName="!px-2 !py-1.5">
{t('comments.aria.resolveComment', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
<Divider type="vertical" className="h-3.5" />
<Tooltip
popupContent={t('comments.aria.previousComment', { ns: 'workflow' })}
position="top"
popupClassName="!px-2 !py-1.5"
>
<button
type="button"
disabled={!canGoPrev || loading}
className={cn('flex h-6 w-6 items-center justify-center rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary disabled:cursor-not-allowed disabled:text-text-disabled disabled:hover:bg-transparent disabled:hover:text-text-disabled')}
onClick={onPrev}
aria-label={t('comments.aria.previousComment', { ns: 'workflow' })}
>
<RiArrowUpSLine className="h-4 w-4" />
</button>
<Tooltip>
<TooltipTrigger
render={(
<span className="inline-flex">
<button
type="button"
disabled={!canGoPrev || loading}
className={cn('flex h-6 w-6 items-center justify-center rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary disabled:cursor-not-allowed disabled:text-text-disabled disabled:hover:bg-transparent disabled:hover:text-text-disabled')}
onClick={onPrev}
aria-label={t('comments.aria.previousComment', { ns: 'workflow' })}
>
<RiArrowUpSLine className="h-4 w-4" />
</button>
</span>
)}
/>
<TooltipContent placement="top" popupClassName="!px-2 !py-1.5">
{t('comments.aria.previousComment', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
<Tooltip
popupContent={t('comments.aria.nextComment', { ns: 'workflow' })}
position="top"
popupClassName="!px-2 !py-1.5"
>
<button
type="button"
disabled={!canGoNext || loading}
className={cn('flex h-6 w-6 items-center justify-center rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary disabled:cursor-not-allowed disabled:text-text-disabled disabled:hover:bg-transparent disabled:hover:text-text-disabled')}
onClick={onNext}
aria-label={t('comments.aria.nextComment', { ns: 'workflow' })}
>
<RiArrowDownSLine className="h-4 w-4" />
</button>
<Tooltip>
<TooltipTrigger
render={(
<span className="inline-flex">
<button
type="button"
disabled={!canGoNext || loading}
className={cn('flex h-6 w-6 items-center justify-center rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary disabled:cursor-not-allowed disabled:text-text-disabled disabled:hover:bg-transparent disabled:hover:text-text-disabled')}
onClick={onNext}
aria-label={t('comments.aria.nextComment', { ns: 'workflow' })}
>
<RiArrowDownSLine className="h-4 w-4" />
</button>
</span>
)}
/>
<TooltipContent placement="top" popupClassName="!px-2 !py-1.5">
{t('comments.aria.nextComment', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
<button
type="button"

View File

@ -2,6 +2,7 @@
import type { OnlineUser } from '../collaboration/types'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useReactFlow } from 'reactflow'
import { Avatar } from '@/app/components/base/avatar'
import Divider from '@/app/components/base/divider'
@ -10,8 +11,7 @@ import {
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { useAppContext } from '@/context/app-context'
import { getAvatar } from '@/service/common'
import { cn } from '@/utils/classnames'
@ -52,6 +52,7 @@ const useAvatarUrls = (users: OnlineUser[]) => {
}
const OnlineUsers = () => {
const { t } = useTranslation()
const appId = useStore(s => s.appId)
const { onlineUsers, cursors, isEnabled: isCollaborationEnabled } = useCollaboration(appId as string)
const { userProfile } = useAppContext()
@ -66,7 +67,7 @@ const OnlineUsers = () => {
baseClassName: string,
suffixClassName: string,
) => {
const baseName = user.username || 'User'
const baseName = user.username || t('comments.fallback.user', { ns: 'workflow' })
const isCurrentUser = user.user_id === currentUserId
return (
@ -74,7 +75,7 @@ const OnlineUsers = () => {
<span>{baseName}</span>
{isCurrentUser && (
<span className={suffixClassName}>
(You)
{t('members.you', { ns: 'common' })}
</span>
)}
</span>
@ -123,21 +124,9 @@ const OnlineUsers = () => {
const isCurrentUser = user.user_id === currentUserId
const userColor = isCurrentUser ? undefined : getUserColor(user.user_id)
return (
<Tooltip
key={`${user.sid}-${index}`}
popupContent={renderDisplayName(
user,
'system-xs-medium text-text-secondary',
'text-text-quaternary',
)}
position="bottom"
triggerMethod="hover"
needsDelay={false}
asChild
popupClassName="flex h-[28px] items-center justify-center gap-1 rounded-md border-[0.5px] border-components-panel-border bg-components-tooltip-bg px-3 py-[6px] shadow-lg shadow-shadow-shadow-5 backdrop-blur-[10px]"
noDecoration
>
<div
<Tooltip key={`${user.sid}-${index}`}>
<TooltipTrigger
delay={0}
className={cn(
'relative flex size-6 items-center justify-center',
index > 0 && '-ml-1.5',
@ -147,13 +136,24 @@ const OnlineUsers = () => {
onClick={() => !isCurrentUser && jumpToUserCursor(user.user_id)}
>
<Avatar
name={user.username || 'User'}
name={user.username || t('comments.fallback.user', { ns: 'workflow' })}
avatar={getAvatarUrl(user) ?? null}
size="sm"
className="ring-1 ring-components-panel-bg"
backgroundColor={userColor}
/>
</div>
</TooltipTrigger>
<TooltipContent
placement="bottom"
variant="plain"
popupClassName="flex h-[28px] items-center justify-center gap-1 rounded-md border-[0.5px] border-components-panel-border bg-components-tooltip-bg px-3 py-[6px] shadow-lg shadow-shadow-shadow-5 backdrop-blur-[10px]"
>
{renderDisplayName(
user,
'system-xs-medium text-text-secondary',
'text-text-quaternary',
)}
</TooltipContent>
</Tooltip>
)
})}
@ -217,7 +217,7 @@ const OnlineUsers = () => {
>
<div className="relative">
<Avatar
name={user.username || 'User'}
name={user.username || t('comments.fallback.user', { ns: 'workflow' })}
avatar={getAvatarUrl(user) ?? null}
size="sm"
backgroundColor={userColor}

View File

@ -4,8 +4,7 @@ import type { ToolSetting } from '../types'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Switch from '@/app/components/base/switch'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import FieldCollapse from '@/app/components/workflow/nodes/_base/components/collapse/field-collapse'
import Split from '@/app/components/workflow/nodes/_base/components/split'
import ReferenceToolConfig from './reference-tool-config'
@ -43,25 +42,37 @@ const ComputerUseConfig: FC<Props> = ({
title={(
<div className="flex items-center gap-1">
{t(`${i18nPrefix}.title`, { ns: 'workflow' })}
<Tooltip
popupContent={t(`${i18nPrefix}.tooltip`, { ns: 'workflow' })}
triggerClassName="h-4 w-4"
/>
<Tooltip>
<TooltipTrigger
delay={0}
className="flex h-4 w-4 items-center justify-center"
>
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
</TooltipTrigger>
<TooltipContent>{t(`${i18nPrefix}.tooltip`, { ns: 'workflow' })}</TooltipContent>
</Tooltip>
</div>
)}
noXSpacing
operations={(
<div>
<Tooltip
disabled={!disabledTip}
popupContent={disabledTip}
>
<Switch
size="md"
disabled={isDisabled}
value={enabled}
onChange={onChange}
<Tooltip>
<TooltipTrigger
disabled={!disabledTip}
render={(
<div>
<Switch
size="md"
disabled={isDisabled}
value={enabled}
onChange={onChange}
/>
</div>
)}
/>
{disabledTip && (
<TooltipContent>{disabledTip}</TooltipContent>
)}
</Tooltip>
</div>
)}

View File

@ -1,8 +1,7 @@
import type { ToolValue } from '@/app/components/workflow/block-selector/types'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
import { BoxGroup } from '@/app/components/workflow/nodes/_base/components/layout'
import { cn } from '@/utils/classnames'
@ -42,31 +41,36 @@ const Tools = ({
className: 'px-0',
}}
>
<Tooltip
disabled={!disabledTip}
popupContent={disabledTip}
>
<div className={cn(isDisabled && 'opacity-50')}>
<div className={cn(isDisabled && 'pointer-events-none')}>
<MultipleToolSelector
nodeId={nodeId}
nodeOutputVars={[]}
availableNodes={[]}
value={tools}
label={t('nodes.llm.tools.title', { ns: 'workflow' })}
tooltip={t('nodes.llm.tools.tooltip', { ns: 'workflow' })}
onChange={handleToolsChange}
supportCollapse
disabled={isDisabled}
/>
{!hideMaxIterations && (
<MaxIterations
value={maxIterations}
onChange={handleMaxIterationsChange}
/>
)}
</div>
</div>
<Tooltip>
<TooltipTrigger
disabled={!disabledTip}
render={(
<div className={cn(isDisabled && 'opacity-50')}>
<div className={cn(isDisabled && 'pointer-events-none')}>
<MultipleToolSelector
nodeId={nodeId}
nodeOutputVars={[]}
availableNodes={[]}
value={tools}
label={t('nodes.llm.tools.title', { ns: 'workflow' })}
tooltip={t('nodes.llm.tools.tooltip', { ns: 'workflow' })}
onChange={handleToolsChange}
supportCollapse
disabled={isDisabled}
/>
{!hideMaxIterations && (
<MaxIterations
value={maxIterations}
onChange={handleMaxIterationsChange}
/>
)}
</div>
</div>
)}
/>
{disabledTip && (
<TooltipContent>{disabledTip}</TooltipContent>
)}
</Tooltip>
</BoxGroup>
)

View File

@ -1,6 +1,5 @@
import { memo } from 'react'
import Tooltip from '@/app/components/base/tooltip-plus'
import { useTranslation } from 'react-i18next'
import {
NumberField,
NumberFieldControls,
@ -9,6 +8,7 @@ import {
NumberFieldIncrement,
NumberFieldInput,
} from '@/app/components/base/ui/number-field'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { cn } from '@/utils/classnames'
@ -19,14 +19,23 @@ type MaxIterationsProps = {
disabled?: boolean
}
const MaxIterations = ({ value = 10, onChange, className, disabled }: MaxIterationsProps) => {
const { t } = useTranslation()
return (
<div className={cn('mt-3 flex h-10 items-center justify-between', className)}>
<div className="flex items-center">
<div className="mr-0.5 truncate uppercase text-text-secondary system-sm-semibold">Max Iterations</div>
<Tooltip
popupContent="Max Iterations is the maximum number of iterations to run the tool."
triggerClassName="shrink-0 w-4 h-4"
/>
<div className="mr-0.5 truncate uppercase text-text-secondary system-sm-semibold">
{t('nodes.agent.maxIterations', { ns: 'workflow' })}
</div>
<Tooltip>
<TooltipTrigger
delay={0}
className="flex h-4 w-4 shrink-0 items-center justify-center"
>
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
</TooltipTrigger>
<TooltipContent>{t('nodes.llm.tools.maxIterationsTooltip', { ns: 'workflow' })}</TooltipContent>
</Tooltip>
</div>
<NumberField
value={value}

View File

@ -4,8 +4,7 @@ import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { AssembleVariablesAlt } from '@/app/components/base/icons/src/vender/line/general'
import { Agent } from '@/app/components/base/icons/src/vender/workflow'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { NodeSourceHandle } from '@/app/components/workflow/nodes/_base/components/node-handle'
import { cn } from '@/utils/classnames'
@ -38,17 +37,22 @@ const SubGraphStartNode = ({ id, data }: NodeProps<SubGraphStartNodeData>) => {
showTitle ? 'gap-1.5 px-2' : 'w-11',
)}
>
<Tooltip popupContent={tooltip} asChild={false}>
<div
className={cn(
'flex h-6 w-6 items-center justify-center border-[0.5px] border-components-panel-border-subtle',
iconType === 'agent'
? 'rounded-[8px] bg-util-colors-indigo-indigo-500'
: 'rounded-full bg-util-colors-blue-brand-blue-brand-500',
<Tooltip>
<TooltipTrigger
render={(
<div
className={cn(
'flex h-6 w-6 items-center justify-center border-[0.5px] border-components-panel-border-subtle',
iconType === 'agent'
? 'rounded-[8px] bg-util-colors-indigo-indigo-500'
: 'rounded-full bg-util-colors-blue-brand-blue-brand-500',
)}
>
<Icon className="h-3 w-3 text-text-primary-on-surface" />
</div>
)}
>
<Icon className="h-3 w-3 text-text-primary-on-surface" />
</div>
/>
<TooltipContent>{tooltip}</TooltipContent>
</Tooltip>
{showTitle && (
<span className="max-w-[160px] truncate text-text-secondary system-xs-medium">

View File

@ -10,8 +10,8 @@ import ActionButton from '@/app/components/base/action-button'
import Button from '@/app/components/base/button'
import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows'
import Loading from '@/app/components/base/loading'
import Tooltip from '@/app/components/base/tooltip-plus'
import { toast } from '@/app/components/base/ui/toast'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { submitHumanInputForm } from '@/service/workflow'
import { cn } from '@/utils/classnames'
import {
@ -126,14 +126,19 @@ const WorkflowPreview = () => {
<div className="flex items-center justify-between p-4 pb-1 text-base font-semibold text-text-primary">
{`${t('singleRun.testRun', { ns: 'workflow' })}${workflowRunningData ? formatWorkflowRunIdentifier(workflowRunningData.result.finished_at, t('common.running', { ns: 'workflow' })) : ''}`}
<div className="flex items-center gap-1">
<Tooltip popupContent={t('operation.refresh', { ns: 'common' })}>
<ActionButton onClick={() => {
setUserSelectedTab(null)
handleClearWorkflowRunHistory()
}}
>
<RefreshCcw01 className="h-4 w-4" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton onClick={() => {
setUserSelectedTab(null)
handleClearWorkflowRunHistory()
}}
>
<RefreshCcw01 className="h-4 w-4" />
</ActionButton>
)}
/>
<TooltipContent>{t('operation.refresh', { ns: 'common' })}</TooltipContent>
</Tooltip>
<div className="mx-3 h-3.5 w-[1px] bg-divider-regular" />
<div className="cursor-pointer p-1" onClick={() => handleCancelDebugAndPreviewPanel()}>

View File

@ -20,7 +20,7 @@ import {
import { useTranslation } from 'react-i18next'
import { useStore as useReactFlowStore } from 'reactflow'
import { shallow } from 'zustand/shallow'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { useCollaborativeWorkflow } from '@/app/components/workflow/hooks/use-collaborative-workflow'
import { useNodesInteractions, useNodesReadOnly, useNodesSyncDraft } from './hooks'
import { useSelectionInteractions } from './hooks/use-selection-interactions'
@ -54,13 +54,14 @@ type AlignButtonProps = {
const AlignButton: FC<AlignButtonProps> = ({ config, label, onClick, position = 'bottom' }) => {
return (
<Tooltip position={position} popupContent={label}>
<div
<Tooltip>
<TooltipTrigger
className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-md text-text-secondary hover:bg-state-base-hover"
onClick={() => onClick(config.type)}
>
{config.icon}
</div>
</TooltipTrigger>
<TooltipContent placement={position}>{label}</TooltipContent>
</Tooltip>
)
}

View File

@ -15,7 +15,7 @@ import {
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { START_TAB_ID } from '@/app/components/workflow/skill/constants'
import { useSkillAssetNodeMap } from '@/app/components/workflow/skill/hooks/file-tree/data/use-skill-asset-tree'
import { getFileIconType } from '@/app/components/workflow/skill/utils/file-utils'
@ -169,40 +169,48 @@ const FileReferenceBlock = ({ nodeKey, resourceId }: FileReferenceBlockProps) =>
offset={4}
>
<PortalToFollowElemTrigger ref={ref} className="inline-flex">
<Tooltip popupContent={tooltipContent} disabled={!tooltipContent}>
<span
className={cn(
'inline-flex min-w-[18px] select-none items-center gap-[2px] overflow-hidden rounded-[5px] border py-[1px] pl-[1px] pr-[4px] shadow-xs',
isInteractive ? 'cursor-pointer' : 'cursor-default',
isMissing ? 'border-state-warning-active bg-state-warning-hover' : 'border-state-accent-hover-alt bg-state-accent-hover',
isSelected && 'border-text-accent',
<Tooltip>
<TooltipTrigger
disabled={!tooltipContent}
render={(
<span
className={cn(
'inline-flex min-w-[18px] select-none items-center gap-[2px] overflow-hidden rounded-[5px] border py-[1px] pl-[1px] pr-[4px] shadow-xs',
isInteractive ? 'cursor-pointer' : 'cursor-default',
isMissing ? 'border-state-warning-active bg-state-warning-hover' : 'border-state-accent-hover-alt bg-state-accent-hover',
isSelected && 'border-text-accent',
)}
onMouseDown={() => {
if (!isInteractive)
return
setOpen(prev => !prev)
}}
>
<span className="flex items-center justify-center p-px">
{isFolder
? <span className={cn('i-ri-folder-line size-[14px]', isMissing ? 'text-text-warning' : 'text-text-accent')} aria-hidden="true" />
: (
<FileTypeIcon
type={(iconType || 'document') as FileAppearanceType}
size="sm"
className={cn('!size-[14px]', isMissing && '!text-text-warning')}
/>
)}
</span>
<span className={cn('max-w-[180px] truncate text-[12px] font-medium leading-4', isMissing ? 'text-text-warning' : 'text-text-accent')}>
{displayName}
</span>
{
isMissing && (
<span className="i-ri-alert-fill size-3 text-text-warning" />
)
}
</span>
)}
onMouseDown={() => {
if (!isInteractive)
return
setOpen(prev => !prev)
}}
>
<span className="flex items-center justify-center p-px">
{isFolder
? <span className={cn('i-ri-folder-line size-[14px]', isMissing ? 'text-text-warning' : 'text-text-accent')} aria-hidden="true" />
: (
<FileTypeIcon
type={(iconType || 'document') as FileAppearanceType}
size="sm"
className={cn('!size-[14px]', isMissing && '!text-text-warning')}
/>
)}
</span>
<span className={cn('max-w-[180px] truncate text-[12px] font-medium leading-4', isMissing ? 'text-text-warning' : 'text-text-accent')}>
{displayName}
</span>
{
isMissing && (
<span className="i-ri-alert-fill size-3 text-text-warning" />
)
}
</span>
/>
{tooltipContent && (
<TooltipContent>{tooltipContent}</TooltipContent>
)}
</Tooltip>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className="z-[1000]">

View File

@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'
import { useStore as useAppStore } from '@/app/components/app/store'
import FileTypeIcon from '@/app/components/base/file-uploader/file-type-icon'
import Loading from '@/app/components/base/loading'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import SkillEditor from '@/app/components/workflow/skill/editor/skill-editor'
import { useFileTypeInfo } from '@/app/components/workflow/skill/hooks/use-file-type-info'
import { getFileIconType } from '@/app/components/workflow/skill/utils/file-utils'
@ -121,22 +121,29 @@ const FilePreviewPanel = ({ resourceId, currentNode, className, style, onClose }
</div>
)}
</div>
<Tooltip
popupContent={t('skillEditor.openInSkillEditor', { ns: 'workflow' })}
disabled={!canOpenInEditor}
>
<button
type="button"
onClick={handleOpenInEditor}
<Tooltip>
<TooltipTrigger
disabled={!canOpenInEditor}
className={cn(
'inline-flex size-6 items-center justify-center rounded-md text-text-tertiary transition hover:bg-state-base-hover',
!canOpenInEditor && 'cursor-not-allowed opacity-40 hover:bg-transparent',
render={(
<span className="inline-flex">
<button
type="button"
onClick={handleOpenInEditor}
disabled={!canOpenInEditor}
className={cn(
'inline-flex size-6 items-center justify-center rounded-md text-text-tertiary transition hover:bg-state-base-hover',
!canOpenInEditor && 'cursor-not-allowed opacity-40 hover:bg-transparent',
)}
aria-label={t('skillEditor.openInSkillEditor', { ns: 'workflow' })}
>
<span className="i-ri-external-link-line size-4" />
</button>
</span>
)}
aria-label={t('skillEditor.openInSkillEditor', { ns: 'workflow' })}
>
<span className="i-ri-external-link-line size-4" />
</button>
/>
{canOpenInEditor && (
<TooltipContent>{t('skillEditor.openInSkillEditor', { ns: 'workflow' })}</TooltipContent>
)}
</Tooltip>
<button
type="button"

View File

@ -14,8 +14,7 @@ import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general'
// eslint-disable-next-line no-restricted-imports
import Modal from '@/app/components/base/modal'
import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import ToolAuthorizationSection from '@/app/components/plugins/plugin-detail-panel/tool-selector/sections/tool-authorization-section'
import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance'
@ -606,50 +605,56 @@ const ToolBlockComponent = ({
return (
<>
<span ref={ref} className="inline-flex">
<Tooltip
disabled={!isToolMissing || isToolDisabled}
offset={4}
noDecoration
popupClassName="bg-transparent p-0"
popupContent={missingTooltipContent}
>
<span
className={cn(
'group/tool inline-flex items-center gap-[2px] rounded-[5px] border py-px pl-px pr-[3px] shadow-xs',
isTriggerInteractive ? 'cursor-pointer' : 'cursor-default',
isWarningStyle ? 'border-state-warning-active bg-state-warning-hover' : 'border-state-accent-hover-alt bg-state-accent-hover',
isSelected && 'border-text-accent',
)}
title={`${provider}.${tool}`}
data-tool-config-id={configId}
onMouseDown={() => {
if (!isTriggerInteractive)
return
if (!currentProvider || !currentTool)
return
if (configuredToolValue)
setToolValue(configuredToolValue)
setIsSettingOpen(true)
}}
>
{renderIcon()}
<span className={cn('max-w-[180px] truncate system-xs-medium', isWarningStyle ? 'text-text-warning' : 'text-text-accent')}>
{isToolMissing ? missingDisplayLabel : displayLabel}
</span>
{(isToolMissing || isToolDisabled) && (
<>
<span className="flex h-4 items-center justify-center p-[2px] text-text-warning">
<span className="i-ri-alert-fill h-3 w-3" />
<Tooltip>
<TooltipTrigger
disabled={!isToolMissing || isToolDisabled}
render={(
<span
className={cn(
'group/tool inline-flex items-center gap-[2px] rounded-[5px] border py-px pl-px pr-[3px] shadow-xs',
isTriggerInteractive ? 'cursor-pointer' : 'cursor-default',
isWarningStyle ? 'border-state-warning-active bg-state-warning-hover' : 'border-state-accent-hover-alt bg-state-accent-hover',
isSelected && 'border-text-accent',
)}
title={`${provider}.${tool}`}
data-tool-config-id={configId}
onMouseDown={() => {
if (!isTriggerInteractive)
return
if (!currentProvider || !currentTool)
return
if (configuredToolValue)
setToolValue(configuredToolValue)
setIsSettingOpen(true)
}}
>
{renderIcon()}
<span className={cn('max-w-[180px] truncate system-xs-medium', isWarningStyle ? 'text-text-warning' : 'text-text-accent')}>
{isToolMissing ? missingDisplayLabel : displayLabel}
</span>
</>
)}
{!isToolMissing && !isToolDisabled && needAuthorization && (
<span className="flex h-4 items-center gap-0.5 rounded-[5px] border border-text-warning bg-components-badge-bg-dimm px-1 text-text-warning system-2xs-medium-uppercase">
{authBadgeLabel}
<span className="i-ri-alert-fill h-3 w-3" />
{(isToolMissing || isToolDisabled) && (
<span className="flex h-4 items-center justify-center p-[2px] text-text-warning">
<span className="i-ri-alert-fill h-3 w-3" />
</span>
)}
{!isToolMissing && !isToolDisabled && needAuthorization && (
<span className="flex h-4 items-center gap-0.5 rounded-[5px] border border-text-warning bg-components-badge-bg-dimm px-1 text-text-warning system-2xs-medium-uppercase">
{authBadgeLabel}
<span className="i-ri-alert-fill h-3 w-3" />
</span>
)}
</span>
)}
</span>
/>
{isToolMissing && !isToolDisabled && (
<TooltipContent
sideOffset={4}
variant="plain"
popupClassName="bg-transparent p-0"
>
{missingTooltipContent}
</TooltipContent>
)}
</Tooltip>
</span>
{useModalValue && (

View File

@ -14,8 +14,7 @@ import AppIcon from '@/app/components/base/app-icon'
import Modal from '@/app/components/base/modal'
import { useSelectOrDelete } from '@/app/components/base/prompt-editor/hooks'
import Switch from '@/app/components/base/switch'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import ToolAuthorizationSection from '@/app/components/plugins/plugin-detail-panel/tool-selector/sections/tool-authorization-section'
import { ReadmeEntrance } from '@/app/components/plugins/readme-panel/entrance'
@ -910,35 +909,43 @@ const ToolGroupBlockComponent = ({
return (
<>
<span ref={ref} className="inline-flex">
<Tooltip
disabled={!isToolMissing || isToolDisabled}
offset={4}
noDecoration
popupClassName="bg-transparent p-0"
popupContent={missingTooltipContent}
>
<span
className={cn(
'inline-flex items-center gap-[2px] rounded-[5px] border px-px py-[1px] shadow-xs',
isTriggerInteractive ? 'group cursor-pointer' : 'cursor-default',
isWarningStyle ? 'border-state-warning-active bg-state-warning-hover' : 'border-state-accent-hover-alt bg-state-accent-hover',
isSelected && 'border-text-accent',
<Tooltip>
<TooltipTrigger
disabled={!isToolMissing || isToolDisabled}
render={(
<span
className={cn(
'inline-flex items-center gap-[2px] rounded-[5px] border px-px py-[1px] shadow-xs',
isTriggerInteractive ? 'group cursor-pointer' : 'cursor-default',
isWarningStyle ? 'border-state-warning-active bg-state-warning-hover' : 'border-state-accent-hover-alt bg-state-accent-hover',
isSelected && 'border-text-accent',
)}
title={providerLabel}
onMouseDown={() => {
if (!isTriggerInteractive)
return
if (!toolItems.length)
return
setIsSettingOpen(true)
}}
>
{renderIcon()}
<span className={cn('max-w-[160px] truncate system-xs-medium', isWarningStyle ? 'text-text-warning' : 'text-text-accent')}>
{isToolMissing ? missingDisplayLabel : providerLabel}
</span>
{toolStatusBadge}
</span>
)}
title={providerLabel}
onMouseDown={() => {
if (!isTriggerInteractive)
return
if (!toolItems.length)
return
setIsSettingOpen(true)
}}
>
{renderIcon()}
<span className={cn('max-w-[160px] truncate system-xs-medium', isWarningStyle ? 'text-text-warning' : 'text-text-accent')}>
{isToolMissing ? missingDisplayLabel : providerLabel}
</span>
{toolStatusBadge}
</span>
/>
{isToolMissing && !isToolDisabled && (
<TooltipContent
sideOffset={4}
variant="plain"
popupClassName="bg-transparent p-0"
>
{missingTooltipContent}
</TooltipContent>
)}
</Tooltip>
</span>
{useModalValue && (

View File

@ -4,7 +4,7 @@ import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import { AssembleVariablesAlt } from '@/app/components/base/icons/src/vender/line/general'
import { Agent } from '@/app/components/base/icons/src/vender/workflow'
import Tooltip from '@/app/components/base/tooltip-plus'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
import { cn } from '@/utils/classnames'
import { NodeSourceHandle } from '../../node-handle'
@ -37,10 +37,15 @@ const SubGraphStartNode = ({ id, data }: NodeProps<SubGraphStartNodeData>) => {
showTitle ? 'gap-1.5 px-2' : 'w-11',
)}
>
<Tooltip popupContent={tooltip} asChild={false}>
<div className="flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500">
<Icon className="h-3 w-3 text-text-primary-on-surface" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="flex h-6 w-6 items-center justify-center rounded-full border-[0.5px] border-components-panel-border-subtle bg-util-colors-blue-brand-blue-brand-500">
<Icon className="h-3 w-3 text-text-primary-on-surface" />
</div>
)}
/>
<TooltipContent>{tooltip}</TooltipContent>
</Tooltip>
{showTitle && (
<span className="max-w-[160px] truncate text-text-secondary system-xs-medium">

View File

@ -223,6 +223,7 @@
"roadmap": "See our roadmap",
"showMyCreatedAppsOnly": "Created by me",
"skills.comingSoon": "Workspace skills coming soon. Stay tuned.",
"skills.title": "Skills",
"structOutput.LLMResponse": "LLM Response",
"structOutput.configure": "Configure",
"structOutput.disabledByComputerUse": "Structured Output is disabled when Agent Mode is enabled.",

View File

@ -44,6 +44,10 @@
"generation.errorMsg.moreThanMaxLengthLine": "Row {{rowIndex}}: {{varName}} value can not be more than {{maxLength}} characters",
"generation.execution": "Run",
"generation.executions": "{{num}} runs",
"generation.feedback.dislike": "Undesirable Response",
"generation.feedback.like": "Great Rating",
"generation.feedback.undoDislike": "Undo Undesirable Response",
"generation.feedback.undoLike": "Undo Great Rating",
"generation.field": "Field",
"generation.noData": "AI will give you what you want here.",
"generation.queryPlaceholder": "Write your query content...",

View File

@ -195,6 +195,7 @@
"common.input": "Input",
"common.insertVarTip": "Press the '/' key to insert quickly",
"common.jinjaEditorPlaceholder": "Type '/' or '{' to insert variable",
"common.json": "JSON",
"common.jumpToNode": "Jump to this node",
"common.latestPublished": "Latest Published",
"common.learnMore": "Learn More",
@ -867,6 +868,7 @@
"nodes.llm.singleRun.variable": "Variable",
"nodes.llm.sysQueryInUser": "sys.query in user message is required",
"nodes.llm.tools.disabledByStructuredOutput": "Tools are disabled when Structured Output is enabled.",
"nodes.llm.tools.maxIterationsTooltip": "Max Iterations is the maximum number of iterations to run the tool.",
"nodes.llm.tools.title": "Tools",
"nodes.llm.tools.tooltip": "Enable the LLM to call external tools during execution. The model autonomously decides when and which tools to invoke based on context, and may iterate multiple times to refine results.",
"nodes.llm.variables": "variables",