Compare commits

...

3 Commits

Author SHA1 Message Date
yyh
3b2f61e8cd test(web): query usage-priority option buttons by accessible name
The popover trigger now also exposes role="button", so indexing into
getAllByRole('button') no longer points at the option buttons. Switch
to getByRole('button', { name }) so the lookups are robust to extra
buttons in the subtree.

Made-with: Cursor
2026-04-19 16:24:03 +08:00
yyh
4237a10f35 fix(web): collapse nested card decoration in modelNotSupported popover
The popup wrapper carried its own panel-bg + padding + shadow on top of
an inner card that already provides border / tooltip-bg / blur / shadow,
producing a "card on card" look. Make the popup wrapper transparent so
only the inner styled card shows.

Made-with: Cursor
2026-04-19 16:22:22 +08:00
yyh
49b9b6baef chore(web): migrate infotip-style tooltips to popover
Switch 13 infotip / static-trigger usages of @langgenius/dify-ui/tooltip
over to @langgenius/dify-ui/popover with openOnHover, so the descriptive
content is reachable on touch and via screen readers. Visual presentation
is preserved by setting popupClassName to match the original tooltip
styling.

Triggers that already own a primary click action (model selector rows,
variable chips, online-user avatars, etc.) are intentionally left on
Tooltip pending a separate selection-with-preview redesign, since
swapping in Popover would have click both fire that action and toggle
the popup.

Made-with: Cursor
2026-04-19 16:07:06 +08:00
15 changed files with 220 additions and 128 deletions

View File

@ -4,10 +4,10 @@ import type { AppPublisherProps } from './index'
import type { PublishWorkflowParams } from '@/types/workflow'
import { Button } from '@langgenius/dify-ui/button'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@langgenius/dify-ui/tooltip'
Popover,
PopoverContent,
PopoverTrigger,
} from '@langgenius/dify-ui/popover'
import { useTranslation } from 'react-i18next'
import Divider from '@/app/components/base/divider'
import { CodeBrowser } from '@/app/components/base/icons/src/vender/line/development'
@ -240,12 +240,19 @@ const ActionTooltip = ({
return <>{children}</>
return (
<Tooltip>
<TooltipTrigger render={<div className="flex">{children}</div>} />
<TooltipContent>
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
render={<div className="flex">{children}</div>}
/>
<PopoverContent
placement="top"
popupClassName="max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg"
>
{tooltip}
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
)
}

View File

@ -4,12 +4,12 @@ import type { ExternalDataTool } from '@/models/common'
import type { PromptRole, PromptVariable } from '@/models/debug'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { toast } from '@langgenius/dify-ui/toast'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@langgenius/dify-ui/tooltip'
Popover,
PopoverContent,
PopoverTrigger,
} from '@langgenius/dify-ui/popover'
import { toast } from '@langgenius/dify-ui/toast'
import {
RiDeleteBinLine,
RiErrorWarningFill,
@ -183,18 +183,24 @@ const AdvancedPromptInput: FC<Props> = ({
<div className="text-sm font-semibold text-indigo-800 uppercase">
{t('pageTitle.line1', { ns: 'appDebug' })}
</div>
<Tooltip>
<TooltipTrigger
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={t('promptTip', { ns: 'appDebug' })}
render={(
<span className="ml-1 i-ri-question-line h-4 w-4 shrink-0 text-text-quaternary" />
)}
/>
<TooltipContent>
<PopoverContent
placement="top"
popupClassName="max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg"
>
<div className="w-[180px]">
{t('promptTip', { ns: 'appDebug' })}
</div>
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
</div>
)}
<div className={cn(s.optionWrap, 'items-center space-x-1')}>

View File

@ -4,12 +4,12 @@ import type { ExternalDataTool } from '@/models/common'
import type { PromptVariable } from '@/models/debug'
import type { GenRes } from '@/service/debug'
import { cn } from '@langgenius/dify-ui/cn'
import { toast } from '@langgenius/dify-ui/toast'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@langgenius/dify-ui/tooltip'
Popover,
PopoverContent,
PopoverTrigger,
} from '@langgenius/dify-ui/popover'
import { toast } from '@langgenius/dify-ui/toast'
import { useBoolean } from 'ahooks'
import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
@ -183,18 +183,24 @@ const Prompt: FC<ISimplePromptInput> = ({
<div className="flex items-center space-x-1">
<div className="system-sm-semibold-uppercase text-text-secondary">{mode !== AppModeEnum.COMPLETION ? t('chatSubTitle', { ns: 'appDebug' }) : t('completionSubTitle', { ns: 'appDebug' })}</div>
{!readonly && (
<Tooltip>
<TooltipTrigger
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={t('promptTip', { ns: 'appDebug' })}
render={(
<span className="ml-1 i-ri-question-line h-4 w-4 shrink-0 text-text-quaternary" />
)}
/>
<TooltipContent>
<PopoverContent
placement="top"
popupClassName="max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg"
>
<div className="w-[180px]">
{t('promptTip', { ns: 'appDebug' })}
</div>
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
)}
</div>
<div className="flex items-center">

View File

@ -16,10 +16,10 @@ import {
} from '@langgenius/dify-ui/alert-dialog'
import { Button } from '@langgenius/dify-ui/button'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@langgenius/dify-ui/tooltip'
Popover,
PopoverContent,
PopoverTrigger,
} from '@langgenius/dify-ui/popover'
import { RiArrowRightSLine, RiBookOpenLine, RiBuildingLine, RiEqualizer2Line, RiExternalLinkLine, RiGlobalLine, RiLockLine, RiPaintBrushLine, RiVerifiedBadgeLine, RiWindowLine } from '@remixicon/react'
import CopyFeedback from '@/app/components/base/copy-feedback'
import Divider from '@/app/components/base/divider'
@ -87,12 +87,19 @@ const MaybeTooltip = ({
return <>{children}</>
return (
<Tooltip>
<TooltipTrigger render={<div>{children}</div>} />
<TooltipContent className={tooltipClassName}>
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
render={<div>{children}</div>}
/>
<PopoverContent
placement="top"
popupClassName={`max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg ${tooltipClassName ?? ''}`}
>
{content}
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
)
}

View File

@ -1,7 +1,7 @@
import type { InvitationResult } from '@/models/common'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { IS_CE_EDITION } from '@/config'
@ -73,8 +73,11 @@ const InvitedModal = ({
{
failedInvitationResults.map(item => (
<div key={item.email} className="flex justify-center rounded-md border border-red-300 bg-orange-50 px-1">
<Tooltip>
<TooltipTrigger
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={item.message}
render={(
<div className="flex items-center justify-center gap-1 text-sm">
{item.email}
@ -82,10 +85,13 @@ const InvitedModal = ({
</div>
)}
/>
<TooltipContent>
<PopoverContent
placement="top"
popupClassName="max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg"
>
{item.message}
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
</div>
),
)

View File

@ -4,10 +4,10 @@ import type {
NodeOutPutVar,
} from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger, SelectValue } from '@langgenius/dify-ui/select'
import { Slider } from '@langgenius/dify-ui/slider'
import { Switch } from '@langgenius/dify-ui/switch'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import PromptEditor from '@/app/components/base/prompt-editor'
@ -349,18 +349,24 @@ function ParameterItem({
</div>
{
parameterRule.help && (
<Tooltip>
<TooltipTrigger
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={parameterRule.help[language] || parameterRule.help.en_US}
render={(
<span className="mr-1 flex h-4 w-4 shrink-0 items-center justify-center">
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary" />
</span>
)}
/>
<TooltipContent className="mr-1">
<PopoverContent
placement="top"
popupClassName="mr-1 max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg"
>
<div className="w-[150px] whitespace-pre-wrap">{parameterRule.help[language] || parameterRule.help.en_US}</div>
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
)
}
</div>

View File

@ -2,6 +2,9 @@ import { fireEvent, render, screen } from '@testing-library/react'
import { PreferredProviderTypeEnum } from '../../../declarations'
import UsagePrioritySection from '../usage-priority-section'
const AI_CREDITS_BUTTON_NAME = 'common.modelProvider.card.aiCreditsOption'
const API_KEY_BUTTON_NAME = 'common.modelProvider.card.apiKeyOption'
describe('UsagePrioritySection', () => {
const onSelect = vi.fn()
@ -15,7 +18,8 @@ describe('UsagePrioritySection', () => {
render(<UsagePrioritySection value="credits" onSelect={onSelect} />)
expect(screen.getByText(/usagePriority/))!.toBeInTheDocument()
expect(screen.getAllByRole('button')).toHaveLength(2)
expect(screen.getByRole('button', { name: AI_CREDITS_BUTTON_NAME })).toBeInTheDocument()
expect(screen.getByRole('button', { name: API_KEY_BUTTON_NAME })).toBeInTheDocument()
})
})
@ -24,24 +28,26 @@ describe('UsagePrioritySection', () => {
it('should highlight AI credits option when value is credits', () => {
render(<UsagePrioritySection value="credits" onSelect={onSelect} />)
const buttons = screen.getAllByRole('button')
expect(buttons[0]!.className).toContain('border-components-option-card-option-selected-border')
expect(buttons[1]!.className).not.toContain('border-components-option-card-option-selected-border')
const aiCredits = screen.getByRole('button', { name: AI_CREDITS_BUTTON_NAME })
const apiKey = screen.getByRole('button', { name: API_KEY_BUTTON_NAME })
expect(aiCredits.className).toContain('border-components-option-card-option-selected-border')
expect(apiKey.className).not.toContain('border-components-option-card-option-selected-border')
})
it('should highlight API key option when value is apiKey', () => {
render(<UsagePrioritySection value="apiKey" onSelect={onSelect} />)
const buttons = screen.getAllByRole('button')
expect(buttons[0]!.className).not.toContain('border-components-option-card-option-selected-border')
expect(buttons[1]!.className).toContain('border-components-option-card-option-selected-border')
const aiCredits = screen.getByRole('button', { name: AI_CREDITS_BUTTON_NAME })
const apiKey = screen.getByRole('button', { name: API_KEY_BUTTON_NAME })
expect(aiCredits.className).not.toContain('border-components-option-card-option-selected-border')
expect(apiKey.className).toContain('border-components-option-card-option-selected-border')
})
it('should highlight API key option when value is apiKeyOnly', () => {
render(<UsagePrioritySection value="apiKeyOnly" onSelect={onSelect} />)
const buttons = screen.getAllByRole('button')
expect(buttons[1]!.className).toContain('border-components-option-card-option-selected-border')
const apiKey = screen.getByRole('button', { name: API_KEY_BUTTON_NAME })
expect(apiKey.className).toContain('border-components-option-card-option-selected-border')
})
})
@ -50,7 +56,7 @@ describe('UsagePrioritySection', () => {
it('should call onSelect with system when clicking AI credits option', () => {
render(<UsagePrioritySection value="apiKey" onSelect={onSelect} />)
fireEvent.click(screen.getAllByRole('button')[0]!)
fireEvent.click(screen.getByRole('button', { name: AI_CREDITS_BUTTON_NAME }))
expect(onSelect).toHaveBeenCalledWith(PreferredProviderTypeEnum.system)
})
@ -58,7 +64,7 @@ describe('UsagePrioritySection', () => {
it('should call onSelect with custom when clicking API key option', () => {
render(<UsagePrioritySection value="credits" onSelect={onSelect} />)
fireEvent.click(screen.getAllByRole('button')[1]!)
fireEvent.click(screen.getByRole('button', { name: API_KEY_BUTTON_NAME }))
expect(onSelect).toHaveBeenCalledWith(PreferredProviderTypeEnum.custom)
})

View File

@ -1,6 +1,6 @@
import type { UsagePriority } from '../use-credential-panel-state'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { useTranslation } from 'react-i18next'
import { PreferredProviderTypeEnum } from '../../declarations'
@ -31,8 +31,10 @@ export default function UsagePrioritySection({ value, disabled, onSelect }: Usag
<span className="truncate system-sm-medium text-text-secondary">
{t('modelProvider.card.usagePriority', { ns: 'common' })}
</span>
<Tooltip>
<TooltipTrigger
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={t('modelProvider.card.usagePriorityTip', { ns: 'common' })}
render={(
<span className="flex h-4 w-4 shrink-0 items-center justify-center">
@ -40,10 +42,13 @@ export default function UsagePrioritySection({ value, disabled, onSelect }: Usag
</span>
)}
/>
<TooltipContent>
<PopoverContent
placement="top"
popupClassName="max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg"
>
{t('modelProvider.card.usagePriorityTip', { ns: 'common' })}
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
</div>
<div className="flex shrink-0 items-center gap-1">
{options.map(option => (

View File

@ -3,6 +3,7 @@ import type { ModelProvider } from '../declarations'
import type { Plugin } from '@/app/components/plugins/types'
import type { ModelProviderQuotaGetPaid } from '@/types/model-provider'
import { cn } from '@langgenius/dify-ui/cn'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useBoolean } from 'ahooks'
import * as React from 'react'
@ -99,8 +100,10 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
<div className="relative">
<div className="mb-2 flex h-4 items-center system-xs-medium-uppercase text-text-tertiary">
{t('modelProvider.quota', { ns: 'common' })}
<Tooltip>
<TooltipTrigger
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={tipText}
render={(
<span className="ml-0.5 flex h-4 w-4 shrink-0 items-center justify-center">
@ -108,10 +111,13 @@ const QuotaPanel: FC<QuotaPanelProps> = ({
</span>
)}
/>
<TooltipContent>
<PopoverContent
placement="top"
popupClassName="max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg"
>
{tipText}
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-1 text-xs text-text-tertiary">

View File

@ -10,12 +10,12 @@ import {
DialogContent,
DialogTitle,
} from '@langgenius/dify-ui/dialog'
import { toast } from '@langgenius/dify-ui/toast'
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@langgenius/dify-ui/tooltip'
Popover,
PopoverContent,
PopoverTrigger,
} from '@langgenius/dify-ui/popover'
import { toast } from '@langgenius/dify-ui/toast'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useAppContext } from '@/context/app-context'
@ -138,8 +138,10 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
return (
<div className="flex min-h-6 items-center text-[13px] font-medium text-text-secondary">
{t(labelKey, { ns: 'common' })}
<Tooltip>
<TooltipTrigger
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={tipText}
render={(
<span className="ml-0.5 flex h-4 w-4 shrink-0 items-center justify-center">
@ -147,12 +149,15 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
</span>
)}
/>
<TooltipContent>
<PopoverContent
placement="top"
popupClassName="max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg"
>
<div className="w-[261px] text-text-tertiary">
{tipText}
</div>
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
</div>
)
}

View File

@ -1,10 +1,10 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { FieldTitle } from '../field-title'
vi.mock('@langgenius/dify-ui/tooltip', () => ({
Tooltip: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
TooltipTrigger: ({ render }: { render: React.ReactNode }) => <>{render}</>,
TooltipContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
vi.mock('@langgenius/dify-ui/popover', () => ({
Popover: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
PopoverTrigger: ({ render }: { render: React.ReactNode }) => <>{render}</>,
PopoverContent: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}))
describe('FieldTitle', () => {

View File

@ -1,6 +1,6 @@
import type { ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import {
memo,
useState,
@ -62,18 +62,25 @@ export const FieldTitle = memo(({
}
{
tooltip && (
<Tooltip>
<TooltipTrigger
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={tooltip}
onClick={e => e.stopPropagation()}
render={(
<span className="ml-1 flex h-4 w-4 shrink-0 items-center justify-center">
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
<PopoverContent
placement="top"
popupClassName="max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg"
>
{tooltip}
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
)
}
</div>

View File

@ -2,7 +2,7 @@ import type { TFunction } from 'i18next'
import type { ReactElement } from 'react'
import type { IterationNodeType } from '@/app/components/workflow/nodes/iteration/types'
import type { NodeProps } from '@/app/components/workflow/types'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { BlockEnum, NodeRunningStatus } from '@/app/components/workflow/types'
type HeaderMetaProps = {
@ -23,19 +23,27 @@ export const NodeHeaderMeta = ({
return (
<>
{data.type === BlockEnum.Iteration && (data as IterationNodeType).is_parallel && (
<Tooltip>
<TooltipTrigger>
<div className="ml-1 flex items-center justify-center rounded-[5px] border border-text-warning px-[5px] py-[3px] system-2xs-medium-uppercase text-text-warning">
{t('nodes.iteration.parallelModeUpper', { ns: 'workflow' })}
</div>
</TooltipTrigger>
<TooltipContent className="w-[180px]">
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={t('nodes.iteration.parallelModeEnableTitle', { ns: 'workflow' })}
render={(
<div className="ml-1 flex items-center justify-center rounded-[5px] border border-text-warning px-[5px] py-[3px] system-2xs-medium-uppercase text-text-warning">
{t('nodes.iteration.parallelModeUpper', { ns: 'workflow' })}
</div>
)}
/>
<PopoverContent
placement="top"
popupClassName="w-[180px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg"
>
<div className="font-extrabold">
{t('nodes.iteration.parallelModeEnableTitle', { ns: 'workflow' })}
</div>
{t('nodes.iteration.parallelModeEnableDesc', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
)}
{!!(data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running) && (
<div className="mr-1.5 text-xs font-medium text-text-accent">

View File

@ -1,7 +1,7 @@
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 { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import MemoryConfig from '@/app/components/workflow/nodes/_base/components/memory-config'
@ -50,18 +50,24 @@ const PanelMemorySection: FC<Props> = ({
<div className="flex h-8 items-center justify-between rounded-lg bg-components-input-bg-normal pr-2 pl-3">
<div className="flex items-center space-x-1">
<div className="text-xs font-semibold text-text-secondary uppercase">{t('nodes.common.memories.title', { ns: 'workflow' })}</div>
<Tooltip>
<TooltipTrigger
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={t('nodes.common.memories.tip', { ns: 'workflow' })}
render={(
<span className="ml-1 flex h-4 w-4 shrink-0 items-center justify-center">
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
<PopoverContent
placement="top"
popupClassName="max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg"
>
{t('nodes.common.memories.tip', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
</div>
<div className="flex h-[18px] items-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1 text-xs font-semibold text-text-tertiary uppercase">
{t('nodes.common.memories.builtIn', { ns: 'workflow' })}
@ -72,18 +78,24 @@ const PanelMemorySection: FC<Props> = ({
title={(
<div className="flex items-center space-x-1">
<div className="text-xs font-semibold text-text-secondary uppercase">user</div>
<Tooltip>
<TooltipTrigger
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={t('nodes.llm.roleDescription.user', { ns: 'workflow' })}
render={(
<span className="ml-1 flex h-4 w-4 shrink-0 items-center justify-center">
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
<PopoverContent
placement="top"
popupClassName="max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 text-left system-xs-regular wrap-break-word text-text-tertiary shadow-lg"
>
<div className="max-w-[180px]">{t('nodes.llm.roleDescription.user', { ns: 'workflow' })}</div>
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
</div>
)}
value={inputs.memory.query_prompt_template || '{{#sys.query#}}'}

View File

@ -1,8 +1,7 @@
import type { FC } from 'react'
import type { LLMNodeType, StructuredOutput } from '../types'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
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 OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
@ -41,35 +40,41 @@ const PanelOutputSection: FC<Props> = ({
operations={(
<div className="mr-4 flex shrink-0 items-center">
{(!isModelSupportStructuredOutput && !!inputs.structured_output_enabled) && (
<Tooltip>
<TooltipTrigger
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={t('structOutput.modelNotSupported', { ns: 'app' })}
render={(
<div>
<RiAlertFill className="mr-1 size-4 text-text-warning-secondary" />
<div className="mr-1">
<span aria-hidden className="i-ri-alert-fill block size-4 text-text-warning-secondary" />
</div>
)}
/>
<TooltipContent>
<PopoverContent popupClassName="border-0 bg-transparent p-0 shadow-none">
<div className="w-[232px] rounded-xl border-[0.5px] border-components-panel-border bg-components-tooltip-bg px-4 py-3.5 shadow-lg backdrop-blur-[5px]">
<div className="title-xs-semi-bold text-text-primary">{t('structOutput.modelNotSupported', { ns: 'app' })}</div>
<div className="mt-1 body-xs-regular text-text-secondary">{t('structOutput.modelNotSupportedTip', { ns: 'app' })}</div>
</div>
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
)}
<div className="mr-0.5 system-xs-medium-uppercase text-text-tertiary">{t('structOutput.structured', { ns: 'app' })}</div>
<Tooltip>
<TooltipTrigger
<Popover>
<PopoverTrigger
openOnHover
nativeButton={false}
aria-label={t('structOutput.structuredTip', { ns: 'app' })}
render={(
<div>
<RiQuestionLine className="size-3.5 text-text-quaternary" />
<span aria-hidden className="i-ri-question-line block size-3.5 text-text-quaternary" />
</div>
)}
/>
<TooltipContent>
<PopoverContent popupClassName="max-w-[300px] rounded-md border-0 bg-components-panel-bg px-3 py-2 system-xs-regular text-text-tertiary shadow-lg">
<div className="max-w-[150px]">{t('structOutput.structuredTip', { ns: 'app' })}</div>
</TooltipContent>
</Tooltip>
</PopoverContent>
</Popover>
<Switch
className="ml-2"
checked={!!inputs.structured_output_enabled}