@@ -212,31 +216,16 @@ const ChatInputArea = ({ readonly, botName, customPlaceholder, showFeatureBar, s
{shouldShowFooterNotice && (
-
-
{footerNotice}
+
{footerNotice}
{shouldShowFooterNoticeTooltip && (
-
-
-
-
- )}
- />
-
- {footerNoticeTooltip}
-
-
+
+ {footerNoticeTooltip}
+
)}
diff --git a/web/app/components/base/features/new-feature-panel/conversation-opener/__tests__/modal.spec.tsx b/web/app/components/base/features/new-feature-panel/conversation-opener/__tests__/modal.spec.tsx
index e41079e66a5..e309c253104 100644
--- a/web/app/components/base/features/new-feature-panel/conversation-opener/__tests__/modal.spec.tsx
+++ b/web/app/components/base/features/new-feature-panel/conversation-opener/__tests__/modal.spec.tsx
@@ -373,7 +373,7 @@ describe('OpeningSettingModal', () => {
expect(screen.queryByText(/openingStatement\.openingQuestionDescription/)).not.toBeInTheDocument()
})
- it('should show the opening questions description in a tooltip', async () => {
+ it('should show the opening questions description in an infotip', async () => {
await render(
{
/>,
)
- act(() => {
- fireEvent.mouseEnter(screen.getByRole('button', { name: /openingStatement\.openingQuestionDescription/ }))
- })
+ await userEvent.hover(screen.getByRole('button', { name: /openingStatement\.openingQuestionDescription/ }))
- expect(screen.getByText(/openingStatement\.openingQuestionDescription/)).toBeInTheDocument()
+ expect(await screen.findByText(/openingStatement\.openingQuestionDescription/)).toBeInTheDocument()
})
it('should call onAutoAddPromptVariable when confirm add is clicked', async () => {
diff --git a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx
index 1fcdbd922e6..5fac2ef2b86 100644
--- a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx
+++ b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx
@@ -121,7 +121,6 @@ const OpeningSettingModal = ({
aria-label={t('openingStatement.openingQuestionDescription', { ns: 'appDebug' })}
className="size-3.5"
popupClassName="max-w-[220px] system-sm-regular text-text-secondary"
- delay={0}
>
{t('openingStatement.openingQuestionDescription', { ns: 'appDebug' })}
diff --git a/web/app/components/base/infotip/__tests__/index.spec.tsx b/web/app/components/base/infotip/__tests__/index.spec.tsx
new file mode 100644
index 00000000000..31b035b5692
--- /dev/null
+++ b/web/app/components/base/infotip/__tests__/index.spec.tsx
@@ -0,0 +1,53 @@
+import type { ReactNode } from 'react'
+import { render, screen, waitFor } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { createPortal } from 'react-dom'
+import { Infotip } from '../index'
+
+function ClickBoundary({ children, onClick }: { children: ReactNode, onClick: () => void }) {
+ return (
+
+ )
+}
+
+describe('Infotip', () => {
+ it('should open with keyboard activation', async () => {
+ const user = userEvent.setup()
+ render(Rate limit details)
+
+ screen.getByRole('button', { name: 'Rate limits' }).focus()
+ await user.keyboard('{Enter}')
+
+ expect(await screen.findByRole('dialog')).toHaveTextContent('Rate limit details')
+ })
+
+ it('should close the dialog with Escape', async () => {
+ const user = userEvent.setup()
+ render(Rate limit details)
+
+ await user.click(screen.getByRole('button', { name: 'Rate limits' }))
+ expect(await screen.findByRole('dialog')).toBeInTheDocument()
+
+ await user.keyboard('{Escape}')
+
+ await waitFor(() => {
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
+ })
+ })
+
+ it('should not propagate trigger clicks', async () => {
+ const user = userEvent.setup()
+ const parentClick = vi.fn()
+ render(
+
+ Rate limit details
+ ,
+ )
+
+ await user.click(screen.getByRole('button', { name: 'Rate limits' }))
+
+ expect(parentClick).not.toHaveBeenCalled()
+ })
+})
diff --git a/web/app/components/base/infotip/index.tsx b/web/app/components/base/infotip/index.tsx
index 3a80d1f5c94..3b5927ea83c 100644
--- a/web/app/components/base/infotip/index.tsx
+++ b/web/app/components/base/infotip/index.tsx
@@ -1,12 +1,25 @@
'use client'
-import type { Placement } from '@langgenius/dify-ui/popover'
import type { MouseEvent, ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
+const iconClassNames = {
+ question: 'i-ri-question-line',
+ information: 'i-ri-information-line',
+} as const
+
+const iconSizeClassNames = {
+ small: 'size-3',
+ medium: 'size-3.5',
+ large: 'size-4',
+} as const
+
+type InfotipIconVariant = keyof typeof iconClassNames
+type InfotipIconSize = keyof typeof iconSizeClassNames
+
/**
- * Infotip — a `?` icon that reveals a long-form explanation on hover / focus / tap.
+ * Infotip — an info glyph that reveals a long-form explanation on hover / keyboard activation / tap.
*
* Implements the pattern Base UI calls an "infotip":
* https://base-ui.com/react/components/tooltip#infotips
@@ -32,34 +45,25 @@ import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/pop
type InfotipProps = {
/** Popup content. Rich nodes are allowed. */
'children': ReactNode
- /** Accessible name for the trigger. Required; should match the popup text. */
+ /** Accessible name for the icon-only trigger. */
'aria-label': string
- /** Placement of the popup relative to the trigger. Defaults to `top`. */
- 'placement'?: Placement
- /** Distance between the trigger and popup. Defaults to the popover primitive spacing. */
- 'sideOffset'?: number
- /** Extra classes on the outer trigger wrapper (layout / margin). */
+ /** Extra classes on the trigger for contextual layout and color. */
'className'?: string
- /** Extra classes on the `?` icon itself (size / color overrides). */
- 'iconClassName'?: string
+ /** Icon glyph. Defaults to `question`. */
+ 'iconVariant'?: InfotipIconVariant
+ /** Icon size. Defaults to `medium` (14px). */
+ 'iconSize'?: InfotipIconSize
/** Extra classes on the popup body (width / padding / whitespace overrides). */
'popupClassName'?: string
- /** Hover open delay in ms. Defaults to 300 to match the app-wide Tooltip delay. */
- 'delay'?: number
- /** Hover close delay in ms. Defaults to 200 to match the app-wide Tooltip delay. */
- 'closeDelay'?: number
}
export function Infotip({
children,
'aria-label': ariaLabel,
- placement = 'top',
- sideOffset,
className,
- iconClassName,
+ iconVariant = 'question',
+ iconSize = 'medium',
popupClassName,
- delay = 300,
- closeDelay = 200,
}: InfotipProps) {
const handleClick = (event: MouseEvent) => {
event.stopPropagation()
@@ -69,20 +73,19 @@ export function Infotip({
-
+
{children}
diff --git a/web/app/components/datasets/create/step-two/components/general-chunking-options.tsx b/web/app/components/datasets/create/step-two/components/general-chunking-options.tsx
index ad217d5b9be..eacece64773 100644
--- a/web/app/components/datasets/create/step-two/components/general-chunking-options.tsx
+++ b/web/app/components/datasets/create/step-two/components/general-chunking-options.tsx
@@ -196,7 +196,6 @@ export const GeneralChunkingOptions: FC = ({
{t('stepTwo.QATip', { ns: 'datasetCreation' })}
diff --git a/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx b/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx
index b13f4978031..97610096ee6 100644
--- a/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx
+++ b/web/app/components/datasets/metadata/edit-metadata-batch/modal.tsx
@@ -142,8 +142,7 @@ const EditMetadataBatchModal: FC = ({ datasetId, documentNum, list, onSav
{t(`${i18nPrefix}.applyToAllSelectDocumentTip`, { ns: 'dataset' })}
diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx
index c3558ac968b..9b8509d3998 100644
--- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx
+++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx
@@ -156,7 +156,7 @@ const ModelLoadBalancingConfigs = ({
{t('modelProvider.loadBalancingInfo', { ns: 'common' })}
diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx
index cbfc68e4b14..a5db2c37e28 100644
--- a/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx
@@ -128,7 +128,6 @@ export const CreateSubscriptionButton = ({ buttonType = CreateButtonType.FULL_BU
{t('subscription.addType.options.manual.tip', { ns: 'pluginTrigger' })}
diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx
index 2af0bde981f..121964e4cb2 100644
--- a/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx
@@ -34,7 +34,6 @@ export const SubscriptionListView: React.FC = ({
{t('subscription.list.tip', { ns: 'pluginTrigger' })}
diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx
index 1a254e99a0f..8547d9647c4 100644
--- a/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx
@@ -37,7 +37,6 @@ export const SubscriptionSelectorView: React.FC = ({
{t('subscription.list.tip', { ns: 'pluginTrigger' })}
diff --git a/web/app/components/tools/workflow-tool/index.tsx b/web/app/components/tools/workflow-tool/index.tsx
index e51e3fe1655..f8ed8ad5478 100644
--- a/web/app/components/tools/workflow-tool/index.tsx
+++ b/web/app/components/tools/workflow-tool/index.tsx
@@ -75,7 +75,6 @@ const InfoTooltip = ({ children }: { children: string }) => {
{children}
diff --git a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/search-method-option.tsx b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/search-method-option.tsx
index 5303abd38b8..6ab59952704 100644
--- a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/search-method-option.tsx
+++ b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/search-method-option.tsx
@@ -303,7 +303,6 @@ export function SearchMethodOption({
{rerankModelTip}
diff --git a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx
index 8de280eb570..cd009e409ae 100644
--- a/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx
+++ b/web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx
@@ -72,7 +72,6 @@ export function TopKAndScoreThreshold({
{topKTip}
@@ -115,7 +114,6 @@ export function TopKAndScoreThreshold({
{scoreThresholdTip}
diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx
index 651ac8518fa..f4a1870967f 100644
--- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx
+++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx
@@ -83,7 +83,6 @@ const PromptEditor: FC = ({
{t('nodes.llm.jsonSchema.promptTooltip', { ns: 'workflow' })}
diff --git a/web/app/components/workflow/nodes/parameter-extractor/panel.tsx b/web/app/components/workflow/nodes/parameter-extractor/panel.tsx
index 698222af757..e2088051982 100644
--- a/web/app/components/workflow/nodes/parameter-extractor/panel.tsx
+++ b/web/app/components/workflow/nodes/parameter-extractor/panel.tsx
@@ -134,7 +134,6 @@ const Panel: FC> = ({
{t(`${i18nPrefix}.instructionTip`, { ns: 'workflow' })}
diff --git a/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx b/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx
index 752a73270bd..291216bdd58 100644
--- a/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx
+++ b/web/app/components/workflow/nodes/question-classifier/components/advanced-setting.tsx
@@ -51,7 +51,6 @@ const AdvancedSetting: FC = ({
{t(`${i18nPrefix}.instructionTip`, { ns: 'workflow' })}
diff --git a/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx b/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx
index 5c47b9a9e7a..d9a9d7e9d1a 100644
--- a/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx
+++ b/web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx
@@ -60,7 +60,7 @@ const MonthlyDaysSelector = ({ selectedDays, onChange }: MonthlyDaysSelectorProp
{t('nodes.triggerSchedule.lastDayTooltip', { ns: 'workflow' })}
diff --git a/web/app/components/workflow/panel/env-panel/variable-modal.tsx b/web/app/components/workflow/panel/env-panel/variable-modal.tsx
index 14b5d94404d..7f848f981bc 100644
--- a/web/app/components/workflow/panel/env-panel/variable-modal.tsx
+++ b/web/app/components/workflow/panel/env-panel/variable-modal.tsx
@@ -132,7 +132,6 @@ const VariableModal = ({
{t('env.modal.secretTip', { ns: 'workflow' })}
diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx
index 1c13730d78b..9f7e9ee8031 100644
--- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx
+++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/files/index.tsx
@@ -321,8 +321,8 @@ function AgentBuildNoteInfotip() {
return (