diff --git a/web/app/components/app/configuration/dataset-config/card-item/item.tsx b/web/app/components/app/configuration/dataset-config/card-item/item.tsx index 4feba8b01e..85d46122a3 100644 --- a/web/app/components/app/configuration/dataset-config/card-item/item.tsx +++ b/web/app/components/app/configuration/dataset-config/card-item/item.tsx @@ -8,16 +8,13 @@ import { import { useTranslation } from 'react-i18next' import SettingsModal from '../settings-modal' import type { DataSet } from '@/models/datasets' -import { DataSourceType } from '@/models/datasets' -import FileIcon from '@/app/components/base/file-icon' -import { Folder } from '@/app/components/base/icons/src/vender/solid/files' -import { Globe06 } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import Drawer from '@/app/components/base/drawer' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import Badge from '@/app/components/base/badge' import { useKnowledge } from '@/hooks/use-knowledge' import cn from '@/utils/classnames' +import AppIcon from '@/app/components/base/app-icon' type ItemProps = { className?: string @@ -47,33 +44,26 @@ const Item: FC = ({ const [isDeleting, setIsDeleting] = useState(false) + const iconInfo = config.icon_info || { + icon: '📙', + icon_type: 'emoji', + icon_background: '#FFF4ED', + icon_url: '', + } + return (
- { - config.data_source_type === DataSourceType.FILE && ( -
- -
- ) - } - { - config.data_source_type === DataSourceType.NOTION && ( -
- -
- ) - } - { - config.data_source_type === DataSourceType.WEB && ( -
- -
- ) - } +
{config.name}
diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx index cc2fb061b8..5b01c36d40 100644 --- a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx +++ b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx @@ -31,6 +31,7 @@ import { import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' import { fetchMembers } from '@/service/common' import type { Member } from '@/models/common' +import { IndexingType } from '@/app/components/datasets/create/step-two' type SettingsModalProps = { currentDataset: DataSet @@ -54,8 +55,6 @@ const SettingsModal: FC = ({ const { data: embeddingsModelList } = useModelList(ModelTypeEnum.textEmbedding) const { modelList: rerankModelList, - defaultModel: rerankDefaultModel, - currentModel: isRerankDefaultModelValid, } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.rerank) const { t } = useTranslation() const { notify } = useToastContext() @@ -73,6 +72,7 @@ const SettingsModal: FC = ({ const [indexMethod, setIndexMethod] = useState(currentDataset.indexing_technique) const [retrievalConfig, setRetrievalConfig] = useState(localeCurrentDataset?.retrieval_model_dict as RetrievalConfig) + const [keywordNumber, setKeywordNumber] = useState(currentDataset.keyword_number ?? 10) const handleValueChange = (type: string, value: string) => { setLocaleCurrentDataset({ ...localeCurrentDataset, [type]: value }) @@ -124,6 +124,7 @@ const SettingsModal: FC = ({ description, permission, indexing_technique: indexMethod, + keyword_number: keywordNumber, retrieval_model: { ...retrievalConfig, score_threshold: retrievalConfig.score_threshold_enabled ? retrievalConfig.score_threshold : 0, @@ -248,13 +249,15 @@ const SettingsModal: FC = ({ setIndexMethod(v!)} + onChange={setIndexMethod} currentValue={currentDataset.indexing_technique} + keywordNumber={keywordNumber} + onKeywordNumberChange={setKeywordNumber} />
)} - {indexMethod === 'high_quality' && ( + {indexMethod === IndexingType.QUALIFIED && (
{t('datasetSettings.form.embeddingModel')}
@@ -333,7 +336,7 @@ const SettingsModal: FC = ({
- {indexMethod === 'high_quality' + {indexMethod === IndexingType.QUALIFIED ? ( ({ + init: jest.fn(), +})) + +// Mock emoji data +jest.mock('@emoji-mart/data', () => ({})) + +// Mock the ahooks useHover hook +jest.mock('ahooks', () => ({ + useHover: jest.fn(() => false), +})) + +describe('AppIcon', () => { + beforeEach(() => { + // Mock custom element + if (!customElements.get('em-emoji')) { + customElements.define('em-emoji', class extends HTMLElement { + constructor() { + super() + } + + // Mock basic functionality + connectedCallback() { + this.innerHTML = '🤖' + } + }) + } + + // Reset mocks + require('ahooks').useHover.mockReset().mockReturnValue(false) + }) + + it('renders default emoji when no icon or image is provided', () => { + render() + const emojiElement = document.querySelector('em-emoji') + expect(emojiElement).toBeInTheDocument() + expect(emojiElement?.getAttribute('id')).toBe('🤖') + }) + + it('renders with custom emoji when icon is provided', () => { + render() + const emojiElement = document.querySelector('em-emoji') + expect(emojiElement).toBeInTheDocument() + expect(emojiElement?.getAttribute('id')).toBe('smile') + }) + + it('renders image when iconType is image and imageUrl is provided', () => { + render() + const imgElement = screen.getByAltText('app icon') + expect(imgElement).toBeInTheDocument() + expect(imgElement).toHaveAttribute('src', 'test-image.jpg') + }) + + it('renders innerIcon when provided', () => { + render(Custom Icon
} />) + const innerIcon = screen.getByTestId('inner-icon') + expect(innerIcon).toBeInTheDocument() + }) + + it('applies size classes correctly', () => { + const { container: xsContainer } = render() + expect(xsContainer.firstChild).toHaveClass('w-4 h-4 rounded-[4px]') + + const { container: tinyContainer } = render() + expect(tinyContainer.firstChild).toHaveClass('w-6 h-6 rounded-md') + + const { container: smallContainer } = render() + expect(smallContainer.firstChild).toHaveClass('w-8 h-8 rounded-lg') + + const { container: mediumContainer } = render() + expect(mediumContainer.firstChild).toHaveClass('w-9 h-9 rounded-[10px]') + + const { container: largeContainer } = render() + expect(largeContainer.firstChild).toHaveClass('w-10 h-10 rounded-[10px]') + + const { container: xlContainer } = render() + expect(xlContainer.firstChild).toHaveClass('w-12 h-12 rounded-xl') + + const { container: xxlContainer } = render() + expect(xxlContainer.firstChild).toHaveClass('w-14 h-14 rounded-2xl') + }) + + it('applies rounded class when rounded=true', () => { + const { container } = render() + expect(container.firstChild).toHaveClass('rounded-full') + }) + + it('applies custom background color', () => { + const { container } = render() + expect(container.firstChild).toHaveStyle('background: #FF5500') + }) + + it('uses default background color when no background is provided for non-image icons', () => { + const { container } = render() + expect(container.firstChild).toHaveStyle('background: #FFEAD5') + }) + + it('does not apply background style for image icons', () => { + const { container } = render() + // Should not have the background style from the prop + expect(container.firstChild).not.toHaveStyle('background: #FF5500') + }) + + it('calls onClick handler when clicked', () => { + const handleClick = jest.fn() + const { container } = render() + fireEvent.click(container.firstChild!) + + expect(handleClick).toHaveBeenCalledTimes(1) + }) + + it('applies custom className', () => { + const { container } = render() + expect(container.firstChild).toHaveClass('custom-class') + }) + + it('does not display edit icon when showEditIcon=false', () => { + render() + const editIcon = screen.queryByRole('svg') + expect(editIcon).not.toBeInTheDocument() + }) + + it('displays edit icon when showEditIcon=true and hovering', () => { + // Mock the useHover hook to return true for this test + require('ahooks').useHover.mockReturnValue(true) + + render() + const editIcon = document.querySelector('svg') + expect(editIcon).toBeInTheDocument() + }) + + it('does not display edit icon when showEditIcon=true but not hovering', () => { + // useHover returns false by default from our mock setup + render() + const editIcon = document.querySelector('svg') + expect(editIcon).not.toBeInTheDocument() + }) + + it('handles conditional isValidImageIcon check correctly', () => { + // Case 1: Valid image icon + const { rerender } = render( + , + ) + expect(screen.getByAltText('app icon')).toBeInTheDocument() + + // Case 2: Invalid - missing image URL + rerender() + expect(screen.queryByAltText('app icon')).not.toBeInTheDocument() + + // Case 3: Invalid - wrong icon type + rerender() + expect(screen.queryByAltText('app icon')).not.toBeInTheDocument() + }) +}) diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx index bee387d549..dd280f7578 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/dataset-item.tsx @@ -8,15 +8,13 @@ import { } from '@remixicon/react' import { useTranslation } from 'react-i18next' import type { DataSet } from '@/models/datasets' -import { DataSourceType } from '@/models/datasets' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' -import FileIcon from '@/app/components/base/file-icon' -import { Folder } from '@/app/components/base/icons/src/vender/solid/files' import SettingsModal from '@/app/components/app/configuration/dataset-config/settings-modal' import Drawer from '@/app/components/base/drawer' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import Badge from '@/app/components/base/badge' import { useKnowledge } from '@/hooks/use-knowledge' +import AppIcon from '@/app/components/base/app-icon' type Props = { payload: DataSet @@ -54,25 +52,28 @@ const DatasetItem: FC = ({ onRemove() }, [onRemove]) + const iconInfo = payload.icon_info || { + icon: '📙', + icon_type: 'emoji', + icon_background: '#FFF4ED', + icon_url: '', + } + return (
+ ? 'border-state-destructive-border bg-state-destructive-hover' + : 'bg-components-panel-on-panel-item-bg hover:bg-components-panel-on-panel-item-bg-hover' + }`}>
- { - payload.data_source_type === DataSourceType.NOTION - ? ( -
- -
- ) - :
- -
- } +
{payload.name}
{!readonly && (