mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 10:06:51 +08:00
refactor(web): migrate legacy tooltip to infotip (#35774)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
4b7dc17546
commit
1359c03216
@ -197,21 +197,11 @@
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/app-sidebar/basic.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app-sidebar/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app-sidebar/toggle-button.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/annotation/add-annotation-modal/edit-item/index.tsx": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
@ -351,16 +341,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/configuration/config-vision/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/configuration/config-vision/param-config-content.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/configuration/config/agent/agent-setting/index.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@ -445,21 +425,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/app/configuration/config/config-audio.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/configuration/config/config-document.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/configuration/dataset-config/context-var/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/configuration/dataset-config/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -470,11 +435,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/configuration/dataset-config/params-config/config-content.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/app/configuration/dataset-config/params-config/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -1726,11 +1686,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/param-item/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/prompt-editor/index.stories.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
@ -2047,11 +2002,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/billing/priority-label/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/billing/type.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 4
|
||||
@ -2077,11 +2027,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-input.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/common/image-uploader/store.tsx": {
|
||||
"react-refresh/only-export-components": {
|
||||
"count": 3
|
||||
@ -2092,11 +2037,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/common/retrieval-param-config/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/dsl-confirm-modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -2115,11 +2055,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/create-from-pipeline/list/template-card/details/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/create-from-pipeline/list/template-card/details/types.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 1
|
||||
@ -2130,11 +2065,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/create/embedding-process/indexing-progress-item.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/create/empty-dataset-creation-modal/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
@ -2165,16 +2095,6 @@
|
||||
"count": 5
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/create/step-two/components/indexing-mode-section.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/create/step-two/components/inputs.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/create/step-two/hooks/index.ts": {
|
||||
"no-barrel-files/no-barrel-files": {
|
||||
"count": 6
|
||||
@ -2209,16 +2129,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/create/website/base/checkbox-with-label.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/create/website/base/field.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/create/website/firecrawl/index.tsx": {
|
||||
"no-console": {
|
||||
"count": 1
|
||||
@ -2327,11 +2237,6 @@
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/checkbox-with-label.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/create-from-pipeline/data-source/website-crawl/base/options/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -2477,11 +2382,6 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/documents/status-item/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/external-knowledge-base/create/ExternalApiSelect.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
@ -2565,11 +2465,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/metadata/metadata-document/info-group.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/metadata/types.ts": {
|
||||
"erasable-syntax-only/enums": {
|
||||
"count": 2
|
||||
@ -2590,11 +2485,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/datasets/settings/summary-index-setting.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/develop/code.tsx": {
|
||||
"ts/no-empty-object-type": {
|
||||
"count": 1
|
||||
@ -3341,11 +3231,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/panel/input-field/label-right-content/global-inputs.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/panel/test-run/preparation/document-processing/index.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -3932,11 +3817,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/field.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 1
|
||||
@ -3980,11 +3860,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/option-card.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/prompt/editor.tsx": {
|
||||
"ts/no-explicit-any": {
|
||||
"count": 4
|
||||
@ -4046,9 +3921,6 @@
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/_base/components/workflow-panel/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"react/set-state-in-effect": {
|
||||
"count": 3
|
||||
},
|
||||
@ -4539,14 +4411,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/llm/components/config-prompt-item.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
},
|
||||
"ts/no-explicit-any": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"web/app/components/workflow/nodes/llm/components/config-prompt.tsx": {
|
||||
"react/unsupported-syntax": {
|
||||
"count": 1
|
||||
|
||||
@ -7,12 +7,6 @@ vi.mock('@/app/components/base/icons/src/vender/workflow', () => ({
|
||||
WindowCursor: (props: React.SVGProps<SVGSVGElement>) => <svg data-testid="webapp-icon" {...props} />,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ popupContent }: { popupContent: React.ReactNode }) => (
|
||||
<div data-testid="tooltip">{popupContent}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('../../base/app-icon', () => ({
|
||||
default: ({ icon, background, innerIcon, className }: {
|
||||
icon?: string
|
||||
@ -75,13 +69,12 @@ describe('AppBasic', () => {
|
||||
|
||||
it('should show hover tip when provided', () => {
|
||||
render(<AppBasic name="My App" type="Chatbot" hoverTip="Some tip" />)
|
||||
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
|
||||
expect(screen.getByText('Some tip')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Some tip')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not show hover tip when not provided', () => {
|
||||
render(<AppBasic name="My App" type="Chatbot" />)
|
||||
expect(screen.queryByTestId('tooltip')).not.toBeInTheDocument()
|
||||
expect(screen.queryByLabelText('Some tip')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
ApiAggregate,
|
||||
WindowCursor,
|
||||
} from '@/app/components/base/icons/src/vender/workflow'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import AppIcon from '../base/app-icon'
|
||||
|
||||
type IAppBasicProps = {
|
||||
@ -82,16 +82,9 @@ export default function AppBasic({ icon, icon_background, name, isExternal, type
|
||||
</div>
|
||||
{hoverTip
|
||||
&& (
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[240px]">
|
||||
{hoverTip}
|
||||
</div>
|
||||
)}
|
||||
popupClassName="ml-1"
|
||||
triggerClassName="w-4 h-4 ml-1"
|
||||
position="top"
|
||||
/>
|
||||
<Infotip aria-label={hoverTip} className="ml-1" popupClassName="w-[240px]">
|
||||
{hoverTip}
|
||||
</Infotip>
|
||||
)}
|
||||
</div>
|
||||
{!hideType && isExtraInLine && (
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { RiArrowLeftSLine, RiArrowRightSLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '../base/tooltip'
|
||||
import ShortcutsName from '../workflow/shortcuts-name'
|
||||
|
||||
type TooltipContentProps = {
|
||||
type ToggleTooltipContentProps = {
|
||||
expand: boolean
|
||||
}
|
||||
|
||||
const TOGGLE_SHORTCUT = ['ctrl', 'B']
|
||||
|
||||
const TooltipContent = ({
|
||||
const ToggleTooltipContent = ({
|
||||
expand,
|
||||
}: TooltipContentProps) => {
|
||||
}: ToggleTooltipContentProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
@ -37,22 +37,21 @@ const ToggleButton = ({
|
||||
className,
|
||||
}: ToggleButtonProps) => {
|
||||
return (
|
||||
<Tooltip
|
||||
popupContent={<TooltipContent expand={expand} />}
|
||||
popupClassName="p-1.5 rounded-lg"
|
||||
position="right"
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={handleToggle}
|
||||
className={cn('rounded-full px-1', className)}
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<Button
|
||||
size="small"
|
||||
onClick={handleToggle}
|
||||
className={cn('rounded-full px-1', className)}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{
|
||||
expand
|
||||
? <RiArrowLeftSLine className="size-4" />
|
||||
: <RiArrowRightSLine className="size-4" />
|
||||
}
|
||||
</Button>
|
||||
{expand ? <RiArrowLeftSLine className="size-4" /> : <RiArrowRightSLine className="size-4" />}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent placement="right" className="rounded-lg p-1.5">
|
||||
<ToggleTooltipContent expand={expand} />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import { useContext } from 'use-context-selector'
|
||||
// import { Resolution } from '@/types/app'
|
||||
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import { Vision } from '@/app/components/base/icons/src/vender/features'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
// import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
|
||||
@ -70,13 +70,12 @@ const ConfigVision: FC = () => {
|
||||
</div>
|
||||
<div className="flex grow items-center">
|
||||
<div className="mr-1 system-sm-semibold text-text-secondary">{t('vision.name', { ns: 'appDebug' })}</div>
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[180px]">
|
||||
{t('vision.description', { ns: 'appDebug' })}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Infotip
|
||||
aria-label={t('vision.description', { ns: 'appDebug' })}
|
||||
popupClassName="w-[180px]"
|
||||
>
|
||||
{t('vision.description', { ns: 'appDebug' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center">
|
||||
{readonly
|
||||
@ -84,15 +83,14 @@ const ConfigVision: FC = () => {
|
||||
<>
|
||||
<div className="mr-2 flex items-center gap-0.5">
|
||||
<div className="system-xs-medium-uppercase text-text-tertiary">{t('vision.visionSettings.resolution', { ns: 'appDebug' })}</div>
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[180px]">
|
||||
{t('vision.visionSettings.resolutionTooltip', { ns: 'appDebug' }).split('\n').map(item => (
|
||||
<div key={item}>{item}</div>
|
||||
))}
|
||||
</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>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<OptionCard
|
||||
|
||||
@ -6,8 +6,8 @@ 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 Tooltip from '@/app/components/base/tooltip'
|
||||
import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
|
||||
import { Resolution, TransferMethod } from '@/types/app'
|
||||
|
||||
@ -47,15 +47,14 @@ const ParamConfigContent: FC = () => {
|
||||
<div>
|
||||
<div className="mb-2 flex items-center space-x-1">
|
||||
<div className="text-[13px] leading-[18px] font-semibold text-text-secondary">{t('vision.visionSettings.resolution', { ns: 'appDebug' })}</div>
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[180px]">
|
||||
{t('vision.visionSettings.resolutionTooltip', { ns: 'appDebug' }).split('\n').map(item => (
|
||||
<div key={item}>{item}</div>
|
||||
))}
|
||||
</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>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<OptionCard
|
||||
|
||||
@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import { Microphone01 } from '@/app/components/base/icons/src/vender/features'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
|
||||
@ -57,13 +57,12 @@ const ConfigAudio: FC = () => {
|
||||
</div>
|
||||
<div className="flex grow items-center">
|
||||
<div className="mr-1 system-sm-semibold text-text-secondary">{t('feature.audioUpload.title', { ns: 'appDebug' })}</div>
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[180px]">
|
||||
{t('feature.audioUpload.description', { ns: 'appDebug' })}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Infotip
|
||||
aria-label={t('feature.audioUpload.description', { ns: 'appDebug' })}
|
||||
popupClassName="w-[180px]"
|
||||
>
|
||||
{t('feature.audioUpload.description', { ns: 'appDebug' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
{!readonly && (
|
||||
<div className="flex shrink-0 items-center">
|
||||
|
||||
@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import { Document } from '@/app/components/base/icons/src/vender/features'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
import ConfigContext from '@/context/debug-configuration'
|
||||
|
||||
@ -57,13 +57,12 @@ const ConfigDocument: FC = () => {
|
||||
</div>
|
||||
<div className="flex grow items-center">
|
||||
<div className="mr-1 system-sm-semibold text-text-secondary">{t('feature.documentUpload.title', { ns: 'appDebug' })}</div>
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[180px]">
|
||||
{t('feature.documentUpload.description', { ns: 'appDebug' })}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Infotip
|
||||
aria-label={t('feature.documentUpload.description', { ns: 'appDebug' })}
|
||||
popupClassName="w-[180px]"
|
||||
>
|
||||
{t('feature.documentUpload.description', { ns: 'appDebug' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
{!readonly && (
|
||||
<div className="flex shrink-0 items-center">
|
||||
|
||||
@ -275,7 +275,7 @@ describe('ContextVar', () => {
|
||||
// Act
|
||||
render(<ContextVar {...props} />)
|
||||
|
||||
const varPickerTrigger = screen.getByTestId('popover-trigger')
|
||||
const varPickerTrigger = screen.getAllByTestId('popover-trigger').at(-1)!
|
||||
|
||||
await user.click(varPickerTrigger!)
|
||||
expect(screen.getByTestId('popover-content'))!.toBeInTheDocument()
|
||||
@ -296,7 +296,7 @@ describe('ContextVar', () => {
|
||||
// Act
|
||||
render(<ContextVar {...props} />)
|
||||
|
||||
const varPickerTrigger = screen.getByTestId('popover-trigger')
|
||||
const varPickerTrigger = screen.getAllByTestId('popover-trigger').at(-1)!
|
||||
|
||||
// Open dropdown
|
||||
await user.click(varPickerTrigger!)
|
||||
|
||||
@ -5,7 +5,7 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { BracketsX } from '@/app/components/base/icons/src/vender/line/development'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import VarPicker from './var-picker'
|
||||
|
||||
const ContextVar: FC<Props> = (props) => {
|
||||
@ -20,13 +20,12 @@ const ContextVar: FC<Props> = (props) => {
|
||||
<BracketsX className="h-4 w-4 text-text-accent" />
|
||||
</div>
|
||||
<div className="mr-1 text-sm font-medium text-text-secondary">{t('feature.dataSet.queryVariable.title', { ns: 'appDebug' })}</div>
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[180px]">
|
||||
{t('feature.dataSet.queryVariable.tip', { ns: 'appDebug' })}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Infotip
|
||||
aria-label={t('feature.dataSet.queryVariable.tip', { ns: 'appDebug' })}
|
||||
popupClassName="w-[180px]"
|
||||
>
|
||||
{t('feature.dataSet.queryVariable.tip', { ns: 'appDebug' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
|
||||
<VarPicker {...props} />
|
||||
|
||||
@ -15,9 +15,9 @@ import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { memo, useCallback, useEffect, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item'
|
||||
import TopKItem from '@/app/components/base/param-item/top-k-item'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useCurrentProviderAndModel, useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
@ -244,15 +244,14 @@ const ConfigContent: FC<Props> = ({
|
||||
onClick={() => handleRerankModeChange(option.value)}
|
||||
>
|
||||
<div className="truncate">{option.label}</div>
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[200px]">
|
||||
{option.tips}
|
||||
</div>
|
||||
)}
|
||||
popupClassName="ml-0.5"
|
||||
triggerClassName="ml-0.5 w-3.5 h-3.5"
|
||||
/>
|
||||
<Infotip
|
||||
aria-label={option.tips}
|
||||
className="ml-0.5 h-3.5 w-3.5"
|
||||
iconClassName="h-3.5 w-3.5"
|
||||
popupClassName="w-[200px]"
|
||||
>
|
||||
{option.tips}
|
||||
</Infotip>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
@ -273,15 +272,13 @@ const ConfigContent: FC<Props> = ({
|
||||
)
|
||||
}
|
||||
<div className="ml-1 system-sm-semibold leading-[32px] text-text-secondary">{t('modelProvider.rerankModel.key', { ns: 'common' })}</div>
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[200px]">
|
||||
{t('modelProvider.rerankModel.tip', { ns: 'common' })}
|
||||
</div>
|
||||
)}
|
||||
popupClassName="ml-1"
|
||||
triggerClassName="ml-1 w-4 h-4"
|
||||
/>
|
||||
<Infotip
|
||||
aria-label={t('modelProvider.rerankModel.tip', { ns: 'common' })}
|
||||
className="ml-1"
|
||||
popupClassName="w-[200px]"
|
||||
>
|
||||
{t('modelProvider.rerankModel.tip', { ns: 'common' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
{
|
||||
showRerankModel && (
|
||||
@ -363,9 +360,9 @@ const ConfigContent: FC<Props> = ({
|
||||
<div className="mt-4">
|
||||
<div className="flex items-center space-x-0.5">
|
||||
<div className="text-[13px] leading-[32px] font-medium text-text-primary">{t('modelProvider.systemReasoningModel.key', { ns: 'common' })}</div>
|
||||
<Tooltip
|
||||
popupContent={t('modelProvider.systemReasoningModel.tip', { ns: 'common' })}
|
||||
/>
|
||||
<Infotip aria-label={t('modelProvider.systemReasoningModel.tip', { ns: 'common' })}>
|
||||
{t('modelProvider.systemReasoningModel.tip', { ns: 'common' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
<ModelParameterModal
|
||||
isInWorkflow={isInWorkflow}
|
||||
|
||||
@ -48,24 +48,21 @@ describe('ThemeSelector', () => {
|
||||
it('should call setTheme with light when light option is clicked', () => {
|
||||
render(<ThemeSelector />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
const lightButton = screen.getByText(/light/i).closest('button')!
|
||||
fireEvent.click(lightButton)
|
||||
fireEvent.click(screen.getByText(/light/i))
|
||||
expect(mockSetTheme).toHaveBeenCalledWith('light')
|
||||
})
|
||||
|
||||
it('should call setTheme with dark when dark option is clicked', () => {
|
||||
render(<ThemeSelector />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
const darkButton = screen.getByText(/dark/i).closest('button')!
|
||||
fireEvent.click(darkButton)
|
||||
fireEvent.click(screen.getByText(/dark/i))
|
||||
expect(mockSetTheme).toHaveBeenCalledWith('dark')
|
||||
})
|
||||
|
||||
it('should call setTheme with system when system option is clicked', () => {
|
||||
render(<ThemeSelector />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
const systemButton = screen.getByText(/auto/i).closest('button')!
|
||||
fireEvent.click(systemButton)
|
||||
fireEvent.click(screen.getByText(/auto/i))
|
||||
expect(mockSetTheme).toHaveBeenCalledWith('system')
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,6 +8,8 @@ type CheckboxProps = {
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
indeterminate?: boolean
|
||||
ariaLabel?: string
|
||||
ariaLabelledBy?: string
|
||||
}
|
||||
|
||||
const Checkbox = ({
|
||||
@ -17,6 +19,8 @@ const Checkbox = ({
|
||||
className,
|
||||
disabled,
|
||||
indeterminate,
|
||||
ariaLabel,
|
||||
ariaLabelledBy,
|
||||
}: CheckboxProps) => {
|
||||
const checkClassName = (checked || indeterminate)
|
||||
? 'bg-components-checkbox-bg text-components-checkbox-icon hover:bg-components-checkbox-bg-hover'
|
||||
@ -52,6 +56,8 @@ const Checkbox = ({
|
||||
role="checkbox"
|
||||
aria-checked={indeterminate ? 'mixed' : !!checked}
|
||||
aria-disabled={!!disabled}
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
>
|
||||
{!checked && indeterminate && <IndeterminateIcon />}
|
||||
|
||||
@ -27,17 +27,15 @@ describe('ParamItem', () => {
|
||||
})
|
||||
|
||||
it('should render a tooltip trigger by default', () => {
|
||||
const { container } = render(<ParamItem {...defaultProps} tip="Some tip text" />)
|
||||
render(<ParamItem {...defaultProps} tip="Some tip text" />)
|
||||
|
||||
// Tooltip trigger icon should be rendered (the data-state div)
|
||||
expect(container.querySelector('[data-state]')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Some tip text')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render tooltip trigger when noTooltip is true', () => {
|
||||
const { container } = render(<ParamItem {...defaultProps} noTooltip tip="Hidden tip" />)
|
||||
render(<ParamItem {...defaultProps} noTooltip tip="Hidden tip" />)
|
||||
|
||||
// No tooltip trigger icon should be rendered
|
||||
expect(container.querySelector('[data-state]')).not.toBeInTheDocument()
|
||||
expect(screen.queryByLabelText('Hidden tip')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render a switch when hasSwitch is true', () => {
|
||||
|
||||
@ -24,10 +24,9 @@ describe('ScoreThresholdItem', () => {
|
||||
})
|
||||
|
||||
it('should render tooltip trigger', () => {
|
||||
const { container } = render(<ScoreThresholdItem {...defaultProps} />)
|
||||
render(<ScoreThresholdItem {...defaultProps} />)
|
||||
|
||||
// Tooltip trigger icon should be rendered
|
||||
expect(container.querySelector('[data-state]')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('appDebug.datasetConfig.score_thresholdTip')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render InputNumber and Slider', () => {
|
||||
|
||||
@ -29,10 +29,9 @@ describe('TopKItem', () => {
|
||||
})
|
||||
|
||||
it('should render tooltip trigger', () => {
|
||||
const { container } = render(<TopKItem {...defaultProps} />)
|
||||
render(<TopKItem {...defaultProps} />)
|
||||
|
||||
// Tooltip trigger icon should be rendered
|
||||
expect(container.querySelector('[data-state]')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('appDebug.datasetConfig.top_kTip')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render InputNumber and Slider', () => {
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
} from '@langgenius/dify-ui/number-field'
|
||||
import { Slider } from '@langgenius/dify-ui/slider'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@ -44,11 +44,10 @@ const ParamItem: FC<Props> = ({ className, id, name, noTooltip, tip, step = 0.1,
|
||||
/>
|
||||
)}
|
||||
<span className="mr-1 system-sm-semibold text-text-secondary">{name}</span>
|
||||
{!noTooltip && (
|
||||
<Tooltip
|
||||
triggerClassName="w-4 h-4 shrink-0"
|
||||
popupContent={<div className="w-[200px]">{tip}</div>}
|
||||
/>
|
||||
{!noTooltip && tip && (
|
||||
<Infotip aria-label={tip} popupClassName="w-[200px]">
|
||||
{tip}
|
||||
</Infotip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuRadioItemIndicator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
|
||||
export type Theme = 'light' | 'dark' | 'system'
|
||||
|
||||
export default function ThemeSelector() {
|
||||
const { t } = useTranslation()
|
||||
const { theme, setTheme } = useTheme()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const handleThemeChange = (newTheme: Theme) => {
|
||||
setTheme(newTheme)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const getCurrentIcon = () => {
|
||||
@ -31,70 +31,36 @@ export default function ThemeSelector() {
|
||||
}
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement="bottom-end"
|
||||
offset={{ mainAxis: 6 }}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
onClick={() => setOpen(!open)}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={(
|
||||
<ActionButton
|
||||
aria-label={t('theme.theme', { ns: 'common' })}
|
||||
className="h-8 w-8 p-[6px] data-popup-open:bg-state-base-hover"
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<ActionButton
|
||||
className={`h-8 w-8 p-[6px] ${open && 'bg-state-base-hover'}`}
|
||||
>
|
||||
{getCurrentIcon()}
|
||||
</ActionButton>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className="z-1000">
|
||||
<div className="flex w-[144px] flex-col items-start rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg">
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover"
|
||||
onClick={() => handleThemeChange('light')}
|
||||
>
|
||||
{getCurrentIcon()}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={6} popupClassName="w-[144px]">
|
||||
<DropdownMenuRadioGroup value={theme || 'system'} onValueChange={value => handleThemeChange(value as Theme)}>
|
||||
<DropdownMenuRadioItem value="light" closeOnClick>
|
||||
<span className="i-ri-sun-line h-4 w-4 text-text-tertiary" />
|
||||
<div className="flex grow items-center justify-start px-1">
|
||||
<span className="system-md-regular">{t('theme.light', { ns: 'common' })}</span>
|
||||
</div>
|
||||
{theme === 'light' && (
|
||||
<div className="flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
<span className="i-ri-check-line h-4 w-4 text-text-accent" data-testid="light-icon" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover"
|
||||
onClick={() => handleThemeChange('dark')}
|
||||
>
|
||||
<span className="grow px-1 system-md-regular">{t('theme.light', { ns: 'common' })}</span>
|
||||
<DropdownMenuRadioItemIndicator data-testid="light-icon" />
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="dark" closeOnClick>
|
||||
<span className="i-ri-moon-line h-4 w-4 text-text-tertiary" />
|
||||
<div className="flex grow items-center justify-start px-1">
|
||||
<span className="system-md-regular">{t('theme.dark', { ns: 'common' })}</span>
|
||||
</div>
|
||||
{theme === 'dark' && (
|
||||
<div className="flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
<span className="i-ri-check-line h-4 w-4 text-text-accent" data-testid="dark-icon" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-1 rounded-lg px-2 py-1.5 text-text-secondary hover:bg-state-base-hover"
|
||||
onClick={() => handleThemeChange('system')}
|
||||
>
|
||||
<span className="grow px-1 system-md-regular">{t('theme.dark', { ns: 'common' })}</span>
|
||||
<DropdownMenuRadioItemIndicator data-testid="dark-icon" />
|
||||
</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="system" closeOnClick>
|
||||
<span className="i-ri-computer-line h-4 w-4 text-text-tertiary" />
|
||||
<div className="flex grow items-center justify-start px-1">
|
||||
<span className="system-md-regular">{t('theme.auto', { ns: 'common' })}</span>
|
||||
</div>
|
||||
{theme === 'system' && (
|
||||
<div className="flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
<span className="i-ri-check-line h-4 w-4 text-text-accent" data-testid="system-icon" />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
<span className="grow px-1 system-md-regular">{t('theme.auto', { ns: 'common' })}</span>
|
||||
<DropdownMenuRadioItemIndicator data-testid="system-icon" />
|
||||
</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { Mock } from 'vitest'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { TooltipProvider } from '@langgenius/dify-ui/tooltip'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { createMockPlan } from '@/__mocks__/provider-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { Plan } from '../../type'
|
||||
@ -15,6 +16,14 @@ const setupPlan = (planType: Plan) => {
|
||||
useProviderContextMock.mockReturnValue(createMockPlan(planType))
|
||||
}
|
||||
|
||||
const renderPriorityLabel = (className?: string) => {
|
||||
return render(
|
||||
<TooltipProvider delay={0} closeDelay={0}>
|
||||
<PriorityLabel className={className} />
|
||||
</TooltipProvider>,
|
||||
)
|
||||
}
|
||||
|
||||
describe('PriorityLabel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@ -24,7 +33,7 @@ describe('PriorityLabel', () => {
|
||||
it('should render the standard priority label when plan is sandbox', () => {
|
||||
setupPlan(Plan.sandbox)
|
||||
|
||||
render(<PriorityLabel />)
|
||||
renderPriorityLabel()
|
||||
|
||||
expect(screen.getByText('billing.plansCommon.priority.standard')).toBeInTheDocument()
|
||||
})
|
||||
@ -35,7 +44,7 @@ describe('PriorityLabel', () => {
|
||||
it('should apply custom className to the label container', () => {
|
||||
setupPlan(Plan.sandbox)
|
||||
|
||||
render(<PriorityLabel className="custom-class" />)
|
||||
renderPriorityLabel('custom-class')
|
||||
|
||||
const label = screen.getByText('billing.plansCommon.priority.standard').closest('div')
|
||||
expect(label).toHaveClass('custom-class')
|
||||
@ -47,7 +56,7 @@ describe('PriorityLabel', () => {
|
||||
it('should render priority label and icon when plan is professional', () => {
|
||||
setupPlan(Plan.professional)
|
||||
|
||||
const { container } = render(<PriorityLabel />)
|
||||
const { container } = renderPriorityLabel()
|
||||
|
||||
expect(screen.getByText('billing.plansCommon.priority.priority')).toBeInTheDocument()
|
||||
expect(container.querySelector('svg')).toBeInTheDocument()
|
||||
@ -56,7 +65,7 @@ describe('PriorityLabel', () => {
|
||||
it('should render top priority label and icon when plan is team', () => {
|
||||
setupPlan(Plan.team)
|
||||
|
||||
const { container } = render(<PriorityLabel />)
|
||||
const { container } = renderPriorityLabel()
|
||||
|
||||
expect(screen.getByText('billing.plansCommon.priority.top-priority')).toBeInTheDocument()
|
||||
expect(container.querySelector('svg')).toBeInTheDocument()
|
||||
@ -65,7 +74,7 @@ describe('PriorityLabel', () => {
|
||||
it('should render standard label without icon when plan is sandbox', () => {
|
||||
setupPlan(Plan.sandbox)
|
||||
|
||||
const { container } = render(<PriorityLabel />)
|
||||
const { container } = renderPriorityLabel()
|
||||
|
||||
expect(screen.getByText('billing.plansCommon.priority.standard')).toBeInTheDocument()
|
||||
expect(container.querySelector('svg')).not.toBeInTheDocument()
|
||||
@ -77,7 +86,7 @@ describe('PriorityLabel', () => {
|
||||
it('should render top-priority label with icon for enterprise plan', () => {
|
||||
setupPlan(Plan.enterprise)
|
||||
|
||||
const { container } = render(<PriorityLabel />)
|
||||
const { container } = renderPriorityLabel()
|
||||
|
||||
expect(screen.getByText('billing.plansCommon.priority.top-priority')).toBeInTheDocument()
|
||||
expect(container.querySelector('svg')).toBeInTheDocument()
|
||||
@ -85,29 +94,21 @@ describe('PriorityLabel', () => {
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should show the tip text when priority is not top priority', async () => {
|
||||
it('should render a non-top priority trigger without mounting tooltip content by default', () => {
|
||||
setupPlan(Plan.sandbox)
|
||||
|
||||
render(<PriorityLabel />)
|
||||
const label = screen.getByText('billing.plansCommon.priority.standard').closest('div')
|
||||
fireEvent.mouseEnter(label as HTMLElement)
|
||||
renderPriorityLabel()
|
||||
|
||||
expect(await screen.findByText(
|
||||
'billing.plansCommon.documentProcessingPriority: billing.plansCommon.priority.standard',
|
||||
)).toBeInTheDocument()
|
||||
expect(screen.getByText('billing.plansCommon.documentProcessingPriorityTip')).toBeInTheDocument()
|
||||
expect(screen.getByText('billing.plansCommon.priority.standard')).toBeInTheDocument()
|
||||
expect(screen.queryByText('billing.plansCommon.documentProcessingPriority')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide the tip text when priority is top priority', async () => {
|
||||
it('should render a top priority trigger without mounting upgrade tip by default', () => {
|
||||
setupPlan(Plan.enterprise)
|
||||
|
||||
render(<PriorityLabel />)
|
||||
const label = screen.getByText('billing.plansCommon.priority.top-priority').closest('div')
|
||||
fireEvent.mouseEnter(label as HTMLElement)
|
||||
renderPriorityLabel()
|
||||
|
||||
expect(await screen.findByText(
|
||||
'billing.plansCommon.documentProcessingPriority: billing.plansCommon.priority.top-priority',
|
||||
)).toBeInTheDocument()
|
||||
expect(screen.getByText('billing.plansCommon.priority.top-priority')).toBeInTheDocument()
|
||||
expect(screen.queryByText('billing.plansCommon.documentProcessingPriorityTip')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { RiAedFill } from '@remixicon/react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import {
|
||||
DocumentProcessingPriority,
|
||||
@ -31,8 +31,25 @@ const PriorityLabel = ({ className }: PriorityLabelProps) => {
|
||||
}, [plan])
|
||||
|
||||
return (
|
||||
<Tooltip popupContent={(
|
||||
<div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<div
|
||||
className={cn(
|
||||
'ml-1 inline-flex h-[18px] shrink-0 items-center rounded-[5px] border border-text-accent-secondary bg-components-badge-bg-dimm px-[5px] system-2xs-medium text-text-accent-secondary',
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{
|
||||
(plan.type === Plan.professional || plan.type === Plan.team || plan.type === Plan.enterprise) && (
|
||||
<RiAedFill className="mr-0.5 size-3" />
|
||||
)
|
||||
}
|
||||
<span>{t(`plansCommon.priority.${priority}`, { ns: 'billing' })}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="mb-1 text-xs font-semibold text-text-primary">
|
||||
{t('plansCommon.documentProcessingPriority', { ns: 'billing' })}
|
||||
:
|
||||
@ -44,22 +61,7 @@ const PriorityLabel = ({ className }: PriorityLabelProps) => {
|
||||
<div className="text-xs text-text-secondary">{t('plansCommon.documentProcessingPriorityTip', { ns: 'billing' })}</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'ml-1 inline-flex h-[18px] shrink-0 items-center rounded-[5px] border border-text-accent-secondary bg-components-badge-bg-dimm px-[5px] system-2xs-medium text-text-accent-secondary',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{
|
||||
(plan.type === Plan.professional || plan.type === Plan.team || plan.type === Plan.enterprise) && (
|
||||
<RiAedFill className="mr-0.5 size-3" />
|
||||
)
|
||||
}
|
||||
<span>{t(`plansCommon.priority.${priority}`, { ns: 'billing' })}</span>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { RiImageAddLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { ACCEPT_TYPES } from '../constants'
|
||||
import { useUpload } from '../hooks/use-upload'
|
||||
import { useFileStoreWithSelector } from '../store'
|
||||
@ -29,20 +29,14 @@ const ImageUploader = () => {
|
||||
onChange={fileChangeHandle}
|
||||
/>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Tooltip
|
||||
popupContent={t('imageUploader.tooltip', {
|
||||
ns: 'datasetHitTesting',
|
||||
size: fileUploadConfig.imageFileSizeLimit,
|
||||
batchCount: fileUploadConfig.imageFileBatchLimit,
|
||||
})}
|
||||
popupClassName="system-xs-medium p-1.5 rounded-lg text-text-secondary"
|
||||
position="top"
|
||||
offset={4}
|
||||
disabled={files.length === 0}
|
||||
>
|
||||
<div
|
||||
className="group flex cursor-pointer items-center gap-x-2"
|
||||
onClick={selectHandle}
|
||||
<Tooltip disabled={files.length === 0}>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<div
|
||||
className="group flex cursor-pointer items-center gap-x-2"
|
||||
onClick={selectHandle}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<div className="flex size-8 items-center justify-center rounded-lg border border-dashed border-components-dropzone-border bg-components-button-tertiary-bg group-hover:bg-components-button-tertiary-bg-hover">
|
||||
<RiImageAddLine className="size-4 text-text-tertiary" />
|
||||
@ -56,7 +50,14 @@ const ImageUploader = () => {
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent sideOffset={4} className="rounded-lg p-1.5 system-xs-medium text-text-secondary">
|
||||
{t('imageUploader.tooltip', {
|
||||
ns: 'datasetHitTesting',
|
||||
size: fileUploadConfig.imageFileSizeLimit,
|
||||
batchCount: fileUploadConfig.imageFileBatchLimit,
|
||||
})}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -134,12 +134,6 @@ vi.mock('@langgenius/dify-ui/switch', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ popupContent }: { popupContent: React.ReactNode }) => (
|
||||
<div data-testid="tooltip">{popupContent}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
describe('RetrievalParamConfig', () => {
|
||||
const createDefaultConfig = (overrides?: Partial<RetrievalConfig>): RetrievalConfig => ({
|
||||
search_method: RETRIEVE_METHOD.semantic,
|
||||
@ -799,7 +793,7 @@ describe('RetrievalParamConfig', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip'))!.toBeInTheDocument()
|
||||
expect(screen.getByLabelText('common.modelProvider.rerankModel.tip'))!.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -10,10 +10,10 @@ import { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import WeightedScore from '@/app/components/app/configuration/dataset-config/params-config/weighted-score'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item'
|
||||
import TopKItem from '@/app/components/base/param-item/top-k-item'
|
||||
import RadioCard from '@/app/components/base/radio-card'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useCurrentProviderAndModel, useModelListAndDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||
@ -127,11 +127,12 @@ const RetrievalParamConfig: FC<Props> = ({
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<span className="mr-0.5 system-sm-semibold text-text-secondary">{t('modelProvider.rerankModel.key', { ns: 'common' })}</span>
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className="w-[200px]">{t('modelProvider.rerankModel.tip', { ns: 'common' })}</div>
|
||||
}
|
||||
/>
|
||||
<Infotip
|
||||
aria-label={t('modelProvider.rerankModel.tip', { ns: 'common' })}
|
||||
popupClassName="w-[200px]"
|
||||
>
|
||||
{t('modelProvider.rerankModel.tip', { ns: 'common' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
|
||||
@ -5,8 +5,8 @@ import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import WorkflowPreview from '@/app/components/workflow/workflow-preview'
|
||||
import { usePipelineTemplateById } from '@/service/use-pipeline'
|
||||
import ChunkStructureCard from './chunk-structure-card'
|
||||
@ -111,10 +111,12 @@ const Details = ({
|
||||
<span className="system-sm-semibold-uppercase text-text-secondary">
|
||||
{t('details.structure', { ns: 'datasetPipeline' })}
|
||||
</span>
|
||||
<Tooltip
|
||||
<Infotip
|
||||
aria-label={t('details.structureTooltip', { ns: 'datasetPipeline' })}
|
||||
popupClassName="max-w-[240px]"
|
||||
popupContent={t('details.structureTooltip', { ns: 'datasetPipeline' })}
|
||||
/>
|
||||
>
|
||||
{t('details.structureTooltip', { ns: 'datasetPipeline' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
<ChunkStructureCard {...chunkStructureConfig[pipelineTemplateInfo.chunk_structure]} />
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { IndexingStatusResponse } from '@/models/datasets'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
@ -14,12 +13,6 @@ vi.mock('../../../common/document-file-icon', () => ({
|
||||
vi.mock('@/app/components/base/notion-icon', () => ({
|
||||
default: ({ src }: { src?: string }) => <span data-testid="notion-icon">{src}</span>,
|
||||
}))
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ children, popupContent }: { children?: ReactNode, popupContent?: ReactNode }) => (
|
||||
<div data-testid="tooltip" data-content={popupContent}>{children}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
describe('IndexingProgressItem', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@ -100,7 +93,7 @@ describe('IndexingProgressItem', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-content', 'Parse failed')
|
||||
expect(screen.getByLabelText('Parse failed')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show priority label when billing is enabled', () => {
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import type { FC } from 'react'
|
||||
import type { IndexingStatusResponse } from '@/models/datasets'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import {
|
||||
RiCheckboxCircleFill,
|
||||
RiErrorWarningFill,
|
||||
} from '@remixicon/react'
|
||||
import NotionIcon from '@/app/components/base/notion-icon'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import PriorityLabel from '@/app/components/billing/priority-label'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
import DocumentFileIcon from '../../common/document-file-icon'
|
||||
@ -27,14 +27,16 @@ const StatusIcon: FC<{ status: string, error?: string }> = ({ status, error }) =
|
||||
|
||||
if (status === 'error') {
|
||||
return (
|
||||
<Tooltip
|
||||
popupClassName="px-4 py-[14px] max-w-60 body-xs-regular text-text-secondary border-[0.5px] border-components-panel-border rounded-xl"
|
||||
offset={4}
|
||||
popupContent={error}
|
||||
>
|
||||
<span>
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={<span aria-label={error || 'Error'} />}>
|
||||
<RiErrorWarningFill className="size-4 shrink-0 text-text-destructive" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
sideOffset={4}
|
||||
className="max-w-60 rounded-xl border-[0.5px] border-components-panel-border px-4 py-[14px] body-xs-regular text-text-secondary"
|
||||
>
|
||||
{error}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@ -165,6 +165,13 @@ describe('IndexingModeSection', () => {
|
||||
const economicalText = screen.getByText(`${ns}.stepTwo.economical`)
|
||||
const card = economicalText.closest('[class*="rounded-xl"]')
|
||||
expect(card)!.toHaveClass('pointer-events-none')
|
||||
expect(screen.getByText(`${ns}.stepTwo.notAvailableForQA`))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show parent-child disabled reason inline on economical option', () => {
|
||||
render(<IndexingModeSection {...defaultProps} docForm={ChunkingMode.parentChild} />)
|
||||
|
||||
expect(screen.getByText(`${ns}.stepTwo.notAvailableForParentChild`))!.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -30,9 +30,7 @@ describe('DelimiterInput', () => {
|
||||
|
||||
it('should render tooltip content', () => {
|
||||
render(<DelimiterInput />)
|
||||
// Tooltip triggers render; component mounts without error
|
||||
// Tooltip triggers render; component mounts without error
|
||||
expect(screen.getByText(`${ns}.stepTwo.separator`))!.toBeInTheDocument()
|
||||
expect(screen.getByLabelText(`${ns}.stepTwo.separatorTip`))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should suppress onChange during IME composition', () => {
|
||||
@ -103,6 +101,7 @@ describe('OverlapInput', () => {
|
||||
it('should render overlap label', () => {
|
||||
render(<OverlapInput onChange={vi.fn()} />)
|
||||
expect(screen.getAllByText(new RegExp(`${ns}.stepTwo.overlap`)).length).toBeGreaterThan(0)
|
||||
expect(screen.getByLabelText(`${ns}.stepTwo.overlapTip`))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render number input', () => {
|
||||
|
||||
@ -3,14 +3,20 @@
|
||||
import type { FC } from 'react'
|
||||
import type { DefaultModel, Model } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { RetrievalConfig } from '@/types/app'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogActions,
|
||||
AlertDialogCancelButton,
|
||||
AlertDialogConfirmButton,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogTitle,
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import CustomDialog from '@/app/components/base/dialog'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
|
||||
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
|
||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||
@ -65,6 +71,13 @@ export const IndexingModeSection: FC<IndexingModeSectionProps> = ({
|
||||
const docLink = useDocLink()
|
||||
|
||||
const getIndexingTechnique = () => indexType
|
||||
const economicalDisabledReason = (() => {
|
||||
if (docForm === ChunkingMode.qa)
|
||||
return t('stepTwo.notAvailableForQA', { ns: 'datasetCreation' })
|
||||
|
||||
if (docForm !== ChunkingMode.text)
|
||||
return t('stepTwo.notAvailableForParentChild', { ns: 'datasetCreation' })
|
||||
})()
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -72,7 +85,33 @@ export const IndexingModeSection: FC<IndexingModeSectionProps> = ({
|
||||
<div className="mb-1 system-md-semibold text-text-secondary">
|
||||
{t('stepTwo.indexMode', { ns: 'datasetCreation' })}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertDialog
|
||||
open={isQAConfirmDialogOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onQAConfirmDialogClose()
|
||||
}}
|
||||
>
|
||||
<AlertDialogContent className="w-[432px]">
|
||||
<div className="flex flex-col gap-2 p-6 pb-4">
|
||||
<AlertDialogTitle className="text-lg leading-7 font-semibold text-text-primary">
|
||||
{t('stepTwo.qaSwitchHighQualityTipTitle', { ns: 'datasetCreation' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="text-sm leading-5 text-text-secondary">
|
||||
{t('stepTwo.qaSwitchHighQualityTipContent', { ns: 'datasetCreation' })}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton variant="secondary">
|
||||
{t('stepTwo.cancel', { ns: 'datasetCreation' })}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton tone="default" onClick={onQAConfirmDialogConfirm}>
|
||||
{t('stepTwo.switch', { ns: 'datasetCreation' })}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<div className="flex items-stretch gap-2">
|
||||
{/* Qualified option */}
|
||||
{(!hasSetIndexType || (hasSetIndexType && indexType === IndexingType.QUALIFIED)) && (
|
||||
<OptionCard
|
||||
@ -106,49 +145,15 @@ export const IndexingModeSection: FC<IndexingModeSectionProps> = ({
|
||||
|
||||
{/* Economical option */}
|
||||
{(!hasSetIndexType || (hasSetIndexType && indexType === IndexingType.ECONOMICAL)) && (
|
||||
<>
|
||||
<CustomDialog show={isQAConfirmDialogOpen} onClose={onQAConfirmDialogClose} className="w-[432px]">
|
||||
<header className="mb-4 pt-6">
|
||||
<h2 className="text-lg font-semibold text-text-primary">
|
||||
{t('stepTwo.qaSwitchHighQualityTipTitle', { ns: 'datasetCreation' })}
|
||||
</h2>
|
||||
<p className="mt-2 text-sm font-normal text-text-secondary">
|
||||
{t('stepTwo.qaSwitchHighQualityTipContent', { ns: 'datasetCreation' })}
|
||||
</p>
|
||||
</header>
|
||||
<div className="flex gap-2 pb-6">
|
||||
<Button className="ml-auto" onClick={onQAConfirmDialogClose}>
|
||||
{t('stepTwo.cancel', { ns: 'datasetCreation' })}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={onQAConfirmDialogConfirm}>
|
||||
{t('stepTwo.switch', { ns: 'datasetCreation' })}
|
||||
</Button>
|
||||
</div>
|
||||
</CustomDialog>
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="rounded-lg border-components-panel-border bg-components-tooltip-bg p-3 text-xs font-medium text-text-secondary shadow-lg">
|
||||
{docForm === ChunkingMode.qa
|
||||
? t('stepTwo.notAvailableForQA', { ns: 'datasetCreation' })
|
||||
: t('stepTwo.notAvailableForParentChild', { ns: 'datasetCreation' })}
|
||||
</div>
|
||||
)}
|
||||
noDecoration
|
||||
position="top"
|
||||
asChild={false}
|
||||
triggerClassName="flex-1 self-stretch"
|
||||
>
|
||||
<OptionCard
|
||||
className="h-full"
|
||||
title={t('stepTwo.economical', { ns: 'datasetCreation' })}
|
||||
description={t('stepTwo.economicalTip', { ns: 'datasetCreation' })}
|
||||
icon={<img src={indexMethodIcon.economical} alt="" />}
|
||||
isActive={!hasSetIndexType && indexType === IndexingType.ECONOMICAL}
|
||||
disabled={hasSetIndexType || docForm !== ChunkingMode.text}
|
||||
onSwitched={() => onIndexTypeChange(IndexingType.ECONOMICAL)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
<OptionCard
|
||||
className="flex-1 self-stretch"
|
||||
title={t('stepTwo.economical', { ns: 'datasetCreation' })}
|
||||
description={economicalDisabledReason || t('stepTwo.economicalTip', { ns: 'datasetCreation' })}
|
||||
icon={<img src={indexMethodIcon.economical} alt="" />}
|
||||
isActive={!hasSetIndexType && indexType === IndexingType.ECONOMICAL}
|
||||
disabled={hasSetIndexType || !!economicalDisabledReason}
|
||||
onSwitched={() => onIndexTypeChange(IndexingType.ECONOMICAL)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@ -12,8 +12,8 @@ import {
|
||||
} from '@langgenius/dify-ui/number-field'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { env } from '@/env'
|
||||
|
||||
const TextLabel: FC<PropsWithChildren> = (props) => {
|
||||
@ -38,13 +38,9 @@ export const DelimiterInput: FC<InputProps & { tooltip?: string }> = ({ tooltip,
|
||||
<FormField label={(
|
||||
<div className="mb-1 flex items-center">
|
||||
<span className="mr-0.5 system-sm-semibold">{t('stepTwo.separator', { ns: 'datasetCreation' })}</span>
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="max-w-[200px]">
|
||||
{tooltip || t('stepTwo.separatorTip', { ns: 'datasetCreation' })}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Infotip aria-label={tooltip || t('stepTwo.separatorTip', { ns: 'datasetCreation' })} popupClassName="max-w-[200px]">
|
||||
{tooltip || t('stepTwo.separatorTip', { ns: 'datasetCreation' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
@ -154,13 +150,9 @@ export const OverlapInput: FC<CompoundNumberInputProps> = (props) => {
|
||||
<FormField label={(
|
||||
<div className="mb-1 flex items-center">
|
||||
<span className="system-sm-semibold">{t('stepTwo.overlap', { ns: 'datasetCreation' })}</span>
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="max-w-[200px]">
|
||||
{t('stepTwo.overlapTip', { ns: 'datasetCreation' })}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Infotip aria-label={t('stepTwo.overlapTip', { ns: 'datasetCreation' })} popupClassName="max-w-[200px]">
|
||||
{t('stepTwo.overlapTip', { ns: 'datasetCreation' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
|
||||
@ -20,7 +20,7 @@ type OptionCardHeaderProps = {
|
||||
export const OptionCardHeader: FC<OptionCardHeaderProps> = (props) => {
|
||||
const { icon, title, description, isActive, activeClassName, effectImg, disabled } = props
|
||||
return (
|
||||
<div className={cn('relative flex h-full overflow-hidden rounded-t-xl', isActive && activeClassName, !disabled && 'cursor-pointer')}>
|
||||
<div className={cn('relative flex flex-1 overflow-hidden rounded-t-xl', isActive && activeClassName, !disabled && 'cursor-pointer')}>
|
||||
<div className="relative flex size-14 items-center justify-center overflow-hidden">
|
||||
{isActive && effectImg && <img src={effectImg} className="absolute top-0 left-0 h-full w-full" alt="" width={56} height={56} />}
|
||||
<div className="p-1">
|
||||
@ -63,7 +63,7 @@ export const OptionCard: FC<OptionCardProps> = (
|
||||
const { icon, className, title, description, isActive, children, actions, activeHeaderClassName, style, effectImg, onSwitched, noHighlight, disabled, ...rest } = props
|
||||
return (
|
||||
<div
|
||||
className={cn('rounded-xl bg-components-option-card-option-bg shadow-xs', (isActive && !noHighlight)
|
||||
className={cn('flex flex-col rounded-xl bg-components-option-card-option-bg shadow-xs', (isActive && !noHighlight)
|
||||
? 'border-[1.5px] border-components-option-card-option-selected-border'
|
||||
: 'border border-components-option-card-option-border', disabled && 'pointer-events-none opacity-50', className)}
|
||||
style={{
|
||||
|
||||
@ -2,10 +2,6 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import CheckboxWithLabel from '../checkbox-with-label'
|
||||
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ popupContent }: { popupContent?: React.ReactNode }) => <div data-testid="tooltip">{popupContent}</div>,
|
||||
}))
|
||||
|
||||
describe('CheckboxWithLabel', () => {
|
||||
const onChange = vi.fn()
|
||||
|
||||
@ -27,12 +23,12 @@ describe('CheckboxWithLabel', () => {
|
||||
tooltip="Help text"
|
||||
/>,
|
||||
)
|
||||
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Help text')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render tooltip when not provided', () => {
|
||||
render(<CheckboxWithLabel isChecked={false} onChange={onChange} label="Option" />)
|
||||
expect(screen.queryByTestId('tooltip')).not.toBeInTheDocument()
|
||||
expect(screen.queryByLabelText('Help text')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should toggle checked state on checkbox click', () => {
|
||||
|
||||
@ -2,10 +2,6 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import Field from '../field'
|
||||
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ popupContent }: { popupContent?: React.ReactNode }) => <div data-testid="tooltip">{popupContent}</div>,
|
||||
}))
|
||||
|
||||
describe('WebsiteField', () => {
|
||||
const onChange = vi.fn()
|
||||
|
||||
@ -30,7 +26,7 @@ describe('WebsiteField', () => {
|
||||
|
||||
it('should render tooltip when provided', () => {
|
||||
render(<Field label="URL" value="" onChange={onChange} tooltip="Enter full URL" />)
|
||||
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Enter full URL')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should pass value and onChange to Input', () => {
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
import type { FC } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import * as React from 'react'
|
||||
import { useId } from 'react'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@ -24,19 +25,28 @@ const CheckboxWithLabel: FC<Props> = ({
|
||||
tooltip,
|
||||
testId,
|
||||
}) => {
|
||||
const labelId = useId()
|
||||
const handleToggle = () => onChange(!isChecked)
|
||||
|
||||
return (
|
||||
<label className={cn(className, 'flex h-7 items-center space-x-2')}>
|
||||
<Checkbox checked={isChecked} onCheck={() => onChange(!isChecked)} id={testId} />
|
||||
<div className={cn('text-sm font-normal text-text-secondary', labelClassName)}>{label}</div>
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className="w-[200px]">{tooltip}</div>
|
||||
}
|
||||
triggerClassName="ml-0.5 w-4 h-4"
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
<div className={cn(className, 'flex h-7 items-center')}>
|
||||
<Checkbox checked={isChecked} onCheck={handleToggle} id={testId} ariaLabelledBy={labelId} />
|
||||
<div className="ml-2 flex min-w-0 items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
id={labelId}
|
||||
className={cn('min-w-0 cursor-pointer border-0 bg-transparent p-0 text-left text-sm font-normal text-text-secondary', labelClassName)}
|
||||
onClick={handleToggle}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
{tooltip && (
|
||||
<Infotip aria-label={tooltip} popupClassName="w-[200px]">
|
||||
{tooltip}
|
||||
</Infotip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(CheckboxWithLabel)
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import * as React from 'react'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import Input from './input'
|
||||
|
||||
type Props = {
|
||||
@ -37,12 +37,9 @@ const Field: FC<Props> = ({
|
||||
</div>
|
||||
{isRequired && <span className="ml-0.5 text-xs font-semibold text-text-destructive">*</span>}
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className="w-[200px]">{tooltip}</div>
|
||||
}
|
||||
triggerClassName="ml-0.5 w-4 h-4"
|
||||
/>
|
||||
<Infotip aria-label={tooltip} className="ml-0.5" popupClassName="w-[200px]">
|
||||
{tooltip}
|
||||
</Infotip>
|
||||
)}
|
||||
</div>
|
||||
<Input
|
||||
|
||||
@ -8,10 +8,6 @@ vi.mock('@/app/components/base/checkbox', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ popupContent }: { popupContent: string }) => <div data-testid="tooltip">{popupContent}</div>,
|
||||
}))
|
||||
|
||||
describe('CheckboxWithLabel', () => {
|
||||
const defaultProps = {
|
||||
isChecked: false,
|
||||
@ -35,12 +31,12 @@ describe('CheckboxWithLabel', () => {
|
||||
|
||||
it('should render tooltip when provided', () => {
|
||||
render(<CheckboxWithLabel {...defaultProps} tooltip="Help text" />)
|
||||
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Help text')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render tooltip when not provided', () => {
|
||||
render(<CheckboxWithLabel {...defaultProps} />)
|
||||
expect(screen.queryByTestId('tooltip')).not.toBeInTheDocument()
|
||||
expect(screen.queryByLabelText('Help text')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply custom className', () => {
|
||||
|
||||
@ -62,16 +62,13 @@ describe('CheckboxWithLabel', () => {
|
||||
it('should render tooltip when provided', () => {
|
||||
render(<CheckboxWithLabel {...defaultProps} tooltip="Helpful tooltip text" />)
|
||||
|
||||
// Assert - Tooltip trigger should be present
|
||||
const tooltipTrigger = document.querySelector('[class*="ml-0.5"]')
|
||||
expect(tooltipTrigger)!.toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Helpful tooltip text'))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render tooltip when not provided', () => {
|
||||
render(<CheckboxWithLabel {...defaultProps} />)
|
||||
|
||||
const tooltipTrigger = document.querySelector('[class*="ml-0.5"]')
|
||||
expect(tooltipTrigger).not.toBeInTheDocument()
|
||||
expect(screen.queryByLabelText('Helpful tooltip text')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -81,8 +78,7 @@ describe('CheckboxWithLabel', () => {
|
||||
<CheckboxWithLabel {...defaultProps} className="custom-class" />,
|
||||
)
|
||||
|
||||
const label = container.querySelector('label')
|
||||
expect(label)!.toHaveClass('custom-class')
|
||||
expect(container.firstChild)!.toHaveClass('custom-class')
|
||||
})
|
||||
|
||||
it('should apply custom labelClassName', () => {
|
||||
@ -114,16 +110,14 @@ describe('CheckboxWithLabel', () => {
|
||||
expect(mockOnChange).toHaveBeenCalledWith(false)
|
||||
})
|
||||
|
||||
it('should not trigger onChange when clicking label text due to custom checkbox', () => {
|
||||
it('should trigger onChange when clicking label text', () => {
|
||||
const mockOnChange = vi.fn()
|
||||
render(<CheckboxWithLabel {...defaultProps} onChange={mockOnChange} />)
|
||||
|
||||
// Act - Click on the label text element
|
||||
const labelText = screen.getByText('Test Label')
|
||||
fireEvent.click(labelText)
|
||||
|
||||
// Assert - Custom checkbox does not support native label-input click forwarding
|
||||
expect(mockOnChange).not.toHaveBeenCalled()
|
||||
expect(mockOnChange).toHaveBeenCalledWith(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -386,15 +380,14 @@ describe('CrawledResult', () => {
|
||||
it('should pass showPreview to items', () => {
|
||||
render(<CrawledResult {...defaultProps} showPreview={true} />)
|
||||
|
||||
// Assert - Preview buttons should be visible
|
||||
const buttons = screen.getAllByRole('button')
|
||||
const buttons = screen.getAllByRole('button', { name: 'datasetCreation.stepOne.website.preview' })
|
||||
expect(buttons.length).toBe(3)
|
||||
})
|
||||
|
||||
it('should not show preview buttons when showPreview is false', () => {
|
||||
render(<CrawledResult {...defaultProps} showPreview={false} />)
|
||||
|
||||
expect(screen.queryByRole('button')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: 'datasetCreation.stepOne.website.preview' })).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -507,7 +500,7 @@ describe('CrawledResult', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
const buttons = screen.getAllByRole('button')
|
||||
const buttons = screen.getAllByRole('button', { name: 'datasetCreation.stepOne.website.preview' })
|
||||
fireEvent.click(buttons[1]!) // Second item's preview button
|
||||
|
||||
expect(mockOnPreview).toHaveBeenCalledWith(list[1], 1)
|
||||
@ -796,7 +789,7 @@ describe('Base Components Integration', () => {
|
||||
expect(mockOnSelectedChange).toHaveBeenCalledWith([list[0]])
|
||||
|
||||
// Act - Preview second item
|
||||
const previewButtons = screen.getAllByRole('button')
|
||||
const previewButtons = screen.getAllByRole('button', { name: 'datasetCreation.stepOne.website.preview' })
|
||||
fireEvent.click(previewButtons[1]!)
|
||||
|
||||
expect(mockOnPreview).toHaveBeenCalledWith(list[1], 1)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
'use client'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import * as React from 'react'
|
||||
import { useId } from 'react'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
|
||||
type CheckboxWithLabelProps = {
|
||||
className?: string
|
||||
@ -21,19 +22,28 @@ const CheckboxWithLabel = ({
|
||||
labelClassName,
|
||||
tooltip,
|
||||
}: CheckboxWithLabelProps) => {
|
||||
const labelId = useId()
|
||||
const handleToggle = () => onChange(!isChecked)
|
||||
|
||||
return (
|
||||
<label className={cn('flex items-center space-x-2', className)}>
|
||||
<Checkbox checked={isChecked} onCheck={() => onChange(!isChecked)} />
|
||||
<div className={cn('system-sm-medium text-text-secondary', labelClassName)}>{label}</div>
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className="w-[200px]">{tooltip}</div>
|
||||
}
|
||||
triggerClassName="ml-0.5 w-4 h-4"
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
<div className={cn('flex items-center', className)}>
|
||||
<Checkbox checked={isChecked} onCheck={handleToggle} ariaLabelledBy={labelId} />
|
||||
<div className="ml-2 flex min-w-0 items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
id={labelId}
|
||||
className={cn('min-w-0 cursor-pointer border-0 bg-transparent p-0 text-left system-sm-medium text-text-secondary', labelClassName)}
|
||||
onClick={handleToggle}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
{tooltip && (
|
||||
<Infotip aria-label={tooltip} popupClassName="w-[200px]">
|
||||
{tooltip}
|
||||
</Infotip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default React.memo(CheckboxWithLabel)
|
||||
|
||||
@ -140,12 +140,12 @@ describe('StatusItem', () => {
|
||||
describe('error message tooltip', () => {
|
||||
it('should show tooltip trigger when error message is provided', () => {
|
||||
render(<StatusItem status="error" errorMessage="Test error message" />)
|
||||
expect(screen.getByTestId('error-tooltip-trigger')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Test error message')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not show tooltip trigger when no error message', () => {
|
||||
render(<StatusItem status="error" />)
|
||||
expect(screen.queryByTestId('error-tooltip-trigger')).not.toBeInTheDocument()
|
||||
expect(screen.queryByLabelText('Test error message')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -5,11 +5,12 @@ import type { DocumentDisplayStatus } from '@/models/datasets'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { useDocumentDelete, useDocumentDisable, useDocumentEnable } from '@/service/knowledge/use-document'
|
||||
import { asyncRunSafe } from '@/utils'
|
||||
@ -81,11 +82,33 @@ const StatusItem = ({ status, reverse = false, scene = 'list', textCls = '', err
|
||||
<span className={cn(`${STATUS_TEXT_COLOR_MAP[DOC_INDEX_STATUS_MAP[localStatus].color as keyof typeof STATUS_TEXT_COLOR_MAP]} text-sm`, textCls)}>
|
||||
{DOC_INDEX_STATUS_MAP[localStatus]?.text}
|
||||
</span>
|
||||
{errorMessage && (<Tooltip popupContent={<div className="max-w-[260px] break-all">{errorMessage}</div>} triggerClassName="ml-1 w-4 h-4" triggerTestId="error-tooltip-trigger" />)}
|
||||
{errorMessage && (
|
||||
<Infotip
|
||||
aria-label={errorMessage}
|
||||
className="ml-1"
|
||||
popupClassName="max-w-[260px] break-all"
|
||||
>
|
||||
{errorMessage}
|
||||
</Infotip>
|
||||
)}
|
||||
{scene === 'detail' && (
|
||||
<div className="ml-1.5 flex items-center justify-between">
|
||||
<Tooltip popupContent={t('list.action.enableWarning', { ns: 'datasetDocuments' })} popupClassName="text-text-secondary system-xs-medium" disabled={!archived}>
|
||||
<Switch checked={archived ? false : enabled} onCheckedChange={v => !archived && handleSwitch(v ? 'enable' : 'disable')} disabled={embedding || archived} size="md" />
|
||||
<Tooltip disabled={!archived}>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<span className="flex">
|
||||
<Switch
|
||||
checked={archived ? false : enabled}
|
||||
onCheckedChange={v => !archived && handleSwitch(v ? 'enable' : 'disable')}
|
||||
disabled={embedding || archived}
|
||||
size="md"
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent className="system-xs-medium text-text-secondary">
|
||||
{t('list.action.enableWarning', { ns: 'datasetDocuments' })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -124,9 +124,7 @@ describe('InfoGroup', () => {
|
||||
titleTooltip="This is a tooltip"
|
||||
/>,
|
||||
)
|
||||
// Tooltip icon should be present
|
||||
const tooltipIcon = screen.getByText('Test').closest('.flex')?.querySelector('svg')
|
||||
expect(tooltipIcon)!.toBeInTheDocument()
|
||||
expect(screen.getByLabelText('This is a tooltip'))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render headerRight content', () => {
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
import type { FC } from 'react'
|
||||
import type { MetadataItemWithValue } from '../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { RiDeleteBinLine, RiQuestionLine } from '@remixicon/react'
|
||||
import { RiDeleteBinLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { useRouter } from '@/next/navigation'
|
||||
import AddMetadataButton from '../add-metadata-button'
|
||||
@ -64,9 +64,9 @@ const InfoGroup: FC<Props> = ({
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className={cn('text-text-secondary', uppercaseTitle ? 'system-xs-semibold-uppercase' : 'system-md-semibold')}>{title}</div>
|
||||
{titleTooltip && (
|
||||
<Tooltip popupContent={<div className="max-w-[240px]">{titleTooltip}</div>}>
|
||||
<div><RiQuestionLine className="size-3.5 text-text-tertiary" /></div>
|
||||
</Tooltip>
|
||||
<Infotip aria-label={titleTooltip} popupClassName="max-w-[240px]">
|
||||
{titleTooltip}
|
||||
</Infotip>
|
||||
)}
|
||||
</div>
|
||||
{headerRight}
|
||||
|
||||
@ -8,8 +8,8 @@ import {
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
|
||||
@ -65,11 +65,12 @@ const SummaryIndexSetting = ({
|
||||
<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' })}
|
||||
<Tooltip
|
||||
triggerClassName="ml-1 h-4 w-4 shrink-0"
|
||||
popupContent={t('form.summaryAutoGenTip', { ns: 'datasetSettings' })}
|
||||
<Infotip
|
||||
aria-label={t('form.summaryAutoGenTip', { ns: 'datasetSettings' })}
|
||||
className="ml-1"
|
||||
>
|
||||
</Tooltip>
|
||||
{t('form.summaryAutoGenTip', { ns: 'datasetSettings' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
<Switch
|
||||
checked={summaryIndexSetting?.enable ?? false}
|
||||
|
||||
@ -1,14 +1,6 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import GlobalInputs from '../global-inputs'
|
||||
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({
|
||||
popupContent,
|
||||
}: {
|
||||
popupContent: React.ReactNode
|
||||
}) => <div data-testid="tooltip">{popupContent}</div>,
|
||||
}))
|
||||
|
||||
describe('GlobalInputs', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@ -18,6 +10,6 @@ describe('GlobalInputs', () => {
|
||||
render(<GlobalInputs />)
|
||||
|
||||
expect(screen.getByText('datasetPipeline.inputFieldPanel.globalInputs.title')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('tooltip')).toHaveTextContent('datasetPipeline.inputFieldPanel.globalInputs.tooltip')
|
||||
expect(screen.getByLabelText('datasetPipeline.inputFieldPanel.globalInputs.tooltip')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -20,12 +20,6 @@ vi.mock('@/app/components/workflow/hooks', () => ({
|
||||
useToolIcon: (nodeData: DataSourceNodeType) => nodeData.provider_name || 'default-icon',
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ popupContent, popupClassName }: { popupContent: string, popupClassName?: string }) => (
|
||||
<div data-testid="tooltip" data-content={popupContent} className={popupClassName} />
|
||||
),
|
||||
}))
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
vi.clearAllMocks()
|
||||
@ -161,21 +155,19 @@ describe('GlobalInputs', () => {
|
||||
it('should render tooltip component', () => {
|
||||
render(<GlobalInputs />)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('datasetPipeline.inputFieldPanel.globalInputs.tooltip')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should pass correct tooltip content', () => {
|
||||
render(<GlobalInputs />)
|
||||
|
||||
const tooltip = screen.getByTestId('tooltip')
|
||||
expect(tooltip).toHaveAttribute('data-content', 'datasetPipeline.inputFieldPanel.globalInputs.tooltip')
|
||||
expect(screen.getByLabelText('datasetPipeline.inputFieldPanel.globalInputs.tooltip')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have correct tooltip className', () => {
|
||||
it('should render the tooltip trigger as an icon-sized button', () => {
|
||||
render(<GlobalInputs />)
|
||||
|
||||
const tooltip = screen.getByTestId('tooltip')
|
||||
expect(tooltip).toHaveClass('w-[240px]')
|
||||
expect(screen.getByLabelText('datasetPipeline.inputFieldPanel.globalInputs.tooltip')).toHaveClass('h-4', 'w-4')
|
||||
})
|
||||
|
||||
it('should have correct container layout', () => {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
|
||||
const GlobalInputs = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -10,10 +10,9 @@ const GlobalInputs = () => {
|
||||
<span className="system-sm-semibold-uppercase text-text-secondary">
|
||||
{t('inputFieldPanel.globalInputs.title', { ns: 'datasetPipeline' })}
|
||||
</span>
|
||||
<Tooltip
|
||||
popupContent={t('inputFieldPanel.globalInputs.tooltip', { ns: 'datasetPipeline' })}
|
||||
popupClassName="w-[240px]"
|
||||
/>
|
||||
<Infotip aria-label={t('inputFieldPanel.globalInputs.tooltip', { ns: 'datasetPipeline' })} popupClassName="w-[240px]">
|
||||
{t('inputFieldPanel.globalInputs.tooltip', { ns: 'datasetPipeline' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -330,27 +330,6 @@ describe('publisher', () => {
|
||||
})
|
||||
expect(mockSetShowPricingModal).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should keep confirm dialog mounted when first publish opens follow-up overlay', async () => {
|
||||
mockPublishedAt.mockReturnValue(null)
|
||||
renderWithQueryClient(<Publisher />)
|
||||
|
||||
fireEvent.click(screen.getByText('workflow.common.publish'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('workflow.common.publishUpdate')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /workflow.common.publishUpdate/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('pipeline.common.confirmPublish')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.mouseDown(document.body)
|
||||
|
||||
expect(screen.getByText('pipeline.common.confirmPublish')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import Field from '../field'
|
||||
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({ popupContent }: { popupContent: React.ReactNode }) => <div>{popupContent}</div>,
|
||||
}))
|
||||
|
||||
describe('Field', () => {
|
||||
it('should render subtitle styling, tooltip, operations, warning dot and required marker', () => {
|
||||
const { container } = render(
|
||||
@ -19,7 +15,7 @@ describe('Field', () => {
|
||||
)
|
||||
|
||||
expect(screen.getByText('Knowledge')).toBeInTheDocument()
|
||||
expect(screen.getByText('tooltip text')).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('tooltip text')).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'operation' })).toBeInTheDocument()
|
||||
expect(screen.getByText('*')).toBeInTheDocument()
|
||||
expect(container.querySelector('.system-xs-medium-uppercase')).not.toBeNull()
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
} from '@remixicon/react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@ -21,6 +21,17 @@ type Props = {
|
||||
warningDot?: boolean
|
||||
}
|
||||
|
||||
const getTextFromNode = (node: ReactNode): string | undefined => {
|
||||
if (typeof node === 'string' || typeof node === 'number')
|
||||
return `${node}`
|
||||
|
||||
if (Array.isArray(node))
|
||||
return node.map(getTextFromNode).filter(Boolean).join(' ')
|
||||
|
||||
if (React.isValidElement<{ children?: ReactNode }>(node))
|
||||
return getTextFromNode(node.props.children)
|
||||
}
|
||||
|
||||
const Field: FC<Props> = ({
|
||||
className,
|
||||
title,
|
||||
@ -36,6 +47,8 @@ const Field: FC<Props> = ({
|
||||
const [fold, {
|
||||
toggle: toggleFold,
|
||||
}] = useBoolean(true)
|
||||
const tooltipLabel = tooltip ? getTextFromNode(tooltip) || getTextFromNode(title) || 'Help' : undefined
|
||||
|
||||
return (
|
||||
<div className={cn(className, inline && 'flex w-full items-center justify-between')}>
|
||||
<div
|
||||
@ -51,12 +64,10 @@ const Field: FC<Props> = ({
|
||||
{' '}
|
||||
{required && <span className="text-text-destructive">*</span>}
|
||||
</div>
|
||||
{!!tooltip && (
|
||||
<Tooltip
|
||||
popupContent={tooltip}
|
||||
popupClassName="ml-1"
|
||||
triggerClassName="w-4 h-4 ml-1"
|
||||
/>
|
||||
{!!tooltip && !!tooltipLabel && (
|
||||
<Infotip aria-label={tooltipLabel} className="ml-1">
|
||||
{tooltip}
|
||||
</Infotip>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex">
|
||||
|
||||
@ -5,7 +5,7 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import * as React from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
|
||||
const variants = cva([], {
|
||||
variants: {
|
||||
@ -60,13 +60,9 @@ const OptionCard: FC<Props> = ({
|
||||
<span>{title}</span>
|
||||
{tooltip
|
||||
&& (
|
||||
<Tooltip
|
||||
popupContent={(
|
||||
<div className="w-[240px]">
|
||||
{tooltip}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<Infotip aria-label={tooltip} popupClassName="w-[240px]">
|
||||
{tooltip}
|
||||
</Infotip>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -349,8 +349,9 @@ describe('workflow-panel index', () => {
|
||||
expect(mockSaveStateToHistory).toHaveBeenCalled()
|
||||
fireEvent.click(screen.getByText('authorized-in-node'))
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'workflow.panel.runThisStep' }))
|
||||
|
||||
const clickableItems = container.querySelectorAll('.cursor-pointer')
|
||||
fireEvent.click(clickableItems[0] as HTMLElement)
|
||||
fireEvent.click(clickableItems[clickableItems.length - 1] as HTMLElement)
|
||||
|
||||
expect(mockHandleSingleRun).toHaveBeenCalledTimes(1)
|
||||
@ -587,8 +588,7 @@ describe('workflow-panel index', () => {
|
||||
expect(root.style.right).toBe('240px')
|
||||
expect(root.className).toContain('absolute')
|
||||
|
||||
const clickableItems = container.querySelectorAll('.cursor-pointer')
|
||||
fireEvent.click(clickableItems[0] as HTMLElement)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'workflow.debug.variableInspect.trigger.stop' }))
|
||||
|
||||
expect(mockHandleStop).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@ -2,6 +2,11 @@ import type { FC, ReactNode } from 'react'
|
||||
import type { SimpleSubscription } from '@/app/components/plugins/plugin-detail-panel/subscription-list'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@langgenius/dify-ui/tooltip'
|
||||
import {
|
||||
RiCloseLine,
|
||||
RiPlayLargeLine,
|
||||
@ -21,7 +26,6 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { Stop } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { UserAvatarList } from '@/app/components/base/user-avatar-list'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
@ -469,6 +473,11 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const runThisStepLabel = t('panel.runThisStep', { ns: 'workflow' })
|
||||
const singleRunActionLabel = isSingleRunning
|
||||
? t('debug.variableInspect.trigger.stop', { ns: 'workflow' })
|
||||
: runThisStepLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@ -516,26 +525,31 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
<div className="flex shrink-0 items-center text-text-tertiary">
|
||||
{
|
||||
isSupportSingleRun && !nodesReadOnly && (
|
||||
<Tooltip
|
||||
popupContent={t('panel.runThisStep', { ns: 'workflow' })}
|
||||
popupClassName="mr-1"
|
||||
disabled={isSingleRunning}
|
||||
>
|
||||
<div
|
||||
className="mr-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover"
|
||||
onClick={() => {
|
||||
if (isSingleRunning)
|
||||
handleStop()
|
||||
else
|
||||
handleSingleRun()
|
||||
}}
|
||||
>
|
||||
{
|
||||
isSingleRunning
|
||||
? <Stop className="h-4 w-4 text-text-tertiary" />
|
||||
: <RiPlayLargeLine className="h-4 w-4 text-text-tertiary" />
|
||||
}
|
||||
</div>
|
||||
<Tooltip disabled={isSingleRunning}>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<button
|
||||
type="button"
|
||||
aria-label={singleRunActionLabel}
|
||||
className="mr-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-0 hover:bg-state-base-hover focus-visible:ring-1 focus-visible:ring-components-input-border-hover focus-visible:outline-hidden"
|
||||
onClick={() => {
|
||||
if (isSingleRunning)
|
||||
handleStop()
|
||||
else
|
||||
handleSingleRun()
|
||||
}}
|
||||
>
|
||||
{
|
||||
isSingleRunning
|
||||
? <Stop aria-hidden className="h-4 w-4 text-text-tertiary" />
|
||||
: <RiPlayLargeLine aria-hidden className="h-4 w-4 text-text-tertiary" />
|
||||
}
|
||||
</button>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent className="mr-1">
|
||||
{runThisStepLabel}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { ModelConfig, PromptItem, Variable } from '../../../types'
|
||||
import type { ModelConfig, Node, NodeOutPutVar, PromptItem, Variable } from '../../../types'
|
||||
import * as React from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
|
||||
import { PromptRole } from '@/models/debug'
|
||||
@ -35,10 +35,10 @@ type Props = {
|
||||
history: boolean
|
||||
query: boolean
|
||||
}
|
||||
availableVars: any
|
||||
availableNodes: any
|
||||
availableVars: NodeOutPutVar[]
|
||||
availableNodes: Node[]
|
||||
varList: Variable[]
|
||||
handleAddVariable: (payload: any) => void
|
||||
handleAddVariable: (payload: Variable) => void
|
||||
modelConfig?: ModelConfig
|
||||
}
|
||||
|
||||
@ -119,12 +119,14 @@ const ConfigPromptItem: FC<Props> = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div className="max-w-[180px]">{!!payload.role && t(`${i18nPrefix}.roleDescription.${payload.role}`, { ns: 'workflow' })}</div>
|
||||
}
|
||||
triggerClassName="w-4 h-4"
|
||||
/>
|
||||
{!!payload.role && (
|
||||
<Infotip
|
||||
aria-label={t(`${i18nPrefix}.roleDescription.${payload.role}`, { ns: 'workflow' })}
|
||||
popupClassName="w-[180px]"
|
||||
>
|
||||
{t(`${i18nPrefix}.roleDescription.${payload.role}`, { ns: 'workflow' })}
|
||||
</Infotip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
value={payload.edition_type === EditionType.jinja2 ? (payload.jinja2_text || '') : payload.text}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { FC } from 'react'
|
||||
import type { LLMNodeType } from '../types'
|
||||
import type { Memory, Node, NodeOutPutVar } from '@/app/components/workflow/types'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import MemoryConfig from '@/app/components/workflow/nodes/_base/components/memory-config'
|
||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
|
||||
@ -50,18 +50,9 @@ const PanelMemorySection: FC<Props> = ({
|
||||
<div className="flex h-8 items-center justify-between rounded-lg bg-components-input-bg-normal pr-2 pl-3">
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="text-xs font-semibold text-text-secondary uppercase">{t('nodes.common.memories.title', { ns: 'workflow' })}</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<span className="ml-1 flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
{t('nodes.common.memories.tip', { ns: 'workflow' })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Infotip aria-label={t('nodes.common.memories.tip', { ns: 'workflow' })}>
|
||||
{t('nodes.common.memories.tip', { ns: 'workflow' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
<div className="flex h-[18px] items-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1 text-xs font-semibold text-text-tertiary uppercase">
|
||||
{t('nodes.common.memories.builtIn', { ns: 'workflow' })}
|
||||
@ -72,18 +63,12 @@ const PanelMemorySection: FC<Props> = ({
|
||||
title={(
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="text-xs font-semibold text-text-secondary uppercase">user</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<span className="ml-1 flex h-4 w-4 shrink-0 items-center justify-center">
|
||||
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
<div className="max-w-[180px]">{t('nodes.llm.roleDescription.user', { ns: 'workflow' })}</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Infotip
|
||||
aria-label={t('nodes.llm.roleDescription.user', { ns: 'workflow' })}
|
||||
popupClassName="w-[180px]"
|
||||
>
|
||||
{t('nodes.llm.roleDescription.user', { ns: 'workflow' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
)}
|
||||
value={inputs.memory.query_prompt_template || '{{#sys.query#}}'}
|
||||
|
||||
@ -2,9 +2,9 @@ import type { FC } from 'react'
|
||||
import type { LLMNodeType, StructuredOutput } from '../types'
|
||||
import { Switch } from '@langgenius/dify-ui/switch'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { RiAlertFill, RiQuestionLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import StructureOutput from './structure-output'
|
||||
@ -45,31 +45,23 @@ const PanelOutputSection: FC<Props> = ({
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<div>
|
||||
<RiAlertFill className="mr-1 size-4 text-text-warning-secondary" />
|
||||
<span className="mr-1 i-ri-alert-fill size-4 text-text-warning-secondary" />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
<div className="w-[232px] rounded-xl border-[0.5px] border-components-panel-border bg-components-tooltip-bg px-4 py-3.5 shadow-lg backdrop-blur-[5px]">
|
||||
<div className="title-xs-semi-bold text-text-primary">{t('structOutput.modelNotSupported', { ns: 'app' })}</div>
|
||||
<div className="mt-1 body-xs-regular text-text-secondary">{t('structOutput.modelNotSupportedTip', { ns: 'app' })}</div>
|
||||
</div>
|
||||
<TooltipContent className="w-[232px] rounded-xl border-[0.5px] border-components-panel-border bg-components-tooltip-bg px-4 py-3.5 shadow-lg backdrop-blur-[5px]">
|
||||
<div className="title-xs-semi-bold text-text-primary">{t('structOutput.modelNotSupported', { ns: 'app' })}</div>
|
||||
<div className="mt-1 body-xs-regular text-text-secondary">{t('structOutput.modelNotSupportedTip', { ns: 'app' })}</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div className="mr-0.5 system-xs-medium-uppercase text-text-tertiary">{t('structOutput.structured', { ns: 'app' })}</div>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<div>
|
||||
<RiQuestionLine className="size-3.5 text-text-quaternary" />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent>
|
||||
<div className="max-w-[150px]">{t('structOutput.structuredTip', { ns: 'app' })}</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Infotip
|
||||
aria-label={t('structOutput.structuredTip', { ns: 'app' })}
|
||||
popupClassName="w-[150px]"
|
||||
>
|
||||
{t('structOutput.structuredTip', { ns: 'app' })}
|
||||
</Infotip>
|
||||
<Switch
|
||||
className="ml-2"
|
||||
checked={!!inputs.structured_output_enabled}
|
||||
|
||||
@ -68,20 +68,11 @@ export const OVERLAY_RESTRICTED_IMPORT_PATTERNS = [
|
||||
]
|
||||
|
||||
export const OVERLAY_MIGRATION_LEGACY_BASE_FILES = [
|
||||
'app/components/base/chat/chat-with-history/header/mobile-operation-dropdown.tsx',
|
||||
'app/components/base/chat/chat-with-history/header/operation.tsx',
|
||||
'app/components/base/chat/chat-with-history/sidebar/operation.tsx',
|
||||
'app/components/base/chat/chat/citation/popup.tsx',
|
||||
'app/components/base/chat/chat/citation/progress-tooltip.tsx',
|
||||
'app/components/base/chat/chat/citation/tooltip.tsx',
|
||||
'app/components/base/chip/index.tsx',
|
||||
'app/components/base/date-and-time-picker/date-picker/index.tsx',
|
||||
'app/components/base/date-and-time-picker/time-picker/index.tsx',
|
||||
'app/components/base/modal/modal.tsx',
|
||||
'app/components/base/prompt-editor/plugins/context-block/component.tsx',
|
||||
'app/components/base/prompt-editor/plugins/history-block/component.tsx',
|
||||
'app/components/base/sort/index.tsx',
|
||||
'app/components/base/theme-selector.tsx',
|
||||
'app/components/base/tooltip/index.tsx',
|
||||
]
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user