refactor(ui): expose compound Infotip primitives and migrate consumers (#42681)

This commit is contained in:
yyh authored and GitHub committed 2026-09-22 03:09:18 +00:00
1 parent 206cf8fb94
commit bfcd0fdb39
94 files changed
+1329 -787

No files matched your search

+1 -1
View File
@@ -44,7 +44,7 @@ Import `styles.css` once from the consumer's root stylesheet or entrypoint.
| Layout | `./scroll-area`, `./separator` |
| Media | `./avatar` |
| Navigation | `./breadcrumb`, `./file-tree`, `./pagination`, `./tabs` |
| Overlay and menu | `./alert-dialog`, `./context-menu`, `./dialog`, `./drawer`, `./dropdown-menu`, `./popover`, `./preview-card`, `./tooltip` |
| Overlay and menu | `./alert-dialog`, `./context-menu`, `./dialog`, `./drawer`, `./dropdown-menu`, `./infotip`, `./popover`, `./preview-card`, `./tooltip` |
| Search and pick | `./autocomplete`, `./combobox`, `./select` |
Utilities:
+4
View File
@@ -172,6 +172,10 @@
"./separator": {
"types": "./src/separator/index.tsx",
"import": "./src/separator/index.tsx"
},
"./infotip": {
"types": "./src/infotip/index.tsx",
"import": "./src/infotip/index.tsx"
}
},
"scripts": {
@@ -0,0 +1,47 @@
import * as React from 'react'
import { createPortal } from 'react-dom'
import { render } from 'vitest-browser-react'
import { Infotip, InfotipContent, InfotipTrigger } from '..'
describe('Infotip composition', () => {
it('preserves caller handlers and prevents an enclosing action from firing', async () => {
const parentClick = vi.fn()
const triggerClick = vi.fn()
const triggerRef = React.createRef<HTMLButtonElement>()
const screen = await render(
<button type="button" onClick={parentClick}>
Parent action
{createPortal(
<Infotip>
<InfotipTrigger aria-label="Details" onClick={triggerClick} ref={triggerRef} />
<InfotipContent aria-label="Details">Explanation</InfotipContent>
</Infotip>,
document.body,
)}
</button>,
)
await screen.getByRole('button', { name: 'Details' }).click()
await expect
.element(screen.getByRole('dialog', { name: 'Details' }))
.toHaveTextContent('Explanation')
expect(triggerRef.current?.tagName).toBe('BUTTON')
expect(triggerClick).toHaveBeenCalledOnce()
expect(parentClick).not.toHaveBeenCalled()
})
it('lets a caller cancel opening without losing trigger event composition', async () => {
const onOpenChange = vi.fn()
const screen = await render(
<Infotip onOpenChange={onOpenChange}>
<InfotipTrigger
aria-label="Unavailable details"
openOnHover={false}
onClick={(event) => event.preventBaseUIHandler()}
/>
<InfotipContent aria-label="Details">Explanation</InfotipContent>
</Infotip>,
)
await screen.getByRole('button', { name: 'Unavailable details' }).click()
expect(onOpenChange).not.toHaveBeenCalled()
})
})
@@ -0,0 +1,47 @@
import type * as React from 'react'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
export function infotipTypeContracts(
triggerRef: React.Ref<HTMLButtonElement>,
popupRef: React.Ref<HTMLDivElement>,
) {
const composition = (
<Infotip<{ title: string }>>
{({ payload }) => (
<>
<InfotipTrigger
aria-label="Details"
ref={triggerRef}
payload={{ title: 'Details' }}
iconVariant="information"
iconSize="small"
className={(state) => (state.open ? 'text-text-accent' : undefined)}
style={(state) => ({ opacity: state.disabled ? 0.5 : 1 })}
/>
<InfotipContent
ref={popupRef}
aria-label="Details"
placement="bottom-start"
sideOffset={({ side }) => (side === 'top' ? 4 : 8)}
className={(state) => (state.open ? 'w-60' : undefined)}
style={(state) => ({ opacity: state.open ? 1 : 0 })}
>
{payload?.title}
</InfotipContent>
</>
)}
</Infotip>
)
const labelledTrigger = <InfotipTrigger aria-labelledby="visible-label" />
// @ts-expect-error An icon-only trigger requires an accessible name.
const unnamed = <InfotipTrigger />
// @ts-expect-error The icon is owned by InfotipTrigger.
const children = <InfotipTrigger aria-label="Details">Custom trigger</InfotipTrigger>
// @ts-expect-error InfotipTrigger always renders a native button.
const render = <InfotipTrigger aria-label="Details" render={<div />} />
const state = (
// @ts-expect-error Popup state is separate from trigger state.
<InfotipContent className={(state) => String(state.disabled)}>Details</InfotipContent>
)
return { composition, labelledTrigger, unnamed, children, render, state }
}
@@ -0,0 +1,105 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import * as React from 'react'
import { expect, userEvent, waitFor, within } from 'storybook/test'
import { Infotip, InfotipContent, InfotipTrigger } from '.'
import { Button } from '../button'
const meta = {
title: 'Base/UI/Infotip',
component: Infotip,
parameters: {
layout: 'centered',
docs: {
description: {
component:
'An information button that opens explanatory content using Popover semantics. Compose the root, icon trigger, and content. Name the trigger and the dialog; use aria-labelledby when a visible heading exists. The popup remains available to keyboard and touch users and may contain links.',
},
},
},
} satisfies Meta<typeof Infotip>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
render: () => (
<div className="flex items-center gap-1 text-text-secondary">
Document processing
<Infotip>
<InfotipTrigger aria-label="Document processing" />
<InfotipContent aria-label="Document processing">
Documents are processed in the order they are received. Larger files can take longer to
finish indexing.
</InfotipContent>
</Infotip>
</div>
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
const body = within(canvasElement.ownerDocument.body)
const trigger = canvas.getByRole('button', { name: 'Document processing' })
await userEvent.hover(trigger)
const popup = await body.findByRole('dialog', { name: 'Document processing' })
await userEvent.hover(popup)
await waitFor(() => expect(popup).toBeVisible())
await userEvent.keyboard('{Escape}')
await waitFor(() => expect(body.queryByRole('dialog')).not.toBeInTheDocument())
},
}
export const WithLink: Story = {
render: () => (
<Infotip>
<InfotipTrigger aria-label="Agent instructions" iconVariant="information" />
<InfotipContent aria-label="Agent instructions" className="w-60">
Describe the agent’s task and the rules it should follow.{' '}
<a href="https://docs.dify.ai" className="text-text-accent underline">
Read the documentation
</a>
</InfotipContent>
</Infotip>
),
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
const body = within(canvasElement.ownerDocument.body)
const trigger = canvas.getByRole('button', { name: 'Agent instructions' })
trigger.focus()
await userEvent.keyboard('{Enter}')
const popup = await body.findByRole('dialog', { name: 'Agent instructions' })
await waitFor(() =>
expect(within(popup).getByRole('link', { name: 'Read the documentation' })).toHaveFocus(),
)
await userEvent.keyboard('{Escape}')
await waitFor(() => expect(body.queryByRole('dialog')).not.toBeInTheDocument())
expect(trigger).toHaveFocus()
},
}
function ControlledExample() {
const [open, setOpen] = React.useState(false)
return (
<div className="flex items-center gap-3">
<Button onClick={() => setOpen(true)}>Show explanation</Button>
<Infotip open={open} onOpenChange={setOpen}>
<InfotipTrigger aria-label="Processing error" iconVariant="warning" />
<InfotipContent aria-label="Processing error">
The file could not be processed. Review its contents before trying again.
</InfotipContent>
</Infotip>
</div>
)
}
export const Controlled: Story = {
render: () => <ControlledExample />,
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
const body = within(canvasElement.ownerDocument.body)
await userEvent.click(canvas.getByRole('button', { name: 'Show explanation' }))
await waitFor(() =>
expect(body.getByRole('dialog', { name: 'Processing error' })).toBeVisible(),
)
await userEvent.keyboard('{Escape}')
await waitFor(() => expect(body.queryByRole('dialog')).not.toBeInTheDocument())
},
}
+112
View File
@@ -0,0 +1,112 @@
'use client'
import type * as React from 'react'
import type { Placement } from '../placement'
import { Popover as BasePopover } from '@base-ui/react/popover'
import { cn } from '../cn'
import { iconButtonVariants } from '../icon-button'
import { resolveClassName } from '../internals/resolve-class-name'
import { floatingPopupAnimationClassName } from '../overlay-shared'
import { parsePlacement } from '../placement'
const iconClassNames = {
question: 'i-ri-question-line',
information: 'i-ri-information-line',
warning: 'i-ri-error-warning-line',
} as const
const iconSizeClassNames = {
small: 'size-3',
medium: 'size-3.5',
large: 'size-4',
} as const
const Infotip = BasePopover.Root
type InfotipProps<Payload = unknown> = BasePopover.Root.Props<Payload>
type InfotipTriggerProps<Payload = unknown> = Omit<
BasePopover.Trigger.Props<Payload>,
'children' | 'render' | 'nativeButton' | 'aria-label' | 'aria-labelledby'
> &
(
| { 'aria-label': string; 'aria-labelledby'?: never }
| { 'aria-label'?: never; 'aria-labelledby': string }
) & {
iconVariant?: keyof typeof iconClassNames
iconSize?: keyof typeof iconSizeClassNames
}
function InfotipTrigger<Payload = unknown>({
className,
iconVariant = 'question',
iconSize = 'medium',
openOnHover = true,
delay = 300,
closeDelay = 200,
onClick,
...props
}: InfotipTriggerProps<Payload>) {
return (
<BasePopover.Trigger
{...props}
openOnHover={openOnHover}
delay={delay}
closeDelay={closeDelay}
onClick={(event) => {
event.stopPropagation()
onClick?.(event)
}}
className={(state) =>
cn(
iconButtonVariants({ variant: null, size: 'xs' }),
'shrink-0 border-0 bg-transparent text-text-quaternary outline-hidden hover:text-text-tertiary',
resolveClassName(className, state),
)
}
>
<span aria-hidden className={cn(iconClassNames[iconVariant], iconSizeClassNames[iconSize])} />
</BasePopover.Trigger>
)
}
type InfotipContentProps = Omit<BasePopover.Popup.Props, 'children'> &
Pick<BasePopover.Positioner.Props, 'sideOffset' | 'alignOffset'> & {
children: React.ReactNode
placement?: Placement
}
function InfotipContent({
placement = 'top',
sideOffset = 8,
alignOffset = 0,
className,
...props
}: InfotipContentProps) {
const { side, align } = parsePlacement(placement)
return (
<BasePopover.Portal>
<BasePopover.Positioner
side={side}
align={align}
sideOffset={sideOffset}
alignOffset={alignOffset}
className="z-50 outline-hidden"
>
<BasePopover.Popup
{...props}
className={(state) =>
cn(
'outline-hidden focus:outline-hidden focus-visible:outline-hidden',
floatingPopupAnimationClassName,
'max-w-75 rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg px-3 py-2 system-xs-regular text-text-secondary shadow-[0px_12px_16px_-4px_var(--color-shadow-shadow-5),0px_4px_6px_-2px_var(--color-shadow-shadow-1)] backdrop-blur-[5px]',
resolveClassName(className, state),
)
}
/>
</BasePopover.Positioner>
</BasePopover.Portal>
)
}
export { Infotip, InfotipContent, InfotipTrigger }
export type { InfotipContentProps, InfotipProps, InfotipTriggerProps }
+1 -1
View File
@@ -17,7 +17,7 @@ Web owns application-specific requirements and consumes shared architecture guid
- Give save and submit flows a real form boundary with visible labels and accessible errors. Use Dify UI `Form` when its structured submission and validation contract is the owner; otherwise use a native form. Follow the canonical [form contract].
- Follow the canonical [Button contract] and [IconButton contract] for action semantics, loading, accessible names, and primitive composition. Do not add a Web wrapper that hides those contracts.
- Follow [Accessible names and descriptions] when choosing or changing visible labels, ARIA naming, descriptions, or visually hidden text. Web owns localization and feature-specific status announcements; do not redefine the Dify UI naming contract locally.
- Follow the [Dify UI overlay contract] for primitive selection, portals, focus, and layering. Reuse the Web `Infotip` composite for an info glyph that opens explanatory content. Do not introduce a generic Web wrapper that recreates Dify UI overlay behavior.
- Follow the [Dify UI overlay contract] for primitive selection, portals, focus, and layering. Use `@langgenius/dify-ui/infotip` for an info glyph that opens explanatory content. Do not introduce a generic Web wrapper that recreates Dify UI overlay behavior.
- For custom SVG icons, follow `../packages/iconify-collections/README.md`; do not add generated React icons under `app/components/base/icons/src/`.
- `docs/test.md` is the single source of truth for Web automated-test policy. Skills may route and execute that policy but must not redefine it.
@@ -7,11 +7,11 @@ import type {
import type { AccessMode } from '@/models/access-control'
import { Button } from '@langgenius/dify-ui/button'
import { DialogDescription, DialogTitle } from '@langgenius/dify-ui/dialog'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { RadioGroup } from '@langgenius/dify-ui/radio-group'
import { useId } from 'react'
import { useTranslation } from 'react-i18next'
import { AccessMode as AccessModeValue } from '@/models/access-control'
import { Infotip } from '../../base/infotip'
import AccessControlDialog from './access-control-dialog'
import AccessControlItem from './access-control-item'
import SpecificGroupsOrMembers, { WebAppSSONotEnabledTip } from './specific-groups-or-members'
@@ -111,15 +111,22 @@ export function AccessControlForm({
{t(($) => $['accessControlDialog.accessItems.anyone'], { ns: 'app' })}
</p>
{publicAccessDisabled && (
<Infotip
aria-label={t(($) => $['accessControlDialog.webAppPublicAccessDisabledTip'], {
ns: 'app',
})}
className="h-4 w-4 shrink-0 text-text-warning-secondary hover:text-text-warning-secondary"
>
{t(($) => $['accessControlDialog.webAppPublicAccessDisabledTip'], {
ns: 'app',
})}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['accessControlDialog.webAppPublicAccessDisabledTip'], {
ns: 'app',
})}
className="h-4 w-4 shrink-0 text-text-warning-secondary hover:text-text-warning-secondary"
/>
<InfotipContent
aria-label={t(($) => $['accessControlDialog.webAppPublicAccessDisabledTip'], {
ns: 'app',
})}
>
{t(($) => $['accessControlDialog.webAppPublicAccessDisabledTip'], {
ns: 'app',
})}
</InfotipContent>
</Infotip>
)}
</div>
@@ -3,10 +3,10 @@ import type { AccessControlAccount, AccessControlGroup } from '@/models/access-c
import { Avatar } from '@langgenius/dify-ui/avatar'
import { Button } from '@langgenius/dify-ui/button'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useTranslation } from 'react-i18next'
import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder'
import { AccessMode } from '@/models/access-control'
import { Infotip } from '../../base/infotip'
import AddMemberOrGroupDialog from './add-member-or-group-pop'
export type AccessControlSubjects = {
@@ -222,12 +222,13 @@ export function WebAppSSONotEnabledTip() {
const tip = t(($) => $['accessControlDialog.webAppSSONotEnabledTip'], { ns: 'app' })
return (
<Infotip
aria-label={tip}
className="text-text-warning-secondary hover:text-text-warning-secondary"
iconSize="large"
>
{tip}
<Infotip>
<InfotipTrigger
aria-label={tip}
className="text-text-warning-secondary hover:text-text-warning-secondary"
iconSize="large"
/>
<InfotipContent aria-label={tip}>{tip}</InfotipContent>
</Infotip>
)
}
@@ -4,6 +4,7 @@ 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 { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { RiDeleteBinLine, RiErrorWarningFill } from '@remixicon/react'
import { useBoolean } from 'ahooks'
import copy from 'copy-to-clipboard'
@@ -14,7 +15,6 @@ import { useContext } from 'use-context-selector'
import { ADD_EXTERNAL_DATA_TOOL } from '@/app/components/app/configuration/config-var'
import { toast } from '@/app/components/app/configuration/toast'
import { Copy, CopyCheck } from '@/app/components/base/icons/src/vender/line/files'
import { Infotip } from '@/app/components/base/infotip'
import PromptEditor from '@/app/components/base/prompt-editor'
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
import ConfigContext from '@/context/debug-configuration'
@@ -189,12 +189,17 @@ const AdvancedPromptInput: FC<Props> = ({
<div className="text-sm font-semibold text-indigo-800 uppercase">
{t(($) => $['pageTitle.line1'], { ns: 'appDebug' })}
</div>
<Infotip
aria-label={t(($) => $.promptTip, { ns: 'appDebug' })}
className="ml-1"
popupClassName="w-[180px]"
>
{t(($) => $.promptTip, { ns: 'appDebug' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $.promptTip, { ns: 'appDebug' })}
className="ml-1"
/>
<InfotipContent
aria-label={t(($) => $.promptTip, { ns: 'appDebug' })}
className="w-[180px]"
>
{t(($) => $.promptTip, { ns: 'appDebug' })}
</InfotipContent>
</Infotip>
</div>
)}
@@ -4,6 +4,7 @@ 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 { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useBoolean } from 'ahooks'
import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
@@ -16,7 +17,6 @@ import AutomaticBtn from '@/app/components/app/configuration/config/automatic/au
import GetAutomaticResModal from '@/app/components/app/configuration/config/automatic/get-automatic-res'
import { toast } from '@/app/components/app/configuration/toast'
import { useFeaturesStore } from '@/app/components/base/features/hooks'
import { Infotip } from '@/app/components/base/infotip'
import PromptEditor from '@/app/components/base/prompt-editor'
import { PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER } from '@/app/components/base/prompt-editor/plugins/update-block'
import { INSERT_VARIABLE_VALUE_BLOCK_COMMAND } from '@/app/components/base/prompt-editor/plugins/variable-block'
@@ -206,12 +206,17 @@ const Prompt: FC<ISimplePromptInput> = ({
{title}
</h2>
{!readonly && (
<Infotip
aria-label={t(($) => $.promptTip, { ns: 'appDebug' })}
className="ml-1"
popupClassName="w-[180px]"
>
{t(($) => $.promptTip, { ns: 'appDebug' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $.promptTip, { ns: 'appDebug' })}
className="ml-1"
/>
<InfotipContent
aria-label={t(($) => $.promptTip, { ns: 'appDebug' })}
className="w-[180px]"
>
{t(($) => $.promptTip, { ns: 'appDebug' })}
</InfotipContent>
</Infotip>
)}
</div>
@@ -6,6 +6,7 @@ import type { SelectorTranslate } from '@/app/components/app/configuration/utils
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import type { InputVar, UploadFileSetting } from '@/app/components/workflow/types'
import { Checkbox } from '@langgenius/dify-ui/checkbox'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Input } from '@langgenius/dify-ui/input'
import { NumberField, NumberFieldGroup, NumberFieldInput } from '@langgenius/dify-ui/number-field'
import {
@@ -26,7 +27,6 @@ import * as React from 'react'
import { Trans } from 'react-i18next'
import { getStringSelectorTranslate } from '@/app/components/app/configuration/utils'
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
import { Infotip } from '@/app/components/base/infotip'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import FileUploadSetting from '@/app/components/workflow/nodes/_base/components/file-upload-setting'
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
@@ -409,21 +409,24 @@ const ConfigModalFormFields: FC<ConfigModalFormFieldsProps> = ({
</span>
</label>
<div className="flex items-center gap-1">
<Infotip aria-label={hiddenDescriptionAriaLabel}>
<Trans
i18nKey={($) => $['variableConfig.hiddenDescription']}
ns="appDebug"
components={{
docLink: (
<a
href={docLink('/use-dify/nodes/user-input#hide-and-pre-fill-input-fields')}
target="_blank"
rel="noopener noreferrer"
className="text-text-accent hover:underline"
/>
),
}}
/>
<Infotip>
<InfotipTrigger aria-label={hiddenDescriptionAriaLabel} />
<InfotipContent aria-label={hiddenDescriptionAriaLabel}>
<Trans
i18nKey={($) => $['variableConfig.hiddenDescription']}
ns="appDebug"
components={{
docLink: (
<a
href={docLink('/use-dify/nodes/user-input#hide-and-pre-fill-input-fields')}
target="_blank"
rel="noopener noreferrer"
className="text-text-accent hover:underline"
/>
),
}}
/>
</InfotipContent>
</Infotip>
</div>
</div>
@@ -13,6 +13,7 @@ import {
AlertDialogTitle,
} from '@langgenius/dify-ui/alert-dialog'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useBoolean } from 'ahooks'
import { produce } from 'immer'
import * as React from 'react'
@@ -21,7 +22,6 @@ import { useTranslation } from 'react-i18next'
import { ReactSortable } from 'react-sortablejs'
import { useContext } from 'use-context-selector'
import { toast } from '@/app/components/app/configuration/toast'
import { Infotip } from '@/app/components/base/infotip'
import { useKeyboardSortable } from '@/app/components/base/keyboard-sortable/use-keyboard-sortable'
import { InputVarType } from '@/app/components/workflow/types'
import ConfigContext from '@/context/debug-configuration'
@@ -315,11 +315,14 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
<div className="flex items-center">
<h2 className="mr-1">{t(($) => $.variableTitle, { ns: 'appDebug' })}</h2>
{!readonly && (
<Infotip
aria-label={t(($) => $.variableTip, { ns: 'appDebug' })}
popupClassName="w-[180px]"
>
{t(($) => $.variableTip, { ns: 'appDebug' })}
<Infotip>
<InfotipTrigger aria-label={t(($) => $.variableTip, { ns: 'appDebug' })} />
<InfotipContent
aria-label={t(($) => $.variableTip, { ns: 'appDebug' })}
className="w-[180px]"
>
{t(($) => $.variableTip, { ns: 'appDebug' })}
</InfotipContent>
</Infotip>
)}
</div>
@@ -1,6 +1,7 @@
'use client'
import type { FC } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Switch } from '@langgenius/dify-ui/switch'
import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
@@ -11,7 +12,6 @@ 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 { 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'
@@ -75,11 +75,14 @@ const ConfigVision: FC = () => {
<h2 id={titleId} className="mr-1 system-sm-semibold text-text-secondary">
{t(($) => $['vision.name'], { ns: 'appDebug' })}
</h2>
<Infotip
aria-label={t(($) => $['vision.description'], { ns: 'appDebug' })}
popupClassName="w-[180px]"
>
{t(($) => $['vision.description'], { ns: 'appDebug' })}
<Infotip>
<InfotipTrigger aria-label={t(($) => $['vision.description'], { ns: 'appDebug' })} />
<InfotipContent
aria-label={t(($) => $['vision.description'], { ns: 'appDebug' })}
className="w-[180px]"
>
{t(($) => $['vision.description'], { ns: 'appDebug' })}
</InfotipContent>
</Infotip>
</div>
<div className="flex shrink-0 items-center">
@@ -89,17 +92,24 @@ const ConfigVision: FC = () => {
<div className="system-xs-medium-uppercase text-text-tertiary">
{t(($) => $['vision.visionSettings.resolution'], { ns: 'appDebug' })}
</div>
<Infotip
aria-label={t(($) => $['vision.visionSettings.resolutionTooltip'], {
ns: 'appDebug',
})}
popupClassName="w-[180px]"
>
{t(($) => $['vision.visionSettings.resolutionTooltip'], { ns: 'appDebug' })
.split('\n')
.map((item) => (
<div key={item}>{item}</div>
))}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['vision.visionSettings.resolutionTooltip'], {
ns: 'appDebug',
})}
/>
<InfotipContent
aria-label={t(($) => $['vision.visionSettings.resolutionTooltip'], {
ns: 'appDebug',
})}
className="w-[180px]"
>
{t(($) => $['vision.visionSettings.resolutionTooltip'], { ns: 'appDebug' })
.split('\n')
.map((item) => (
<div key={item}>{item}</div>
))}
</InfotipContent>
</Infotip>
</div>
<div className="flex items-center gap-1">
@@ -3,13 +3,13 @@ import type { FC } from 'react'
import type { FileUpload } from '@/app/components/base/features/types'
import { Field, FieldItem } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { RadioGroup, RadioItem } from '@langgenius/dify-ui/radio-group'
import { produce } from 'immer'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
import { Infotip } from '@/app/components/base/infotip'
import ParamItem from '@/app/components/base/param-item'
import { Resolution, TransferMethod } from '@/types/app'
@@ -70,17 +70,24 @@ const ParamConfigContent: FC = () => {
<FieldsetLegend className="m-0 py-0 text-[13px] leading-4.5 font-semibold text-text-secondary">
{t(($) => $['vision.visionSettings.resolution'], { ns: 'appDebug' })}
</FieldsetLegend>
<Infotip
aria-label={t(($) => $['vision.visionSettings.resolutionTooltip'], {
ns: 'appDebug',
})}
popupClassName="w-[180px]"
>
{t(($) => $['vision.visionSettings.resolutionTooltip'], { ns: 'appDebug' })
.split('\n')
.map((item) => (
<div key={item}>{item}</div>
))}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['vision.visionSettings.resolutionTooltip'], {
ns: 'appDebug',
})}
/>
<InfotipContent
aria-label={t(($) => $['vision.visionSettings.resolutionTooltip'], {
ns: 'appDebug',
})}
className="w-[180px]"
>
{t(($) => $['vision.visionSettings.resolutionTooltip'], { ns: 'appDebug' })
.split('\n')
.map((item) => (
<div key={item}>{item}</div>
))}
</InfotipContent>
</Infotip>
</div>
<div className="flex items-center gap-1">
@@ -1,8 +1,8 @@
'use client'
import type { FC } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import * as React from 'react'
import { Infotip } from '@/app/components/base/infotip'
type Props = Readonly<{
className?: string
@@ -23,8 +23,11 @@ const ItemPanel: FC<Props> = ({ className, icon, name, description, children })
<div className="flex items-center">
{icon}
<div className="mr-1 ml-3 text-sm/6 font-semibold text-text-secondary">{name}</div>
<Infotip aria-label={description} popupClassName="w-[180px]">
{description}
<Infotip>
<InfotipTrigger aria-label={description} />
<InfotipContent aria-label={description} className="w-[180px]">
{description}
</InfotipContent>
</Infotip>
</div>
<div>{children}</div>
@@ -6,6 +6,7 @@ import type { ToolWithProvider } from '@/app/components/workflow/types'
import type { AgentTool } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { Switch } from '@langgenius/dify-ui/switch'
@@ -21,7 +22,6 @@ import { OperationButton } from '@/app/components/app/configuration/base/operati
import AppIcon from '@/app/components/base/app-icon'
import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import { Infotip } from '@/app/components/base/infotip'
import { parseToolProviderType } from '@/app/components/tools/provider-type'
import { CollectionType } from '@/app/components/tools/types'
import {
@@ -163,8 +163,15 @@ const AgentTools: FC = () => {
title={
<div className="flex items-center">
<h2 className="mr-1">{t(($) => $['agent.tools.name'], { ns: 'appDebug' })}</h2>
<Infotip aria-label={t(($) => $['agent.tools.description'], { ns: 'appDebug' })}>
{t(($) => $['agent.tools.description'], { ns: 'appDebug' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['agent.tools.description'], { ns: 'appDebug' })}
/>
<InfotipContent
aria-label={t(($) => $['agent.tools.description'], { ns: 'appDebug' })}
>
{t(($) => $['agent.tools.description'], { ns: 'appDebug' })}
</InfotipContent>
</Infotip>
</div>
}
@@ -239,24 +246,26 @@ const AgentTools: FC = () => {
</span>
<span className="text-text-tertiary">{item.tool_label}</span>
{!item.isDeleted && !readonly && (
<Infotip
aria-label={item.tool_name}
className="ml-0.5 opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
popupClassName="w-[220px]"
>
<div>
<div className="mb-1.5 text-text-secondary">{item.tool_name}</div>
<div className="mb-1.5 text-text-tertiary">
{t(($) => $.toolNameUsageTip, { ns: 'tools' })}
<Infotip>
<InfotipTrigger
aria-label={item.tool_name}
className="ml-0.5 opacity-0 group-hover:opacity-100 focus-visible:opacity-100"
/>
<InfotipContent aria-label={item.tool_name} className="w-[220px]">
<div>
<div className="mb-1.5 text-text-secondary">{item.tool_name}</div>
<div className="mb-1.5 text-text-tertiary">
{t(($) => $.toolNameUsageTip, { ns: 'tools' })}
</div>
<button
type="button"
className="cursor-pointer rounded-sm border-none bg-transparent p-0 text-left text-text-accent outline-hidden hover:underline focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
onClick={() => void writeTextToClipboard(item.tool_name)}
>
{t(($) => $.copyToolName, { ns: 'tools' })}
</button>
</div>
<button
type="button"
className="cursor-pointer rounded-sm border-none bg-transparent p-0 text-left text-text-accent outline-hidden hover:underline focus-visible:ring-1 focus-visible:ring-components-input-border-hover"
onClick={() => void writeTextToClipboard(item.tool_name)}
>
{t(($) => $.copyToolName, { ns: 'tools' })}
</button>
</div>
</InfotipContent>
</Infotip>
)}
</div>
@@ -1,5 +1,6 @@
'use client'
import type { FC } from 'react'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Switch } from '@langgenius/dify-ui/switch'
import { produce } from 'immer'
import * as React from 'react'
@@ -8,7 +9,6 @@ import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
import { Microphone01 } from '@/app/components/base/icons/src/vender/features'
import { Infotip } from '@/app/components/base/infotip'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import ConfigContext from '@/context/debug-configuration'
@@ -54,11 +54,16 @@ const ConfigAudio: FC = () => {
<div className="mr-1 system-sm-semibold text-text-secondary">
{t(($) => $['feature.audioUpload.title'], { ns: 'appDebug' })}
</div>
<Infotip
aria-label={t(($) => $['feature.audioUpload.description'], { ns: 'appDebug' })}
popupClassName="w-[180px]"
>
{t(($) => $['feature.audioUpload.description'], { ns: 'appDebug' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['feature.audioUpload.description'], { ns: 'appDebug' })}
/>
<InfotipContent
aria-label={t(($) => $['feature.audioUpload.description'], { ns: 'appDebug' })}
className="w-[180px]"
>
{t(($) => $['feature.audioUpload.description'], { ns: 'appDebug' })}
</InfotipContent>
</Infotip>
</div>
{!readonly && (
@@ -1,5 +1,6 @@
'use client'
import type { FC } from 'react'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Switch } from '@langgenius/dify-ui/switch'
import { produce } from 'immer'
import * as React from 'react'
@@ -8,7 +9,6 @@ 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 { Infotip } from '@/app/components/base/infotip'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import ConfigContext from '@/context/debug-configuration'
@@ -55,11 +55,16 @@ const ConfigDocument: FC = () => {
<div className="mr-1 system-sm-semibold text-text-secondary">
{t(($) => $['feature.documentUpload.title'], { ns: 'appDebug' })}
</div>
<Infotip
aria-label={t(($) => $['feature.documentUpload.description'], { ns: 'appDebug' })}
popupClassName="w-[180px]"
>
{t(($) => $['feature.documentUpload.description'], { ns: 'appDebug' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['feature.documentUpload.description'], { ns: 'appDebug' })}
/>
<InfotipContent
aria-label={t(($) => $['feature.documentUpload.description'], { ns: 'appDebug' })}
className="w-[180px]"
>
{t(($) => $['feature.documentUpload.description'], { ns: 'appDebug' })}
</InfotipContent>
</Infotip>
</div>
{!readonly && (
@@ -2,10 +2,10 @@
import type { FC } from 'react'
import type { Props } from './var-picker'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { BracketsX } from '@/app/components/base/icons/src/vender/line/development'
import { Infotip } from '@/app/components/base/infotip'
import VarPicker from './var-picker'
const ContextVar: FC<Props> = (props) => {
@@ -29,11 +29,16 @@ const ContextVar: FC<Props> = (props) => {
<div className="mr-1 text-sm font-medium text-text-secondary">
{t(($) => $['feature.dataSet.queryVariable.title'], { ns: 'appDebug' })}
</div>
<Infotip
aria-label={t(($) => $['feature.dataSet.queryVariable.tip'], { ns: 'appDebug' })}
popupClassName="w-[180px]"
>
{t(($) => $['feature.dataSet.queryVariable.tip'], { ns: 'appDebug' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['feature.dataSet.queryVariable.tip'], { ns: 'appDebug' })}
/>
<InfotipContent
aria-label={t(($) => $['feature.dataSet.queryVariable.tip'], { ns: 'appDebug' })}
className="w-[180px]"
>
{t(($) => $['feature.dataSet.queryVariable.tip'], { ns: 'appDebug' })}
</InfotipContent>
</Infotip>
</div>
@@ -6,12 +6,12 @@ import type { ModelConfig } from '@/app/components/workflow/types'
import type { DataSet } from '@/models/datasets'
import type { DatasetConfigs } from '@/models/debug'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Separator } from '@langgenius/dify-ui/separator'
import { Switch } from '@langgenius/dify-ui/switch'
import { memo, useCallback, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from '@/app/components/app/configuration/toast'
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 { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
@@ -244,12 +244,11 @@ const ConfigContent: FC<Props> = ({
onClick={() => handleRerankModeChange(option.value)}
>
<div className="truncate">{option.label}</div>
<Infotip
aria-label={option.tips}
className="ml-0.5 size-3.5"
popupClassName="w-[200px]"
>
{option.tips}
<Infotip>
<InfotipTrigger aria-label={option.tips} className="ml-0.5 size-3.5" />
<InfotipContent aria-label={option.tips} className="w-[200px]">
{option.tips}
</InfotipContent>
</Infotip>
</div>
))}
@@ -268,12 +267,17 @@ const ConfigContent: FC<Props> = ({
<div className="ml-1 system-sm-semibold leading-8 text-text-secondary">
{t(($) => $['modelProvider.rerankModel.key'], { ns: 'common' })}
</div>
<Infotip
aria-label={t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' })}
className="ml-1"
popupClassName="w-[200px]"
>
{t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' })}
className="ml-1"
/>
<InfotipContent
aria-label={t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' })}
className="w-[200px]"
>
{t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' })}
</InfotipContent>
</Infotip>
</div>
{showRerankModel && (
@@ -344,10 +348,15 @@ const ConfigContent: FC<Props> = ({
<div className="text-[13px] leading-8 font-medium text-text-primary">
{t(($) => $['modelProvider.systemReasoningModel.key'], { ns: 'common' })}
</div>
<Infotip
aria-label={t(($) => $['modelProvider.systemReasoningModel.tip'], { ns: 'common' })}
>
{t(($) => $['modelProvider.systemReasoningModel.tip'], { ns: 'common' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['modelProvider.systemReasoningModel.tip'], { ns: 'common' })}
/>
<InfotipContent
aria-label={t(($) => $['modelProvider.systemReasoningModel.tip'], { ns: 'common' })}
>
{t(($) => $['modelProvider.systemReasoningModel.tip'], { ns: 'common' })}
</InfotipContent>
</Infotip>
</div>
<ModelParameterModal
@@ -4,11 +4,11 @@ import type { Dayjs } from 'dayjs'
import type { SelectorParam } from 'i18next'
import type { FC } from 'react'
import type { ChartRow } from './app-chart-utils'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useQuery } from '@tanstack/react-query'
import ReactECharts from 'echarts-for-react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { LoadingPlaceholder } from '@/app/components/base/loading-placeholder'
import { consoleQuery } from '@/service/console'
import {
@@ -102,8 +102,11 @@ const Chart: React.FC<IChartProps> = ({
{title}
</div>
{explanation && (
<Infotip aria-label={explanation} className="ml-1" popupClassName="w-[240px]">
{explanation}
<Infotip>
<InfotipTrigger aria-label={explanation} className="ml-1" />
<InfotipContent aria-label={explanation} className="w-[240px]">
{explanation}
</InfotipContent>
</Infotip>
)}
</div>
@@ -5,6 +5,7 @@ import type { InputForm } from '../type'
import type { FileUpload } from '@/app/components/base/features/types'
import type { SpeechToTextTarget } from '@/app/components/base/voice-input/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { noop } from 'es-toolkit/function'
import { decode } from 'html-entities'
import { useCallback, useRef, useState } from 'react'
@@ -14,7 +15,6 @@ import FeatureBar from '@/app/components/base/features/new-feature-panel/feature
import { FileListInChatInput } from '@/app/components/base/file-uploader'
import { useFile } from '@/app/components/base/file-uploader/hooks'
import { FileContextProvider, useFileStore } from '@/app/components/base/file-uploader/store'
import { Infotip } from '@/app/components/base/infotip'
import VoiceInput from '@/app/components/base/voice-input'
import { toast } from '@/app/notifications'
import { TransferMethod } from '@/types/app'
@@ -379,13 +379,18 @@ const ChatInputArea = ({
<div className="flex items-center gap-1">
<div className="min-w-0 flex-1 body-xs-medium text-text-accent">{footerNotice}</div>
{shouldShowFooterNoticeTooltip && (
<Infotip
aria-label={footerNoticeAriaLabel}
className="ml-auto size-5 rounded-md text-text-accent hover:bg-state-base-hover hover:text-text-accent"
iconVariant="information"
popupClassName="max-w-80 text-start wrap-break-word"
>
{footerNoticeTooltip}
<Infotip>
<InfotipTrigger
aria-label={footerNoticeAriaLabel}
className="ml-auto size-5 rounded-md text-text-accent hover:bg-state-base-hover hover:text-text-accent"
iconVariant="information"
/>
<InfotipContent
aria-label={footerNoticeAriaLabel}
className="max-w-80 text-start wrap-break-word"
>
{footerNoticeTooltip}
</InfotipContent>
</Infotip>
)}
</div>
@@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import * as React from 'react'
import { Infotip } from '@/app/components/base/infotip'
export const Item: FC<{ title: string; tooltip: string; children: React.JSX.Element }> = ({
title,
@@ -12,8 +12,11 @@ export const Item: FC<{ title: string; tooltip: string; children: React.JSX.Elem
<div>
<div className="mb-1 flex items-center space-x-1">
<div className="py-1 system-sm-semibold text-text-secondary">{title}</div>
<Infotip aria-label={tooltip} popupClassName="max-w-[200px] system-sm-regular">
{tooltip}
<Infotip>
<InfotipTrigger aria-label={tooltip} />
<InfotipContent aria-label={tooltip} className="max-w-[200px] system-sm-regular">
{tooltip}
</InfotipContent>
</Infotip>
</div>
<div>{children}</div>
@@ -5,6 +5,7 @@ import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Separator } from '@langgenius/dify-ui/separator'
import { useBoolean } from 'ahooks'
import { produce } from 'immer'
@@ -14,7 +15,6 @@ import { useTranslation } from 'react-i18next'
import { ReactSortable } from 'react-sortablejs'
import ConfirmAddVar from '@/app/components/app/configuration/config-prompt/confirm-add-var'
import { getInputKeys } from '@/app/components/base/block-input'
import { Infotip } from '@/app/components/base/infotip'
import { useKeyboardSortable } from '@/app/components/base/keyboard-sortable/use-keyboard-sortable'
import PromptEditor from '@/app/components/base/prompt-editor'
import { checkKeys, getNewVar } from '@/utils/var'
@@ -147,14 +147,21 @@ const OpeningSettingModal = ({
<div className="text-sm font-medium text-text-primary">
{t(($) => $['openingStatement.openingQuestion'], { ns: 'appDebug' })}
</div>
<Infotip
aria-label={t(($) => $['openingStatement.openingQuestionDescription'], {
ns: 'appDebug',
})}
className="size-3.5"
popupClassName="max-w-[220px] system-sm-regular"
>
{t(($) => $['openingStatement.openingQuestionDescription'], { ns: 'appDebug' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['openingStatement.openingQuestionDescription'], {
ns: 'appDebug',
})}
className="size-3.5"
/>
<InfotipContent
aria-label={t(($) => $['openingStatement.openingQuestionDescription'], {
ns: 'appDebug',
})}
className="max-w-[220px] system-sm-regular"
>
{t(($) => $['openingStatement.openingQuestionDescription'], { ns: 'appDebug' })}
</InfotipContent>
</Infotip>
</div>
<div className="text-xs leading-4.5 font-medium text-text-tertiary">
@@ -1,6 +1,6 @@
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Switch } from '@langgenius/dify-ui/switch'
import * as React from 'react'
import { Infotip } from '@/app/components/base/infotip'
type Props = Readonly<{
icon: React.ReactNode
@@ -40,11 +40,14 @@ const FeatureCard = ({
<div className="flex grow items-center system-sm-semibold text-text-secondary">
<span id={titleId}>{title}</span>
{!!tooltip && (
<Infotip
aria-label={typeof tooltip === 'string' ? tooltip : String(title)}
className="ml-0.5 size-3.5"
>
{tooltip}
<Infotip>
<InfotipTrigger
aria-label={typeof tooltip === 'string' ? tooltip : String(title)}
className="ml-0.5 size-3.5"
/>
<InfotipContent aria-label={typeof tooltip === 'string' ? tooltip : String(title)}>
{tooltip}
</InfotipContent>
</Infotip>
)}
</div>
@@ -1,6 +1,7 @@
'use client'
import type { OnFeaturesChange } from '@/app/components/base/features/types'
import type { I18nKeysWithPrefix } from '@/types/i18n'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import {
Select,
SelectItem,
@@ -19,7 +20,6 @@ import { useTranslation } from 'react-i18next'
import { replace } from 'string-ts'
import { AudioBtn } from '@/app/components/base/audio-btn'
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
import { Infotip } from '@/app/components/base/infotip'
import { languages } from '@/i18n/language'
import { useParams } from '@/next/navigation'
import { consoleQuery } from '@/service/console'
@@ -107,15 +107,20 @@ const VoiceParamConfig = ({ onClose, onChange }: VoiceParamConfigProps) => {
<div className="mb-3">
<div className="mb-1 flex items-center py-1 system-sm-semibold text-text-secondary">
{t(($) => $['voice.voiceSettings.language'], { ns: 'appDebug' })}
<Infotip
aria-label={t(($) => $['voice.voiceSettings.resolutionTooltip'], { ns: 'appDebug' })}
popupClassName="w-[180px]"
>
{t(($) => $['voice.voiceSettings.resolutionTooltip'], { ns: 'appDebug' })
.split('\n')
.map((item) => (
<div key={item}>{item}</div>
))}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['voice.voiceSettings.resolutionTooltip'], { ns: 'appDebug' })}
/>
<InfotipContent
aria-label={t(($) => $['voice.voiceSettings.resolutionTooltip'], { ns: 'appDebug' })}
className="w-[180px]"
>
{t(($) => $['voice.voiceSettings.resolutionTooltip'], { ns: 'appDebug' })
.split('\n')
.map((item) => (
<div key={item}>{item}</div>
))}
</InfotipContent>
</Infotip>
</div>
<Select
@@ -3,6 +3,7 @@ import type { FieldState, FormSchema, TypeWithI18N } from '@/app/components/base
import { cn } from '@langgenius/dify-ui/cn'
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Input } from '@langgenius/dify-ui/input'
import { InputGroup, InputGroupAddon, InputGroupInput } from '@langgenius/dify-ui/input-group'
import { NumberField, NumberFieldGroup, NumberFieldInput } from '@langgenius/dify-ui/number-field'
@@ -23,7 +24,6 @@ import { useTranslation } from 'react-i18next'
import { CheckboxList } from '@/app/components/base/checkbox-list'
import { CopyFeedback } from '@/app/components/base/copy-feedback'
import { FormItemValidateStatusEnum, FormTypeEnum } from '@/app/components/base/form/types'
import { Infotip } from '@/app/components/base/infotip'
import { useRenderI18nObject } from '@/hooks/use-i18n'
import { useTriggerPluginDynamicOptions } from '@/service/use-triggers'
@@ -311,8 +311,11 @@ const BaseField = ({
</span>
)}
{translatedTooltip && (
<Infotip aria-label={translatedTooltip} className="ml-0.5" popupClassName="w-[200px]">
{translatedTooltip}
<Infotip>
<InfotipTrigger aria-label={translatedTooltip} className="ml-0.5" />
<InfotipContent aria-label={translatedTooltip} className="w-[200px]">
{translatedTooltip}
</InfotipContent>
</Infotip>
)}
</div>
@@ -2,10 +2,10 @@ import type { LabelProps } from '../label'
import { cn } from '@langgenius/dify-ui/cn'
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { RadioGroup, RadioItem } from '@langgenius/dify-ui/radio-group'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { TransferMethod } from '@/types/app'
import { useFieldContext } from '../..'
@@ -72,12 +72,11 @@ const UploadMethodField = ({ label, labelOptions, className }: UploadMethodField
<span className="ml-1 system-xs-regular text-text-destructive-secondary">*</span>
)}
{labelOptions?.tooltip && (
<Infotip
aria-label={labelOptions.tooltip}
className="ml-0.5 size-4"
popupClassName="w-[200px]"
>
{labelOptions.tooltip}
<Infotip>
<InfotipTrigger aria-label={labelOptions.tooltip} className="ml-0.5 size-4" />
<InfotipContent aria-label={labelOptions.tooltip} className="w-[200px]">
{labelOptions.tooltip}
</InfotipContent>
</Infotip>
)}
</div>
@@ -1,6 +1,6 @@
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useTranslation } from 'react-i18next'
import { Infotip } from '../../infotip'
export type LabelProps = {
htmlFor: string
@@ -28,8 +28,11 @@ const Label = ({ htmlFor, label, isRequired, showOptional, tooltip, className }:
<div className="ml-1 system-xs-regular text-text-destructive-secondary">*</div>
)}
{tooltip && (
<Infotip aria-label={tooltip} className="ml-0.5 size-4" popupClassName="w-[200px]">
{tooltip}
<Infotip>
<InfotipTrigger aria-label={tooltip} className="ml-0.5 size-4" />
<InfotipContent aria-label={tooltip} className="w-[200px]">
{tooltip}
</InfotipContent>
</Infotip>
)}
</div>
@@ -1,53 +0,0 @@
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 (
<button type="button" aria-label="Parent action" onClick={onClick}>
{createPortal(children, document.body)}
</button>
)
}
describe('Infotip', () => {
it('should open with keyboard activation', async () => {
const user = userEvent.setup()
render(<Infotip aria-label="Rate limits">Rate limit details</Infotip>)
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(<Infotip aria-label="Rate limits">Rate limit details</Infotip>)
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(
<ClickBoundary onClick={parentClick}>
<Infotip aria-label="Rate limits">Rate limit details</Infotip>
</ClickBoundary>,
)
await user.click(screen.getByRole('button', { name: 'Rate limits' }))
expect(parentClick).not.toHaveBeenCalled()
})
})
-106
View File
@@ -1,106 +0,0 @@
'use client'
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',
warning: 'i-ri-error-warning-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 — 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
*
* > "Popups that open when hovering an info icon should use Popover with the
* > `openOnHover` prop on the trigger instead of a tooltip. This way, touch
* > users and screen reader users can access the content."
*
* Use whenever the trigger is an info glyph whose sole purpose is to open a
* popup (help text, documentation-style explanation). Do NOT use `Tooltip` for
* this — Tooltip is reserved for ephemeral, non-interactive visual labels that
* are unreachable on touch devices and by screen readers.
*
* Base UI rule of thumb:
*
* > "If the trigger's purpose is to open the popup itself, it's a popover.
* > If the trigger's purpose is unrelated to opening the popup, it's a tooltip."
*
* For hover-revealed supplementary previews of a link / row trigger that has
* its own primary click destination, use `PreviewCard` instead.
*/
type InfotipProps = {
/** Popup content. Rich nodes are allowed. */
children: ReactNode
/** Accessible name for the icon-only trigger. */
'aria-label': string
/** Extra classes on the trigger for contextual layout and color. */
className?: 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
/** Distance between the trigger and popup. */
sideOffset?: number
}
export function Infotip({
children,
'aria-label': ariaLabel,
className,
iconVariant = 'question',
iconSize = 'medium',
popupClassName,
sideOffset,
}: InfotipProps) {
const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
event.stopPropagation()
}
return (
<Popover>
<PopoverTrigger
openOnHover
delay={300}
closeDelay={200}
aria-label={ariaLabel}
onClick={handleClick}
className={cn(
'inline-flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent p-0 text-text-quaternary outline-hidden hover:text-text-tertiary focus-visible:ring-2 focus-visible:ring-state-accent-solid',
className,
)}
>
<span
aria-hidden
className={cn(iconClassNames[iconVariant], iconSizeClassNames[iconSize])}
/>
</PopoverTrigger>
<PopoverContent
placement="top"
sideOffset={sideOffset}
className={cn(
'max-w-75 rounded-lg bg-components-tooltip-bg px-3 py-2 system-xs-regular text-text-secondary shadow-[0px_12px_16px_-4px_var(--color-shadow-shadow-5),0px_4px_6px_-2px_var(--color-shadow-shadow-1)] backdrop-blur-[5px]',
popupClassName,
)}
>
{children}
</PopoverContent>
</Popover>
)
}
+6 -3
View File
@@ -1,6 +1,7 @@
'use client'
import type { FC } from 'react'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import {
NumberField,
NumberFieldControls,
@@ -18,7 +19,6 @@ import {
SliderTrack,
} from '@langgenius/dify-ui/slider'
import { Switch } from '@langgenius/dify-ui/switch'
import { Infotip } from '@/app/components/base/infotip'
type Props = Readonly<{
className?: string
@@ -71,8 +71,11 @@ const ParamItem: FC<Props> = ({
)}
<span className="mr-1 system-sm-semibold text-text-secondary">{name}</span>
{!noTooltip && tip && (
<Infotip aria-label={tip} popupClassName="w-[200px]">
{tip}
<Infotip>
<InfotipTrigger aria-label={tip} />
<InfotipContent aria-label={tip} className="w-[200px]">
{tip}
</InfotipContent>
</Infotip>
)}
</div>
@@ -1,9 +1,10 @@
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { RiAedFill } from '@remixicon/react'
import { useQuery } from '@tanstack/react-query'
import { useAtomValue } from 'jotai'
import { useId } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { deploymentEditionAtom } from '@/features/system-features/state'
import { consoleQuery } from '@/service/console'
@@ -13,6 +14,7 @@ type PriorityLabelProps = {
const PriorityLabel = ({ className }: PriorityLabelProps) => {
const { t } = useTranslation()
const titleId = useId()
const deploymentEdition = useAtomValue(deploymentEditionAtom)
const { data: plan } = useQuery(
consoleQuery.features.get.queryOptions({
@@ -33,18 +35,21 @@ const PriorityLabel = ({ className }: PriorityLabelProps) => {
)}
{t(($) => $[`plansCommon.priority.${label}`], { ns: 'billing' })}
</span>
<Infotip
aria-label={t(($) => $['plansCommon.documentProcessingPriority'], { ns: 'billing' })}
>
<div className="font-semibold text-text-primary">
{t(($) => $['plansCommon.documentProcessingPriority'], { ns: 'billing' })}:{' '}
{t(($) => $[`plansCommon.priority.${label}`], { ns: 'billing' })}
</div>
{label !== 'top-priority' && (
<div className="mt-1">
{t(($) => $['plansCommon.documentProcessingPriorityTip'], { ns: 'billing' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['plansCommon.documentProcessingPriority'], { ns: 'billing' })}
/>
<InfotipContent aria-labelledby={titleId}>
<div id={titleId} className="font-semibold text-text-primary">
{t(($) => $['plansCommon.documentProcessingPriority'], { ns: 'billing' })}:{' '}
{t(($) => $[`plansCommon.priority.${label}`], { ns: 'billing' })}
</div>
)}
{label !== 'top-priority' && (
<div className="mt-1">
{t(($) => $['plansCommon.documentProcessingPriorityTip'], { ns: 'billing' })}
</div>
)}
</InfotipContent>
</Infotip>
</div>
)
@@ -2,11 +2,11 @@
import type { MeterTone } from '@langgenius/dify-ui/meter'
import type { ComponentType, FC, ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Meter, MeterIndicator, MeterTrack } from '@langgenius/dify-ui/meter'
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 { NUM_INFINITE } from '../config'
type Props = Readonly<{
@@ -167,8 +167,11 @@ const UsageInfo: FC<Props> = ({
<dt className="flex items-center gap-1 system-xs-medium text-text-tertiary">
{name}
{tooltip && (
<Infotip aria-label={tooltip} popupClassName="w-[180px] max-w-[180px]">
{tooltip}
<Infotip>
<InfotipTrigger aria-label={tooltip} />
<InfotipContent aria-label={tooltip} className="w-[180px] max-w-[180px]">
{tooltip}
</InfotipContent>
</Infotip>
)}
</dt>
@@ -2,6 +2,7 @@
import type { FC } from 'react'
import type { RetrievalConfig } from '@/types/app'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { RadioGroup } from '@langgenius/dify-ui/radio-group'
import { Switch } from '@langgenius/dify-ui/switch'
import * as React from 'react'
@@ -9,7 +10,6 @@ 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'
@@ -128,11 +128,16 @@ const RetrievalParamConfig: FC<Props> = ({
<span className="mr-0.5 system-sm-semibold text-text-secondary">
{t(($) => $['modelProvider.rerankModel.key'], { ns: 'common' })}
</span>
<Infotip
aria-label={t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' })}
popupClassName="w-[200px]"
>
{t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' })}
/>
<InfotipContent
aria-label={t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' })}
className="w-[200px]"
>
{t(($) => $['modelProvider.rerankModel.tip'], { ns: 'common' })}
</InfotipContent>
</Infotip>
</div>
</div>
@@ -1,11 +1,11 @@
import type { AppIconType } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { RiAddLine, RiCloseLine } from '@remixicon/react'
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 { LoadingPlaceholder } from '@/app/components/base/loading-placeholder'
import WorkflowPreview from '@/app/components/workflow/workflow-preview'
import { usePipelineTemplateById } from '@/service/use-pipeline'
@@ -99,11 +99,16 @@ const Details = ({ id, type, onApplyTemplate, onClose }: DetailsProps) => {
<span className="system-sm-semibold-uppercase text-text-secondary">
{t(($) => $['details.structure'], { ns: 'datasetPipeline' })}
</span>
<Infotip
aria-label={t(($) => $['details.structureTooltip'], { ns: 'datasetPipeline' })}
popupClassName="max-w-[240px]"
>
{t(($) => $['details.structureTooltip'], { ns: 'datasetPipeline' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['details.structureTooltip'], { ns: 'datasetPipeline' })}
/>
<InfotipContent
aria-label={t(($) => $['details.structureTooltip'], { ns: 'datasetPipeline' })}
className="max-w-[240px]"
>
{t(($) => $['details.structureTooltip'], { ns: 'datasetPipeline' })}
</InfotipContent>
</Infotip>
</div>
<ChunkStructureCard {...chunkStructureConfig[pipelineTemplateInfo.chunk_structure]} />
@@ -1,9 +1,9 @@
import type { FC } from 'react'
import type { IndexingStatusResponse } from '@/models/datasets'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { RiCheckboxCircleFill } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import NotionIcon from '@/app/components/base/notion-icon'
import PriorityLabel from '@/app/components/billing/priority-label'
import { DataSourceType } from '@/models/datasets'
@@ -28,15 +28,20 @@ const StatusIcon: FC<{ status: string; error?: string }> = ({ status, error }) =
const errorLabel = error || t(($) => $.error, { ns: 'common' })
return (
<Infotip
aria-label={t(($) => $.error, { ns: 'common' })}
iconVariant="warning"
iconSize="large"
className="text-text-destructive"
popupClassName="wrap-anywhere whitespace-pre-wrap"
sideOffset={4}
>
{errorLabel}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $.error, { ns: 'common' })}
iconVariant="warning"
iconSize="large"
className="text-text-destructive"
/>
<InfotipContent
aria-label={t(($) => $.error, { ns: 'common' })}
className="wrap-anywhere whitespace-pre-wrap"
sideOffset={4}
>
{errorLabel}
</InfotipContent>
</Infotip>
)
}
@@ -7,11 +7,11 @@ import type {
} from '@/models/datasets'
import { Button } from '@langgenius/dify-ui/button'
import { Checkbox } from '@langgenius/dify-ui/checkbox'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Separator } from '@langgenius/dify-ui/separator'
import { RiAlertFill, RiSearchEyeLine } from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import SummaryIndexSetting from '@/app/components/datasets/settings/summary-index-setting'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { ChunkingMode } from '@/models/datasets'
@@ -189,11 +189,16 @@ export const GeneralChunkingOptions: FC<GeneralChunkingOptionsProps> = ({
onSelect={onDocLanguageChange}
disabled={currentDocForm !== ChunkingMode.qa}
/>
<Infotip
aria-label={t(($) => $['stepTwo.QATip'], { ns: 'datasetCreation' })}
className="size-3.5"
>
{t(($) => $['stepTwo.QATip'], { ns: 'datasetCreation' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['stepTwo.QATip'], { ns: 'datasetCreation' })}
className="size-3.5"
/>
<InfotipContent
aria-label={t(($) => $['stepTwo.QATip'], { ns: 'datasetCreation' })}
>
{t(($) => $['stepTwo.QATip'], { ns: 'datasetCreation' })}
</InfotipContent>
</Infotip>
</div>
{currentDocForm === ChunkingMode.qa && (
@@ -6,6 +6,7 @@ import type {
} from '@langgenius/dify-ui/number-field'
import type { FC, PropsWithChildren, ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Input } from '@langgenius/dify-ui/input'
import {
NumberField,
@@ -18,7 +19,6 @@ import {
} from '@langgenius/dify-ui/number-field'
import { useId, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { env } from '@/env'
const FormField: FC<PropsWithChildren<{ label: ReactNode }>> = (props) => {
@@ -54,11 +54,16 @@ export const DelimiterInput: FC<
<label htmlFor={inputId} className="mr-0.5 system-sm-semibold">
{t(($) => $['stepTwo.separator'], { ns: 'datasetCreation' })}
</label>
<Infotip
aria-label={tooltip || t(($) => $['stepTwo.separatorTip'], { ns: 'datasetCreation' })}
popupClassName="max-w-[200px]"
>
{tooltip || t(($) => $['stepTwo.separatorTip'], { ns: 'datasetCreation' })}
<Infotip>
<InfotipTrigger
aria-label={tooltip || t(($) => $['stepTwo.separatorTip'], { ns: 'datasetCreation' })}
/>
<InfotipContent
aria-label={tooltip || t(($) => $['stepTwo.separatorTip'], { ns: 'datasetCreation' })}
className="max-w-[200px]"
>
{tooltip || t(($) => $['stepTwo.separatorTip'], { ns: 'datasetCreation' })}
</InfotipContent>
</Infotip>
</div>
}
@@ -185,11 +190,16 @@ export const OverlapInput: FC<LabeledCompoundNumberInputProps> = (props) => {
label={
<div className="mb-1 flex items-center">
<span className="system-sm-semibold">{label}</span>
<Infotip
aria-label={t(($) => $['stepTwo.overlapTip'], { ns: 'datasetCreation' })}
popupClassName="max-w-[200px]"
>
{t(($) => $['stepTwo.overlapTip'], { ns: 'datasetCreation' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['stepTwo.overlapTip'], { ns: 'datasetCreation' })}
/>
<InfotipContent
aria-label={t(($) => $['stepTwo.overlapTip'], { ns: 'datasetCreation' })}
className="max-w-[200px]"
>
{t(($) => $['stepTwo.overlapTip'], { ns: 'datasetCreation' })}
</InfotipContent>
</Infotip>
</div>
}
@@ -1,7 +1,7 @@
'use client'
import { Checkbox } from '@langgenius/dify-ui/checkbox'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip } from '@/app/components/base/infotip'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
type Props = Readonly<{
className?: string
@@ -35,8 +35,11 @@ export default function CheckboxWithLabel({
</label>
<div className="ml-1 flex min-w-0 items-center">
{tooltip && (
<Infotip aria-label={tooltip} popupClassName="w-[200px]">
{tooltip}
<Infotip>
<InfotipTrigger aria-label={tooltip} />
<InfotipContent aria-label={tooltip} className="w-[200px]">
{tooltip}
</InfotipContent>
</Infotip>
)}
</div>
@@ -1,8 +1,8 @@
'use client'
import type { FC } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import * as React from 'react'
import { Infotip } from '@/app/components/base/infotip'
import Input from './text-input'
type Props = Readonly<{
@@ -43,8 +43,11 @@ const Field: FC<Props> = ({
<span className="ml-0.5 text-xs font-semibold text-text-destructive">*</span>
)}
{tooltip && (
<Infotip aria-label={tooltip} className="ml-0.5" popupClassName="w-[200px]">
{tooltip}
<Infotip>
<InfotipTrigger aria-label={tooltip} className="ml-0.5" />
<InfotipContent aria-label={tooltip} className="w-[200px]">
{tooltip}
</InfotipContent>
</Infotip>
)}
</div>
@@ -1,7 +1,7 @@
'use client'
import { Checkbox } from '@langgenius/dify-ui/checkbox'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip } from '@/app/components/base/infotip'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
type CheckboxWithLabelProps = {
className?: string
@@ -35,8 +35,11 @@ export default function CheckboxWithLabel({
</label>
<div className="ml-1 flex min-w-0 items-center">
{tooltip && (
<Infotip aria-label={tooltip} popupClassName="w-[200px]">
{tooltip}
<Infotip>
<InfotipTrigger aria-label={tooltip} />
<InfotipContent aria-label={tooltip} className="w-[200px]">
{tooltip}
</InfotipContent>
</Infotip>
)}
</div>
@@ -3,6 +3,7 @@ import type { OperationName } from '../types'
import type { CommonResponse } from '@/models/common'
import type { DocumentDisplayStatus } from '@/models/datasets'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { Switch } from '@langgenius/dify-ui/switch'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
@@ -10,7 +11,6 @@ import { useDebounceFn } from 'ahooks'
import * as React from 'react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { toast } from '@/app/notifications'
import {
useDocumentDelete,
@@ -109,12 +109,11 @@ const StatusItem = ({
{statusItem.text}
</span>
{errorMessage && (
<Infotip
aria-label={errorMessage}
className="ml-1"
popupClassName="max-w-[260px] break-all"
>
{errorMessage}
<Infotip>
<InfotipTrigger aria-label={errorMessage} className="ml-1" />
<InfotipContent aria-label={errorMessage} className="max-w-[260px] break-all">
{errorMessage}
</InfotipContent>
</Infotip>
)}
{scene === 'detail' && (
@@ -5,6 +5,7 @@ import { Button } from '@langgenius/dify-ui/button'
import { Checkbox } from '@langgenius/dify-ui/checkbox'
import { Dialog, DialogClose, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Separator } from '@langgenius/dify-ui/separator'
import { produce } from 'immer'
import * as React from 'react'
@@ -12,7 +13,6 @@ import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from '@/app/notifications'
import { useCreateMetaData } from '@/service/knowledge/use-metadata'
import { Infotip } from '../../../base/infotip'
import useCheckMetadataName from '../hooks/use-check-metadata-name'
import { DatasetMetadataPicker } from '../metadata-dataset/dataset-metadata-picker'
import { UpdateType } from '../types'
@@ -204,14 +204,21 @@ const EditMetadataBatchModal: FC<Props> = ({
{t(($) => $[`${i18nPrefix}.applyToAllSelectDocument`], { ns: 'dataset' })}
</span>
</label>
<Infotip
aria-label={t(($) => $[`${i18nPrefix}.applyToAllSelectDocumentTip`], {
ns: 'dataset',
})}
className="p-px text-text-tertiary"
popupClassName="max-w-[240px]"
>
{t(($) => $[`${i18nPrefix}.applyToAllSelectDocumentTip`], { ns: 'dataset' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $[`${i18nPrefix}.applyToAllSelectDocumentTip`], {
ns: 'dataset',
})}
className="p-px text-text-tertiary"
/>
<InfotipContent
aria-label={t(($) => $[`${i18nPrefix}.applyToAllSelectDocumentTip`], {
ns: 'dataset',
})}
className="max-w-[240px]"
>
{t(($) => $[`${i18nPrefix}.applyToAllSelectDocumentTip`], { ns: 'dataset' })}
</InfotipContent>
</Infotip>
</div>
<div className="flex items-center space-x-2">
@@ -23,6 +23,7 @@ import {
DrawerTitle,
DrawerViewport,
} from '@langgenius/dify-ui/drawer'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Input } from '@langgenius/dify-ui/input'
import { Switch } from '@langgenius/dify-ui/switch'
import { RiAddLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react'
@@ -30,7 +31,6 @@ import { useBoolean, useHover } from 'ahooks'
import * as React from 'react'
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { CreateMetadataModal } from '@/app/components/datasets/metadata/metadata-dataset/create-metadata-modal'
import { toast } from '@/app/notifications'
import { getIconClassName } from '../utils/get-icon'
@@ -265,11 +265,20 @@ const DatasetMetadataDrawer: FC<Props> = ({
<div className="mr-0.5 ml-2 system-sm-semibold text-text-secondary">
{t(($) => $[`${i18nPrefix}.builtIn`], { ns: 'dataset' })}
</div>
<Infotip
aria-label={t(($) => $[`${i18nPrefix}.builtInDescription`], { ns: 'dataset' })}
popupClassName="max-w-[100px]"
>
{t(($) => $[`${i18nPrefix}.builtInDescription`], { ns: 'dataset' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $[`${i18nPrefix}.builtInDescription`], {
ns: 'dataset',
})}
/>
<InfotipContent
aria-label={t(($) => $[`${i18nPrefix}.builtInDescription`], {
ns: 'dataset',
})}
className="max-w-[100px]"
>
{t(($) => $[`${i18nPrefix}.builtInDescription`], { ns: 'dataset' })}
</InfotipContent>
</Infotip>
</div>
@@ -2,11 +2,11 @@
import type { FC } from 'react'
import type { BuiltInMetadataItem, MetadataItemWithValue } from '../types'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Separator } from '@langgenius/dify-ui/separator'
import { RiDeleteBinLine } from '@remixicon/react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import useTimestamp from '@/hooks/use-timestamp'
import { useRouter } from '@/next/navigation'
import InputCombined from '../edit-metadata-batch/input-combined'
@@ -70,8 +70,11 @@ const InfoGroup: FC<Props> = ({
{title}
</div>
{titleTooltip && (
<Infotip aria-label={titleTooltip} popupClassName="max-w-[240px]">
{titleTooltip}
<Infotip>
<InfotipTrigger aria-label={titleTooltip} />
<InfotipContent aria-label={titleTooltip} className="max-w-[240px]">
{titleTooltip}
</InfotipContent>
</Infotip>
)}
</div>
@@ -1,4 +1,5 @@
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import {
NumberField,
NumberFieldControls,
@@ -18,7 +19,6 @@ import {
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
const MIN_KEYWORD_NUMBER = 0
const MAX_KEYWORD_NUMBER = 50
@@ -44,8 +44,9 @@ const KeyWordNumber = ({ keywordNumber, onKeywordNumberChange }: KeyWordNumberPr
<FieldsetLegend className="sr-only">{label}</FieldsetLegend>
<div className="flex grow items-center gap-x-0.5">
<div className="truncate system-xs-medium text-text-secondary">{label}</div>
<Infotip aria-label={label} className="size-3.5">
{label}
<Infotip>
<InfotipTrigger aria-label={label} className="size-3.5" />
<InfotipContent aria-label={label}>{label}</InfotipContent>
</Infotip>
</div>
<Slider
@@ -1,11 +1,11 @@
import type { DefaultModel } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Switch } from '@langgenius/dify-ui/switch'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { useQuery } from '@tanstack/react-query'
import { memo, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { ModelSelector } from '@/app/components/header/account-setting/model-provider-page/model-selector'
import { consoleQuery } from '@/service/console'
@@ -73,11 +73,16 @@ const SummaryIndexSetting = ({
<div className="flex h-6 items-center justify-between">
<div className="flex items-center system-sm-semibold-uppercase text-text-secondary">
{t(($) => $['form.summaryAutoGen'], { ns: 'datasetSettings' })}
<Infotip
aria-label={t(($) => $['form.summaryAutoGenTip'], { ns: 'datasetSettings' })}
className="ml-1"
>
{t(($) => $['form.summaryAutoGenTip'], { ns: 'datasetSettings' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['form.summaryAutoGenTip'], { ns: 'datasetSettings' })}
className="ml-1"
/>
<InfotipContent
aria-label={t(($) => $['form.summaryAutoGenTip'], { ns: 'datasetSettings' })}
>
{t(($) => $['form.summaryAutoGenTip'], { ns: 'datasetSettings' })}
</InfotipContent>
</Infotip>
</div>
<Switch
@@ -13,6 +13,7 @@ import type { NodeOutPutVar } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio-group'
import {
Select,
@@ -24,7 +25,6 @@ import {
SelectTrigger,
} from '@langgenius/dify-ui/select'
import { useCallback, useState } from 'react'
import { Infotip } from '@/app/components/base/infotip'
import { AppSelector } from '@/app/components/plugins/plugin-detail-panel/app-selector'
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector'
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
@@ -158,8 +158,11 @@ function Form<
const infotip = formSchema.tooltip
const infotipText = infotip?.[language] || infotip?.en_US
const infotipContent = infotipText && (
<Infotip aria-label={infotipText} className="ml-1" popupClassName="w-[200px] max-w-[200px]">
{infotipText}
<Infotip>
<InfotipTrigger aria-label={infotipText} className="ml-1" />
<InfotipContent aria-label={infotipText} className="w-[200px] max-w-[200px]">
{infotipText}
</InfotipContent>
</Infotip>
)
if (override) {
@@ -3,6 +3,7 @@ import type { Node, NodeOutPutVar } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio-group'
import {
Select,
@@ -25,7 +26,6 @@ import {
import { Switch } from '@langgenius/dify-ui/switch'
import { useEffect, useId, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import PromptEditor from '@/app/components/base/prompt-editor'
import TagInput from '@/app/components/base/tag-input'
import { BlockEnum } from '@/app/components/workflow/types'
@@ -447,12 +447,17 @@ function ParameterItem({
{sliderLabel}
</div>
{parameterRule.help && (
<Infotip
aria-label={parameterRule.help[language] || parameterRule.help.en_US}
className="mr-1"
popupClassName="w-[150px] whitespace-pre-wrap"
>
{parameterRule.help[language] || parameterRule.help.en_US}
<Infotip>
<InfotipTrigger
aria-label={parameterRule.help[language] || parameterRule.help.en_US}
className="mr-1"
/>
<InfotipContent
aria-label={parameterRule.help[language] || parameterRule.help.en_US}
className="w-[150px] whitespace-pre-wrap"
>
{parameterRule.help[language] || parameterRule.help.en_US}
</InfotipContent>
</Infotip>
)}
</div>
@@ -1,7 +1,7 @@
import type { UsagePriority } from '../use-credential-panel-state'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { PreferredProviderTypeEnum } from '../../declarations'
type UsagePrioritySectionProps = {
@@ -38,7 +38,10 @@ export default function UsagePrioritySection({
<span className="truncate system-sm-medium text-text-secondary">
{t(($) => $['modelProvider.card.usagePriority'], { ns: 'common' })}
</span>
<Infotip aria-label={usagePriorityTip}>{usagePriorityTip}</Infotip>
<Infotip>
<InfotipTrigger aria-label={usagePriorityTip} />
<InfotipContent aria-label={usagePriorityTip}>{usagePriorityTip}</InfotipContent>
</Infotip>
</div>
<div className="flex shrink-0 items-center gap-1">
{options.map((option) => {
@@ -9,6 +9,7 @@ import type {
ModelProvider,
} from '../declarations'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { Switch } from '@langgenius/dify-ui/switch'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
@@ -17,7 +18,6 @@ import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import Badge from '@/app/components/base/badge/index'
import GridMask from '@/app/components/base/grid-mask'
import { Infotip } from '@/app/components/base/infotip'
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
import s from '@/app/components/custom/style.module.css'
import { AddCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
@@ -188,12 +188,17 @@ const ModelLoadBalancingConfigs = ({
<div className="grow">
<div className="flex items-center gap-1 text-sm text-text-primary">
{t(($) => $['modelProvider.loadBalancing'], { ns: 'common' })}
<Infotip
aria-label={t(($) => $['modelProvider.loadBalancingInfo'], { ns: 'common' })}
className="size-3"
iconSize="small"
>
{t(($) => $['modelProvider.loadBalancingInfo'], { ns: 'common' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['modelProvider.loadBalancingInfo'], { ns: 'common' })}
className="size-3"
iconSize="small"
/>
<InfotipContent
aria-label={t(($) => $['modelProvider.loadBalancingInfo'], { ns: 'common' })}
>
{t(($) => $['modelProvider.loadBalancingInfo'], { ns: 'common' })}
</InfotipContent>
</Infotip>
</div>
<div className="text-xs text-text-tertiary">
@@ -4,12 +4,12 @@ import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Dialog, DialogClose, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useQuery } from '@tanstack/react-query'
import { useAtomValue } from 'jotai'
import { parseAsStringLiteral, useQueryState } from 'nuqs'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { toast } from '@/app/notifications'
import { workspacePermissionKeysAtom } from '@/context/permission-state'
import { consoleClient, consoleQuery } from '@/service/console'
@@ -214,12 +214,11 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
return (
<div className="flex min-h-6 items-center text-[13px] font-medium text-text-secondary">
{t(($) => $[labelKey], { ns: 'common' })}
<Infotip
aria-label={tipText}
className="ml-0.5 text-text-tertiary"
popupClassName="w-[261px]"
>
{tipText}
<Infotip>
<InfotipTrigger aria-label={tipText} className="ml-0.5 text-text-tertiary" />
<InfotipContent aria-label={tipText} className="w-[261px]">
{tipText}
</InfotipContent>
</Infotip>
</div>
)
@@ -7,11 +7,11 @@ import type {
} from '@dify/contracts/api/console/workflow-run-archives/types.gen'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { skipToken, useMutation, useQuery, useSuspenseQuery } from '@tanstack/react-query'
import { useQueryState } from 'nuqs'
import { useEffect, useId, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { SkeletonRectangle } from '@/app/components/base/skeleton'
import {
pricingQueryParamName,
@@ -422,12 +422,17 @@ function WorkflowArchiveMonthRow({ archive }: { archive: WorkflowRunArchiveMonth
)}
<span id={downloadActionLabelId}>{buttonContent}</span>
</Button>
<Infotip
aria-label={`${t(($) => $['operation.learnMore'], { ns: 'common' })}: ${archiveMonth}`}
iconVariant={isFailed ? 'warning' : 'information'}
popupClassName="wrap-anywhere whitespace-pre-wrap"
>
{downloadHint}
<Infotip>
<InfotipTrigger
aria-label={`${t(($) => $['operation.learnMore'], { ns: 'common' })}: ${archiveMonth}`}
iconVariant={isFailed ? 'warning' : 'information'}
/>
<InfotipContent
aria-label={`${t(($) => $['operation.learnMore'], { ns: 'common' })}: ${archiveMonth}`}
className="wrap-anywhere whitespace-pre-wrap"
>
{downloadHint}
</InfotipContent>
</Infotip>
</div>
</td>
@@ -4,10 +4,10 @@ import type { NodeOutPutVar } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from '@langgenius/dify-ui/collapsible'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Separator } from '@langgenius/dify-ui/separator'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import { useMCPToolAvailability } from '@/app/components/workflow/nodes/_base/components/mcp-tool-availability'
import { useRefWithInit } from '@/hooks/use-ref-with-init'
@@ -153,11 +153,14 @@ const MultipleToolSelector = ({
</>
)}
{tooltip ? (
<Infotip
aria-label={typeof tooltip === 'string' ? tooltip : label}
className="size-3.5"
>
{tooltip}
<Infotip>
<InfotipTrigger
aria-label={typeof tooltip === 'string' ? tooltip : label}
className="size-3.5"
/>
<InfotipContent aria-label={typeof tooltip === 'string' ? tooltip : label}>
{tooltip}
</InfotipContent>
</Infotip>
) : null}
</div>
@@ -2,6 +2,7 @@ import type { TriggerSubscriptionBuilder } from '@/app/components/workflow/block
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import {
Select,
SelectContent,
@@ -14,7 +15,6 @@ import { useBoolean } from 'ahooks'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Badge from '@/app/components/base/badge'
import { Infotip } from '@/app/components/base/infotip'
import { toast } from '@/app/notifications'
import { openOAuthPopup } from '@/hooks/use-oauth'
import {
@@ -184,13 +184,20 @@ export const CreateSubscriptionButton = ({
ns: 'pluginTrigger',
}),
extra: (
<Infotip
aria-label={t(($) => $['subscription.addType.options.manual.tip'], {
ns: 'pluginTrigger',
})}
className="size-3.5"
>
{t(($) => $['subscription.addType.options.manual.tip'], { ns: 'pluginTrigger' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['subscription.addType.options.manual.tip'], {
ns: 'pluginTrigger',
})}
className="size-3.5"
/>
<InfotipContent
aria-label={t(($) => $['subscription.addType.options.manual.tip'], {
ns: 'pluginTrigger',
})}
>
{t(($) => $['subscription.addType.options.manual.tip'], { ns: 'pluginTrigger' })}
</InfotipContent>
</Infotip>
),
show: supportedMethods.includes(SupportedCreationMethods.MANUAL),
@@ -1,9 +1,9 @@
'use client'
import type { PluginDetail } from '@/app/components/plugins/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { CreateSubscriptionButton } from './create'
import { CreateButtonType } from './create/types'
import SubscriptionCard from './subscription-card'
@@ -31,11 +31,16 @@ export const SubscriptionListView: React.FC<SubscriptionListViewProps> = ({
<span className="system-sm-semibold-uppercase text-text-secondary">
{t(($) => $['subscription.listNum'], { ns: 'pluginTrigger', num: subscriptionCount })}
</span>
<Infotip
aria-label={t(($) => $['subscription.list.tip'], { ns: 'pluginTrigger' })}
className="size-3.5"
>
{t(($) => $['subscription.list.tip'], { ns: 'pluginTrigger' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['subscription.list.tip'], { ns: 'pluginTrigger' })}
className="size-3.5"
/>
<InfotipContent
aria-label={t(($) => $['subscription.list.tip'], { ns: 'pluginTrigger' })}
>
{t(($) => $['subscription.list.tip'], { ns: 'pluginTrigger' })}
</InfotipContent>
</Infotip>
</div>
)}
@@ -2,10 +2,10 @@
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { CreateSubscriptionButton } from './create'
import { CreateButtonType } from './create/types'
import { DeleteConfirm } from './delete-confirm'
@@ -33,11 +33,16 @@ export const SubscriptionSelectorView: React.FC<SubscriptionSelectorProps> = ({
<span className="system-sm-semibold-uppercase text-text-secondary">
{t(($) => $['subscription.listNum'], { ns: 'pluginTrigger', num: subscriptionCount })}
</span>
<Infotip
aria-label={t(($) => $['subscription.list.tip'], { ns: 'pluginTrigger' })}
className="size-3.5"
>
{t(($) => $['subscription.list.tip'], { ns: 'pluginTrigger' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['subscription.list.tip'], { ns: 'pluginTrigger' })}
className="size-3.5"
/>
<InfotipContent
aria-label={t(($) => $['subscription.list.tip'], { ns: 'pluginTrigger' })}
>
{t(($) => $['subscription.list.tip'], { ns: 'pluginTrigger' })}
</InfotipContent>
</Infotip>
</div>
<CreateSubscriptionButton buttonType={CreateButtonType.ICON_BUTTON} shape="circle" />
@@ -4,6 +4,7 @@ import type { ToolFormSchema } from '@/app/components/tools/utils/to-form-schema
import type { SchemaRoot } from '@/app/components/workflow/nodes/llm/types'
import type { NodeOutPutVar, ValueSelector } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Input } from '@langgenius/dify-ui/input'
import {
Select,
@@ -19,7 +20,6 @@ import { useSuspenseQuery } from '@tanstack/react-query'
import { useBoolean } from 'ahooks'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { AppSelector } from '@/app/components/plugins/plugin-detail-panel/app-selector'
@@ -148,8 +148,11 @@ const ReasoningConfigForm: React.FC<Props> = ({
const fieldTitle = getFieldTitle(label, language)
const tooltipText = tooltip?.[language] || tooltip?.en_US
const tooltipContent = tooltipText && (
<Infotip aria-label={tooltipText} className="ml-0.5 size-4" popupClassName="w-[200px]">
{tooltipText}
<Infotip>
<InfotipTrigger aria-label={tooltipText} className="ml-0.5 size-4" />
<InfotipContent aria-label={tooltipText} className="w-[200px]">
{tooltipText}
</InfotipContent>
</Infotip>
)
const varInput = value[variable]!.value
@@ -3,12 +3,12 @@ import type { Node } from '@/app/components/workflow/types'
import type { InputVar, RAGPipelineVariables } from '@/models/pipeline'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Separator } from '@langgenius/dify-ui/separator'
import { RiCloseLine, RiEyeLine } from '@remixicon/react'
import { memo, useCallback, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { useNodes } from 'reactflow'
import { Infotip } from '@/app/components/base/infotip'
import { useHooksStore } from '@/app/components/workflow/hooks-store'
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
import { useStore } from '@/app/components/workflow/store'
@@ -133,13 +133,20 @@ const InputFieldPanel = () => {
<span className="system-sm-semibold-uppercase text-text-secondary">
{t(($) => $['inputFieldPanel.uniqueInputs.title'], { ns: 'datasetPipeline' })}
</span>
<Infotip
aria-label={t(($) => $['inputFieldPanel.uniqueInputs.tooltip'], {
ns: 'datasetPipeline',
})}
popupClassName="max-w-[240px]"
>
{t(($) => $['inputFieldPanel.uniqueInputs.tooltip'], { ns: 'datasetPipeline' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['inputFieldPanel.uniqueInputs.tooltip'], {
ns: 'datasetPipeline',
})}
/>
<InfotipContent
aria-label={t(($) => $['inputFieldPanel.uniqueInputs.tooltip'], {
ns: 'datasetPipeline',
})}
className="max-w-[240px]"
>
{t(($) => $['inputFieldPanel.uniqueInputs.tooltip'], { ns: 'datasetPipeline' })}
</InfotipContent>
</Infotip>
</div>
<div className="flex flex-col gap-y-1 py-1">
@@ -1,6 +1,6 @@
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
const GlobalInputs = () => {
const { t } = useTranslation()
@@ -10,11 +10,20 @@ const GlobalInputs = () => {
<span className="system-sm-semibold-uppercase text-text-secondary">
{t(($) => $['inputFieldPanel.globalInputs.title'], { ns: 'datasetPipeline' })}
</span>
<Infotip
aria-label={t(($) => $['inputFieldPanel.globalInputs.tooltip'], { ns: 'datasetPipeline' })}
popupClassName="w-[240px]"
>
{t(($) => $['inputFieldPanel.globalInputs.tooltip'], { ns: 'datasetPipeline' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['inputFieldPanel.globalInputs.tooltip'], {
ns: 'datasetPipeline',
})}
/>
<InfotipContent
aria-label={t(($) => $['inputFieldPanel.globalInputs.tooltip'], {
ns: 'datasetPipeline',
})}
className="w-[240px]"
>
{t(($) => $['inputFieldPanel.globalInputs.tooltip'], { ns: 'datasetPipeline' })}
</InfotipContent>
</Infotip>
</div>
)
@@ -14,6 +14,7 @@ import {
} from '@langgenius/dify-ui/drawer'
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Input } from '@langgenius/dify-ui/input'
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio-group'
import {
@@ -25,7 +26,6 @@ import {
} from '@langgenius/dify-ui/scroll-area'
import { useId, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { AuthHeaderPrefix, AuthType } from '@/app/components/tools/types'
type Props = Readonly<{
@@ -213,14 +213,21 @@ export default function ConfigCredential({ positionCenter, credential, onChange,
<label htmlFor={apiKeyHeaderInputId}>
{t(($) => $['createTool.authMethod.key'], { ns: 'tools' })}
</label>
<Infotip
aria-label={t(($) => $['createTool.authMethod.keyTooltip'], {
ns: 'tools',
})}
className="ml-0.5 size-4"
popupClassName="w-[261px] text-text-tertiary"
>
{t(($) => $['createTool.authMethod.keyTooltip'], { ns: 'tools' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['createTool.authMethod.keyTooltip'], {
ns: 'tools',
})}
className="ml-0.5 size-4"
/>
<InfotipContent
aria-label={t(($) => $['createTool.authMethod.keyTooltip'], {
ns: 'tools',
})}
className="w-[261px] text-text-tertiary"
>
{t(($) => $['createTool.authMethod.keyTooltip'], { ns: 'tools' })}
</InfotipContent>
</Infotip>
</div>
<Input
@@ -273,16 +280,23 @@ export default function ConfigCredential({ positionCenter, credential, onChange,
<label htmlFor={apiKeyQueryInputId}>
{t(($) => $['createTool.authMethod.queryParam'], { ns: 'tools' })}
</label>
<Infotip
aria-label={t(($) => $['createTool.authMethod.queryParamTooltip'], {
ns: 'tools',
})}
className="ml-0.5 size-4"
popupClassName="w-[261px] text-text-tertiary"
>
{t(($) => $['createTool.authMethod.queryParamTooltip'], {
ns: 'tools',
})}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['createTool.authMethod.queryParamTooltip'], {
ns: 'tools',
})}
className="ml-0.5 size-4"
/>
<InfotipContent
aria-label={t(($) => $['createTool.authMethod.queryParamTooltip'], {
ns: 'tools',
})}
className="w-[261px] text-text-tertiary"
>
{t(($) => $['createTool.authMethod.queryParamTooltip'], {
ns: 'tools',
})}
</InfotipContent>
</Infotip>
</div>
<Input
@@ -21,6 +21,7 @@ import {
DrawerViewport,
} from '@langgenius/dify-ui/drawer'
import { Field, FieldError, FieldLabel } from '@langgenius/dify-ui/field'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Input } from '@langgenius/dify-ui/input'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { produce } from 'immer'
@@ -29,7 +30,6 @@ import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
import AppIconPicker from '@/app/components/base/app-icon-picker'
import { Infotip } from '@/app/components/base/infotip'
import LabelSelector from '@/app/components/tools/labels/selector'
import ConfirmModal from '@/app/components/tools/workflow-tool/confirm-modal'
import MethodSelector from '@/app/components/tools/workflow-tool/method-selector'
@@ -88,8 +88,11 @@ type WorkflowToolDrawerFrameProps = {
const InfoTooltip = ({ children }: { children: string }) => {
return (
<Infotip aria-label={children} className="ml-1 size-3.5" popupClassName="w-[180px]">
{children}
<Infotip>
<InfotipTrigger aria-label={children} className="ml-1 size-3.5" />
<InfotipContent aria-label={children} className="w-[180px]">
{children}
</InfotipContent>
</Infotip>
)
}
@@ -182,33 +185,38 @@ const WorkflowToolOutputName = React.memo(
</span>
)}
{hasReservedNameConflict || hasDuplicateNameConflict ? (
<Infotip
aria-label={issueLabel}
className="text-text-warning-secondary"
iconSize="small"
iconVariant="warning"
popupClassName={hasDuplicateNameConflict ? 'w-60' : 'w-45'}
>
<div className="space-y-2">
{hasReservedNameConflict ? <p>{reservedOutputDuplicateTip}</p> : null}
{hasDuplicateNameConflict ? (
<div className="space-y-1.5">
<p>{duplicateOutputTip}</p>
{sources.length > 0 ? (
<ul className="space-y-1">
{sources.map((source) => {
const sourceTitle = getSourceNodeDisplayName(source, sources)
return (
<li key={source.nodeId} className="wrap-break-word">
{sourceNodeLabel}: <span translate="no">{sourceTitle}</span>
</li>
)
})}
</ul>
) : null}
</div>
) : null}
</div>
<Infotip>
<InfotipTrigger
aria-label={issueLabel}
className="text-text-warning-secondary"
iconSize="small"
iconVariant="warning"
/>
<InfotipContent
aria-label={issueLabel}
className={hasDuplicateNameConflict ? 'w-60' : 'w-45'}
>
<div className="space-y-2">
{hasReservedNameConflict ? <p>{reservedOutputDuplicateTip}</p> : null}
{hasDuplicateNameConflict ? (
<div className="space-y-1.5">
<p>{duplicateOutputTip}</p>
{sources.length > 0 ? (
<ul className="space-y-1">
{sources.map((source) => {
const sourceTitle = getSourceNodeDisplayName(source, sources)
return (
<li key={source.nodeId} className="wrap-break-word">
{sourceNodeLabel}: <span translate="no">{sourceTitle}</span>
</li>
)
})}
</ul>
) : null}
</div>
) : null}
</div>
</InfotipContent>
</Infotip>
) : null}
</div>
@@ -7,11 +7,11 @@ import type {
ListRef,
} from '@/app/components/workflow/block-selector/marketplace-plugin/list'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { useSuspenseQuery } from '@tanstack/react-query'
import { memo, useEffect, useMemo, useRef, useState } from 'react'
import { memo, useEffect, useId, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { SearchInput } from '@/app/components/base/search-input'
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hooks'
@@ -32,25 +32,30 @@ const DEFAULT_TAGS: ListProps['tags'] = []
const NotFoundWarn = (props: { title: string; description: ReactNode }) => {
const { title, description } = props
const titleId = useId()
const { t } = useTranslation()
return (
<Infotip
aria-label={title}
iconVariant="warning"
iconSize="large"
className="text-text-destructive"
popupClassName="w-45"
>
<div className="space-y-1">
<h3 className="font-semibold text-text-primary">{title}</h3>
<p>{description}</p>
<p>
<Link href="/plugins" className="text-text-accent">
{t(($) => $['nodes.agent.linkToPlugin'], { ns: 'workflow' })}
</Link>
</p>
</div>
<Infotip>
<InfotipTrigger
aria-label={title}
iconVariant="warning"
iconSize="large"
className="text-text-destructive"
/>
<InfotipContent aria-labelledby={titleId} className="w-45">
<div className="space-y-1">
<h3 id={titleId} className="font-semibold text-text-primary">
{title}
</h3>
<p>{description}</p>
<p>
<Link href="/plugins" className="text-text-accent">
{t(($) => $['nodes.agent.linkToPlugin'], { ns: 'workflow' })}
</Link>
</p>
</div>
</InfotipContent>
</Infotip>
)
}
@@ -1,7 +1,7 @@
import type { DefaultValueForm } from './types'
import type { CommonNodeType, Node } from '@/app/components/workflow/types'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import {
Collapse,
CollapseActions,
@@ -43,8 +43,15 @@ const ErrorHandle = ({ id, data }: ErrorHandleProps) => {
</CollapseTitle>
{!!error_strategy && <CollapseIndicator />}
</CollapseTrigger>
<Infotip aria-label={t(($) => $['nodes.common.errorHandle.tip'], { ns: 'workflow' })}>
{t(($) => $['nodes.common.errorHandle.tip'], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['nodes.common.errorHandle.tip'], { ns: 'workflow' })}
/>
<InfotipContent
aria-label={t(($) => $['nodes.common.errorHandle.tip'], { ns: 'workflow' })}
>
{t(($) => $['nodes.common.errorHandle.tip'], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
<CollapseActions>
<div className="pr-4">
@@ -1,10 +1,10 @@
'use client'
import type { FC, ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { RiArrowDownSLine } from '@remixicon/react'
import * as React from 'react'
import { useState } from 'react'
import { Infotip } from '@/app/components/base/infotip'
type Props = Readonly<{
className?: string
@@ -66,8 +66,9 @@ const Field: FC<Props> = ({
{title} {required && <span className="text-text-destructive">*</span>}
</div>
{!!tooltip && !!tooltipLabel && (
<Infotip aria-label={tooltipLabel} className="ml-1">
{tooltip}
<Infotip>
<InfotipTrigger aria-label={tooltipLabel} className="ml-1" />
<InfotipContent aria-label={tooltipLabel}>{tooltip}</InfotipContent>
</Infotip>
)}
</div>
@@ -1,7 +1,7 @@
import type { ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { memo, useState } from 'react'
import { Infotip } from '@/app/components/base/infotip'
export type FieldTitleProps = {
title?: string
@@ -57,8 +57,9 @@ export const FieldTitle = memo(
/>
)}
{tooltip && (
<Infotip aria-label={tooltip} className="ml-1">
{tooltip}
<Infotip>
<InfotipTrigger aria-label={tooltip} className="ml-1" />
<InfotipContent aria-label={tooltip}>{tooltip}</InfotipContent>
</Infotip>
)}
</div>
@@ -2,10 +2,10 @@
import type { VariantProps } from 'class-variance-authority'
import type { FC } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { cva } from 'class-variance-authority'
import * as React from 'react'
import { useCallback } from 'react'
import { Infotip } from '@/app/components/base/infotip'
const variants = cva([], {
variants: {
@@ -63,8 +63,11 @@ const OptionCard: FC<Props> = ({
>
<span>{title}</span>
{tooltip && (
<Infotip aria-label={tooltip} popupClassName="w-[240px]">
{tooltip}
<Infotip>
<InfotipTrigger aria-label={tooltip} />
<InfotipContent aria-label={tooltip} className="w-[240px]">
{tooltip}
</InfotipContent>
</Infotip>
)}
</div>
@@ -2,7 +2,8 @@ import type { SelectorParam } 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 { Infotip } from '@/app/components/base/infotip'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useId } from 'react'
import { BlockEnum, NodeRunningStatus } from '@/app/components/workflow/types'
type HeaderMetaProps = {
@@ -19,6 +20,7 @@ export type WorkflowTranslator = (
) => string
export const NodeHeaderMeta = ({ data, hasVarValue, isLoading, loopIndex, t }: HeaderMetaProps) => {
const titleId = useId()
return (
<>
{data.type === BlockEnum.Iteration && (data as IterationNodeType).is_parallel && (
@@ -26,14 +28,18 @@ export const NodeHeaderMeta = ({ data, hasVarValue, isLoading, loopIndex, t }: H
<span className="rounded-[5px] border border-text-warning px-1.25 py-0.75 system-2xs-medium-uppercase text-text-warning">
{t(($) => $['nodes.iteration.parallelModeUpper'], { ns: 'workflow' })}
</span>
<Infotip
aria-label={t(($) => $['nodes.iteration.parallelModeEnableTitle'], { ns: 'workflow' })}
popupClassName="w-45"
>
<div className="font-semibold text-text-primary">
{t(($) => $['nodes.iteration.parallelModeEnableTitle'], { ns: 'workflow' })}
</div>
{t(($) => $['nodes.iteration.parallelModeEnableDesc'], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['nodes.iteration.parallelModeEnableTitle'], {
ns: 'workflow',
})}
/>
<InfotipContent aria-labelledby={titleId} className="w-45">
<div id={titleId} className="font-semibold text-text-primary">
{t(($) => $['nodes.iteration.parallelModeEnableTitle'], { ns: 'workflow' })}
</div>
{t(($) => $['nodes.iteration.parallelModeEnableDesc'], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
</div>
)}
@@ -4,12 +4,12 @@ import type { AgentOutputTypeOptionValue } from '@/app/components/base/prompt-ed
import type { WorkflowNodesMap } from '@/app/components/base/prompt-editor/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { useBoolean } from 'ahooks'
import { $insertNodes } from 'lexical'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import PromptEditor from '@/app/components/base/prompt-editor'
import { $createCustomTextNode } from '@/app/components/base/prompt-editor/plugins/custom-text/node'
import { useDocLink } from '@/context/i18n'
@@ -97,17 +97,24 @@ export function AgentTaskField({
<FieldLabel className="min-w-0 py-1 system-sm-semibold-uppercase! text-text-secondary">
{t(($) => $[`${i18nPrefix}.task.label`], { ns: 'workflow' })}
</FieldLabel>
<Infotip aria-label={t(($) => $[`${i18nPrefix}.task.tooltip`], { ns: 'workflow' })}>
<span>{t(($) => $[`${i18nPrefix}.task.tooltip`], { ns: 'workflow' })}</span>{' '}
<a
href={docLink('/use-dify/nodes/agent#give-it-a-task')}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-0.5 rounded-sm text-text-accent hover:underline focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden"
<Infotip>
<InfotipTrigger
aria-label={t(($) => $[`${i18nPrefix}.task.tooltip`], { ns: 'workflow' })}
/>
<InfotipContent
aria-label={t(($) => $[`${i18nPrefix}.task.tooltip`], { ns: 'workflow' })}
>
{t(($) => $[`${i18nPrefix}.task.learnMore`], { ns: 'workflow' })}
<span aria-hidden className="i-ri-external-link-line size-3" />
</a>
<span>{t(($) => $[`${i18nPrefix}.task.tooltip`], { ns: 'workflow' })}</span>{' '}
<a
href={docLink('/use-dify/nodes/agent#give-it-a-task')}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-0.5 rounded-sm text-text-accent hover:underline focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden"
>
{t(($) => $[`${i18nPrefix}.task.learnMore`], { ns: 'workflow' })}
<span aria-hidden className="i-ri-external-link-line size-3" />
</a>
</InfotipContent>
</Infotip>
</div>
<div
@@ -1,9 +1,9 @@
import type { DeliveryMethod, DeliveryMethodType, FormInputItem } from '../../types'
import type { Node, NodeOutPutVar } from '@/app/components/workflow/types'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { produce } from 'immer'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { useNodesSyncDraft } from '../../../../hooks/use-nodes-sync-draft'
import MethodItem from './method-item'
import MethodSelector from './method-selector'
@@ -66,10 +66,15 @@ const DeliveryMethodForm: React.FC<Props> = ({
<div className="system-sm-semibold-uppercase text-text-secondary">
{t(($) => $[`${i18nPrefix}.deliveryMethod.title`], { ns: 'workflow' })}
</div>
<Infotip
aria-label={t(($) => $[`${i18nPrefix}.deliveryMethod.tooltip`], { ns: 'workflow' })}
>
{t(($) => $[`${i18nPrefix}.deliveryMethod.tooltip`], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $[`${i18nPrefix}.deliveryMethod.tooltip`], { ns: 'workflow' })}
/>
<InfotipContent
aria-label={t(($) => $[`${i18nPrefix}.deliveryMethod.tooltip`], { ns: 'workflow' })}
>
{t(($) => $[`${i18nPrefix}.deliveryMethod.tooltip`], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
</div>
{!readonly && (
@@ -4,6 +4,7 @@ import type { NodePanelProps, Var } from '@/app/components/workflow/types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Separator } from '@langgenius/dify-ui/separator'
import {
RiAddLine,
@@ -16,7 +17,6 @@ import copy from 'copy-to-clipboard'
import * as React from 'react'
import { useCallback, useState } 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 useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
@@ -115,10 +115,15 @@ const Panel: FC<NodePanelProps<HumanInputNodeType>> = ({ id, data }) => {
<div className="system-sm-semibold-uppercase text-text-secondary">
{t(($) => $[`${i18nPrefix}.formContent.title`], { ns: 'workflow' })}
</div>
<Infotip
aria-label={t(($) => $[`${i18nPrefix}.formContent.tooltip`], { ns: 'workflow' })}
>
{t(($) => $[`${i18nPrefix}.formContent.tooltip`], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $[`${i18nPrefix}.formContent.tooltip`], { ns: 'workflow' })}
/>
<InfotipContent
aria-label={t(($) => $[`${i18nPrefix}.formContent.tooltip`], { ns: 'workflow' })}
>
{t(($) => $[`${i18nPrefix}.formContent.tooltip`], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
</div>
{!readOnly && (
@@ -193,10 +198,15 @@ const Panel: FC<NodePanelProps<HumanInputNodeType>> = ({ id, data }) => {
<div className="system-sm-semibold-uppercase text-text-secondary">
{t(($) => $[`${i18nPrefix}.userActions.title`], { ns: 'workflow' })}
</div>
<Infotip
aria-label={t(($) => $[`${i18nPrefix}.userActions.tooltip`], { ns: 'workflow' })}
>
{t(($) => $[`${i18nPrefix}.userActions.tooltip`], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $[`${i18nPrefix}.userActions.tooltip`], { ns: 'workflow' })}
/>
<InfotipContent
aria-label={t(($) => $[`${i18nPrefix}.userActions.tooltip`], { ns: 'workflow' })}
>
{t(($) => $[`${i18nPrefix}.userActions.tooltip`], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
</div>
{!readOnly && (
@@ -1,5 +1,6 @@
import { cn } from '@langgenius/dify-ui/cn'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import {
NumberField,
NumberFieldControls,
@@ -19,7 +20,6 @@ import {
import { memo, useCallback, useId } from 'react'
import { useTranslation } from 'react-i18next'
import { Economic, HighQuality } from '@/app/components/base/icons/src/vender/knowledge'
import { Infotip } from '@/app/components/base/infotip'
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
import { ChunkStructureEnum, IndexMethodEnum } from '../types'
import OptionCard from './option-card'
@@ -113,8 +113,11 @@ const IndexMethod = ({
>
{keywordNumberLabel}
</label>
<Infotip aria-label={keywordNumberLabel} className="ml-0.5 size-3.5">
{keywordNumberLabel}
<Infotip>
<InfotipTrigger aria-label={keywordNumberLabel} className="ml-0.5 size-3.5" />
<InfotipContent aria-label={keywordNumberLabel}>
{keywordNumberLabel}
</InfotipContent>
</Infotip>
</div>
<Slider
@@ -6,6 +6,7 @@ import type { HybridSearchModeOption, Option } from './type'
import { cn } from '@langgenius/dify-ui/cn'
import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { RadioControl, RadioGroup, RadioItem } from '@langgenius/dify-ui/radio-group'
import { Switch } from '@langgenius/dify-ui/switch'
import { useTranslation } from 'react-i18next'
@@ -18,7 +19,6 @@ import {
OptionCardEffectPurple,
OptionCardEffectTeal,
} from '@/app/components/base/icons/src/public/knowledge'
import { Infotip } from '@/app/components/base/infotip'
import { DEFAULT_WEIGHTED_SCORE } from '@/models/datasets'
import { HybridSearchModeEnum, RetrievalSearchMethodEnum } from '../../types'
import RerankingModelSelector from './reranking-model-selector'
@@ -282,8 +282,12 @@ export function SearchMethodOption({
/>
<span className="truncate">{rerankModelLabel}</span>
</FieldLabel>
<Infotip aria-label={rerankModelTip} className="ml-0.5 size-3.5 shrink-0">
{rerankModelTip}
<Infotip>
<InfotipTrigger
aria-label={rerankModelTip}
className="ml-0.5 size-3.5 shrink-0"
/>
<InfotipContent aria-label={rerankModelTip}>{rerankModelTip}</InfotipContent>
</Infotip>
</div>
</Field>
@@ -1,5 +1,6 @@
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import {
NumberField,
NumberFieldControls,
@@ -10,7 +11,6 @@ import {
} from '@langgenius/dify-ui/number-field'
import { Switch } from '@langgenius/dify-ui/switch'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { env } from '@/env'
export type TopKFieldProps = {
@@ -67,8 +67,9 @@ export function TopKAndScoreThreshold({
<Field name="top_k" className="gap-0">
<div className="mb-0.5 flex h-6 items-center">
<FieldLabel className="py-0 system-xs-medium text-text-secondary">{topKLabel}</FieldLabel>
<Infotip aria-label={topKTip} className="ml-0.5 size-3.5">
{topKTip}
<Infotip>
<InfotipTrigger aria-label={topKTip} className="ml-0.5 size-3.5" />
<InfotipContent aria-label={topKTip}>{topKTip}</InfotipContent>
</Infotip>
</div>
<NumberField
@@ -102,8 +103,9 @@ export function TopKAndScoreThreshold({
/>
<span className="grow truncate">{scoreThresholdLabel}</span>
</FieldLabel>
<Infotip aria-label={scoreThresholdTip} className="ml-0.5 size-3.5">
{scoreThresholdTip}
<Infotip>
<InfotipTrigger aria-label={scoreThresholdTip} className="ml-0.5 size-3.5" />
<InfotipContent aria-label={scoreThresholdTip}>{scoreThresholdTip}</InfotipContent>
</Infotip>
</div>
</Field>
@@ -1,8 +1,8 @@
import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { noop } from 'es-toolkit/function'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import {
Collapse,
@@ -58,11 +58,16 @@ const MetadataFilter = ({
</CollapseTitle>
{metadataFilterMode === MetadataFilteringModeEnum.automatic && <CollapseIndicator />}
</CollapseTrigger>
<Infotip
aria-label={t(($) => $['nodes.knowledgeRetrieval.metadata.tip'], { ns: 'workflow' })}
popupClassName="w-[200px]"
>
{t(($) => $['nodes.knowledgeRetrieval.metadata.tip'], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['nodes.knowledgeRetrieval.metadata.tip'], { ns: 'workflow' })}
/>
<InfotipContent
aria-label={t(($) => $['nodes.knowledgeRetrieval.metadata.tip'], { ns: 'workflow' })}
className="w-[200px]"
>
{t(($) => $['nodes.knowledgeRetrieval.metadata.tip'], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
<CollapseActions>
<div className="flex items-center pr-4">
@@ -2,10 +2,10 @@
import type { SelectorParam } from 'i18next'
import type { FC } from 'react'
import type { ModelConfig, Node, NodeOutPutVar, PromptItem, Variable } from '../../../types'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
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'
@@ -127,8 +127,11 @@ const ConfigPromptItem: FC<Props> = ({
)}
{roleDescription && (
<Infotip aria-label={roleDescription} popupClassName="w-[180px]">
{roleDescription}
<Infotip>
<InfotipTrigger aria-label={roleDescription} />
<InfotipContent aria-label={roleDescription} className="w-[180px]">
{roleDescription}
</InfotipContent>
</Infotip>
)}
</div>
@@ -2,12 +2,12 @@ import type { FC } from 'react'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { Model } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Textarea } from '@langgenius/dify-ui/textarea'
import { RiCloseLine, RiSparklingFill } from '@remixicon/react'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
type ModelInfo = {
@@ -83,11 +83,16 @@ const PromptEditor: FC<PromptEditorProps> = ({
<div className="flex flex-col gap-y-1 px-4 py-2">
<div className="flex h-6 items-center system-sm-semibold-uppercase text-text-secondary">
<span>{t(($) => $['nodes.llm.jsonSchema.instruction'], { ns: 'workflow' })}</span>
<Infotip
aria-label={t(($) => $['nodes.llm.jsonSchema.promptTooltip'], { ns: 'workflow' })}
className="size-3.5"
>
{t(($) => $['nodes.llm.jsonSchema.promptTooltip'], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['nodes.llm.jsonSchema.promptTooltip'], { ns: 'workflow' })}
className="size-3.5"
/>
<InfotipContent
aria-label={t(($) => $['nodes.llm.jsonSchema.promptTooltip'], { ns: 'workflow' })}
>
{t(($) => $['nodes.llm.jsonSchema.promptTooltip'], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
</div>
<div className="flex items-center">
@@ -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 { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
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'
import { FlowType } from '@/types/common'
@@ -71,8 +71,15 @@ const PanelMemorySection: FC<Props> = ({
<div className="text-xs font-semibold text-text-secondary uppercase">
{t(($) => $['nodes.common.memories.title'], { ns: 'workflow' })}
</div>
<Infotip aria-label={t(($) => $['nodes.common.memories.tip'], { ns: 'workflow' })}>
{t(($) => $['nodes.common.memories.tip'], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['nodes.common.memories.tip'], { ns: 'workflow' })}
/>
<InfotipContent
aria-label={t(($) => $['nodes.common.memories.tip'], { ns: 'workflow' })}
>
{t(($) => $['nodes.common.memories.tip'], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
</div>
<div className="flex h-4.5 items-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1 text-xs font-semibold text-text-tertiary uppercase">
@@ -84,11 +91,16 @@ 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>
<Infotip
aria-label={t(($) => $['nodes.llm.roleDescription.user'], { ns: 'workflow' })}
popupClassName="w-[180px]"
>
{t(($) => $['nodes.llm.roleDescription.user'], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['nodes.llm.roleDescription.user'], { ns: 'workflow' })}
/>
<InfotipContent
aria-label={t(($) => $['nodes.llm.roleDescription.user'], { ns: 'workflow' })}
className="w-[180px]"
>
{t(($) => $['nodes.llm.roleDescription.user'], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
</div>
}
@@ -1,9 +1,9 @@
import type { FC } from 'react'
import type { LLMNodeType, StructuredOutput } from '../types'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Switch } from '@langgenius/dify-ui/switch'
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'
@@ -30,6 +30,7 @@ const PanelOutputSection: FC<Props> = ({
handleStructureOutputChange,
}) => {
const { t } = useTranslation()
const warningTitleId = React.useId()
return (
<>
@@ -40,29 +41,36 @@ const PanelOutputSection: FC<Props> = ({
operations={
<div className="mr-4 flex shrink-0 items-center">
{!isModelSupportStructuredOutput && !!inputs.structured_output_enabled && (
<Infotip
aria-label={t(($) => $['structOutput.modelNotSupported'], { ns: 'app' })}
iconVariant="warning"
iconSize="large"
className="mr-1 text-text-warning-secondary"
popupClassName="w-58"
>
<div className="title-xs-semi-bold text-text-primary">
{t(($) => $['structOutput.modelNotSupported'], { ns: 'app' })}
</div>
<div className="mt-1">
{t(($) => $['structOutput.modelNotSupportedTip'], { ns: 'app' })}
</div>
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['structOutput.modelNotSupported'], { ns: 'app' })}
iconVariant="warning"
iconSize="large"
className="mr-1 text-text-warning-secondary"
/>
<InfotipContent aria-labelledby={warningTitleId} className="w-58">
<div id={warningTitleId} className="title-xs-semi-bold text-text-primary">
{t(($) => $['structOutput.modelNotSupported'], { ns: 'app' })}
</div>
<div className="mt-1">
{t(($) => $['structOutput.modelNotSupportedTip'], { ns: 'app' })}
</div>
</InfotipContent>
</Infotip>
)}
<div className="mr-0.5 system-xs-medium-uppercase text-text-tertiary">
{t(($) => $['structOutput.structured'], { ns: 'app' })}
</div>
<Infotip
aria-label={t(($) => $['structOutput.structuredTip'], { ns: 'app' })}
popupClassName="w-[150px]"
>
{t(($) => $['structOutput.structuredTip'], { ns: 'app' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['structOutput.structuredTip'], { ns: 'app' })}
/>
<InfotipContent
aria-label={t(($) => $['structOutput.structuredTip'], { ns: 'app' })}
className="w-[150px]"
>
{t(($) => $['structOutput.structuredTip'], { ns: 'app' })}
</InfotipContent>
</Infotip>
<Switch
className="ml-2"
@@ -1,9 +1,9 @@
import type { FC } from 'react'
import type { ParameterExtractorNodeType } from './types'
import type { NodePanelProps } from '@/app/components/workflow/types'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
import Field from '@/app/components/workflow/nodes/_base/components/field'
@@ -120,12 +120,17 @@ const Panel: FC<NodePanelProps<ParameterExtractorNodeType>> = ({ id, data }) =>
<span className="uppercase">
{t(($) => $[`${i18nPrefix}.instruction`], { ns: 'workflow' })}
</span>
<Infotip
aria-label={t(($) => $[`${i18nPrefix}.instructionTip`], { ns: 'workflow' })}
className="ml-0.5 size-3.5"
popupClassName="w-[120px]"
>
{t(($) => $[`${i18nPrefix}.instructionTip`], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $[`${i18nPrefix}.instructionTip`], { ns: 'workflow' })}
className="ml-0.5 size-3.5"
/>
<InfotipContent
aria-label={t(($) => $[`${i18nPrefix}.instructionTip`], { ns: 'workflow' })}
className="w-[120px]"
>
{t(($) => $[`${i18nPrefix}.instructionTip`], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
</div>
}
@@ -1,9 +1,9 @@
'use client'
import type { FC } from 'react'
import type { Memory, Node, NodeOutPutVar } from '@/app/components/workflow/types'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
import MemoryConfig from '../../_base/components/memory-config'
@@ -50,12 +50,17 @@ const AdvancedSetting: FC<Props> = ({
<span className="uppercase">
{t(($) => $[`${i18nPrefix}.instruction`], { ns: 'workflow' })}
</span>
<Infotip
aria-label={t(($) => $[`${i18nPrefix}.instructionTip`], { ns: 'workflow' })}
className="ml-0.5 size-3.5"
popupClassName="w-[120px]"
>
{t(($) => $[`${i18nPrefix}.instructionTip`], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $[`${i18nPrefix}.instructionTip`], { ns: 'workflow' })}
className="ml-0.5 size-3.5"
/>
<InfotipContent
aria-label={t(($) => $[`${i18nPrefix}.instructionTip`], { ns: 'workflow' })}
className="w-[120px]"
>
{t(($) => $[`${i18nPrefix}.instructionTip`], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
</div>
}
@@ -5,8 +5,8 @@ import type { CredentialFormSchema } from '@/app/components/header/account-setti
import type { Tool } from '@/app/components/tools/types'
import type { ToolWithProvider } from '@/app/components/workflow/types'
import { Button } from '@langgenius/dify-ui/button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useId, useState } from 'react'
import { Infotip } from '@/app/components/base/infotip'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { SchemaModal } from '@/app/components/plugins/plugin-detail-panel/tool-selector/components/schema-modal'
@@ -102,12 +102,11 @@ const ToolFormItem: FC<Props> = ({
<div className="ml-1 system-xs-regular text-text-destructive-secondary">*</div>
)}
{!showDescription && tooltip && (
<Infotip
aria-label={tooltip[language] || tooltip.en_US}
className="ml-1"
popupClassName="w-[200px]"
>
{tooltip[language] || tooltip.en_US}
<Infotip>
<InfotipTrigger aria-label={tooltip[language] || tooltip.en_US} className="ml-1" />
<InfotipContent aria-label={tooltip[language] || tooltip.en_US} className="w-[200px]">
{tooltip[language] || tooltip.en_US}
</InfotipContent>
</Infotip>
)}
{showSchemaButton && (
@@ -5,8 +5,8 @@ import type { Event } from '@/app/components/tools/types'
import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
import type { PluginTriggerVarInputs } from '@/app/components/workflow/nodes/trigger-plugin/types'
import { Button } from '@langgenius/dify-ui/button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useId, useState } from 'react'
import { Infotip } from '@/app/components/base/infotip'
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { SchemaModal } from '@/app/components/plugins/plugin-detail-panel/tool-selector/components/schema-modal'
@@ -59,12 +59,14 @@ const TriggerFormItem: FC<Props> = ({
<div className="ml-1 system-xs-regular text-text-destructive-secondary">*</div>
)}
{!showDescription && tooltip && (
<Infotip
aria-label={tooltip[language] || tooltip.en_US}
className="ml-1 size-4"
popupClassName="w-[200px]"
>
{tooltip[language] || tooltip.en_US}
<Infotip>
<InfotipTrigger
aria-label={tooltip[language] || tooltip.en_US}
className="ml-1 size-4"
/>
<InfotipContent aria-label={tooltip[language] || tooltip.en_US} className="w-[200px]">
{tooltip[language] || tooltip.en_US}
</InfotipContent>
</Infotip>
)}
{showSchemaButton && (
@@ -1,7 +1,7 @@
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
type MonthlyDaysSelectorProps = {
selectedDays: (number | 'last')[]
@@ -55,14 +55,21 @@ const MonthlyDaysSelector = ({ selectedDays, onChange }: MonthlyDaysSelectorProp
>
{t(($) => $['nodes.triggerSchedule.lastDay'], { ns: 'workflow' })}
</button>
<Infotip
aria-label={t(($) => $['nodes.triggerSchedule.lastDayTooltip'], {
ns: 'workflow',
})}
className="mr-1 size-3"
iconSize="small"
>
{t(($) => $['nodes.triggerSchedule.lastDayTooltip'], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['nodes.triggerSchedule.lastDayTooltip'], {
ns: 'workflow',
})}
className="mr-1 size-3"
iconSize="small"
/>
<InfotipContent
aria-label={t(($) => $['nodes.triggerSchedule.lastDayTooltip'], {
ns: 'workflow',
})}
>
{t(($) => $['nodes.triggerSchedule.lastDayTooltip'], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
</div>
) : (
@@ -3,6 +3,7 @@ import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
import { IconButton } from '@langgenius/dify-ui/icon-button'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Input } from '@langgenius/dify-ui/input'
import { NumberField, NumberFieldGroup, NumberFieldInput } from '@langgenius/dify-ui/number-field'
import { Textarea } from '@langgenius/dify-ui/textarea'
@@ -10,7 +11,6 @@ import * as React from 'react'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { v4 as uuid4 } from 'uuid'
import { Infotip } from '@/app/components/base/infotip'
import { isLLMEnvironmentVariableValue } from '@/app/components/workflow/llm-environment-variable'
import { LLMEnvironmentVariableValueField } from '@/app/components/workflow/llm-environment-variable-value-field'
import { useWorkflowStore } from '@/app/components/workflow/store'
@@ -199,12 +199,17 @@ const VariableModal = ({ env, onClose, onSave }: ModalPropsType) => {
>
Secret
</button>
<Infotip
aria-label={t(($) => $['env.modal.secretTip'], { ns: 'workflow' })}
className="absolute top-1/2 right-1 size-3.5 -translate-y-1/2"
popupClassName="w-[240px]"
>
{t(($) => $['env.modal.secretTip'], { ns: 'workflow' })}
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['env.modal.secretTip'], { ns: 'workflow' })}
className="absolute top-1/2 right-1 size-3.5 -translate-y-1/2"
/>
<InfotipContent
aria-label={t(($) => $['env.modal.secretTip'], { ns: 'workflow' })}
className="w-[240px]"
>
{t(($) => $['env.modal.secretTip'], { ns: 'workflow' })}
</InfotipContent>
</Infotip>
</div>
<button
@@ -1,4 +1,3 @@
import type { ReactNode } from 'react'
import type { PromptEditorProps } from '@/app/components/base/prompt-editor'
import type { AgentTool } from '@/features/agent-v2/agent-composer/form-state'
import { act, fireEvent, screen, waitFor } from '@testing-library/react'
@@ -215,10 +214,6 @@ vi.mock('lexical', () => ({
SELECTION_CHANGE_COMMAND: Symbol('selection-change-command'),
}))
vi.mock('@/app/components/base/infotip', () => ({
Infotip: ({ children }: { children: ReactNode }) => <span>{children}</span>,
}))
vi.mock('foxact/use-clipboard', () => ({
useClipboard: mockUseClipboard,
}))
@@ -4,7 +4,7 @@ import type { ReactNode } from 'react'
import type { AgentBuildDraftChangeSection } from '../build-draft-changes-context'
import { cn } from '@langgenius/dify-ui/cn'
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from '@langgenius/dify-ui/collapsible'
import { Infotip } from '@/app/components/base/infotip'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { AgentBuildDraftChangeDot } from '../build-draft-change-dot'
import { useIsAgentBuildDraftSectionChanged } from '../build-draft-changes-context'
@@ -75,12 +75,11 @@ export function ConfigureSection({
</CollapsibleTrigger>
</Heading>
{hasTip && (
<Infotip
aria-label={tipAriaLabel}
className="ml-0.5 size-3.5"
popupClassName="max-w-64"
>
{tip}
<Infotip>
<InfotipTrigger aria-label={tipAriaLabel} className="ml-0.5 size-3.5" />
<InfotipContent aria-label={tipAriaLabel} className="max-w-64">
{tip}
</InfotipContent>
</Infotip>
)}
<span
@@ -12,12 +12,12 @@ import {
FileTreeIcon,
FileTreeLabel,
} from '@langgenius/dify-ui/file-tree'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { noop } from 'es-toolkit/function'
import { useAtomValue, useSetAtom } from 'jotai'
import { useCallback, useRef, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { useDocLink } from '@/context/i18n'
import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store'
import {
@@ -363,21 +363,28 @@ function AgentBuildNoteInfotip() {
const docLink = useDocLink()
return (
<Infotip
aria-label={t(($) => $['agentDetail.configure.files.buildNote.tooltip'])}
className="size-5 text-text-quaternary hover:text-text-quaternary"
iconSize="large"
popupClassName="w-[230px] rounded-xl px-4 py-3.5"
>
<p className="body-xs-regular text-text-secondary">
<Trans
i18nKey={($) => $['agentDetail.configure.files.buildNote.richTooltip']}
ns="agentV2"
components={{
docLink: <DocsLink href={docLink('/use-dify/build/new-agent/build#the-build-note')} />,
}}
/>
</p>
<Infotip>
<InfotipTrigger
aria-label={t(($) => $['agentDetail.configure.files.buildNote.tooltip'])}
className="size-5 text-text-quaternary hover:text-text-quaternary"
iconSize="large"
/>
<InfotipContent
aria-label={t(($) => $['agentDetail.configure.files.buildNote.tooltip'])}
className="w-[230px] rounded-xl px-4 py-3.5"
>
<p className="body-xs-regular text-text-secondary">
<Trans
i18nKey={($) => $['agentDetail.configure.files.buildNote.richTooltip']}
ns="agentV2"
components={{
docLink: (
<DocsLink href={docLink('/use-dify/build/new-agent/build#the-build-note')} />
),
}}
/>
</p>
</InfotipContent>
</Infotip>
)
}
@@ -16,6 +16,7 @@ import type {
AgentTool,
} from '@/features/agent-v2/agent-composer/form-state'
import { cn } from '@langgenius/dify-ui/cn'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Kbd } from '@langgenius/dify-ui/kbd'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
@@ -34,7 +35,6 @@ import {
} from 'lexical'
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import PromptEditor from '@/app/components/base/prompt-editor'
import BlockIcon from '@/app/components/workflow/block-icon'
import { BlockEnum } from '@/app/components/workflow/types'
@@ -1087,8 +1087,11 @@ export function AgentPromptEditor() {
>
{t(($) => $['agentDetail.configure.prompt.label'])}
</h3>
<Infotip aria-label={promptTip} popupClassName="max-w-64">
<AgentConfigureTipContent type="prompt" />
<Infotip>
<InfotipTrigger aria-label={promptTip} />
<InfotipContent aria-label={promptTip} className="max-w-64">
<AgentConfigureTipContent type="prompt" />
</InfotipContent>
</Infotip>
</div>
<Tooltip>
@@ -12,11 +12,11 @@ import type {
} from './working-directory-breadcrumb'
import type { AgentFileNode } from '@/features/agent-v2/agent-composer/form-state'
import { Dialog } from '@langgenius/dify-ui/dialog'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import { Tabs, TabsIndicator, TabsList, TabsPanel, TabsTab } from '@langgenius/dify-ui/tabs'
import { skipToken, useMutation, useQueries, useQuery } from '@tanstack/react-query'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { toast } from '@/app/notifications'
import { consoleClient, consoleQuery } from '@/service/console'
import { downloadUrl } from '@/utils/download'
@@ -575,12 +575,14 @@ export function AgentWorkingDirectoryPanel({
>
{t(($) => $['agentDetail.configure.workingDirectory.persistentFiles'])}
</TabsTab>
<Infotip
aria-label={persistentFilesTooltip}
iconVariant="information"
popupClassName="w-64"
>
{persistentFilesTooltip}
<Infotip>
<InfotipTrigger
aria-label={persistentFilesTooltip}
iconVariant="information"
/>
<InfotipContent aria-label={persistentFilesTooltip} className="w-64">
{persistentFilesTooltip}
</InfotipContent>
</Infotip>
</div>
<div className="flex h-full items-center gap-0.5">
@@ -590,12 +592,14 @@ export function AgentWorkingDirectoryPanel({
>
{t(($) => $['agentDetail.configure.workingDirectory.temporaryFiles'])}
</TabsTab>
<Infotip
aria-label={temporaryFilesTooltip}
iconVariant="information"
popupClassName="w-64"
>
{temporaryFilesTooltip}
<Infotip>
<InfotipTrigger
aria-label={temporaryFilesTooltip}
iconVariant="information"
/>
<InfotipContent aria-label={temporaryFilesTooltip} className="w-64">
{temporaryFilesTooltip}
</InfotipContent>
</Infotip>
</div>
<TabsIndicator
@@ -2,9 +2,9 @@
import type { AgentMonitoringChartRow, AgentMonitoringChartType } from './chart-utils'
import type { I18nKeysWithPrefix } from '@/types/i18n'
import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/infotip'
import ReactECharts from 'echarts-for-react'
import { useTranslation } from 'react-i18next'
import { Infotip } from '@/app/components/base/infotip'
import { buildChartOptions, getChartValueField, getTokenSummary } from './chart-utils'
type AgentMonitoringChartProps = {
@@ -51,7 +51,12 @@ export function AgentMonitoringChart({
<h3 className="truncate system-md-semibold text-text-secondary">
{t(($) => $[titleKey])}
</h3>
<Infotip aria-label={t(($) => $[explanationKey])}>{t(($) => $[explanationKey])}</Infotip>
<Infotip>
<InfotipTrigger aria-label={t(($) => $[explanationKey])} />
<InfotipContent aria-label={t(($) => $[explanationKey])}>
{t(($) => $[explanationKey])}
</InfotipContent>
</Infotip>
</div>
</div>
+2 -2
View File
@@ -38,7 +38,7 @@ it('allows shortcut row composition and width constraints without restricting ot
const diagnostics = lintFixture(`
import { TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { PopoverContent } from '@langgenius/dify-ui/popover'
import { Infotip } from '@/app/components/base/infotip'
import { InfotipContent } from '@langgenius/dify-ui/infotip'
const examples = <>
<TooltipContent className="flex items-center gap-1 max-w-60" />
<TooltipContent className="w-58 max-w-65" />
@@ -46,7 +46,7 @@ it('allows shortcut row composition and width constraints without restricting ot
<TooltipContent />
<TooltipTrigger className="border-0 bg-transparent" />
<PopoverContent className="shadow-none" />
<Infotip popupClassName="shadow-none" />
<InfotipContent className="shadow-none" />
<div className="shadow-none" />
</>
`)