diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 97ca562d057..03da1cd4e9d 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -1560,19 +1560,6 @@ "count": 3 } }, - "web/app/components/datasets/create/file-preview/index.tsx": { - "eslint-react/set-state-in-effect": { - "count": 1 - } - }, - "web/app/components/datasets/create/file-uploader/components/file-list-item.tsx": { - "jsx-a11y/click-events-have-key-events": { - "count": 2 - }, - "jsx-a11y/no-static-element-interactions": { - "count": 2 - } - }, "web/app/components/datasets/create/file-uploader/hooks/use-file-upload.ts": { "no-restricted-imports": { "count": 1 @@ -1583,14 +1570,6 @@ "count": 1 } }, - "web/app/components/datasets/create/step-one/components/data-source-type-selector.tsx": { - "jsx-a11y/click-events-have-key-events": { - "count": 1 - }, - "jsx-a11y/no-static-element-interactions": { - "count": 1 - } - }, "web/app/components/datasets/create/step-one/components/index.ts": { "no-barrel-files/no-barrel-files": { "count": 3 @@ -1601,14 +1580,6 @@ "count": 1 } }, - "web/app/components/datasets/create/step-one/index.tsx": { - "jsx-a11y/click-events-have-key-events": { - "count": 1 - }, - "jsx-a11y/no-static-element-interactions": { - "count": 1 - } - }, "web/app/components/datasets/create/step-two/__tests__/index.spec.tsx": { "dify/require-disable-directive-description": { "count": 3 @@ -1619,14 +1590,6 @@ "count": 5 } }, - "web/app/components/datasets/create/step-two/components/option-card.tsx": { - "jsx-a11y/click-events-have-key-events": { - "count": 1 - }, - "jsx-a11y/no-static-element-interactions": { - "count": 1 - } - }, "web/app/components/datasets/create/step-two/hooks/index.ts": { "no-barrel-files/no-barrel-files": { "count": 6 @@ -2024,14 +1987,6 @@ "count": 1 } }, - "web/app/components/datasets/settings/option-card.tsx": { - "jsx-a11y/click-events-have-key-events": { - "count": 1 - }, - "jsx-a11y/no-static-element-interactions": { - "count": 1 - } - }, "web/app/components/explore/item-operation/__tests__/index.spec.tsx": { "jsx-a11y/click-events-have-key-events": { "count": 1 diff --git a/web/app/components/base/float-right-container/index.tsx b/web/app/components/base/float-right-container/index.tsx index 7c9e0ce7ea0..9dafde68eeb 100644 --- a/web/app/components/base/float-right-container/index.tsx +++ b/web/app/components/base/float-right-container/index.tsx @@ -1,4 +1,5 @@ 'use client' +import type { ComponentProps } from 'react' import { cn } from '@langgenius/dify-ui/cn' import { Drawer, @@ -21,6 +22,7 @@ type IFloatRightContainerProps = { panelClassName?: string title?: string mask?: boolean + finalFocus?: ComponentProps['finalFocus'] } const FloatRightContainer = ({ @@ -32,6 +34,7 @@ const FloatRightContainer = ({ panelClassName, title, mask = true, + finalFocus, }: IFloatRightContainerProps) => { const { t } = useTranslation() @@ -50,6 +53,7 @@ const FloatRightContainer = ({ { it('should render a switch when hasSwitch is true', () => { render() - expect(screen.getByRole('switch')).toBeInTheDocument() + expect(screen.getByRole('switch', { name: 'Test Param' })).toBeInTheDocument() }) it('should not render a switch by default', () => { diff --git a/web/app/components/base/param-item/index.tsx b/web/app/components/base/param-item/index.tsx index d0e31830ba8..97c77abc93f 100644 --- a/web/app/components/base/param-item/index.tsx +++ b/web/app/components/base/param-item/index.tsx @@ -19,6 +19,7 @@ import { SliderTrack, } from '@langgenius/dify-ui/slider' import { Switch } from '@langgenius/dify-ui/switch' +import { useId } from 'react' type Props = Readonly<{ className?: string @@ -53,6 +54,7 @@ const ParamItem: FC = ({ hasSwitch, onSwitchChange, }) => { + const labelId = useId() return (
{name} @@ -60,6 +62,7 @@ const ParamItem: FC = ({
{hasSwitch && ( = ({ }} /> )} - {name} + + {name} + {!noTooltip && tip && ( diff --git a/web/app/components/datasets/common/document-picker/preview-document-picker.tsx b/web/app/components/datasets/common/document-picker/preview-document-picker.tsx index 66a594e6595..998c6d8d7c0 100644 --- a/web/app/components/datasets/common/document-picker/preview-document-picker.tsx +++ b/web/app/components/datasets/common/document-picker/preview-document-picker.tsx @@ -41,18 +41,20 @@ const PreviewDocumentPicker: FC = ({ className, value, files, onChange }) render={
-
-
- +
+
+ {' '} {name || '--'} - +
@@ -63,7 +65,7 @@ const PreviewDocumentPicker: FC = ({ className, value, files, onChange }) sideOffset={4} className="border-none bg-transparent shadow-none" > -
+
{files?.length > 1 && (
{t(($) => $.preprocessDocument, { ns: 'dataset', num: files.length })} @@ -72,7 +74,7 @@ const PreviewDocumentPicker: FC = ({ className, value, files, onChange }) {files?.length > 0 ? ( ) : ( -
+
)} diff --git a/web/app/components/datasets/common/economical-retrieval-method-config/index.tsx b/web/app/components/datasets/common/economical-retrieval-method-config/index.tsx index 9dc163b1308..b4c4b1ab436 100644 --- a/web/app/components/datasets/common/economical-retrieval-method-config/index.tsx +++ b/web/app/components/datasets/common/economical-retrieval-method-config/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' import type { RetrievalConfig } from '@/types/app' +import { RadioGroup } from '@langgenius/dify-ui/radio-group' import * as React from 'react' import { useTranslation } from 'react-i18next' import { RETRIEVE_METHOD } from '@/types/app' @@ -18,26 +19,33 @@ const EconomicalRetrievalMethodConfig: FC = ({ disabled = false, value, o const { t } = useTranslation() return ( - + aria-label={t(($) => $['form.retrievalSetting.method'], { ns: 'datasetSettings' })} + className="block" + value={RETRIEVE_METHOD.keywordSearch} disabled={disabled} - icon={} - iconActiveColor="text-util-colors-purple-purple-600" - title={t(($) => $['retrieval.keyword_search.title'], { ns: 'dataset' })} - description={t(($) => $['retrieval.keyword_search.description'], { ns: 'dataset' })} - isActive - effectColor={EffectColor.purple} - showEffectColor - showChildren - className="gap-x-2" > - - + icon={} + iconActiveColor="text-util-colors-purple-purple-600" + title={t(($) => $['retrieval.keyword_search.title'], { ns: 'dataset' })} + description={t(($) => $['retrieval.keyword_search.description'], { ns: 'dataset' })} + isActive + effectColor={EffectColor.purple} + showEffectColor + showChildren + className="gap-x-2" + > + + + ) } export default React.memo(EconomicalRetrievalMethodConfig) diff --git a/web/app/components/datasets/common/retrieval-method-config/__tests__/index.spec.tsx b/web/app/components/datasets/common/retrieval-method-config/__tests__/index.spec.tsx index 189712d68b9..569d0ebcd2d 100644 --- a/web/app/components/datasets/common/retrieval-method-config/__tests__/index.spec.tsx +++ b/web/app/components/datasets/common/retrieval-method-config/__tests__/index.spec.tsx @@ -1,5 +1,6 @@ import type { RetrievalConfig } from '@/types/app' import { fireEvent, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import * as React from 'react' import { DEFAULT_WEIGHTED_SCORE, RerankingModeEnum, WeightedScoreEnum } from '@/models/datasets' import { consoleQuery } from '@/service/console' @@ -102,6 +103,30 @@ describe('RetrievalMethodConfig', () => { mockIsRerankDefaultModelValid = true }) + it('selects one retrieval mode with arrow keys and keeps parameter controls outside radios', async () => { + const user = userEvent.setup() + const Owner = () => { + const [value, setValue] = React.useState(createMockRetrievalConfig()) + return + } + render() + const semantic = screen.getByRole('radio', { name: 'dataset.retrieval.semantic_search.title' }) + expect( + screen.getByRole('radiogroup', { name: 'datasetSettings.form.retrievalSetting.method' }), + ).toContainElement(semantic) + expect(semantic).toBeChecked() + expect(semantic).not.toContainElement(screen.getByRole('button', { name: 'Update Top K' })) + semantic.focus() + await user.keyboard('{ArrowDown}') + const fullText = screen.getByRole('radio', { name: 'dataset.retrieval.full_text_search.title' }) + expect(fullText).toHaveFocus() + expect(fullText).toBeChecked() + expect(semantic).not.toBeChecked() + await user.click(screen.getByRole('button', { name: 'Update Top K' })) + expect(fullText).toBeChecked() + expect(screen.getAllByRole('radio', { checked: true })).toHaveLength(1) + }) + // Tests for basic rendering describe('Rendering', () => { it('should render all three retrieval methods when all are supported', () => { @@ -213,19 +238,19 @@ describe('RetrievalMethodConfig', () => { renderComponent({ disabled: true }) // When disabled, clicking should not trigger onChange - const semanticOption = screen - .getByText('dataset.retrieval.semantic_search.title') - .closest('div[class*="cursor"]') - expect(semanticOption).toHaveClass('cursor-not-allowed') + const semanticOption = screen.getByRole('radio', { + name: 'dataset.retrieval.semantic_search.title', + }) + expect(semanticOption).toHaveAttribute('aria-disabled', 'true') }) it('should default disabled to false', () => { renderComponent() - const semanticOption = screen - .getByText('dataset.retrieval.semantic_search.title') - .closest('div[class*="cursor"]') - expect(semanticOption).not.toHaveClass('cursor-not-allowed') + const semanticOption = screen.getByRole('radio', { + name: 'dataset.retrieval.semantic_search.title', + }) + expect(semanticOption).not.toBeDisabled() }) }) @@ -238,9 +263,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const semanticOption = screen - .getByText('dataset.retrieval.semantic_search.title') - .closest('div[class*="cursor-pointer"]') + const semanticOption = screen.getByRole('radio', { + name: 'dataset.retrieval.semantic_search.title', + }) fireEvent.click(semanticOption!) expect(onChange).toHaveBeenCalledTimes(1) @@ -259,9 +284,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const fullTextOption = screen - .getByText('dataset.retrieval.full_text_search.title') - .closest('div[class*="cursor-pointer"]') + const fullTextOption = screen.getByRole('radio', { + name: 'dataset.retrieval.full_text_search.title', + }) fireEvent.click(fullTextOption!) expect(onChange).toHaveBeenCalledTimes(1) @@ -280,9 +305,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const hybridOption = screen - .getByText('dataset.retrieval.hybrid_search.title') - .closest('div[class*="cursor-pointer"]') + const hybridOption = screen.getByRole('radio', { + name: 'dataset.retrieval.hybrid_search.title', + }) fireEvent.click(hybridOption!) expect(onChange).toHaveBeenCalledTimes(1) @@ -301,9 +326,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const semanticOption = screen - .getByText('dataset.retrieval.semantic_search.title') - .closest('div[class*="cursor-pointer"]') + const semanticOption = screen.getByRole('radio', { + name: 'dataset.retrieval.semantic_search.title', + }) fireEvent.click(semanticOption!) expect(onChange).not.toHaveBeenCalled() @@ -317,9 +342,9 @@ describe('RetrievalMethodConfig', () => { disabled: true, }) - const fullTextOption = screen - .getByText('dataset.retrieval.full_text_search.title') - .closest('div[class*="cursor"]') + const fullTextOption = screen.getByRole('radio', { + name: 'dataset.retrieval.full_text_search.title', + }) fireEvent.click(fullTextOption!) expect(onChange).not.toHaveBeenCalled() @@ -358,9 +383,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const semanticOption = screen - .getByText('dataset.retrieval.semantic_search.title') - .closest('div[class*="cursor-pointer"]') + const semanticOption = screen.getByRole('radio', { + name: 'dataset.retrieval.semantic_search.title', + }) fireEvent.click(semanticOption!) expect(onChange).toHaveBeenCalledWith( @@ -388,9 +413,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const semanticOption = screen - .getByText('dataset.retrieval.semantic_search.title') - .closest('div[class*="cursor-pointer"]') + const semanticOption = screen.getByRole('radio', { + name: 'dataset.retrieval.semantic_search.title', + }) fireEvent.click(semanticOption!) expect(onChange).toHaveBeenCalledWith( @@ -415,9 +440,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const semanticOption = screen - .getByText('dataset.retrieval.semantic_search.title') - .closest('div[class*="cursor-pointer"]') + const semanticOption = screen.getByRole('radio', { + name: 'dataset.retrieval.semantic_search.title', + }) fireEvent.click(semanticOption!) expect(onChange).toHaveBeenCalledWith( @@ -440,9 +465,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const hybridOption = screen - .getByText('dataset.retrieval.hybrid_search.title') - .closest('div[class*="cursor-pointer"]') + const hybridOption = screen.getByRole('radio', { + name: 'dataset.retrieval.hybrid_search.title', + }) fireEvent.click(hybridOption!) expect(onChange).toHaveBeenCalledWith( @@ -467,9 +492,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const hybridOption = screen - .getByText('dataset.retrieval.hybrid_search.title') - .closest('div[class*="cursor-pointer"]') + const hybridOption = screen.getByRole('radio', { + name: 'dataset.retrieval.hybrid_search.title', + }) fireEvent.click(hybridOption!) expect(onChange).toHaveBeenCalledWith( @@ -489,9 +514,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const hybridOption = screen - .getByText('dataset.retrieval.hybrid_search.title') - .closest('div[class*="cursor-pointer"]') + const hybridOption = screen.getByRole('radio', { + name: 'dataset.retrieval.hybrid_search.title', + }) fireEvent.click(hybridOption!) expect(onChange).toHaveBeenCalledWith( @@ -532,9 +557,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const hybridOption = screen - .getByText('dataset.retrieval.hybrid_search.title') - .closest('div[class*="cursor-pointer"]') + const hybridOption = screen.getByRole('radio', { + name: 'dataset.retrieval.hybrid_search.title', + }) fireEvent.click(hybridOption!) expect(onChange).toHaveBeenCalledWith( @@ -557,9 +582,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const hybridOption = screen - .getByText('dataset.retrieval.hybrid_search.title') - .closest('div[class*="cursor-pointer"]') + const hybridOption = screen.getByRole('radio', { + name: 'dataset.retrieval.hybrid_search.title', + }) fireEvent.click(hybridOption!) expect(onChange).toHaveBeenCalledWith( @@ -587,9 +612,9 @@ describe('RetrievalMethodConfig', () => { const { rerender } = render() - const semanticOption = screen - .getByText('dataset.retrieval.semantic_search.title') - .closest('div[class*="cursor-pointer"]') + const semanticOption = screen.getByRole('radio', { + name: 'dataset.retrieval.semantic_search.title', + }) fireEvent.click(semanticOption!) expect(onChange).toHaveBeenCalledTimes(1) @@ -609,9 +634,9 @@ describe('RetrievalMethodConfig', () => { rerender() - const semanticOption = screen - .getByText('dataset.retrieval.semantic_search.title') - .closest('div[class*="cursor-pointer"]') + const semanticOption = screen.getByRole('radio', { + name: 'dataset.retrieval.semantic_search.title', + }) fireEvent.click(semanticOption!) expect(onChange1).not.toHaveBeenCalled() @@ -667,9 +692,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const semanticOption = screen - .getByText('dataset.retrieval.semantic_search.title') - .closest('div[class*="cursor-pointer"]') + const semanticOption = screen.getByRole('radio', { + name: 'dataset.retrieval.semantic_search.title', + }) fireEvent.click(semanticOption!) expect(onChange).toHaveBeenCalledWith( @@ -698,9 +723,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const hybridOption = screen - .getByText('dataset.retrieval.hybrid_search.title') - .closest('div[class*="cursor-pointer"]') + const hybridOption = screen.getByRole('radio', { + name: 'dataset.retrieval.hybrid_search.title', + }) fireEvent.click(hybridOption!) expect(onChange).toHaveBeenCalledWith( @@ -729,9 +754,9 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const hybridOption = screen - .getByText('dataset.retrieval.hybrid_search.title') - .closest('div[class*="cursor-pointer"]') + const hybridOption = screen.getByRole('radio', { + name: 'dataset.retrieval.hybrid_search.title', + }) fireEvent.click(hybridOption!) expect(onChange).toHaveBeenCalledWith( @@ -751,12 +776,12 @@ describe('RetrievalMethodConfig', () => { onChange, }) - const fullTextOption = screen - .getByText('dataset.retrieval.full_text_search.title') - .closest('div[class*="cursor-pointer"]') - const hybridOption = screen - .getByText('dataset.retrieval.hybrid_search.title') - .closest('div[class*="cursor-pointer"]') + const fullTextOption = screen.getByRole('radio', { + name: 'dataset.retrieval.full_text_search.title', + }) + const hybridOption = screen.getByRole('radio', { + name: 'dataset.retrieval.hybrid_search.title', + }) // Rapid clicks fireEvent.click(fullTextOption!) @@ -829,18 +854,18 @@ describe('RetrievalMethodConfig', () => { describe('disabled prop variations', () => { it('should handle disabled=true', () => { renderComponent({ disabled: true }) - const option = screen - .getByText('dataset.retrieval.semantic_search.title') - .closest('div[class*="cursor"]') - expect(option).toHaveClass('cursor-not-allowed') + const option = screen.getByRole('radio', { + name: 'dataset.retrieval.semantic_search.title', + }) + expect(option).toHaveAttribute('aria-disabled', 'true') }) it('should handle disabled=false', () => { renderComponent({ disabled: false }) - const option = screen - .getByText('dataset.retrieval.semantic_search.title') - .closest('div[class*="cursor"]') - expect(option).toHaveClass('cursor-pointer') + const option = screen.getByRole('radio', { + name: 'dataset.retrieval.semantic_search.title', + }) + expect(option).not.toBeDisabled() }) }) @@ -884,7 +909,7 @@ describe('RetrievalMethodConfig', () => { // The hybrid search option should have the recommended badge // This is verified by checking the isRecommended prop passed to OptionCard const hybridTitle = screen.getByText('dataset.retrieval.hybrid_search.title') - const hybridCard = hybridTitle.closest('div[class*="cursor"]') + const hybridCard = hybridTitle.closest('[role=radio]') // Should contain recommended badge from OptionCard expect( diff --git a/web/app/components/datasets/common/retrieval-method-config/index.tsx b/web/app/components/datasets/common/retrieval-method-config/index.tsx index 22a7abbd60d..59ef65d83e1 100644 --- a/web/app/components/datasets/common/retrieval-method-config/index.tsx +++ b/web/app/components/datasets/common/retrieval-method-config/index.tsx @@ -1,6 +1,7 @@ 'use client' import type { FC } from 'react' import type { RetrievalConfig } from '@/types/app' +import { RadioGroup } from '@langgenius/dify-ui/radio-group' import { useQuery } from '@tanstack/react-query' import * as React from 'react' import { useCallback } from 'react' @@ -105,7 +106,13 @@ const RetrievalMethodConfig: FC = ({ ) return ( -
+ + aria-label={t(($) => $['form.retrievalSetting.method'], { ns: 'datasetSettings' })} + value={value.search_method} + onValueChange={onSwitch} + disabled={disabled} + className="flex flex-col items-stretch gap-x-0 gap-y-2" + > {supportedRetrievalMethods.has(RETRIEVE_METHOD.semantic) && ( = ({ title={t(($) => $['retrieval.semantic_search.title'], { ns: 'dataset' })} description={t(($) => $['retrieval.semantic_search.description'], { ns: 'dataset' })} isActive={value.search_method === RETRIEVE_METHOD.semantic} - onClick={onSwitch} effectColor={EffectColor.purple} showEffectColor showChildren={value.search_method === RETRIEVE_METHOD.semantic} @@ -139,7 +145,6 @@ const RetrievalMethodConfig: FC = ({ title={t(($) => $['retrieval.full_text_search.title'], { ns: 'dataset' })} description={t(($) => $['retrieval.full_text_search.description'], { ns: 'dataset' })} isActive={value.search_method === RETRIEVE_METHOD.fullText} - onClick={onSwitch} effectColor={EffectColor.purple} showEffectColor showChildren={value.search_method === RETRIEVE_METHOD.fullText} @@ -163,7 +168,6 @@ const RetrievalMethodConfig: FC = ({ title={t(($) => $['retrieval.hybrid_search.title'], { ns: 'dataset' })} description={t(($) => $['retrieval.hybrid_search.description'], { ns: 'dataset' })} isActive={value.search_method === RETRIEVE_METHOD.hybrid} - onClick={onSwitch} effectColor={EffectColor.purple} showEffectColor isRecommended @@ -179,7 +183,7 @@ const RetrievalMethodConfig: FC = ({ /> )} -
+ ) } export default React.memo(RetrievalMethodConfig) diff --git a/web/app/components/datasets/common/retrieval-param-config/__tests__/index.spec.tsx b/web/app/components/datasets/common/retrieval-param-config/__tests__/index.spec.tsx index a4fc6898a97..8f62c6f2725 100644 --- a/web/app/components/datasets/common/retrieval-param-config/__tests__/index.spec.tsx +++ b/web/app/components/datasets/common/retrieval-param-config/__tests__/index.spec.tsx @@ -145,7 +145,9 @@ describe('RetrievalParamConfig', () => { />, ) - expect(screen.getByRole('switch')).toBeChecked() + expect( + screen.getByRole('switch', { name: 'common.modelProvider.rerankModel.key' }), + ).toBeChecked() }) it('should render model selector when reranking is enabled', () => { @@ -211,7 +213,7 @@ describe('RetrievalParamConfig', () => { />, ) - fireEvent.click(screen.getByRole('switch')) + fireEvent.click(screen.getByRole('switch', { name: 'common.modelProvider.rerankModel.key' })) expect(mockOnChange).toHaveBeenCalledWith({ ...config, @@ -230,7 +232,7 @@ describe('RetrievalParamConfig', () => { />, ) - fireEvent.click(screen.getByRole('switch')) + fireEvent.click(screen.getByRole('switch', { name: 'common.modelProvider.rerankModel.key' })) expect(mockNotify).toHaveBeenCalledWith('workflow.errorMsg.rerankModelRequired') }) @@ -354,7 +356,9 @@ describe('RetrievalParamConfig', () => { />, ) - expect(screen.getByRole('switch')).toBeChecked() + expect( + screen.getByRole('switch', { name: 'common.modelProvider.rerankModel.key' }), + ).toBeChecked() }) it('should hide score threshold when reranking is disabled for full text search', () => { diff --git a/web/app/components/datasets/common/retrieval-param-config/index.tsx b/web/app/components/datasets/common/retrieval-param-config/index.tsx index 3e71c815137..7e42f8324c8 100644 --- a/web/app/components/datasets/common/retrieval-param-config/index.tsx +++ b/web/app/components/datasets/common/retrieval-param-config/index.tsx @@ -6,7 +6,7 @@ import { Infotip, InfotipContent, InfotipTrigger } from '@langgenius/dify-ui/inf import { RadioGroup } from '@langgenius/dify-ui/radio-group' import { Switch } from '@langgenius/dify-ui/switch' import * as React from 'react' -import { useCallback, useMemo } from 'react' +import { useCallback, useId, useMemo } from 'react' import { useTranslation } from 'react-i18next' import WeightedScore from '@/app/components/app/configuration/dataset-config/params-config/weighted-score' import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item' @@ -40,6 +40,7 @@ const RetrievalParamConfig: FC = ({ onChange, }) => { const { t } = useTranslation() + const rerankLabelId = useId() const canToggleRerankModalEnable = type !== RETRIEVE_METHOD.hybrid const isEconomical = type === RETRIEVE_METHOD.keywordSearch const isHybridSearch = type === RETRIEVE_METHOD.hybrid @@ -111,12 +112,13 @@ const RetrievalParamConfig: FC = ({ ] return ( -
+
{!isEconomical && !isHybridSearch && (
{canToggleRerankModalEnable && ( = ({ /> )}
- + {t(($) => $['modelProvider.rerankModel.key'], { ns: 'common' })} @@ -181,7 +183,7 @@ const RetrievalParamConfig: FC = ({
)} {!isHybridSearch && ( -
+
= ({ aria-label={t(($) => $['modelProvider.rerankModel.key'], { ns: 'common' })} value={value.reranking_mode} onValueChange={handleChangeRerankMode} - className="mb-4 flex gap-2" + className="mb-4 flex gap-2 @max-[28rem]/retrieval:flex-col" > {rerankingModeOptions.map((option) => ( @@ -314,7 +316,9 @@ const RetrievalParamConfig: FC = ({ )} )} -
+
{ render() - expect(screen.getByText('datasetDocuments.embedding.processing')).toBeInTheDocument() + expect(screen.getByRole('status')).toHaveTextContent('datasetDocuments.embedding.processing') }) it('shows that document indexing has completed', () => { @@ -111,7 +111,7 @@ describe('EmbeddingProcess', () => { render() - expect(screen.getByText('datasetDocuments.embedding.completed')).toBeInTheDocument() + expect(screen.getByRole('status')).toHaveTextContent('datasetDocuments.embedding.completed') }) it('shows the vector-space admission alert after processing completes', () => { @@ -131,7 +131,7 @@ describe('EmbeddingProcess', () => { render() - expect(screen.getByText('datasetDocuments.embedding.completed')).toBeInTheDocument() + expect(screen.getByRole('status')).toHaveTextContent('datasetDocuments.embedding.completed') expect( screen.getByText('vector space admission alert 61MB / 50MB without upgrade'), ).toBeInTheDocument() diff --git a/web/app/components/datasets/create/embedding-process/__tests__/indexing-progress-item.spec.tsx b/web/app/components/datasets/create/embedding-process/__tests__/indexing-progress-item.spec.tsx index 45f6a8b6fb4..662e0b75e6e 100644 --- a/web/app/components/datasets/create/embedding-process/__tests__/indexing-progress-item.spec.tsx +++ b/web/app/components/datasets/create/embedding-process/__tests__/indexing-progress-item.spec.tsx @@ -47,6 +47,10 @@ describe('IndexingProgressItem', () => { // Name appears in both the file-icon mock and the display div; verify at least one expect(screen.getAllByText('test.pdf').length).toBeGreaterThanOrEqual(1) expect(screen.getByText('50%')).toBeInTheDocument() + expect(screen.getByRole('progressbar', { name: 'test.pdf' })).toHaveAttribute( + 'aria-valuenow', + '50', + ) }) it('should render file icon for FILE source type', () => { @@ -74,16 +78,21 @@ describe('IndexingProgressItem', () => { expect(screen.getByTestId('notion-icon')).toBeInTheDocument() }) - it('should render success icon for completed status', () => { - render( + it('announces a file completing after processing without retaining its progress bar', () => { + const { rerender } = render() + const status = screen.getByRole('status') + expect(status).toBeEmptyDOMElement() + expect(screen.getByRole('progressbar', { name: 'done.pdf' })).toBeInTheDocument() + + rerender( , ) - // No progress percentage should be shown for completed - expect(screen.queryByText(/%/)).not.toBeInTheDocument() + expect(status).toHaveTextContent('done.pdf: datasetDocuments.embedding.completed') + expect(screen.queryByRole('progressbar')).not.toBeInTheDocument() }) it('opens the full indexing error from the warning button', async () => { @@ -95,7 +104,7 @@ describe('IndexingProgressItem', () => { />, ) - expect(screen.queryByText('Parse failed')).not.toBeInTheDocument() + expect(screen.getByRole('status')).toHaveTextContent('broken.pdf: common.error: Parse failed') await user.click(screen.getByRole('button', { name: 'common.error' })) expect(await screen.findByRole('dialog')).toHaveTextContent('Parse failed') await user.keyboard('{Escape}') diff --git a/web/app/components/datasets/create/embedding-process/index.tsx b/web/app/components/datasets/create/embedding-process/index.tsx index ec701103ca0..b892df1123d 100644 --- a/web/app/components/datasets/create/embedding-process/index.tsx +++ b/web/app/components/datasets/create/embedding-process/index.tsx @@ -38,10 +38,14 @@ const StatusHeader: FC<{ isEmbedding: boolean; isCompleted: boolean }> = ({ const { t } = useTranslation() return ( -
+
{isEmbedding && ( <> - +