mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 10:06:51 +08:00
chore(dify-ui): update tooltip and infotip migration (#35543)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
48e13f65dc
commit
ec450eb7f9
@ -4270,11 +4270,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-panel.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/error-handle/error-handle-type-selector.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -4293,16 +4288,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/form-input-type-switch.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/help-link.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -4502,22 +4487,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/agent/components/model-bar.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-empty-object-type": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/agent/components/tool-icon.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react/unsupported-syntax": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/agent/default.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
@ -4859,11 +4828,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/top-k-and-score-threshold.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/type.ts": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 2
|
||||
@ -4966,14 +4930,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/code-editor.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -5009,11 +4965,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/actions.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/edit-card/auto-width-input.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@ -5235,11 +5186,6 @@
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/tool/components/copy-id.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/tool/components/input-var-list.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 7
|
||||
@ -5405,11 +5351,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/note-node/note-editor/toolbar/command.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/note-node/note-editor/utils.ts": {
|
||||
"regexp/no-useless-quantifier": {
|
||||
"count": 1
|
||||
|
||||
@ -73,7 +73,7 @@ export function Infotip({
|
||||
/>
|
||||
<PopoverContent
|
||||
placement={placement}
|
||||
popupClassName={cn('max-w-[300px] px-3 py-2 system-xs-regular text-text-tertiary', popupClassName)}
|
||||
popupClassName={cn('max-w-[300px] rounded-md px-3 py-2 system-xs-regular text-text-tertiary', popupClassName)}
|
||||
>
|
||||
{children}
|
||||
</PopoverContent>
|
||||
|
||||
@ -225,7 +225,7 @@ describe('FormInputItem branches', () => {
|
||||
})
|
||||
|
||||
expect(screen.getByText('alpha')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
fireEvent.click(screen.getByText('alpha').closest('button') as HTMLButtonElement)
|
||||
fireEvent.click(screen.getByText('beta'))
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
@ -320,9 +320,9 @@ describe('FormInputItem branches', () => {
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button')).not.toBeDisabled()
|
||||
expect(screen.getByText('Select options').closest('button')).not.toBeDisabled()
|
||||
})
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
fireEvent.click(screen.getByText('Select options').closest('button') as HTMLButtonElement)
|
||||
fireEvent.click(screen.getByText('trigger-option'))
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
|
||||
@ -5,7 +5,7 @@ import type {
|
||||
} from '@/app/components/workflow/types'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import Collapse from '../collapse'
|
||||
import DefaultValue from './default-value'
|
||||
import ErrorHandleTypeSelector from './error-handle-type-selector'
|
||||
@ -57,7 +57,9 @@ const ErrorHandle = ({
|
||||
<div className="mr-0.5 system-sm-semibold-uppercase text-text-secondary">
|
||||
{t('nodes.common.errorHandle.title', { ns: 'workflow' })}
|
||||
</div>
|
||||
<Tooltip popupContent={t('nodes.common.errorHandle.tip', { ns: 'workflow' })} />
|
||||
<Infotip aria-label={t('nodes.common.errorHandle.tip', { ns: 'workflow' })}>
|
||||
{t('nodes.common.errorHandle.tip', { ns: 'workflow' })}
|
||||
</Infotip>
|
||||
{collapseIcon}
|
||||
</div>
|
||||
<ErrorHandleTypeSelector
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
RiEditLine,
|
||||
} from '@remixicon/react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { VarType } from '@/app/components/workflow/nodes/tool/types'
|
||||
|
||||
type Props = {
|
||||
@ -19,28 +15,67 @@ const FormInputTypeSwitch: FC<Props> = ({
|
||||
onChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const variableLabel = t('nodes.common.typeSwitch.variable', { ns: 'workflow' })
|
||||
const inputLabel = t('nodes.common.typeSwitch.input', { ns: 'workflow' })
|
||||
|
||||
return (
|
||||
<div className="inline-flex h-8 shrink-0 gap-px rounded-[10px] bg-components-segmented-control-bg-normal p-0.5">
|
||||
<Tooltip
|
||||
popupContent={value === VarType.variable ? '' : t('nodes.common.typeSwitch.variable', { ns: 'workflow' })}
|
||||
>
|
||||
<div
|
||||
className={cn('cursor-pointer rounded-lg px-2.5 py-1.5 text-text-tertiary hover:bg-state-base-hover', value === VarType.variable && 'bg-components-segmented-control-item-active-bg text-text-secondary shadow-xs hover:bg-components-segmented-control-item-active-bg')}
|
||||
onClick={() => onChange(VarType.variable)}
|
||||
>
|
||||
<Variable02 className="h-4 w-4" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
popupContent={value === VarType.constant ? '' : t('nodes.common.typeSwitch.input', { ns: 'workflow' })}
|
||||
>
|
||||
<div
|
||||
className={cn('cursor-pointer rounded-lg px-2.5 py-1.5 text-text-tertiary hover:bg-state-base-hover', value === VarType.constant && 'bg-components-segmented-control-item-active-bg text-text-secondary shadow-xs hover:bg-components-segmented-control-item-active-bg')}
|
||||
onClick={() => onChange(VarType.constant)}
|
||||
>
|
||||
<RiEditLine className="h-4 w-4" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{value === VarType.variable
|
||||
? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={variableLabel}
|
||||
className="cursor-pointer rounded-lg bg-components-segmented-control-item-active-bg px-2.5 py-1.5 text-text-secondary shadow-xs hover:bg-components-segmented-control-item-active-bg"
|
||||
onClick={() => onChange(VarType.variable)}
|
||||
>
|
||||
<Variable02 className="h-4 w-4" />
|
||||
</button>
|
||||
)
|
||||
: (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
aria-label={variableLabel}
|
||||
className="cursor-pointer rounded-lg px-2.5 py-1.5 text-text-tertiary hover:bg-state-base-hover"
|
||||
onClick={() => onChange(VarType.variable)}
|
||||
>
|
||||
<Variable02 className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>{variableLabel}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{value === VarType.constant
|
||||
? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={inputLabel}
|
||||
className="cursor-pointer rounded-lg bg-components-segmented-control-item-active-bg px-2.5 py-1.5 text-text-secondary shadow-xs hover:bg-components-segmented-control-item-active-bg"
|
||||
onClick={() => onChange(VarType.constant)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-edit-line h-4 w-4" />
|
||||
</button>
|
||||
)
|
||||
: (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
aria-label={inputLabel}
|
||||
className="cursor-pointer rounded-lg px-2.5 py-1.5 text-text-tertiary hover:bg-state-base-hover"
|
||||
onClick={() => onChange(VarType.constant)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-edit-line h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>{inputLabel}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { RiBookOpenLine } from '@remixicon/react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import TooltipPlus from '@/app/components/base/tooltip'
|
||||
import { useNodeHelpLink } from '../hooks/use-node-help-link'
|
||||
|
||||
type HelpLinkProps = {
|
||||
@ -17,19 +16,25 @@ const HelpLink = ({
|
||||
if (!link)
|
||||
return null
|
||||
|
||||
return (
|
||||
<TooltipPlus
|
||||
popupContent={t('userProfile.helpCenter', { ns: 'common' })}
|
||||
>
|
||||
<a
|
||||
href={link}
|
||||
target="_blank"
|
||||
className="mr-1 flex h-6 w-6 items-center justify-center rounded-md hover:bg-state-base-hover"
|
||||
>
|
||||
<RiBookOpenLine className="h-4 w-4 text-gray-500" />
|
||||
</a>
|
||||
</TooltipPlus>
|
||||
const label = t('userProfile.helpCenter', { ns: 'common' })
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<a
|
||||
aria-label={label}
|
||||
href={link}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mr-1 flex h-6 w-6 items-center justify-center rounded-md hover:bg-state-base-hover"
|
||||
>
|
||||
<span aria-hidden className="i-ri-book-open-line h-4 w-4 text-gray-500" />
|
||||
</a>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>{label}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { ModelBar } from '../model-bar'
|
||||
|
||||
type ModelProviderItem = {
|
||||
@ -52,11 +52,9 @@ describe('agent/model-bar', () => {
|
||||
|
||||
const emptySelector = screen.getByText((_, element) => element?.textContent === 'no-model:0')
|
||||
|
||||
fireEvent.mouseEnter(emptySelector)
|
||||
|
||||
expect(emptySelector).toBeInTheDocument()
|
||||
expect(screen.getByText('indicator:red')).toBeInTheDocument()
|
||||
expect(screen.getByText('workflow.nodes.agent.modelNotSelected')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('workflow.nodes.agent.modelNotSelected')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render the selected model without warning when it is installed', () => {
|
||||
@ -69,10 +67,8 @@ describe('agent/model-bar', () => {
|
||||
it('should show a warning tooltip when the selected model is not installed', () => {
|
||||
render(<ModelBar provider="openai" model="gpt-4.1" />)
|
||||
|
||||
fireEvent.mouseEnter(screen.getByText('openai/gpt-4.1:1'))
|
||||
|
||||
expect(screen.getByText('openai/gpt-4.1:1')).toBeInTheDocument()
|
||||
expect(screen.getByText('indicator:red')).toBeInTheDocument()
|
||||
expect(screen.getByText('workflow.nodes.agent.modelNotInstallTooltip')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('workflow.nodes.agent.modelNotInstallTooltip')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -87,19 +87,17 @@ describe('agent/tool-icon', () => {
|
||||
|
||||
const { rerender } = render(<ToolIcon id="tool-2" providerName="author/tool-b" />)
|
||||
|
||||
fireEvent.mouseEnter(screen.getByText('app-icon:#fff:B'))
|
||||
expect(screen.getByText('indicator:yellow')).toBeInTheDocument()
|
||||
expect(screen.getByText('workflow.nodes.agent.toolNotAuthorizedTooltip:{"tool":"tool-b"}')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('workflow.nodes.agent.toolNotAuthorizedTooltip:{"tool":"tool-b"}')).toBeInTheDocument()
|
||||
|
||||
mockWorkflowTools = []
|
||||
mockMarketplaceIcon = 'https://example.com/market-tool.png'
|
||||
rerender(<ToolIcon id="tool-3" providerName="market/tool-c" />)
|
||||
|
||||
const marketplaceIcon = screen.getByRole('img', { name: 'tool icon' })
|
||||
fireEvent.mouseEnter(marketplaceIcon)
|
||||
expect(marketplaceIcon).toHaveAttribute('src', 'https://example.com/market-tool.png')
|
||||
expect(screen.getByText('indicator:red')).toBeInTheDocument()
|
||||
expect(screen.getByText('workflow.nodes.agent.toolNotInstallTooltip:{"tool":"tool-c"}')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('workflow.nodes.agent.toolNotInstallTooltip:{"tool":"tool-c"}')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should fall back to the group icon while tool data is still loading', () => {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { FC } from 'react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||
@ -10,7 +10,10 @@ import Indicator from '@/app/components/header/indicator'
|
||||
type ModelBarProps = {
|
||||
provider: string
|
||||
model: string
|
||||
} | {}
|
||||
} | {
|
||||
provider?: never
|
||||
model?: never
|
||||
}
|
||||
|
||||
const useAllModel = () => {
|
||||
const { data: textGeneration } = useModelList(ModelTypeEnum.textGeneration)
|
||||
@ -35,23 +38,27 @@ const useAllModel = () => {
|
||||
export const ModelBar: FC<ModelBarProps> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
const modelList = useAllModel()
|
||||
if (!('provider' in props)) {
|
||||
if (props.provider === undefined) {
|
||||
const tooltip = t('nodes.agent.modelNotSelected', { ns: 'workflow' })
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
popupContent={t('nodes.agent.modelNotSelected', { ns: 'workflow' })}
|
||||
triggerMethod="hover"
|
||||
>
|
||||
<div className="relative">
|
||||
<ModelSelector
|
||||
modelList={[]}
|
||||
triggerClassName="bg-workflow-block-parma-bg h-6! rounded-md!"
|
||||
defaultModel={undefined}
|
||||
showDeprecatedWarnIcon={false}
|
||||
readonly
|
||||
deprecatedClassName="opacity-50"
|
||||
/>
|
||||
<Indicator color="red" className="absolute -top-0.5 -right-0.5" />
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<div className="relative" aria-label={tooltip}>
|
||||
<ModelSelector
|
||||
modelList={[]}
|
||||
triggerClassName="bg-workflow-block-parma-bg h-6! rounded-md!"
|
||||
defaultModel={undefined}
|
||||
showDeprecatedWarnIcon={false}
|
||||
readonly
|
||||
deprecatedClassName="opacity-50"
|
||||
/>
|
||||
<Indicator color="red" className="absolute -top-0.5 -right-0.5" />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>{tooltip}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
@ -59,23 +66,34 @@ export const ModelBar: FC<ModelBarProps> = (props) => {
|
||||
provider => provider.provider === props.provider && provider.models.some(model => model.model === props.model),
|
||||
)
|
||||
const showWarn = modelList && !modelInstalled
|
||||
return modelList && (
|
||||
<Tooltip
|
||||
popupContent={t('nodes.agent.modelNotInstallTooltip', { ns: 'workflow' })}
|
||||
triggerMethod="hover"
|
||||
disabled={!modelList || modelInstalled}
|
||||
>
|
||||
<div className="relative">
|
||||
<ModelSelector
|
||||
modelList={modelList}
|
||||
triggerClassName="bg-workflow-block-parma-bg h-6! rounded-md!"
|
||||
defaultModel={props}
|
||||
showDeprecatedWarnIcon={false}
|
||||
readonly
|
||||
deprecatedClassName="opacity-50"
|
||||
/>
|
||||
{showWarn && <Indicator color="red" className="absolute -top-0.5 -right-0.5" />}
|
||||
</div>
|
||||
if (!modelList)
|
||||
return null
|
||||
|
||||
const modelNotInstalledTooltip = t('nodes.agent.modelNotInstallTooltip', { ns: 'workflow' })
|
||||
const modelSelector = (
|
||||
<div className="relative" aria-label={showWarn ? modelNotInstalledTooltip : undefined}>
|
||||
<ModelSelector
|
||||
modelList={modelList}
|
||||
triggerClassName="bg-workflow-block-parma-bg h-6! rounded-md!"
|
||||
defaultModel={{
|
||||
provider: props.provider,
|
||||
model: props.model,
|
||||
}}
|
||||
showDeprecatedWarnIcon={false}
|
||||
readonly
|
||||
deprecatedClassName="opacity-50"
|
||||
/>
|
||||
{showWarn && <Indicator color="red" className="absolute -top-0.5 -right-0.5" />}
|
||||
</div>
|
||||
)
|
||||
|
||||
if (modelInstalled)
|
||||
return modelSelector
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={modelSelector} />
|
||||
<TooltipContent>{modelNotInstalledTooltip}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { memo, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { Group } from '@/app/components/base/icons/src/vender/other'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools } from '@/service/use-tools'
|
||||
import { getIconFromMarketPlace } from '@/utils/get-icon'
|
||||
@ -62,44 +63,50 @@ export const ToolIcon = memo(({ providerName }: ToolIconProps) => {
|
||||
throw new Error('Unknown status')
|
||||
}, [name, notSuccess, status, t])
|
||||
const [iconFetchError, setIconFetchError] = useState(false)
|
||||
return (
|
||||
<Tooltip
|
||||
triggerMethod="hover"
|
||||
popupContent={tooltip}
|
||||
disabled={!notSuccess}
|
||||
let iconContent: ReactNode = <Group className="h-3 w-3 opacity-35" />
|
||||
|
||||
if (!iconFetchError && icon) {
|
||||
if (typeof icon === 'string') {
|
||||
iconContent = (
|
||||
<img
|
||||
src={icon}
|
||||
alt="tool icon"
|
||||
className={cn('size-3.5 h-full w-full object-cover', notSuccess && 'opacity-50')}
|
||||
onError={() => setIconFetchError(true)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
else if (typeof icon === 'object') {
|
||||
iconContent = (
|
||||
<AppIcon
|
||||
className={cn('size-3.5 h-full w-full object-cover', notSuccess && 'opacity-50')}
|
||||
icon={icon?.content}
|
||||
background={icon?.background}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const iconNode = (
|
||||
<div
|
||||
aria-label={tooltip}
|
||||
className={cn('relative')}
|
||||
ref={containerRef}
|
||||
>
|
||||
<div
|
||||
className={cn('relative')}
|
||||
ref={containerRef}
|
||||
>
|
||||
<div className="flex size-5 items-center justify-center overflow-hidden rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge">
|
||||
{(() => {
|
||||
if (iconFetchError || !icon)
|
||||
return <Group className="h-3 w-3 opacity-35" />
|
||||
if (typeof icon === 'string') {
|
||||
return (
|
||||
<img
|
||||
src={icon}
|
||||
alt="tool icon"
|
||||
className={cn('size-3.5 h-full w-full object-cover', notSuccess && 'opacity-50')}
|
||||
onError={() => setIconFetchError(true)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (typeof icon === 'object') {
|
||||
return (
|
||||
<AppIcon
|
||||
className={cn('size-3.5 h-full w-full object-cover', notSuccess && 'opacity-50')}
|
||||
icon={icon?.content}
|
||||
background={icon?.background}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return <Group className="h-3 w-3 opacity-35" />
|
||||
})()}
|
||||
</div>
|
||||
{indicator && <Indicator color={indicator} className="absolute -top-px -right-px" />}
|
||||
<div className="flex size-5 items-center justify-center overflow-hidden rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge">
|
||||
{iconContent}
|
||||
</div>
|
||||
{indicator && <Indicator color={indicator} className="absolute -top-px -right-px" />}
|
||||
</div>
|
||||
)
|
||||
|
||||
if (!notSuccess || !tooltip)
|
||||
return iconNode
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={iconNode} />
|
||||
<TooltipContent>{tooltip}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
})
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import { env } from '@/env'
|
||||
|
||||
export type TopKAndScoreThresholdProps = {
|
||||
@ -59,10 +59,13 @@ const TopKAndScoreThreshold = ({
|
||||
<div>
|
||||
<div className="mb-0.5 flex h-6 items-center system-xs-medium text-text-secondary">
|
||||
{t('datasetConfig.top_k', { ns: 'appDebug' })}
|
||||
<Tooltip
|
||||
triggerClassName="ml-0.5 shrink-0 w-3.5 h-3.5"
|
||||
popupContent={t('datasetConfig.top_kTip', { ns: 'appDebug' })}
|
||||
/>
|
||||
<Infotip
|
||||
aria-label={t('datasetConfig.top_kTip', { ns: 'appDebug' })}
|
||||
className="ml-0.5 h-3.5 w-3.5"
|
||||
iconClassName="h-3.5 w-3.5"
|
||||
>
|
||||
{t('datasetConfig.top_kTip', { ns: 'appDebug' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
<NumberField
|
||||
disabled={readonly}
|
||||
@ -94,10 +97,13 @@ const TopKAndScoreThreshold = ({
|
||||
<div className="grow truncate system-sm-medium text-text-secondary">
|
||||
{t('datasetConfig.score_threshold', { ns: 'appDebug' })}
|
||||
</div>
|
||||
<Tooltip
|
||||
triggerClassName="shrink-0 ml-0.5 w-3.5 h-3.5"
|
||||
popupContent={t('datasetConfig.score_thresholdTip', { ns: 'appDebug' })}
|
||||
/>
|
||||
<Infotip
|
||||
aria-label={t('datasetConfig.score_thresholdTip', { ns: 'appDebug' })}
|
||||
className="ml-0.5 h-3.5 w-3.5"
|
||||
iconClassName="h-3.5 w-3.5"
|
||||
>
|
||||
{t('datasetConfig.score_thresholdTip', { ns: 'appDebug' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
<NumberField
|
||||
disabled={readonly || !isScoreThresholdEnabled}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import type { FC } from 'react'
|
||||
import type { ComponentProps, FC } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { Editor } from '@monaco-editor/react'
|
||||
import { RiClipboardLine, RiIndentIncrease } from '@remixicon/react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import { Theme } from '@/types/app'
|
||||
|
||||
@ -22,6 +21,10 @@ type CodeEditorProps = {
|
||||
topContent?: React.ReactNode
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
type EditorOnMount = NonNullable<ComponentProps<typeof Editor>['onMount']>
|
||||
type MonacoEditor = Parameters<EditorOnMount>[0]
|
||||
type Monaco = Parameters<EditorOnMount>[1]
|
||||
|
||||
const CodeEditor: FC<CodeEditorProps> = ({
|
||||
value,
|
||||
onUpdate,
|
||||
@ -36,8 +39,8 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const monacoRef = useRef<any>(null)
|
||||
const editorRef = useRef<any>(null)
|
||||
const monacoRef = useRef<Monaco | null>(null)
|
||||
const editorRef = useRef<MonacoEditor | null>(null)
|
||||
const [isMounted, setIsMounted] = React.useState(false)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
@ -50,7 +53,7 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||
}
|
||||
}, [theme])
|
||||
|
||||
const handleEditorDidMount = useCallback((editor: any, monaco: any) => {
|
||||
const handleEditorDidMount = useCallback<EditorOnMount>((editor, monaco) => {
|
||||
editorRef.current = editor
|
||||
monacoRef.current = monaco
|
||||
|
||||
@ -83,7 +86,7 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||
})
|
||||
monaco.editor.setTheme('light-theme')
|
||||
setIsMounted(true)
|
||||
}, [])
|
||||
}, [onBlur, onFocus])
|
||||
|
||||
const formatJsonContent = useCallback(() => {
|
||||
if (editorRef.current)
|
||||
@ -122,24 +125,36 @@ const CodeEditor: FC<CodeEditorProps> = ({
|
||||
</div>
|
||||
<div className="flex items-center gap-x-0.5">
|
||||
{showFormatButton && (
|
||||
<Tooltip popupContent={t('operation.format', { ns: 'common' })}>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center"
|
||||
onClick={formatJsonContent}
|
||||
>
|
||||
<RiIndentIncrease className="h-4 w-4 text-text-tertiary" />
|
||||
</button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t('operation.format', { ns: 'common' })}
|
||||
className="flex h-6 w-6 items-center justify-center"
|
||||
onClick={formatJsonContent}
|
||||
>
|
||||
<span aria-hidden className="i-ri-indent-increase h-4 w-4 text-text-tertiary" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>{t('operation.format', { ns: 'common' })}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip popupContent={t('operation.copy', { ns: 'common' })}>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center"
|
||||
onClick={() => copy(value)}
|
||||
>
|
||||
<RiClipboardLine className="h-4 w-4 text-text-tertiary" />
|
||||
</button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t('operation.copy', { ns: 'common' })}
|
||||
className="flex h-6 w-6 items-center justify-center"
|
||||
onClick={() => copy(value)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-clipboard-line h-4 w-4 text-text-tertiary" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>{t('operation.copy', { ns: 'common' })}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import type { FC } from 'react'
|
||||
import { RiAddCircleLine, RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type ActionsProps = {
|
||||
disableAddBtn: boolean
|
||||
@ -18,36 +17,59 @@ const Actions: FC<ActionsProps> = ({
|
||||
onDelete,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const addChildFieldLabel = t('nodes.llm.jsonSchema.addChildField', { ns: 'workflow' })
|
||||
const editLabel = t('operation.edit', { ns: 'common' })
|
||||
const removeLabel = t('operation.remove', { ns: 'common' })
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-x-0.5">
|
||||
<Tooltip popupContent={t('nodes.llm.jsonSchema.addChildField', { ns: 'workflow' })}>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary disabled:cursor-not-allowed disabled:text-text-disabled"
|
||||
onClick={onAddChildField}
|
||||
disabled={disableAddBtn}
|
||||
>
|
||||
<RiAddCircleLine className="h-4 w-4" />
|
||||
</button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<span className="inline-flex">
|
||||
<button
|
||||
type="button"
|
||||
aria-label={addChildFieldLabel}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary disabled:cursor-not-allowed disabled:text-text-disabled"
|
||||
onClick={onAddChildField}
|
||||
disabled={disableAddBtn}
|
||||
>
|
||||
<span aria-hidden className="i-ri-add-circle-line h-4 w-4" />
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>{addChildFieldLabel}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip popupContent={t('operation.edit', { ns: 'common' })}>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary"
|
||||
onClick={onEdit}
|
||||
>
|
||||
<RiEditLine className="h-4 w-4" />
|
||||
</button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
aria-label={editLabel}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary"
|
||||
onClick={onEdit}
|
||||
>
|
||||
<span aria-hidden className="i-ri-edit-line h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>{editLabel}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip popupContent={t('operation.remove', { ns: 'common' })}>
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive"
|
||||
onClick={onDelete}
|
||||
>
|
||||
<RiDeleteBinLine className="h-4 w-4" />
|
||||
</button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
aria-label={removeLabel}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md text-text-tertiary hover:bg-state-destructive-hover hover:text-text-destructive"
|
||||
onClick={onDelete}
|
||||
>
|
||||
<span aria-hidden className="i-ri-delete-bin-line h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>{removeLabel}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -20,27 +20,21 @@ describe('tool/copy-id', () => {
|
||||
it('should copy content and reset copied state when mouse leaves', () => {
|
||||
const { container } = render(<CopyId content="tool-123" />)
|
||||
|
||||
const trigger = screen.getByText('tool-123').parentElement as HTMLElement
|
||||
const trigger = screen.getByRole('button', { name: 'appOverview.overview.appInfo.embedded.copy' })
|
||||
const wrapper = container.querySelector('.inline-flex') as HTMLElement
|
||||
|
||||
act(() => {
|
||||
fireEvent.mouseEnter(trigger)
|
||||
})
|
||||
expect(screen.getByText('appOverview.overview.appInfo.embedded.copy')).toBeInTheDocument()
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(trigger)
|
||||
vi.advanceTimersByTime(100)
|
||||
})
|
||||
expect(copy).toHaveBeenCalledWith('tool-123')
|
||||
expect(screen.getByText('appOverview.overview.appInfo.embedded.copied')).toBeInTheDocument()
|
||||
expect(trigger).toHaveAccessibleName('appOverview.overview.appInfo.embedded.copied')
|
||||
|
||||
act(() => {
|
||||
fireEvent.mouseLeave(wrapper)
|
||||
vi.advanceTimersByTime(100)
|
||||
fireEvent.mouseEnter(trigger)
|
||||
})
|
||||
expect(screen.getByText('appOverview.overview.appInfo.embedded.copy')).toBeInTheDocument()
|
||||
expect(trigger).toHaveAccessibleName('appOverview.overview.appInfo.embedded.copy')
|
||||
})
|
||||
|
||||
it('should stop click propagation from the outer wrapper', () => {
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
'use client'
|
||||
import { RiFileCopyLine } from '@remixicon/react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { debounce } from 'es-toolkit/compat'
|
||||
import * as React from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type Props = {
|
||||
content: string
|
||||
@ -25,27 +24,33 @@ const CopyFeedbackNew = ({ content }: Props) => {
|
||||
const onMouseLeave = debounce(() => {
|
||||
setIsCopied(false)
|
||||
}, 100)
|
||||
const tooltip = (isCopied
|
||||
? t(`${prefixEmbedded}.copied`, { ns: 'appOverview' })
|
||||
: t(`${prefixEmbedded}.copy`, { ns: 'appOverview' })) || ''
|
||||
|
||||
return (
|
||||
<div className="inline-flex w-full pb-0.5" onClick={e => e.stopPropagation()} onMouseLeave={onMouseLeave}>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
(isCopied
|
||||
? t(`${prefixEmbedded}.copied`, { ns: 'appOverview' })
|
||||
: t(`${prefixEmbedded}.copy`, { ns: 'appOverview' })) || ''
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="group/copy flex w-full items-center gap-0.5"
|
||||
onClick={onClickCopy}
|
||||
>
|
||||
<div
|
||||
className="w-0 grow cursor-pointer truncate system-2xs-regular text-text-quaternary group-hover:text-text-tertiary"
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
<RiFileCopyLine className="h-3 w-3 shrink-0 text-text-tertiary opacity-0 group-hover/copy:opacity-100" />
|
||||
</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
aria-label={tooltip}
|
||||
className="group/copy flex w-full items-center gap-0.5 text-left"
|
||||
onClick={onClickCopy}
|
||||
>
|
||||
<span
|
||||
className="w-0 grow cursor-pointer truncate system-2xs-regular text-text-quaternary group-hover:text-text-tertiary"
|
||||
>
|
||||
{content}
|
||||
</span>
|
||||
<span aria-hidden className="i-ri-file-copy-line h-3 w-3 shrink-0 text-text-tertiary opacity-0 group-hover/copy:opacity-100" />
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{tooltip}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,17 +1,10 @@
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
RiBold,
|
||||
RiItalic,
|
||||
RiLink,
|
||||
RiListUnordered,
|
||||
RiStrikethrough,
|
||||
} from '@remixicon/react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useStore } from '../store'
|
||||
import { useCommand } from './hooks'
|
||||
|
||||
@ -32,15 +25,15 @@ const Command = ({
|
||||
const icon = useMemo(() => {
|
||||
switch (type) {
|
||||
case 'bold':
|
||||
return <RiBold className={cn('h-4 w-4', selectedIsBold && 'text-primary-600')} />
|
||||
return <span aria-hidden className={cn('i-ri-bold h-4 w-4', selectedIsBold && 'text-primary-600')} />
|
||||
case 'italic':
|
||||
return <RiItalic className={cn('h-4 w-4', selectedIsItalic && 'text-primary-600')} />
|
||||
return <span aria-hidden className={cn('i-ri-italic h-4 w-4', selectedIsItalic && 'text-primary-600')} />
|
||||
case 'strikethrough':
|
||||
return <RiStrikethrough className={cn('h-4 w-4', selectedIsStrikeThrough && 'text-primary-600')} />
|
||||
return <span aria-hidden className={cn('i-ri-strikethrough h-4 w-4', selectedIsStrikeThrough && 'text-primary-600')} />
|
||||
case 'link':
|
||||
return <RiLink className={cn('h-4 w-4', selectedIsLink && 'text-primary-600')} />
|
||||
return <span aria-hidden className={cn('i-ri-link h-4 w-4', selectedIsLink && 'text-primary-600')} />
|
||||
case 'bullet':
|
||||
return <RiListUnordered className={cn('h-4 w-4', selectedIsBullet && 'text-primary-600')} />
|
||||
return <span aria-hidden className={cn('i-ri-list-unordered h-4 w-4', selectedIsBullet && 'text-primary-600')} />
|
||||
}
|
||||
}, [type, selectedIsBold, selectedIsItalic, selectedIsStrikeThrough, selectedIsLink, selectedIsBullet])
|
||||
|
||||
@ -60,22 +53,27 @@ const Command = ({
|
||||
}, [type, t])
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
popupContent={tip}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-text-tertiary hover:bg-state-accent-active hover:text-text-accent',
|
||||
type === 'bold' && selectedIsBold && 'bg-state-accent-active',
|
||||
type === 'italic' && selectedIsItalic && 'bg-state-accent-active',
|
||||
type === 'strikethrough' && selectedIsStrikeThrough && 'bg-state-accent-active',
|
||||
type === 'link' && selectedIsLink && 'bg-state-accent-active',
|
||||
type === 'bullet' && selectedIsBullet && 'bg-state-accent-active',
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
aria-label={tip}
|
||||
className={cn(
|
||||
'flex h-8 w-8 cursor-pointer items-center justify-center rounded-md text-text-tertiary hover:bg-state-accent-active hover:text-text-accent',
|
||||
type === 'bold' && selectedIsBold && 'bg-state-accent-active',
|
||||
type === 'italic' && selectedIsItalic && 'bg-state-accent-active',
|
||||
type === 'strikethrough' && selectedIsStrikeThrough && 'bg-state-accent-active',
|
||||
type === 'link' && selectedIsLink && 'bg-state-accent-active',
|
||||
type === 'bullet' && selectedIsBullet && 'bg-state-accent-active',
|
||||
)}
|
||||
onClick={() => handleCommand(type)}
|
||||
>
|
||||
{icon}
|
||||
</button>
|
||||
)}
|
||||
onClick={() => handleCommand(type)}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
/>
|
||||
<TooltipContent>{tip}</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@ -44,12 +44,6 @@ This document tracks the Dify-web migration away from legacy overlay APIs.
|
||||
|
||||
## Allowlist maintenance
|
||||
|
||||
- After each migration batch, run:
|
||||
|
||||
```sh
|
||||
pnpm -C web lint:fix --prune-suppressions <changed-files>
|
||||
```
|
||||
|
||||
- If a migrated file was in the allowlist, remove it from `web/eslint.constants.mjs` in the same PR.
|
||||
- Never increase allowlist scope to bypass new code.
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user