From 1359c032169716ec34ffcaccb611ab895d755d61 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 4 May 2026 21:17:09 +0800 Subject: [PATCH] refactor(web): migrate legacy tooltip to infotip (#35774) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- eslint-suppressions.json | 136 ------------------ .../app-sidebar/__tests__/basic.spec.tsx | 11 +- web/app/components/app-sidebar/basic.tsx | 15 +- .../components/app-sidebar/toggle-button.tsx | 37 +++-- .../app/configuration/config-vision/index.tsx | 32 ++--- .../config-vision/param-config-content.tsx | 19 ++- .../app/configuration/config/config-audio.tsx | 15 +- .../configuration/config/config-document.tsx | 15 +- .../context-var/__tests__/index.spec.tsx | 4 +- .../dataset-config/context-var/index.tsx | 15 +- .../params-config/config-content.tsx | 41 +++--- .../base/__tests__/theme-selector.spec.tsx | 9 +- web/app/components/base/checkbox/index.tsx | 6 + .../base/param-item/__tests__/index.spec.tsx | 10 +- .../__tests__/score-threshold-item.spec.tsx | 5 +- .../param-item/__tests__/top-k-item.spec.tsx | 5 +- web/app/components/base/param-item/index.tsx | 11 +- web/app/components/base/theme-selector.tsx | 104 +++++--------- .../priority-label/__tests__/index.spec.tsx | 45 +++--- .../billing/priority-label/index.tsx | 40 +++--- .../image-input.tsx | 33 ++--- .../__tests__/index.spec.tsx | 8 +- .../common/retrieval-param-config/index.tsx | 13 +- .../list/template-card/details/index.tsx | 10 +- .../__tests__/indexing-progress-item.spec.tsx | 9 +- .../indexing-progress-item.tsx | 18 +-- .../__tests__/indexing-mode-section.spec.tsx | 7 + .../components/__tests__/inputs.spec.tsx | 5 +- .../components/indexing-mode-section.tsx | 99 +++++++------ .../create/step-two/components/inputs.tsx | 22 +-- .../step-two/components/option-card.tsx | 4 +- .../__tests__/checkbox-with-label.spec.tsx | 8 +- .../website/base/__tests__/field.spec.tsx | 6 +- .../website/base/checkbox-with-label.tsx | 36 +++-- .../datasets/create/website/base/field.tsx | 11 +- .../__tests__/checkbox-with-label.spec.tsx | 8 +- .../base/__tests__/index.spec.tsx | 25 ++-- .../base/checkbox-with-label.tsx | 36 +++-- .../status-item/__tests__/index.spec.tsx | 4 +- .../datasets/documents/status-item/index.tsx | 31 +++- .../__tests__/info-group.spec.tsx | 4 +- .../metadata/metadata-document/info-group.tsx | 10 +- .../settings/summary-index-setting.tsx | 11 +- .../__tests__/global-inputs.spec.tsx | 10 +- .../__tests__/index.spec.tsx | 16 +-- .../label-right-content/global-inputs.tsx | 9 +- .../publisher/__tests__/index.spec.tsx | 21 --- .../_base/components/__tests__/field.spec.tsx | 6 +- .../workflow/nodes/_base/components/field.tsx | 25 +++- .../nodes/_base/components/option-card.tsx | 12 +- .../workflow-panel/__tests__/index.spec.tsx | 6 +- .../_base/components/workflow-panel/index.tsx | 56 +++++--- .../llm/components/config-prompt-item.tsx | 24 ++-- .../llm/components/panel-memory-section.tsx | 35 ++--- .../llm/components/panel-output-section.tsx | 30 ++-- web/eslint.constants.mjs | 9 -- 56 files changed, 512 insertions(+), 740 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index f822ac2ae2..586590413f 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -197,21 +197,11 @@ "count": 4 } }, - "web/app/components/app-sidebar/basic.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/app-sidebar/index.tsx": { "ts/no-explicit-any": { "count": 1 } }, - "web/app/components/app-sidebar/toggle-button.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/app/annotation/add-annotation-modal/edit-item/index.tsx": { "erasable-syntax-only/enums": { "count": 1 @@ -351,16 +341,6 @@ "count": 1 } }, - "web/app/components/app/configuration/config-vision/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "web/app/components/app/configuration/config-vision/param-config-content.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/app/configuration/config/agent/agent-setting/index.tsx": { "react/set-state-in-effect": { "count": 1 @@ -445,21 +425,6 @@ "count": 2 } }, - "web/app/components/app/configuration/config/config-audio.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "web/app/components/app/configuration/config/config-document.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "web/app/components/app/configuration/dataset-config/context-var/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/app/configuration/dataset-config/index.tsx": { "ts/no-explicit-any": { "count": 1 @@ -470,11 +435,6 @@ "count": 1 } }, - "web/app/components/app/configuration/dataset-config/params-config/config-content.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/app/configuration/dataset-config/params-config/index.tsx": { "no-restricted-imports": { "count": 1 @@ -1726,11 +1686,6 @@ "count": 1 } }, - "web/app/components/base/param-item/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/base/prompt-editor/index.stories.tsx": { "no-console": { "count": 1 @@ -2047,11 +2002,6 @@ "count": 1 } }, - "web/app/components/billing/priority-label/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/billing/type.ts": { "erasable-syntax-only/enums": { "count": 4 @@ -2077,11 +2027,6 @@ "count": 3 } }, - "web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-input.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/datasets/common/image-uploader/store.tsx": { "react-refresh/only-export-components": { "count": 3 @@ -2092,11 +2037,6 @@ "count": 1 } }, - "web/app/components/datasets/common/retrieval-param-config/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/dsl-confirm-modal.tsx": { "no-restricted-imports": { "count": 1 @@ -2115,11 +2055,6 @@ "count": 1 } }, - "web/app/components/datasets/create-from-pipeline/list/template-card/details/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/datasets/create-from-pipeline/list/template-card/details/types.ts": { "erasable-syntax-only/enums": { "count": 1 @@ -2130,11 +2065,6 @@ "count": 1 } }, - "web/app/components/datasets/create/embedding-process/indexing-progress-item.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx": { "no-restricted-imports": { "count": 1 @@ -2165,16 +2095,6 @@ "count": 5 } }, - "web/app/components/datasets/create/step-two/components/indexing-mode-section.tsx": { - "no-restricted-imports": { - "count": 2 - } - }, - "web/app/components/datasets/create/step-two/components/inputs.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/datasets/create/step-two/hooks/index.ts": { "no-barrel-files/no-barrel-files": { "count": 6 @@ -2209,16 +2129,6 @@ "count": 1 } }, - "web/app/components/datasets/create/website/base/checkbox-with-label.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, - "web/app/components/datasets/create/website/base/field.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/datasets/create/website/firecrawl/index.tsx": { "no-console": { "count": 1 @@ -2327,11 +2237,6 @@ "count": 4 } }, - "web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/checkbox-with-label.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/options/index.tsx": { "ts/no-explicit-any": { "count": 1 @@ -2477,11 +2382,6 @@ "count": 3 } }, - "web/app/components/datasets/documents/status-item/index.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx": { "react/set-state-in-effect": { "count": 1 @@ -2565,11 +2465,6 @@ "count": 1 } }, - "web/app/components/datasets/metadata/metadata-document/info-group.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/datasets/metadata/types.ts": { "erasable-syntax-only/enums": { "count": 2 @@ -2590,11 +2485,6 @@ "count": 1 } }, - "web/app/components/datasets/settings/summary-index-setting.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/develop/code.tsx": { "ts/no-empty-object-type": { "count": 1 @@ -3341,11 +3231,6 @@ "count": 1 } }, - "web/app/components/rag-pipeline/components/panel/input-field/label-right-content/global-inputs.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/index.tsx": { "ts/no-explicit-any": { "count": 1 @@ -3932,11 +3817,6 @@ "count": 1 } }, - "web/app/components/workflow/nodes/_base/components/field.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx": { "ts/no-explicit-any": { "count": 1 @@ -3980,11 +3860,6 @@ "count": 1 } }, - "web/app/components/workflow/nodes/_base/components/option-card.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/workflow/nodes/_base/components/prompt/editor.tsx": { "ts/no-explicit-any": { "count": 4 @@ -4046,9 +3921,6 @@ } }, "web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx": { - "no-restricted-imports": { - "count": 1 - }, "react/set-state-in-effect": { "count": 3 }, @@ -4539,14 +4411,6 @@ "count": 1 } }, - "web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx": { - "no-restricted-imports": { - "count": 1 - }, - "ts/no-explicit-any": { - "count": 3 - } - }, "web/app/components/workflow/nodes/llm/components/config-prompt.tsx": { "react/unsupported-syntax": { "count": 1 diff --git a/web/app/components/app-sidebar/__tests__/basic.spec.tsx b/web/app/components/app-sidebar/__tests__/basic.spec.tsx index 67e708eb02..1abb56d7c6 100644 --- a/web/app/components/app-sidebar/__tests__/basic.spec.tsx +++ b/web/app/components/app-sidebar/__tests__/basic.spec.tsx @@ -7,12 +7,6 @@ vi.mock('@/app/components/base/icons/src/vender/workflow', () => ({ WindowCursor: (props: React.SVGProps) => , })) -vi.mock('@/app/components/base/tooltip', () => ({ - default: ({ popupContent }: { popupContent: React.ReactNode }) => ( -
{popupContent}
- ), -})) - vi.mock('../../base/app-icon', () => ({ default: ({ icon, background, innerIcon, className }: { icon?: string @@ -75,13 +69,12 @@ describe('AppBasic', () => { it('should show hover tip when provided', () => { render() - expect(screen.getByTestId('tooltip')).toBeInTheDocument() - expect(screen.getByText('Some tip')).toBeInTheDocument() + expect(screen.getByLabelText('Some tip')).toBeInTheDocument() }) it('should not show hover tip when not provided', () => { render() - expect(screen.queryByTestId('tooltip')).not.toBeInTheDocument() + expect(screen.queryByLabelText('Some tip')).not.toBeInTheDocument() }) }) diff --git a/web/app/components/app-sidebar/basic.tsx b/web/app/components/app-sidebar/basic.tsx index 2814072860..3c7a7d2098 100644 --- a/web/app/components/app-sidebar/basic.tsx +++ b/web/app/components/app-sidebar/basic.tsx @@ -4,7 +4,7 @@ import { ApiAggregate, WindowCursor, } from '@/app/components/base/icons/src/vender/workflow' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' import AppIcon from '../base/app-icon' type IAppBasicProps = { @@ -82,16 +82,9 @@ export default function AppBasic({ icon, icon_background, name, isExternal, type {hoverTip && ( - - {hoverTip} - - )} - popupClassName="ml-1" - triggerClassName="w-4 h-4 ml-1" - position="top" - /> + + {hoverTip} + )} {!hideType && isExtraInLine && ( diff --git a/web/app/components/app-sidebar/toggle-button.tsx b/web/app/components/app-sidebar/toggle-button.tsx index 6aca77fb4f..9dd5a58ef2 100644 --- a/web/app/components/app-sidebar/toggle-button.tsx +++ b/web/app/components/app-sidebar/toggle-button.tsx @@ -1,20 +1,20 @@ import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react' import * as React from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '../base/tooltip' import ShortcutsName from '../workflow/shortcuts-name' -type TooltipContentProps = { +type ToggleTooltipContentProps = { expand: boolean } const TOGGLE_SHORTCUT = ['ctrl', 'B'] -const TooltipContent = ({ +const ToggleTooltipContent = ({ expand, -}: TooltipContentProps) => { +}: ToggleTooltipContentProps) => { const { t } = useTranslation() return ( @@ -37,22 +37,21 @@ const ToggleButton = ({ className, }: ToggleButtonProps) => { return ( - } - popupClassName="p-1.5 rounded-lg" - position="right" - > - + {expand ? : } + + + + ) } diff --git a/web/app/components/app/configuration/config-vision/index.tsx b/web/app/components/app/configuration/config-vision/index.tsx index b9cb54cc34..81c0ac8450 100644 --- a/web/app/components/app/configuration/config-vision/index.tsx +++ b/web/app/components/app/configuration/config-vision/index.tsx @@ -11,7 +11,7 @@ import { useContext } from 'use-context-selector' // import { Resolution } from '@/types/app' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' import { Vision } from '@/app/components/base/icons/src/vender/features' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card' import { SupportUploadFileTypes } from '@/app/components/workflow/types' // import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card' @@ -70,13 +70,12 @@ const ConfigVision: FC = () => {
{t('vision.name', { ns: 'appDebug' })}
- - {t('vision.description', { ns: 'appDebug' })} -
- )} - /> + + {t('vision.description', { ns: 'appDebug' })} +
{readonly @@ -84,15 +83,14 @@ const ConfigVision: FC = () => { <>
{t('vision.visionSettings.resolution', { ns: 'appDebug' })}
- - {t('vision.visionSettings.resolutionTooltip', { ns: 'appDebug' }).split('\n').map(item => ( -
{item}
- ))} -
- )} - /> + + {t('vision.visionSettings.resolutionTooltip', { ns: 'appDebug' }).split('\n').map(item => ( +
{item}
+ ))} +
{
{t('vision.visionSettings.resolution', { ns: 'appDebug' })}
- - {t('vision.visionSettings.resolutionTooltip', { ns: 'appDebug' }).split('\n').map(item => ( -
{item}
- ))} -
- )} - /> + + {t('vision.visionSettings.resolutionTooltip', { ns: 'appDebug' }).split('\n').map(item => ( +
{item}
+ ))} +
{
{t('feature.audioUpload.title', { ns: 'appDebug' })}
- - {t('feature.audioUpload.description', { ns: 'appDebug' })} -
- )} - /> + + {t('feature.audioUpload.description', { ns: 'appDebug' })} +
{!readonly && (
diff --git a/web/app/components/app/configuration/config/config-document.tsx b/web/app/components/app/configuration/config/config-document.tsx index 156c605267..107e21b36e 100644 --- a/web/app/components/app/configuration/config/config-document.tsx +++ b/web/app/components/app/configuration/config/config-document.tsx @@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' import { Document } from '@/app/components/base/icons/src/vender/features' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' import { SupportUploadFileTypes } from '@/app/components/workflow/types' import ConfigContext from '@/context/debug-configuration' @@ -57,13 +57,12 @@ const ConfigDocument: FC = () => {
{t('feature.documentUpload.title', { ns: 'appDebug' })}
- - {t('feature.documentUpload.description', { ns: 'appDebug' })} -
- )} - /> + + {t('feature.documentUpload.description', { ns: 'appDebug' })} + {!readonly && (
diff --git a/web/app/components/app/configuration/dataset-config/context-var/__tests__/index.spec.tsx b/web/app/components/app/configuration/dataset-config/context-var/__tests__/index.spec.tsx index 91fe47d83d..e8b1583171 100644 --- a/web/app/components/app/configuration/dataset-config/context-var/__tests__/index.spec.tsx +++ b/web/app/components/app/configuration/dataset-config/context-var/__tests__/index.spec.tsx @@ -275,7 +275,7 @@ describe('ContextVar', () => { // Act render() - const varPickerTrigger = screen.getByTestId('popover-trigger') + const varPickerTrigger = screen.getAllByTestId('popover-trigger').at(-1)! await user.click(varPickerTrigger!) expect(screen.getByTestId('popover-content'))!.toBeInTheDocument() @@ -296,7 +296,7 @@ describe('ContextVar', () => { // Act render() - const varPickerTrigger = screen.getByTestId('popover-trigger') + const varPickerTrigger = screen.getAllByTestId('popover-trigger').at(-1)! // Open dropdown await user.click(varPickerTrigger!) diff --git a/web/app/components/app/configuration/dataset-config/context-var/index.tsx b/web/app/components/app/configuration/dataset-config/context-var/index.tsx index 634277c469..60d81548cf 100644 --- a/web/app/components/app/configuration/dataset-config/context-var/index.tsx +++ b/web/app/components/app/configuration/dataset-config/context-var/index.tsx @@ -5,7 +5,7 @@ import { cn } from '@langgenius/dify-ui/cn' import * as React from 'react' import { useTranslation } from 'react-i18next' import { BracketsX } from '@/app/components/base/icons/src/vender/line/development' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' import VarPicker from './var-picker' const ContextVar: FC = (props) => { @@ -20,13 +20,12 @@ const ContextVar: FC = (props) => {
{t('feature.dataSet.queryVariable.title', { ns: 'appDebug' })}
- - {t('feature.dataSet.queryVariable.tip', { ns: 'appDebug' })} - - )} - /> + + {t('feature.dataSet.queryVariable.tip', { ns: 'appDebug' })} + diff --git a/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx b/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx index d0e6b2fe9f..9c50196dcf 100644 --- a/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx +++ b/web/app/components/app/configuration/dataset-config/params-config/config-content.tsx @@ -15,9 +15,9 @@ import { toast } from '@langgenius/dify-ui/toast' import { memo, useCallback, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' +import { Infotip } from '@/app/components/base/infotip' import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item' import TopKItem from '@/app/components/base/param-item/top-k-item' -import Tooltip from '@/app/components/base/tooltip' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' @@ -244,15 +244,14 @@ const ConfigContent: FC = ({ onClick={() => handleRerankModeChange(option.value)} >
{option.label}
- - {option.tips} - - )} - popupClassName="ml-0.5" - triggerClassName="ml-0.5 w-3.5 h-3.5" - /> + + {option.tips} + )) } @@ -273,15 +272,13 @@ const ConfigContent: FC = ({ ) }
{t('modelProvider.rerankModel.key', { ns: 'common' })}
- - {t('modelProvider.rerankModel.tip', { ns: 'common' })} - - )} - popupClassName="ml-1" - triggerClassName="ml-1 w-4 h-4" - /> + + {t('modelProvider.rerankModel.tip', { ns: 'common' })} + { showRerankModel && ( @@ -363,9 +360,9 @@ const ConfigContent: FC = ({
{t('modelProvider.systemReasoningModel.key', { ns: 'common' })}
- + + {t('modelProvider.systemReasoningModel.tip', { ns: 'common' })} +
{ it('should call setTheme with light when light option is clicked', () => { render() fireEvent.click(screen.getByRole('button')) - const lightButton = screen.getByText(/light/i).closest('button')! - fireEvent.click(lightButton) + fireEvent.click(screen.getByText(/light/i)) expect(mockSetTheme).toHaveBeenCalledWith('light') }) it('should call setTheme with dark when dark option is clicked', () => { render() fireEvent.click(screen.getByRole('button')) - const darkButton = screen.getByText(/dark/i).closest('button')! - fireEvent.click(darkButton) + fireEvent.click(screen.getByText(/dark/i)) expect(mockSetTheme).toHaveBeenCalledWith('dark') }) it('should call setTheme with system when system option is clicked', () => { render() fireEvent.click(screen.getByRole('button')) - const systemButton = screen.getByText(/auto/i).closest('button')! - fireEvent.click(systemButton) + fireEvent.click(screen.getByText(/auto/i)) expect(mockSetTheme).toHaveBeenCalledWith('system') }) }) diff --git a/web/app/components/base/checkbox/index.tsx b/web/app/components/base/checkbox/index.tsx index 1abf142a70..7457dc9016 100644 --- a/web/app/components/base/checkbox/index.tsx +++ b/web/app/components/base/checkbox/index.tsx @@ -8,6 +8,8 @@ type CheckboxProps = { className?: string disabled?: boolean indeterminate?: boolean + ariaLabel?: string + ariaLabelledBy?: string } const Checkbox = ({ @@ -17,6 +19,8 @@ const Checkbox = ({ className, disabled, indeterminate, + ariaLabel, + ariaLabelledBy, }: CheckboxProps) => { const checkClassName = (checked || indeterminate) ? 'bg-components-checkbox-bg text-components-checkbox-icon hover:bg-components-checkbox-bg-hover' @@ -52,6 +56,8 @@ const Checkbox = ({ role="checkbox" aria-checked={indeterminate ? 'mixed' : !!checked} aria-disabled={!!disabled} + aria-label={ariaLabel} + aria-labelledby={ariaLabelledBy} tabIndex={disabled ? -1 : 0} > {!checked && indeterminate && } diff --git a/web/app/components/base/param-item/__tests__/index.spec.tsx b/web/app/components/base/param-item/__tests__/index.spec.tsx index 889662c87d..01d28953fa 100644 --- a/web/app/components/base/param-item/__tests__/index.spec.tsx +++ b/web/app/components/base/param-item/__tests__/index.spec.tsx @@ -27,17 +27,15 @@ describe('ParamItem', () => { }) it('should render a tooltip trigger by default', () => { - const { container } = render() + render() - // Tooltip trigger icon should be rendered (the data-state div) - expect(container.querySelector('[data-state]')).toBeInTheDocument() + expect(screen.getByLabelText('Some tip text')).toBeInTheDocument() }) it('should not render tooltip trigger when noTooltip is true', () => { - const { container } = render() + render() - // No tooltip trigger icon should be rendered - expect(container.querySelector('[data-state]')).not.toBeInTheDocument() + expect(screen.queryByLabelText('Hidden tip')).not.toBeInTheDocument() }) it('should render a switch when hasSwitch is true', () => { diff --git a/web/app/components/base/param-item/__tests__/score-threshold-item.spec.tsx b/web/app/components/base/param-item/__tests__/score-threshold-item.spec.tsx index f32a707a52..0fbe727f42 100644 --- a/web/app/components/base/param-item/__tests__/score-threshold-item.spec.tsx +++ b/web/app/components/base/param-item/__tests__/score-threshold-item.spec.tsx @@ -24,10 +24,9 @@ describe('ScoreThresholdItem', () => { }) it('should render tooltip trigger', () => { - const { container } = render() + render() - // Tooltip trigger icon should be rendered - expect(container.querySelector('[data-state]')).toBeInTheDocument() + expect(screen.getByLabelText('appDebug.datasetConfig.score_thresholdTip')).toBeInTheDocument() }) it('should render InputNumber and Slider', () => { diff --git a/web/app/components/base/param-item/__tests__/top-k-item.spec.tsx b/web/app/components/base/param-item/__tests__/top-k-item.spec.tsx index c84fd50518..c843883285 100644 --- a/web/app/components/base/param-item/__tests__/top-k-item.spec.tsx +++ b/web/app/components/base/param-item/__tests__/top-k-item.spec.tsx @@ -29,10 +29,9 @@ describe('TopKItem', () => { }) it('should render tooltip trigger', () => { - const { container } = render() + render() - // Tooltip trigger icon should be rendered - expect(container.querySelector('[data-state]')).toBeInTheDocument() + expect(screen.getByLabelText('appDebug.datasetConfig.top_kTip')).toBeInTheDocument() }) it('should render InputNumber and Slider', () => { diff --git a/web/app/components/base/param-item/index.tsx b/web/app/components/base/param-item/index.tsx index 93eb878551..a44176bf18 100644 --- a/web/app/components/base/param-item/index.tsx +++ b/web/app/components/base/param-item/index.tsx @@ -10,7 +10,7 @@ import { } from '@langgenius/dify-ui/number-field' import { Slider } from '@langgenius/dify-ui/slider' import { Switch } from '@langgenius/dify-ui/switch' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' type Props = { className?: string @@ -44,11 +44,10 @@ const ParamItem: FC = ({ className, id, name, noTooltip, tip, step = 0.1, /> )} {name} - {!noTooltip && ( - {tip}
} - /> + {!noTooltip && tip && ( + + {tip} + )} diff --git a/web/app/components/base/theme-selector.tsx b/web/app/components/base/theme-selector.tsx index 1f173aa885..1676dfff72 100644 --- a/web/app/components/base/theme-selector.tsx +++ b/web/app/components/base/theme-selector.tsx @@ -1,25 +1,25 @@ 'use client' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuRadioItemIndicator, + DropdownMenuTrigger, +} from '@langgenius/dify-ui/dropdown-menu' import { useTheme } from 'next-themes' -import { useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' export type Theme = 'light' | 'dark' | 'system' export default function ThemeSelector() { const { t } = useTranslation() const { theme, setTheme } = useTheme() - const [open, setOpen] = useState(false) const handleThemeChange = (newTheme: Theme) => { setTheme(newTheme) - setOpen(false) } const getCurrentIcon = () => { @@ -31,70 +31,36 @@ export default function ThemeSelector() { } return ( - - setOpen(!open)} + + + )} > - - {getCurrentIcon()} - - - -
- - - -
-
-
+ {t('theme.auto', { ns: 'common' })} + + + + + ) } diff --git a/web/app/components/billing/priority-label/__tests__/index.spec.tsx b/web/app/components/billing/priority-label/__tests__/index.spec.tsx index ef613d76b8..2b6201fdff 100644 --- a/web/app/components/billing/priority-label/__tests__/index.spec.tsx +++ b/web/app/components/billing/priority-label/__tests__/index.spec.tsx @@ -1,5 +1,6 @@ import type { Mock } from 'vitest' -import { fireEvent, render, screen } from '@testing-library/react' +import { TooltipProvider } from '@langgenius/dify-ui/tooltip' +import { render, screen } from '@testing-library/react' import { createMockPlan } from '@/__mocks__/provider-context' import { useProviderContext } from '@/context/provider-context' import { Plan } from '../../type' @@ -15,6 +16,14 @@ const setupPlan = (planType: Plan) => { useProviderContextMock.mockReturnValue(createMockPlan(planType)) } +const renderPriorityLabel = (className?: string) => { + return render( + + + , + ) +} + describe('PriorityLabel', () => { beforeEach(() => { vi.clearAllMocks() @@ -24,7 +33,7 @@ describe('PriorityLabel', () => { it('should render the standard priority label when plan is sandbox', () => { setupPlan(Plan.sandbox) - render() + renderPriorityLabel() expect(screen.getByText('billing.plansCommon.priority.standard')).toBeInTheDocument() }) @@ -35,7 +44,7 @@ describe('PriorityLabel', () => { it('should apply custom className to the label container', () => { setupPlan(Plan.sandbox) - render() + renderPriorityLabel('custom-class') const label = screen.getByText('billing.plansCommon.priority.standard').closest('div') expect(label).toHaveClass('custom-class') @@ -47,7 +56,7 @@ describe('PriorityLabel', () => { it('should render priority label and icon when plan is professional', () => { setupPlan(Plan.professional) - const { container } = render() + const { container } = renderPriorityLabel() expect(screen.getByText('billing.plansCommon.priority.priority')).toBeInTheDocument() expect(container.querySelector('svg')).toBeInTheDocument() @@ -56,7 +65,7 @@ describe('PriorityLabel', () => { it('should render top priority label and icon when plan is team', () => { setupPlan(Plan.team) - const { container } = render() + const { container } = renderPriorityLabel() expect(screen.getByText('billing.plansCommon.priority.top-priority')).toBeInTheDocument() expect(container.querySelector('svg')).toBeInTheDocument() @@ -65,7 +74,7 @@ describe('PriorityLabel', () => { it('should render standard label without icon when plan is sandbox', () => { setupPlan(Plan.sandbox) - const { container } = render() + const { container } = renderPriorityLabel() expect(screen.getByText('billing.plansCommon.priority.standard')).toBeInTheDocument() expect(container.querySelector('svg')).not.toBeInTheDocument() @@ -77,7 +86,7 @@ describe('PriorityLabel', () => { it('should render top-priority label with icon for enterprise plan', () => { setupPlan(Plan.enterprise) - const { container } = render() + const { container } = renderPriorityLabel() expect(screen.getByText('billing.plansCommon.priority.top-priority')).toBeInTheDocument() expect(container.querySelector('svg')).toBeInTheDocument() @@ -85,29 +94,21 @@ describe('PriorityLabel', () => { }) describe('Edge Cases', () => { - it('should show the tip text when priority is not top priority', async () => { + it('should render a non-top priority trigger without mounting tooltip content by default', () => { setupPlan(Plan.sandbox) - render() - const label = screen.getByText('billing.plansCommon.priority.standard').closest('div') - fireEvent.mouseEnter(label as HTMLElement) + renderPriorityLabel() - expect(await screen.findByText( - 'billing.plansCommon.documentProcessingPriority: billing.plansCommon.priority.standard', - )).toBeInTheDocument() - expect(screen.getByText('billing.plansCommon.documentProcessingPriorityTip')).toBeInTheDocument() + expect(screen.getByText('billing.plansCommon.priority.standard')).toBeInTheDocument() + expect(screen.queryByText('billing.plansCommon.documentProcessingPriority')).not.toBeInTheDocument() }) - it('should hide the tip text when priority is top priority', async () => { + it('should render a top priority trigger without mounting upgrade tip by default', () => { setupPlan(Plan.enterprise) - render() - const label = screen.getByText('billing.plansCommon.priority.top-priority').closest('div') - fireEvent.mouseEnter(label as HTMLElement) + renderPriorityLabel() - expect(await screen.findByText( - 'billing.plansCommon.documentProcessingPriority: billing.plansCommon.priority.top-priority', - )).toBeInTheDocument() + expect(screen.getByText('billing.plansCommon.priority.top-priority')).toBeInTheDocument() expect(screen.queryByText('billing.plansCommon.documentProcessingPriorityTip')).not.toBeInTheDocument() }) }) diff --git a/web/app/components/billing/priority-label/index.tsx b/web/app/components/billing/priority-label/index.tsx index 8940b38382..c7b7d17e87 100644 --- a/web/app/components/billing/priority-label/index.tsx +++ b/web/app/components/billing/priority-label/index.tsx @@ -1,8 +1,8 @@ import { cn } from '@langgenius/dify-ui/cn' +import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { RiAedFill } from '@remixicon/react' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' import { useProviderContext } from '@/context/provider-context' import { DocumentProcessingPriority, @@ -31,8 +31,25 @@ const PriorityLabel = ({ className }: PriorityLabelProps) => { }, [plan]) return ( - + + + )} + > + { + (plan.type === Plan.professional || plan.type === Plan.team || plan.type === Plan.enterprise) && ( + + ) + } + {t(`plansCommon.priority.${priority}`, { ns: 'billing' })} + +
{t('plansCommon.documentProcessingPriority', { ns: 'billing' })} : @@ -44,22 +61,7 @@ const PriorityLabel = ({ className }: PriorityLabelProps) => {
{t('plansCommon.documentProcessingPriorityTip', { ns: 'billing' })}
) } -
- )} - > -
- { - (plan.type === Plan.professional || plan.type === Plan.team || plan.type === Plan.enterprise) && ( - - ) - } - {t(`plansCommon.priority.${priority}`, { ns: 'billing' })} -
+
) } diff --git a/web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-input.tsx b/web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-input.tsx index e5b7e79d55..5d43f0b687 100644 --- a/web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-input.tsx +++ b/web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-input.tsx @@ -1,7 +1,7 @@ +import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { RiImageAddLine } from '@remixicon/react' import * as React from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' import { ACCEPT_TYPES } from '../constants' import { useUpload } from '../hooks/use-upload' import { useFileStoreWithSelector } from '../store' @@ -29,20 +29,14 @@ const ImageUploader = () => { onChange={fileChangeHandle} />
- -
+ + )} >
@@ -56,7 +50,14 @@ const ImageUploader = () => { })} )} -
+
+ + {t('imageUploader.tooltip', { + ns: 'datasetHitTesting', + size: fileUploadConfig.imageFileSizeLimit, + batchCount: fileUploadConfig.imageFileBatchLimit, + })} +
diff --git a/web/app/components/datasets/common/retrieval-param-config/__tests__/index.spec.tsx b/web/app/components/datasets/common/retrieval-param-config/__tests__/index.spec.tsx index 1b208c549d..ac5112ad22 100644 --- a/web/app/components/datasets/common/retrieval-param-config/__tests__/index.spec.tsx +++ b/web/app/components/datasets/common/retrieval-param-config/__tests__/index.spec.tsx @@ -134,12 +134,6 @@ vi.mock('@langgenius/dify-ui/switch', () => ({ ), })) -vi.mock('@/app/components/base/tooltip', () => ({ - default: ({ popupContent }: { popupContent: React.ReactNode }) => ( -
{popupContent}
- ), -})) - describe('RetrievalParamConfig', () => { const createDefaultConfig = (overrides?: Partial): RetrievalConfig => ({ search_method: RETRIEVE_METHOD.semantic, @@ -799,7 +793,7 @@ describe('RetrievalParamConfig', () => { />, ) - expect(screen.getByTestId('tooltip'))!.toBeInTheDocument() + expect(screen.getByLabelText('common.modelProvider.rerankModel.tip'))!.toBeInTheDocument() }) }) diff --git a/web/app/components/datasets/common/retrieval-param-config/index.tsx b/web/app/components/datasets/common/retrieval-param-config/index.tsx index 8514e1fae2..93392f2821 100644 --- a/web/app/components/datasets/common/retrieval-param-config/index.tsx +++ b/web/app/components/datasets/common/retrieval-param-config/index.tsx @@ -10,10 +10,10 @@ import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import WeightedScore from '@/app/components/app/configuration/dataset-config/params-config/weighted-score' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' +import { Infotip } from '@/app/components/base/infotip' import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item' import TopKItem from '@/app/components/base/param-item/top-k-item' import RadioCard from '@/app/components/base/radio-card' -import Tooltip from '@/app/components/base/tooltip' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useCurrentProviderAndModel, useModelListAndDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks' import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' @@ -127,11 +127,12 @@ const RetrievalParamConfig: FC = ({ )}
{t('modelProvider.rerankModel.key', { ns: 'common' })} - {t('modelProvider.rerankModel.tip', { ns: 'common' })}
- } - /> + + {t('modelProvider.rerankModel.tip', { ns: 'common' })} + { diff --git a/web/app/components/datasets/create-from-pipeline/list/template-card/details/index.tsx b/web/app/components/datasets/create-from-pipeline/list/template-card/details/index.tsx index 22866b0b9e..d3538d3349 100644 --- a/web/app/components/datasets/create-from-pipeline/list/template-card/details/index.tsx +++ b/web/app/components/datasets/create-from-pipeline/list/template-card/details/index.tsx @@ -5,8 +5,8 @@ import * as React from 'react' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' +import { Infotip } from '@/app/components/base/infotip' import Loading from '@/app/components/base/loading' -import Tooltip from '@/app/components/base/tooltip' import WorkflowPreview from '@/app/components/workflow/workflow-preview' import { usePipelineTemplateById } from '@/service/use-pipeline' import ChunkStructureCard from './chunk-structure-card' @@ -111,10 +111,12 @@ const Details = ({ {t('details.structure', { ns: 'datasetPipeline' })} - + > + {t('details.structureTooltip', { ns: 'datasetPipeline' })} + diff --git a/web/app/components/datasets/create/embedding-process/__tests__/indexing-progress-item.spec.tsx b/web/app/components/datasets/create/embedding-process/__tests__/indexing-progress-item.spec.tsx index e0ae25a80f..f71c4a0cc1 100644 --- a/web/app/components/datasets/create/embedding-process/__tests__/indexing-progress-item.spec.tsx +++ b/web/app/components/datasets/create/embedding-process/__tests__/indexing-progress-item.spec.tsx @@ -1,4 +1,3 @@ -import type { ReactNode } from 'react' import type { IndexingStatusResponse } from '@/models/datasets' import { render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -14,12 +13,6 @@ vi.mock('../../../common/document-file-icon', () => ({ vi.mock('@/app/components/base/notion-icon', () => ({ default: ({ src }: { src?: string }) => {src}, })) -vi.mock('@/app/components/base/tooltip', () => ({ - default: ({ children, popupContent }: { children?: ReactNode, popupContent?: ReactNode }) => ( -
{children}
- ), -})) - describe('IndexingProgressItem', () => { beforeEach(() => { vi.clearAllMocks() @@ -100,7 +93,7 @@ describe('IndexingProgressItem', () => { />, ) - expect(screen.getByTestId('tooltip')).toHaveAttribute('data-content', 'Parse failed') + expect(screen.getByLabelText('Parse failed')).toBeInTheDocument() }) it('should show priority label when billing is enabled', () => { diff --git a/web/app/components/datasets/create/embedding-process/indexing-progress-item.tsx b/web/app/components/datasets/create/embedding-process/indexing-progress-item.tsx index ef54bddfc5..b98d9e7e10 100644 --- a/web/app/components/datasets/create/embedding-process/indexing-progress-item.tsx +++ b/web/app/components/datasets/create/embedding-process/indexing-progress-item.tsx @@ -1,12 +1,12 @@ import type { FC } from 'react' import type { IndexingStatusResponse } from '@/models/datasets' import { cn } from '@langgenius/dify-ui/cn' +import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { RiCheckboxCircleFill, RiErrorWarningFill, } from '@remixicon/react' import NotionIcon from '@/app/components/base/notion-icon' -import Tooltip from '@/app/components/base/tooltip' import PriorityLabel from '@/app/components/billing/priority-label' import { DataSourceType } from '@/models/datasets' import DocumentFileIcon from '../../common/document-file-icon' @@ -27,14 +27,16 @@ const StatusIcon: FC<{ status: string, error?: string }> = ({ status, error }) = if (status === 'error') { return ( - - + + }> - + + + {error} + ) } diff --git a/web/app/components/datasets/create/step-two/components/__tests__/indexing-mode-section.spec.tsx b/web/app/components/datasets/create/step-two/components/__tests__/indexing-mode-section.spec.tsx index 64011c1ebe..3931478f27 100644 --- a/web/app/components/datasets/create/step-two/components/__tests__/indexing-mode-section.spec.tsx +++ b/web/app/components/datasets/create/step-two/components/__tests__/indexing-mode-section.spec.tsx @@ -165,6 +165,13 @@ describe('IndexingModeSection', () => { const economicalText = screen.getByText(`${ns}.stepTwo.economical`) const card = economicalText.closest('[class*="rounded-xl"]') expect(card)!.toHaveClass('pointer-events-none') + expect(screen.getByText(`${ns}.stepTwo.notAvailableForQA`))!.toBeInTheDocument() + }) + + it('should show parent-child disabled reason inline on economical option', () => { + render() + + expect(screen.getByText(`${ns}.stepTwo.notAvailableForParentChild`))!.toBeInTheDocument() }) }) diff --git a/web/app/components/datasets/create/step-two/components/__tests__/inputs.spec.tsx b/web/app/components/datasets/create/step-two/components/__tests__/inputs.spec.tsx index f1ab5392ce..0e6cc10076 100644 --- a/web/app/components/datasets/create/step-two/components/__tests__/inputs.spec.tsx +++ b/web/app/components/datasets/create/step-two/components/__tests__/inputs.spec.tsx @@ -30,9 +30,7 @@ describe('DelimiterInput', () => { it('should render tooltip content', () => { render() - // Tooltip triggers render; component mounts without error - // Tooltip triggers render; component mounts without error - expect(screen.getByText(`${ns}.stepTwo.separator`))!.toBeInTheDocument() + expect(screen.getByLabelText(`${ns}.stepTwo.separatorTip`))!.toBeInTheDocument() }) it('should suppress onChange during IME composition', () => { @@ -103,6 +101,7 @@ describe('OverlapInput', () => { it('should render overlap label', () => { render() expect(screen.getAllByText(new RegExp(`${ns}.stepTwo.overlap`)).length).toBeGreaterThan(0) + expect(screen.getByLabelText(`${ns}.stepTwo.overlapTip`))!.toBeInTheDocument() }) it('should render number input', () => { diff --git a/web/app/components/datasets/create/step-two/components/indexing-mode-section.tsx b/web/app/components/datasets/create/step-two/components/indexing-mode-section.tsx index 1639df2320..2ddcc81d5e 100644 --- a/web/app/components/datasets/create/step-two/components/indexing-mode-section.tsx +++ b/web/app/components/datasets/create/step-two/components/indexing-mode-section.tsx @@ -3,14 +3,20 @@ import type { FC } from 'react' import type { DefaultModel, Model } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { RetrievalConfig } from '@/types/app' -import { Button } from '@langgenius/dify-ui/button' +import { + AlertDialog, + AlertDialogActions, + AlertDialogCancelButton, + AlertDialogConfirmButton, + AlertDialogContent, + AlertDialogDescription, + AlertDialogTitle, +} from '@langgenius/dify-ui/alert-dialog' import { cn } from '@langgenius/dify-ui/cn' import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' -import CustomDialog from '@/app/components/base/dialog' import Divider from '@/app/components/base/divider' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' -import Tooltip from '@/app/components/base/tooltip' import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config' import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config' import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' @@ -65,6 +71,13 @@ export const IndexingModeSection: FC = ({ const docLink = useDocLink() const getIndexingTechnique = () => indexType + const economicalDisabledReason = (() => { + if (docForm === ChunkingMode.qa) + return t('stepTwo.notAvailableForQA', { ns: 'datasetCreation' }) + + if (docForm !== ChunkingMode.text) + return t('stepTwo.notAvailableForParentChild', { ns: 'datasetCreation' }) + })() return ( <> @@ -72,7 +85,33 @@ export const IndexingModeSection: FC = ({
{t('stepTwo.indexMode', { ns: 'datasetCreation' })}
-
+ { + if (!open) + onQAConfirmDialogClose() + }} + > + +
+ + {t('stepTwo.qaSwitchHighQualityTipTitle', { ns: 'datasetCreation' })} + + + {t('stepTwo.qaSwitchHighQualityTipContent', { ns: 'datasetCreation' })} + +
+ + + {t('stepTwo.cancel', { ns: 'datasetCreation' })} + + + {t('stepTwo.switch', { ns: 'datasetCreation' })} + + +
+
+
{/* Qualified option */} {(!hasSetIndexType || (hasSetIndexType && indexType === IndexingType.QUALIFIED)) && ( = ({ {/* Economical option */} {(!hasSetIndexType || (hasSetIndexType && indexType === IndexingType.ECONOMICAL)) && ( - <> - -
-

- {t('stepTwo.qaSwitchHighQualityTipTitle', { ns: 'datasetCreation' })} -

-

- {t('stepTwo.qaSwitchHighQualityTipContent', { ns: 'datasetCreation' })} -

-
-
- - -
-
- - {docForm === ChunkingMode.qa - ? t('stepTwo.notAvailableForQA', { ns: 'datasetCreation' }) - : t('stepTwo.notAvailableForParentChild', { ns: 'datasetCreation' })} -
- )} - noDecoration - position="top" - asChild={false} - triggerClassName="flex-1 self-stretch" - > - } - isActive={!hasSetIndexType && indexType === IndexingType.ECONOMICAL} - disabled={hasSetIndexType || docForm !== ChunkingMode.text} - onSwitched={() => onIndexTypeChange(IndexingType.ECONOMICAL)} - /> - - + } + isActive={!hasSetIndexType && indexType === IndexingType.ECONOMICAL} + disabled={hasSetIndexType || !!economicalDisabledReason} + onSwitched={() => onIndexTypeChange(IndexingType.ECONOMICAL)} + /> )}
diff --git a/web/app/components/datasets/create/step-two/components/inputs.tsx b/web/app/components/datasets/create/step-two/components/inputs.tsx index 12af1de1aa..635056f1b2 100644 --- a/web/app/components/datasets/create/step-two/components/inputs.tsx +++ b/web/app/components/datasets/create/step-two/components/inputs.tsx @@ -12,8 +12,8 @@ import { } from '@langgenius/dify-ui/number-field' import { useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { Infotip } from '@/app/components/base/infotip' import Input from '@/app/components/base/input' -import Tooltip from '@/app/components/base/tooltip' import { env } from '@/env' const TextLabel: FC = (props) => { @@ -38,13 +38,9 @@ export const DelimiterInput: FC = ({ tooltip, {t('stepTwo.separator', { ns: 'datasetCreation' })} - - {tooltip || t('stepTwo.separatorTip', { ns: 'datasetCreation' })} - - )} - /> + + {tooltip || t('stepTwo.separatorTip', { ns: 'datasetCreation' })} + )} > @@ -154,13 +150,9 @@ export const OverlapInput: FC = (props) => { {t('stepTwo.overlap', { ns: 'datasetCreation' })} - - {t('stepTwo.overlapTip', { ns: 'datasetCreation' })} - - )} - /> + + {t('stepTwo.overlapTip', { ns: 'datasetCreation' })} + )} > diff --git a/web/app/components/datasets/create/step-two/components/option-card.tsx b/web/app/components/datasets/create/step-two/components/option-card.tsx index 5334feaac8..43f0924d92 100644 --- a/web/app/components/datasets/create/step-two/components/option-card.tsx +++ b/web/app/components/datasets/create/step-two/components/option-card.tsx @@ -20,7 +20,7 @@ type OptionCardHeaderProps = { export const OptionCardHeader: FC = (props) => { const { icon, title, description, isActive, activeClassName, effectImg, disabled } = props return ( -
+
{isActive && effectImg && }
@@ -63,7 +63,7 @@ export const OptionCard: FC = ( const { icon, className, title, description, isActive, children, actions, activeHeaderClassName, style, effectImg, onSwitched, noHighlight, disabled, ...rest } = props return (
({ - default: ({ popupContent }: { popupContent?: React.ReactNode }) =>
{popupContent}
, -})) - describe('CheckboxWithLabel', () => { const onChange = vi.fn() @@ -27,12 +23,12 @@ describe('CheckboxWithLabel', () => { tooltip="Help text" />, ) - expect(screen.getByTestId('tooltip')).toBeInTheDocument() + expect(screen.getByLabelText('Help text')).toBeInTheDocument() }) it('should not render tooltip when not provided', () => { render() - expect(screen.queryByTestId('tooltip')).not.toBeInTheDocument() + expect(screen.queryByLabelText('Help text')).not.toBeInTheDocument() }) it('should toggle checked state on checkbox click', () => { diff --git a/web/app/components/datasets/create/website/base/__tests__/field.spec.tsx b/web/app/components/datasets/create/website/base/__tests__/field.spec.tsx index 8a2e147d60..a362562437 100644 --- a/web/app/components/datasets/create/website/base/__tests__/field.spec.tsx +++ b/web/app/components/datasets/create/website/base/__tests__/field.spec.tsx @@ -2,10 +2,6 @@ import { fireEvent, render, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import Field from '../field' -vi.mock('@/app/components/base/tooltip', () => ({ - default: ({ popupContent }: { popupContent?: React.ReactNode }) =>
{popupContent}
, -})) - describe('WebsiteField', () => { const onChange = vi.fn() @@ -30,7 +26,7 @@ describe('WebsiteField', () => { it('should render tooltip when provided', () => { render() - expect(screen.getByTestId('tooltip')).toBeInTheDocument() + expect(screen.getByLabelText('Enter full URL')).toBeInTheDocument() }) it('should pass value and onChange to Input', () => { diff --git a/web/app/components/datasets/create/website/base/checkbox-with-label.tsx b/web/app/components/datasets/create/website/base/checkbox-with-label.tsx index 686976826d..117a16f401 100644 --- a/web/app/components/datasets/create/website/base/checkbox-with-label.tsx +++ b/web/app/components/datasets/create/website/base/checkbox-with-label.tsx @@ -2,8 +2,9 @@ import type { FC } from 'react' import { cn } from '@langgenius/dify-ui/cn' import * as React from 'react' +import { useId } from 'react' import Checkbox from '@/app/components/base/checkbox' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' type Props = { className?: string @@ -24,19 +25,28 @@ const CheckboxWithLabel: FC = ({ tooltip, testId, }) => { + const labelId = useId() + const handleToggle = () => onChange(!isChecked) + return ( -
- } - triggerClassName="ml-0.5 w-4 h-4" - /> - )} - +
+ +
+ + {tooltip && ( + + {tooltip} + + )} +
+
) } export default React.memo(CheckboxWithLabel) diff --git a/web/app/components/datasets/create/website/base/field.tsx b/web/app/components/datasets/create/website/base/field.tsx index 860f934da2..423fa54823 100644 --- a/web/app/components/datasets/create/website/base/field.tsx +++ b/web/app/components/datasets/create/website/base/field.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import { cn } from '@langgenius/dify-ui/cn' import * as React from 'react' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' import Input from './input' type Props = { @@ -37,12 +37,9 @@ const Field: FC = ({
{isRequired && *} {tooltip && ( - {tooltip}
- } - triggerClassName="ml-0.5 w-4 h-4" - /> + + {tooltip} + )}
({ ), })) -vi.mock('@/app/components/base/tooltip', () => ({ - default: ({ popupContent }: { popupContent: string }) =>
{popupContent}
, -})) - describe('CheckboxWithLabel', () => { const defaultProps = { isChecked: false, @@ -35,12 +31,12 @@ describe('CheckboxWithLabel', () => { it('should render tooltip when provided', () => { render() - expect(screen.getByTestId('tooltip')).toBeInTheDocument() + expect(screen.getByLabelText('Help text')).toBeInTheDocument() }) it('should not render tooltip when not provided', () => { render() - expect(screen.queryByTestId('tooltip')).not.toBeInTheDocument() + expect(screen.queryByLabelText('Help text')).not.toBeInTheDocument() }) it('should apply custom className', () => { diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/__tests__/index.spec.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/__tests__/index.spec.tsx index fa5633e2df..db3e369722 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/__tests__/index.spec.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/__tests__/index.spec.tsx @@ -62,16 +62,13 @@ describe('CheckboxWithLabel', () => { it('should render tooltip when provided', () => { render() - // Assert - Tooltip trigger should be present - const tooltipTrigger = document.querySelector('[class*="ml-0.5"]') - expect(tooltipTrigger)!.toBeInTheDocument() + expect(screen.getByLabelText('Helpful tooltip text'))!.toBeInTheDocument() }) it('should not render tooltip when not provided', () => { render() - const tooltipTrigger = document.querySelector('[class*="ml-0.5"]') - expect(tooltipTrigger).not.toBeInTheDocument() + expect(screen.queryByLabelText('Helpful tooltip text')).not.toBeInTheDocument() }) }) @@ -81,8 +78,7 @@ describe('CheckboxWithLabel', () => { , ) - const label = container.querySelector('label') - expect(label)!.toHaveClass('custom-class') + expect(container.firstChild)!.toHaveClass('custom-class') }) it('should apply custom labelClassName', () => { @@ -114,16 +110,14 @@ describe('CheckboxWithLabel', () => { expect(mockOnChange).toHaveBeenCalledWith(false) }) - it('should not trigger onChange when clicking label text due to custom checkbox', () => { + it('should trigger onChange when clicking label text', () => { const mockOnChange = vi.fn() render() - // Act - Click on the label text element const labelText = screen.getByText('Test Label') fireEvent.click(labelText) - // Assert - Custom checkbox does not support native label-input click forwarding - expect(mockOnChange).not.toHaveBeenCalled() + expect(mockOnChange).toHaveBeenCalledWith(true) }) }) }) @@ -386,15 +380,14 @@ describe('CrawledResult', () => { it('should pass showPreview to items', () => { render() - // Assert - Preview buttons should be visible - const buttons = screen.getAllByRole('button') + const buttons = screen.getAllByRole('button', { name: 'datasetCreation.stepOne.website.preview' }) expect(buttons.length).toBe(3) }) it('should not show preview buttons when showPreview is false', () => { render() - expect(screen.queryByRole('button')).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: 'datasetCreation.stepOne.website.preview' })).not.toBeInTheDocument() }) }) @@ -507,7 +500,7 @@ describe('CrawledResult', () => { />, ) - const buttons = screen.getAllByRole('button') + const buttons = screen.getAllByRole('button', { name: 'datasetCreation.stepOne.website.preview' }) fireEvent.click(buttons[1]!) // Second item's preview button expect(mockOnPreview).toHaveBeenCalledWith(list[1], 1) @@ -796,7 +789,7 @@ describe('Base Components Integration', () => { expect(mockOnSelectedChange).toHaveBeenCalledWith([list[0]]) // Act - Preview second item - const previewButtons = screen.getAllByRole('button') + const previewButtons = screen.getAllByRole('button', { name: 'datasetCreation.stepOne.website.preview' }) fireEvent.click(previewButtons[1]!) expect(mockOnPreview).toHaveBeenCalledWith(list[1], 1) diff --git a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/checkbox-with-label.tsx b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/checkbox-with-label.tsx index 23dbea51a9..99c6625793 100644 --- a/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/checkbox-with-label.tsx +++ b/web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/checkbox-with-label.tsx @@ -1,8 +1,9 @@ 'use client' import { cn } from '@langgenius/dify-ui/cn' import * as React from 'react' +import { useId } from 'react' import Checkbox from '@/app/components/base/checkbox' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' type CheckboxWithLabelProps = { className?: string @@ -21,19 +22,28 @@ const CheckboxWithLabel = ({ labelClassName, tooltip, }: CheckboxWithLabelProps) => { + const labelId = useId() + const handleToggle = () => onChange(!isChecked) + return ( -
- } - triggerClassName="ml-0.5 w-4 h-4" - /> - )} - +
+ +
+ + {tooltip && ( + + {tooltip} + + )} +
+
) } export default React.memo(CheckboxWithLabel) diff --git a/web/app/components/datasets/documents/status-item/__tests__/index.spec.tsx b/web/app/components/datasets/documents/status-item/__tests__/index.spec.tsx index b69835d7f2..1e651929fe 100644 --- a/web/app/components/datasets/documents/status-item/__tests__/index.spec.tsx +++ b/web/app/components/datasets/documents/status-item/__tests__/index.spec.tsx @@ -140,12 +140,12 @@ describe('StatusItem', () => { describe('error message tooltip', () => { it('should show tooltip trigger when error message is provided', () => { render() - expect(screen.getByTestId('error-tooltip-trigger')).toBeInTheDocument() + expect(screen.getByLabelText('Test error message')).toBeInTheDocument() }) it('should not show tooltip trigger when no error message', () => { render() - expect(screen.queryByTestId('error-tooltip-trigger')).not.toBeInTheDocument() + expect(screen.queryByLabelText('Test error message')).not.toBeInTheDocument() }) }) diff --git a/web/app/components/datasets/documents/status-item/index.tsx b/web/app/components/datasets/documents/status-item/index.tsx index 9970c6bca8..911b1df55e 100644 --- a/web/app/components/datasets/documents/status-item/index.tsx +++ b/web/app/components/datasets/documents/status-item/index.tsx @@ -5,11 +5,12 @@ import type { DocumentDisplayStatus } from '@/models/datasets' import { cn } from '@langgenius/dify-ui/cn' import { Switch } from '@langgenius/dify-ui/switch' import { toast } from '@langgenius/dify-ui/toast' +import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { useDebounceFn } from 'ahooks' import * as React from 'react' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' import Indicator from '@/app/components/header/indicator' import { useDocumentDelete, useDocumentDisable, useDocumentEnable } from '@/service/knowledge/use-document' import { asyncRunSafe } from '@/utils' @@ -81,11 +82,33 @@ const StatusItem = ({ status, reverse = false, scene = 'list', textCls = '', err {DOC_INDEX_STATUS_MAP[localStatus]?.text} - {errorMessage && ({errorMessage}} triggerClassName="ml-1 w-4 h-4" triggerTestId="error-tooltip-trigger" />)} + {errorMessage && ( + + {errorMessage} + + )} {scene === 'detail' && (
- - !archived && handleSwitch(v ? 'enable' : 'disable')} disabled={embedding || archived} size="md" /> + + + !archived && handleSwitch(v ? 'enable' : 'disable')} + disabled={embedding || archived} + size="md" + /> + + )} + /> + + {t('list.action.enableWarning', { ns: 'datasetDocuments' })} +
)} diff --git a/web/app/components/datasets/metadata/metadata-document/__tests__/info-group.spec.tsx b/web/app/components/datasets/metadata/metadata-document/__tests__/info-group.spec.tsx index c176979a5a..c4dc89cafc 100644 --- a/web/app/components/datasets/metadata/metadata-document/__tests__/info-group.spec.tsx +++ b/web/app/components/datasets/metadata/metadata-document/__tests__/info-group.spec.tsx @@ -124,9 +124,7 @@ describe('InfoGroup', () => { titleTooltip="This is a tooltip" />, ) - // Tooltip icon should be present - const tooltipIcon = screen.getByText('Test').closest('.flex')?.querySelector('svg') - expect(tooltipIcon)!.toBeInTheDocument() + expect(screen.getByLabelText('This is a tooltip'))!.toBeInTheDocument() }) it('should render headerRight content', () => { diff --git a/web/app/components/datasets/metadata/metadata-document/info-group.tsx b/web/app/components/datasets/metadata/metadata-document/info-group.tsx index 6447185758..16f5e573fb 100644 --- a/web/app/components/datasets/metadata/metadata-document/info-group.tsx +++ b/web/app/components/datasets/metadata/metadata-document/info-group.tsx @@ -2,11 +2,11 @@ import type { FC } from 'react' import type { MetadataItemWithValue } from '../types' import { cn } from '@langgenius/dify-ui/cn' -import { RiDeleteBinLine, RiQuestionLine } from '@remixicon/react' +import { RiDeleteBinLine } from '@remixicon/react' import * as React from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' import useTimestamp from '@/hooks/use-timestamp' import { useRouter } from '@/next/navigation' import AddMetadataButton from '../add-metadata-button' @@ -64,9 +64,9 @@ const InfoGroup: FC = ({
{title}
{titleTooltip && ( - {titleTooltip}
}> -
-
+ + {titleTooltip} + )} {headerRight} diff --git a/web/app/components/datasets/settings/summary-index-setting.tsx b/web/app/components/datasets/settings/summary-index-setting.tsx index 3bd47bf4b9..0944afc2e1 100644 --- a/web/app/components/datasets/settings/summary-index-setting.tsx +++ b/web/app/components/datasets/settings/summary-index-setting.tsx @@ -8,8 +8,8 @@ import { useMemo, } from 'react' import { useTranslation } from 'react-i18next' +import { Infotip } from '@/app/components/base/infotip' import Textarea from '@/app/components/base/textarea' -import Tooltip from '@/app/components/base/tooltip' import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' @@ -65,11 +65,12 @@ const SummaryIndexSetting = ({
{t('form.summaryAutoGen', { ns: 'datasetSettings' })} - - + {t('form.summaryAutoGenTip', { ns: 'datasetSettings' })} +
({ - default: ({ - popupContent, - }: { - popupContent: React.ReactNode - }) =>
{popupContent}
, -})) - describe('GlobalInputs', () => { beforeEach(() => { vi.clearAllMocks() @@ -18,6 +10,6 @@ describe('GlobalInputs', () => { render() expect(screen.getByText('datasetPipeline.inputFieldPanel.globalInputs.title')).toBeInTheDocument() - expect(screen.getByTestId('tooltip')).toHaveTextContent('datasetPipeline.inputFieldPanel.globalInputs.tooltip') + expect(screen.getByLabelText('datasetPipeline.inputFieldPanel.globalInputs.tooltip')).toBeInTheDocument() }) }) diff --git a/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/__tests__/index.spec.tsx b/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/__tests__/index.spec.tsx index ba9390a028..ab01bdba3b 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/__tests__/index.spec.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/__tests__/index.spec.tsx @@ -20,12 +20,6 @@ vi.mock('@/app/components/workflow/hooks', () => ({ useToolIcon: (nodeData: DataSourceNodeType) => nodeData.provider_name || 'default-icon', })) -vi.mock('@/app/components/base/tooltip', () => ({ - default: ({ popupContent, popupClassName }: { popupContent: string, popupClassName?: string }) => ( -
- ), -})) - afterEach(() => { cleanup() vi.clearAllMocks() @@ -161,21 +155,19 @@ describe('GlobalInputs', () => { it('should render tooltip component', () => { render() - expect(screen.getByTestId('tooltip')).toBeInTheDocument() + expect(screen.getByLabelText('datasetPipeline.inputFieldPanel.globalInputs.tooltip')).toBeInTheDocument() }) it('should pass correct tooltip content', () => { render() - const tooltip = screen.getByTestId('tooltip') - expect(tooltip).toHaveAttribute('data-content', 'datasetPipeline.inputFieldPanel.globalInputs.tooltip') + expect(screen.getByLabelText('datasetPipeline.inputFieldPanel.globalInputs.tooltip')).toBeInTheDocument() }) - it('should have correct tooltip className', () => { + it('should render the tooltip trigger as an icon-sized button', () => { render() - const tooltip = screen.getByTestId('tooltip') - expect(tooltip).toHaveClass('w-[240px]') + expect(screen.getByLabelText('datasetPipeline.inputFieldPanel.globalInputs.tooltip')).toHaveClass('h-4', 'w-4') }) it('should have correct container layout', () => { diff --git a/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/global-inputs.tsx b/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/global-inputs.tsx index 763d1ce631..23d56722a6 100644 --- a/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/global-inputs.tsx +++ b/web/app/components/rag-pipeline/components/panel/input-field/label-right-content/global-inputs.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' const GlobalInputs = () => { const { t } = useTranslation() @@ -10,10 +10,9 @@ const GlobalInputs = () => { {t('inputFieldPanel.globalInputs.title', { ns: 'datasetPipeline' })} - + + {t('inputFieldPanel.globalInputs.tooltip', { ns: 'datasetPipeline' })} +
) } diff --git a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/index.spec.tsx b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/index.spec.tsx index cc2f96aa6e..afd7c04ed1 100644 --- a/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/index.spec.tsx +++ b/web/app/components/rag-pipeline/components/rag-pipeline-header/publisher/__tests__/index.spec.tsx @@ -330,27 +330,6 @@ describe('publisher', () => { }) expect(mockSetShowPricingModal).toHaveBeenCalled() }) - - it('should keep confirm dialog mounted when first publish opens follow-up overlay', async () => { - mockPublishedAt.mockReturnValue(null) - renderWithQueryClient() - - fireEvent.click(screen.getByText('workflow.common.publish')) - - await waitFor(() => { - expect(screen.getByText('workflow.common.publishUpdate')).toBeInTheDocument() - }) - - fireEvent.click(screen.getByRole('button', { name: /workflow.common.publishUpdate/i })) - - await waitFor(() => { - expect(screen.getByText('pipeline.common.confirmPublish')).toBeInTheDocument() - }) - - fireEvent.mouseDown(document.body) - - expect(screen.getByText('pipeline.common.confirmPublish')).toBeInTheDocument() - }) }) }) diff --git a/web/app/components/workflow/nodes/_base/components/__tests__/field.spec.tsx b/web/app/components/workflow/nodes/_base/components/__tests__/field.spec.tsx index e16ed108f7..a40d03603d 100644 --- a/web/app/components/workflow/nodes/_base/components/__tests__/field.spec.tsx +++ b/web/app/components/workflow/nodes/_base/components/__tests__/field.spec.tsx @@ -1,10 +1,6 @@ import { fireEvent, render, screen } from '@testing-library/react' import Field from '../field' -vi.mock('@/app/components/base/tooltip', () => ({ - default: ({ popupContent }: { popupContent: React.ReactNode }) =>
{popupContent}
, -})) - describe('Field', () => { it('should render subtitle styling, tooltip, operations, warning dot and required marker', () => { const { container } = render( @@ -19,7 +15,7 @@ describe('Field', () => { ) expect(screen.getByText('Knowledge')).toBeInTheDocument() - expect(screen.getByText('tooltip text')).toBeInTheDocument() + expect(screen.getByLabelText('tooltip text')).toBeInTheDocument() expect(screen.getByRole('button', { name: 'operation' })).toBeInTheDocument() expect(screen.getByText('*')).toBeInTheDocument() expect(container.querySelector('.system-xs-medium-uppercase')).not.toBeNull() diff --git a/web/app/components/workflow/nodes/_base/components/field.tsx b/web/app/components/workflow/nodes/_base/components/field.tsx index 6c8e26758a..11bb778ce5 100644 --- a/web/app/components/workflow/nodes/_base/components/field.tsx +++ b/web/app/components/workflow/nodes/_base/components/field.tsx @@ -6,7 +6,7 @@ import { } from '@remixicon/react' import { useBoolean } from 'ahooks' import * as React from 'react' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' type Props = { className?: string @@ -21,6 +21,17 @@ type Props = { warningDot?: boolean } +const getTextFromNode = (node: ReactNode): string | undefined => { + if (typeof node === 'string' || typeof node === 'number') + return `${node}` + + if (Array.isArray(node)) + return node.map(getTextFromNode).filter(Boolean).join(' ') + + if (React.isValidElement<{ children?: ReactNode }>(node)) + return getTextFromNode(node.props.children) +} + const Field: FC = ({ className, title, @@ -36,6 +47,8 @@ const Field: FC = ({ const [fold, { toggle: toggleFold, }] = useBoolean(true) + const tooltipLabel = tooltip ? getTextFromNode(tooltip) || getTextFromNode(title) || 'Help' : undefined + return (
= ({ {' '} {required && *}
- {!!tooltip && ( - + {!!tooltip && !!tooltipLabel && ( + + {tooltip} + )}
diff --git a/web/app/components/workflow/nodes/_base/components/option-card.tsx b/web/app/components/workflow/nodes/_base/components/option-card.tsx index dd8ad51437..e23ae22204 100644 --- a/web/app/components/workflow/nodes/_base/components/option-card.tsx +++ b/web/app/components/workflow/nodes/_base/components/option-card.tsx @@ -5,7 +5,7 @@ import { cn } from '@langgenius/dify-ui/cn' import { cva } from 'class-variance-authority' import * as React from 'react' import { useCallback } from 'react' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' const variants = cva([], { variants: { @@ -60,13 +60,9 @@ const OptionCard: FC = ({ {title} {tooltip && ( - - {tooltip} -
- )} - /> + + {tooltip} + )}
) diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/__tests__/index.spec.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/__tests__/index.spec.tsx index 17485c4344..ac920e4862 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/__tests__/index.spec.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/__tests__/index.spec.tsx @@ -349,8 +349,9 @@ describe('workflow-panel index', () => { expect(mockSaveStateToHistory).toHaveBeenCalled() fireEvent.click(screen.getByText('authorized-in-node')) + fireEvent.click(screen.getByRole('button', { name: 'workflow.panel.runThisStep' })) + const clickableItems = container.querySelectorAll('.cursor-pointer') - fireEvent.click(clickableItems[0] as HTMLElement) fireEvent.click(clickableItems[clickableItems.length - 1] as HTMLElement) expect(mockHandleSingleRun).toHaveBeenCalledTimes(1) @@ -587,8 +588,7 @@ describe('workflow-panel index', () => { expect(root.style.right).toBe('240px') expect(root.className).toContain('absolute') - const clickableItems = container.querySelectorAll('.cursor-pointer') - fireEvent.click(clickableItems[0] as HTMLElement) + fireEvent.click(screen.getByRole('button', { name: 'workflow.debug.variableInspect.trigger.stop' })) expect(mockHandleStop).toHaveBeenCalledTimes(1) }) diff --git a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx index 5ebf5ae09b..ee96563761 100644 --- a/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx @@ -2,6 +2,11 @@ import type { FC, ReactNode } from 'react' import type { SimpleSubscription } from '@/app/components/plugins/plugin-detail-panel/subscription-list' import type { Node } from '@/app/components/workflow/types' import { cn } from '@langgenius/dify-ui/cn' +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@langgenius/dify-ui/tooltip' import { RiCloseLine, RiPlayLargeLine, @@ -21,7 +26,6 @@ import { useTranslation } from 'react-i18next' import { useShallow } from 'zustand/react/shallow' import { useStore as useAppStore } from '@/app/components/app/store' import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevices' -import Tooltip from '@/app/components/base/tooltip' import { UserAvatarList } from '@/app/components/base/user-avatar-list' import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' @@ -469,6 +473,11 @@ const BasePanel: FC = ({ ) } + const runThisStepLabel = t('panel.runThisStep', { ns: 'workflow' }) + const singleRunActionLabel = isSingleRunning + ? t('debug.variableInspect.trigger.stop', { ns: 'workflow' }) + : runThisStepLabel + return (
= ({
{ isSupportSingleRun && !nodesReadOnly && ( - -
{ - if (isSingleRunning) - handleStop() - else - handleSingleRun() - }} - > - { - isSingleRunning - ? - : - } -
+ + { + if (isSingleRunning) + handleStop() + else + handleSingleRun() + }} + > + { + isSingleRunning + ? + : + } + + )} + /> + + {runThisStepLabel} + ) } diff --git a/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx b/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx index ddc15b5fdb..c9a55d42ef 100644 --- a/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx +++ b/web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx @@ -1,10 +1,10 @@ 'use client' import type { FC } from 'react' -import type { ModelConfig, PromptItem, Variable } from '../../../types' +import type { ModelConfig, Node, NodeOutPutVar, PromptItem, Variable } from '../../../types' import * as React from 'react' import { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import Tooltip from '@/app/components/base/tooltip' +import { Infotip } from '@/app/components/base/infotip' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector' import { PromptRole } from '@/models/debug' @@ -35,10 +35,10 @@ type Props = { history: boolean query: boolean } - availableVars: any - availableNodes: any + availableVars: NodeOutPutVar[] + availableNodes: Node[] varList: Variable[] - handleAddVariable: (payload: any) => void + handleAddVariable: (payload: Variable) => void modelConfig?: ModelConfig } @@ -119,12 +119,14 @@ const ConfigPromptItem: FC = ({ /> )} - {!!payload.role && t(`${i18nPrefix}.roleDescription.${payload.role}`, { ns: 'workflow' })}
- } - triggerClassName="w-4 h-4" - /> + {!!payload.role && ( + + {t(`${i18nPrefix}.roleDescription.${payload.role}`, { ns: 'workflow' })} + + )}
)} value={payload.edition_type === EditionType.jinja2 ? (payload.jinja2_text || '') : payload.text} diff --git a/web/app/components/workflow/nodes/llm/components/panel-memory-section.tsx b/web/app/components/workflow/nodes/llm/components/panel-memory-section.tsx index 59b38b41db..d9b7612bff 100644 --- a/web/app/components/workflow/nodes/llm/components/panel-memory-section.tsx +++ b/web/app/components/workflow/nodes/llm/components/panel-memory-section.tsx @@ -1,9 +1,9 @@ import type { FC } from 'react' import type { LLMNodeType } from '../types' import type { Memory, Node, NodeOutPutVar } from '@/app/components/workflow/types' -import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import * as React from 'react' import { useTranslation } from 'react-i18next' +import { Infotip } from '@/app/components/base/infotip' import MemoryConfig from '@/app/components/workflow/nodes/_base/components/memory-config' import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor' @@ -50,18 +50,9 @@ const PanelMemorySection: FC = ({
{t('nodes.common.memories.title', { ns: 'workflow' })}
- - - - - )} - /> - - {t('nodes.common.memories.tip', { ns: 'workflow' })} - - + + {t('nodes.common.memories.tip', { ns: 'workflow' })} +
{t('nodes.common.memories.builtIn', { ns: 'workflow' })} @@ -72,18 +63,12 @@ const PanelMemorySection: FC = ({ title={(
user
- - - - - )} - /> - -
{t('nodes.llm.roleDescription.user', { ns: 'workflow' })}
-
-
+ + {t('nodes.llm.roleDescription.user', { ns: 'workflow' })} +
)} value={inputs.memory.query_prompt_template || '{{#sys.query#}}'} diff --git a/web/app/components/workflow/nodes/llm/components/panel-output-section.tsx b/web/app/components/workflow/nodes/llm/components/panel-output-section.tsx index bd333b6b1f..53f7161fba 100644 --- a/web/app/components/workflow/nodes/llm/components/panel-output-section.tsx +++ b/web/app/components/workflow/nodes/llm/components/panel-output-section.tsx @@ -2,9 +2,9 @@ import type { FC } from 'react' import type { LLMNodeType, StructuredOutput } from '../types' import { Switch } from '@langgenius/dify-ui/switch' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' -import { RiAlertFill, RiQuestionLine } from '@remixicon/react' import * as React from 'react' import { useTranslation } from 'react-i18next' +import { Infotip } from '@/app/components/base/infotip' import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' import Split from '@/app/components/workflow/nodes/_base/components/split' import StructureOutput from './structure-output' @@ -45,31 +45,23 @@ const PanelOutputSection: FC = ({ - +
)} /> - -
-
{t('structOutput.modelNotSupported', { ns: 'app' })}
-
{t('structOutput.modelNotSupportedTip', { ns: 'app' })}
-
+ +
{t('structOutput.modelNotSupported', { ns: 'app' })}
+
{t('structOutput.modelNotSupportedTip', { ns: 'app' })}
)}
{t('structOutput.structured', { ns: 'app' })}
- - - -
- )} - /> - -
{t('structOutput.structuredTip', { ns: 'app' })}
-
-
+ + {t('structOutput.structuredTip', { ns: 'app' })} +