diff --git a/api/services/feature_service.py b/api/services/feature_service.py index 754413cee36..45812eebfe7 100644 --- a/api/services/feature_service.py +++ b/api/services/feature_service.py @@ -180,9 +180,13 @@ class FeatureService: system_features.enable_learn_app = dify_config.ENABLE_LEARN_APP system_features.webapp_auth.allow_public_access = dify_config.WEBAPP_PUBLIC_ACCESS_ENABLED system_features.enable_step_by_step_tour = dify_config.ENABLE_STEP_BY_STEP_TOUR - system_features.knowledge_fs_enabled = dify_config.KNOWLEDGE_FS_ENABLED - system_features.knowledge_fs_upload_enabled = bool( + knowledge_fs_enabled = bool( dify_config.KNOWLEDGE_FS_ENABLED + and dify_config.DEPLOYMENT_EDITION in {DeploymentEdition.CLOUD, DeploymentEdition.ENTERPRISE} + ) + system_features.knowledge_fs_enabled = knowledge_fs_enabled + system_features.knowledge_fs_upload_enabled = bool( + knowledge_fs_enabled and dify_config.KNOWLEDGE_FS_BASE_URL and dify_config.KNOWLEDGE_FS_CAPABILITY_V2_ENABLED and dify_config.KNOWLEDGE_FS_CAPABILITY_V2_SIGNING_KID diff --git a/api/tests/unit_tests/services/test_feature_service_knowledge_fs.py b/api/tests/unit_tests/services/test_feature_service_knowledge_fs.py index e76acf06183..3ec4acad483 100644 --- a/api/tests/unit_tests/services/test_feature_service_knowledge_fs.py +++ b/api/tests/unit_tests/services/test_feature_service_knowledge_fs.py @@ -31,6 +31,11 @@ def test_get_system_features_reads_knowledge_fs_availability( signing_ready: bool, upload_enabled: bool, ) -> None: + monkeypatch.setattr( + feature_service_module.dify_config, + "DEPLOYMENT_EDITION", + DeploymentEdition.CLOUD, + ) monkeypatch.setattr(feature_service_module.dify_config, "KNOWLEDGE_FS_ENABLED", enabled) monkeypatch.setattr(feature_service_module.dify_config, "KNOWLEDGE_FS_BASE_URL", base_url) monkeypatch.setattr( @@ -48,10 +53,31 @@ def test_get_system_features_reads_knowledge_fs_availability( "KNOWLEDGE_FS_CAPABILITY_V2_PRIVATE_KEY_PEM", object() if signing_ready else None, ) - result = FeatureService.get_system_features() assert result.knowledge_fs_enabled is enabled assert result.knowledge_fs_upload_enabled is upload_enabled assert result.model_dump()["knowledge_fs_enabled"] is enabled assert result.model_dump()["knowledge_fs_upload_enabled"] is upload_enabled + + +@pytest.mark.parametrize( + ("edition", "expected"), + [(DeploymentEdition.ENTERPRISE, True), (DeploymentEdition.COMMUNITY, False)], +) +def test_get_system_features_controls_knowledge_fs_by_edition( + monkeypatch: pytest.MonkeyPatch, + edition: DeploymentEdition, + expected: bool, +) -> None: + monkeypatch.setattr( + feature_service_module.dify_config, + "DEPLOYMENT_EDITION", + edition, + ) + monkeypatch.setattr(feature_service_module.dify_config, "KNOWLEDGE_FS_ENABLED", True) + monkeypatch.setattr(FeatureService, "_fulfill_params_from_enterprise", lambda _: None) + + result = FeatureService.get_system_features() + + assert result.knowledge_fs_enabled is expected diff --git a/web/app/components/rag-pipeline/hooks/__tests__/use-available-nodes-meta-data.spec.ts b/web/app/components/rag-pipeline/hooks/__tests__/use-available-nodes-meta-data.spec.ts index 66fd1b67acd..731f9322ba4 100644 --- a/web/app/components/rag-pipeline/hooks/__tests__/use-available-nodes-meta-data.spec.ts +++ b/web/app/components/rag-pipeline/hooks/__tests__/use-available-nodes-meta-data.spec.ts @@ -4,6 +4,15 @@ import { BlockEnum } from '@/app/components/workflow/types' import { useAvailableNodesMetaData } from '../use-available-nodes-meta-data' const mockIsAgentV2Enabled = vi.hoisted(() => vi.fn(() => true)) +const mockKnowledgeFsEnabled = vi.hoisted(() => vi.fn(() => true)) + +vi.mock('jotai', () => ({ + useAtomValue: () => mockKnowledgeFsEnabled(), +})) + +vi.mock('@/features/system-features/state', () => ({ + knowledgeFsEnabledAtom: {}, +})) vi.mock('@/context/i18n', () => ({ useDocLink: () => (path?: string) => `https://docs.dify.ai${path || ''}`, @@ -35,6 +44,10 @@ vi.mock('@/app/components/workflow/constants/node', () => ({ metaData: { type: BlockEnum.AgentV2 }, defaultValue: { title: 'Agent' }, }, + { + metaData: { type: BlockEnum.KnowledgeRetrievalV2 }, + defaultValue: { title: 'Knowledge Retrieval V2' }, + }, ], })) @@ -63,6 +76,7 @@ describe('useAvailableNodesMetaData', () => { beforeEach(() => { vi.clearAllMocks() mockIsAgentV2Enabled.mockReturnValue(true) + mockKnowledgeFsEnabled.mockReturnValue(true) }) it('should return nodes and nodesMap', () => { @@ -146,6 +160,7 @@ describe('useAvailableNodesMetaData', () => { expect(nodeTypes).toContain(BlockEnum.LLM) expect(nodeTypes).toContain(BlockEnum.HttpRequest) + expect(nodeTypes).toContain(BlockEnum.KnowledgeRetrievalV2) expect(nodeTypes).not.toContain(BlockEnum.HumanInput) }) @@ -170,4 +185,14 @@ describe('useAvailableNodesMetaData', () => { expect(result.current.nodesMap[BlockEnum.Agent]).toBeDefined() expect(result.current.nodesMap[BlockEnum.AgentV2]).toBeUndefined() }) + + it('should hide Knowledge Retrieval V2 when KnowledgeFS is unavailable', () => { + mockKnowledgeFsEnabled.mockReturnValue(false) + + const { result } = renderHook(() => useAvailableNodesMetaData()) + const nodeTypes = result.current.nodes.map((node) => node.metaData.type) + + expect(nodeTypes).not.toContain(BlockEnum.KnowledgeRetrievalV2) + expect(result.current.nodesMap[BlockEnum.KnowledgeRetrievalV2]).toBeUndefined() + }) }) diff --git a/web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts b/web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts index 6cd8e7de290..4bc7bcd9d9f 100644 --- a/web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts +++ b/web/app/components/rag-pipeline/hooks/use-available-nodes-meta-data.ts @@ -1,5 +1,6 @@ import type { AvailableNodesMetaData } from '@/app/components/workflow/hooks-store/store' import type { I18nKeysWithPrefix } from '@/types/i18n' +import { useAtomValue } from 'jotai' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { WORKFLOW_COMMON_NODES } from '@/app/components/workflow/constants/node' @@ -9,17 +10,20 @@ import knowledgeBaseDefault from '@/app/components/workflow/nodes/knowledge-base import { BlockEnum } from '@/app/components/workflow/types' import { useDocLink } from '@/context/i18n' import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag' +import { knowledgeFsEnabledAtom } from '@/features/system-features/state' export const useAvailableNodesMetaData = () => { const { t } = useTranslation() const docLink = useDocLink() const agentV2Enabled = isAgentV2Enabled() + const knowledgeFsEnabled = useAtomValue(knowledgeFsEnabledAtom) const mergedNodesMetaData = useMemo( () => [ ...WORKFLOW_COMMON_NODES.filter( (node) => node.metaData.type !== BlockEnum.HumanInput && + (knowledgeFsEnabled || node.metaData.type !== BlockEnum.KnowledgeRetrievalV2) && (agentV2Enabled ? node.metaData.type !== BlockEnum.Agent : node.metaData.type !== BlockEnum.AgentV2), @@ -34,7 +38,7 @@ export const useAvailableNodesMetaData = () => { knowledgeBaseDefault, dataSourceEmptyDefault, ], - [agentV2Enabled], + [agentV2Enabled, knowledgeFsEnabled], ) const helpLinkUri = useMemo(